// 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/alloy/alloy_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(lparam)); CefJavaScriptDialogRunnerWin* owner = reinterpret_cast(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( GetWindowLongPtr(dialog, DWLP_USER)); if (owner) { owner->CloseDialog(false, std::wstring()); // 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( GetWindowLongPtr(dialog, DWLP_USER)); std::wstring 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) { user_input.reserve(length); user_input.resize(length - 1); GetDlgItemText(dialog, IDC_PROMPTEDIT, &user_input[0], 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( AlloyBrowserHostImpl* browser, content::JavaScriptDialogType message_type, const std::u16string& display_url, const std::u16string& message_text, const std::u16string& default_prompt_text, DialogClosedCallback callback) { DCHECK(!dialog_win_); message_type_ = message_type; message_text_ = base::UTF16ToWide(message_text); default_prompt_text_ = base::UTF16ToWide(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(this)); DCHECK(dialog_win_); if (!display_url.empty()) { // Add the display URL to the window title. wchar_t text[64]; GetWindowText(dialog_win_, text, sizeof(text) / sizeof(wchar_t)); std::wstring new_window_text = std::wstring(text) + L" - " + base::UTF16ToWide(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 std::wstring& 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, base::WideToUTF16(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(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; }