| // Copyright (c) 2012 The Chromium Embedded Framework Authors. |
| // Portions copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "libcef/browser/native/javascript_dialog_runner_win.h" |
| |
| #include "libcef/browser/browser_host_impl.h" |
| #include "libcef_dll/resource.h" |
| |
| #include "base/files/file_path.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| |
| class CefJavaScriptDialogRunnerWin; |
| |
| HHOOK CefJavaScriptDialogRunnerWin::msg_hook_ = NULL; |
| int CefJavaScriptDialogRunnerWin::msg_hook_user_count_ = 0; |
| |
| INT_PTR CALLBACK CefJavaScriptDialogRunnerWin::DialogProc(HWND dialog, |
| UINT message, |
| WPARAM wparam, |
| LPARAM lparam) { |
| switch (message) { |
| case WM_INITDIALOG: { |
| SetWindowLongPtr(dialog, DWLP_USER, static_cast<LONG_PTR>(lparam)); |
| CefJavaScriptDialogRunnerWin* owner = |
| reinterpret_cast<CefJavaScriptDialogRunnerWin*>(lparam); |
| owner->dialog_win_ = dialog; |
| SetDlgItemText(dialog, IDC_DIALOGTEXT, owner->message_text_.c_str()); |
| if (owner->message_type_ == content::JAVASCRIPT_DIALOG_TYPE_PROMPT) |
| SetDlgItemText(dialog, IDC_PROMPTEDIT, |
| owner->default_prompt_text_.c_str()); |
| break; |
| } |
| case WM_CLOSE: { |
| CefJavaScriptDialogRunnerWin* owner = |
| reinterpret_cast<CefJavaScriptDialogRunnerWin*>( |
| GetWindowLongPtr(dialog, DWLP_USER)); |
| if (owner) { |
| owner->CloseDialog(false, base::string16()); |
| |
| // No need for the system to call DestroyWindow() because it will be |
| // called by the Cancel() method. |
| return 0; |
| } |
| break; |
| } |
| case WM_COMMAND: { |
| CefJavaScriptDialogRunnerWin* owner = |
| reinterpret_cast<CefJavaScriptDialogRunnerWin*>( |
| GetWindowLongPtr(dialog, DWLP_USER)); |
| base::string16 user_input; |
| bool finish = false; |
| bool result = false; |
| switch (LOWORD(wparam)) { |
| case IDOK: |
| finish = true; |
| result = true; |
| if (owner->message_type_ == content::JAVASCRIPT_DIALOG_TYPE_PROMPT) { |
| size_t length = |
| GetWindowTextLength(GetDlgItem(dialog, IDC_PROMPTEDIT)) + 1; |
| if (length > 1) { |
| GetDlgItemText(dialog, IDC_PROMPTEDIT, |
| base::WriteInto(&user_input, length), length); |
| } |
| } |
| break; |
| case IDCANCEL: |
| finish = true; |
| result = false; |
| break; |
| } |
| if (finish) { |
| owner->CloseDialog(result, user_input); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| CefJavaScriptDialogRunnerWin::CefJavaScriptDialogRunnerWin() |
| : dialog_win_(NULL), parent_win_(NULL), hook_installed_(false) {} |
| |
| CefJavaScriptDialogRunnerWin::~CefJavaScriptDialogRunnerWin() { |
| Cancel(); |
| } |
| |
| void CefJavaScriptDialogRunnerWin::Run( |
| CefBrowserHostImpl* browser, |
| content::JavaScriptDialogType message_type, |
| const base::string16& display_url, |
| const base::string16& message_text, |
| const base::string16& default_prompt_text, |
| DialogClosedCallback callback) { |
| DCHECK(!dialog_win_); |
| |
| message_type_ = message_type; |
| message_text_ = message_text; |
| default_prompt_text_ = default_prompt_text; |
| callback_ = std::move(callback); |
| |
| InstallMessageHook(); |
| hook_installed_ = true; |
| |
| int dialog_type; |
| if (message_type == content::JAVASCRIPT_DIALOG_TYPE_ALERT) |
| dialog_type = IDD_ALERT; |
| else if (message_type == content::JAVASCRIPT_DIALOG_TYPE_CONFIRM) |
| dialog_type = IDD_CONFIRM; |
| else // JAVASCRIPT_DIALOG_TYPE_PROMPT |
| dialog_type = IDD_PROMPT; |
| |
| base::FilePath file_path; |
| HMODULE hModule = NULL; |
| |
| // Try to load the dialog from the DLL. |
| if (base::PathService::Get(base::DIR_MODULE, &file_path)) { |
| file_path = file_path.Append(L"libcef.dll"); |
| hModule = ::GetModuleHandle(file_path.value().c_str()); |
| } |
| if (!hModule) |
| hModule = ::GetModuleHandle(NULL); |
| DCHECK(hModule); |
| |
| parent_win_ = GetAncestor(browser->GetWindowHandle(), GA_ROOT); |
| dialog_win_ = |
| CreateDialogParam(hModule, MAKEINTRESOURCE(dialog_type), parent_win_, |
| DialogProc, reinterpret_cast<LPARAM>(this)); |
| DCHECK(dialog_win_); |
| |
| if (!display_url.empty()) { |
| // Add the display URL to the window title. |
| TCHAR text[64]; |
| GetWindowText(dialog_win_, text, sizeof(text) / sizeof(TCHAR)); |
| |
| base::string16 new_window_text = |
| text + base::ASCIIToUTF16(" - ") + display_url; |
| SetWindowText(dialog_win_, new_window_text.c_str()); |
| } |
| |
| // Disable the parent window so the user can't interact with it. |
| if (IsWindowEnabled(parent_win_)) |
| EnableWindow(parent_win_, FALSE); |
| |
| ShowWindow(dialog_win_, SW_SHOWNORMAL); |
| } |
| |
| void CefJavaScriptDialogRunnerWin::Cancel() { |
| // Re-enable the parent before closing the popup to avoid focus/activation/ |
| // z-order issues. |
| if (parent_win_ && IsWindow(parent_win_) && !IsWindowEnabled(parent_win_)) { |
| EnableWindow(parent_win_, TRUE); |
| parent_win_ = NULL; |
| } |
| |
| if (dialog_win_ && IsWindow(dialog_win_)) { |
| SetWindowLongPtr(dialog_win_, DWLP_USER, NULL); |
| DestroyWindow(dialog_win_); |
| dialog_win_ = NULL; |
| } |
| |
| if (hook_installed_) { |
| UninstallMessageHook(); |
| hook_installed_ = false; |
| } |
| } |
| |
| void CefJavaScriptDialogRunnerWin::CloseDialog( |
| bool success, |
| const base::string16& user_input) { |
| // Run the callback first so that RenderProcessHostImpl::IsBlocked is |
| // cleared. Otherwise, RenderWidgetHostImpl::IsIgnoringInputEvents will |
| // return true and RenderWidgetHostViewAura::OnWindowFocused will fail to |
| // re-assign browser focus. |
| std::move(callback_).Run(success, user_input); |
| Cancel(); |
| } |
| |
| // static |
| LRESULT CALLBACK CefJavaScriptDialogRunnerWin::GetMsgProc(int code, |
| WPARAM wparam, |
| LPARAM lparam) { |
| // Mostly borrowed from http://support.microsoft.com/kb/q187988/ |
| // and http://www.codeproject.com/KB/atl/cdialogmessagehook.aspx. |
| LPMSG msg = reinterpret_cast<LPMSG>(lparam); |
| if (code >= 0 && wparam == PM_REMOVE && msg->message >= WM_KEYFIRST && |
| msg->message <= WM_KEYLAST) { |
| HWND hwnd = GetActiveWindow(); |
| if (::IsWindow(hwnd) && ::IsDialogMessage(hwnd, msg)) { |
| // The value returned from this hookproc is ignored, and it cannot |
| // be used to tell Windows the message has been handled. To avoid |
| // further processing, convert the message to WM_NULL before |
| // returning. |
| msg->hwnd = NULL; |
| msg->message = WM_NULL; |
| msg->lParam = 0L; |
| msg->wParam = 0; |
| } |
| } |
| |
| // Passes the hook information to the next hook procedure in |
| // the current hook chain. |
| return ::CallNextHookEx(msg_hook_, code, wparam, lparam); |
| } |
| |
| // static |
| bool CefJavaScriptDialogRunnerWin::InstallMessageHook() { |
| msg_hook_user_count_++; |
| |
| // Make sure we only call this once. |
| if (msg_hook_ != NULL) |
| return true; |
| |
| msg_hook_ = ::SetWindowsHookEx(WH_GETMESSAGE, |
| &CefJavaScriptDialogRunnerWin::GetMsgProc, |
| NULL, GetCurrentThreadId()); |
| DCHECK(msg_hook_ != NULL); |
| return msg_hook_ != NULL; |
| } |
| |
| // static |
| bool CefJavaScriptDialogRunnerWin::UninstallMessageHook() { |
| msg_hook_user_count_--; |
| DCHECK_GE(msg_hook_user_count_, 0); |
| |
| if (msg_hook_user_count_ > 0) |
| return true; |
| |
| DCHECK(msg_hook_ != NULL); |
| BOOL result = ::UnhookWindowsHookEx(msg_hook_); |
| DCHECK(result); |
| msg_hook_ = NULL; |
| |
| return result != FALSE; |
| } |