1 // Copyright (c) 2012 The Chromium Embedded Framework Authors.
2 // Portions copyright (c) 2012 The Chromium Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5
6 #include "libcef/browser/native/javascript_dialog_runner_win.h"
7
8 #include "libcef/browser/alloy/alloy_browser_host_impl.h"
9 #include "libcef_dll/resource.h"
10
11 #include "base/files/file_path.h"
12 #include "base/path_service.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15
16 class CefJavaScriptDialogRunnerWin;
17
18 HHOOK CefJavaScriptDialogRunnerWin::msg_hook_ = NULL;
19 int CefJavaScriptDialogRunnerWin::msg_hook_user_count_ = 0;
20
DialogProc(HWND dialog,UINT message,WPARAM wparam,LPARAM lparam)21 INT_PTR CALLBACK CefJavaScriptDialogRunnerWin::DialogProc(HWND dialog,
22 UINT message,
23 WPARAM wparam,
24 LPARAM lparam) {
25 switch (message) {
26 case WM_INITDIALOG: {
27 SetWindowLongPtr(dialog, DWLP_USER, static_cast<LONG_PTR>(lparam));
28 CefJavaScriptDialogRunnerWin* owner =
29 reinterpret_cast<CefJavaScriptDialogRunnerWin*>(lparam);
30 owner->dialog_win_ = dialog;
31 SetDlgItemText(dialog, IDC_DIALOGTEXT, owner->message_text_.c_str());
32 if (owner->message_type_ == content::JAVASCRIPT_DIALOG_TYPE_PROMPT)
33 SetDlgItemText(dialog, IDC_PROMPTEDIT,
34 owner->default_prompt_text_.c_str());
35 break;
36 }
37 case WM_CLOSE: {
38 CefJavaScriptDialogRunnerWin* owner =
39 reinterpret_cast<CefJavaScriptDialogRunnerWin*>(
40 GetWindowLongPtr(dialog, DWLP_USER));
41 if (owner) {
42 owner->CloseDialog(false, std::wstring());
43
44 // No need for the system to call DestroyWindow() because it will be
45 // called by the Cancel() method.
46 return 0;
47 }
48 break;
49 }
50 case WM_COMMAND: {
51 CefJavaScriptDialogRunnerWin* owner =
52 reinterpret_cast<CefJavaScriptDialogRunnerWin*>(
53 GetWindowLongPtr(dialog, DWLP_USER));
54 std::wstring user_input;
55 bool finish = false;
56 bool result = false;
57 switch (LOWORD(wparam)) {
58 case IDOK:
59 finish = true;
60 result = true;
61 if (owner->message_type_ == content::JAVASCRIPT_DIALOG_TYPE_PROMPT) {
62 size_t length =
63 GetWindowTextLength(GetDlgItem(dialog, IDC_PROMPTEDIT)) + 1;
64 if (length > 1) {
65 user_input.reserve(length);
66 user_input.resize(length - 1);
67 GetDlgItemText(dialog, IDC_PROMPTEDIT, &user_input[0], length);
68 }
69 }
70 break;
71 case IDCANCEL:
72 finish = true;
73 result = false;
74 break;
75 }
76 if (finish) {
77 owner->CloseDialog(result, user_input);
78 }
79 break;
80 }
81 default:
82 break;
83 }
84 return 0;
85 }
86
CefJavaScriptDialogRunnerWin()87 CefJavaScriptDialogRunnerWin::CefJavaScriptDialogRunnerWin()
88 : dialog_win_(NULL), parent_win_(NULL), hook_installed_(false) {}
89
~CefJavaScriptDialogRunnerWin()90 CefJavaScriptDialogRunnerWin::~CefJavaScriptDialogRunnerWin() {
91 Cancel();
92 }
93
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)94 void CefJavaScriptDialogRunnerWin::Run(
95 AlloyBrowserHostImpl* browser,
96 content::JavaScriptDialogType message_type,
97 const std::u16string& display_url,
98 const std::u16string& message_text,
99 const std::u16string& default_prompt_text,
100 DialogClosedCallback callback) {
101 DCHECK(!dialog_win_);
102
103 message_type_ = message_type;
104 message_text_ = base::UTF16ToWide(message_text);
105 default_prompt_text_ = base::UTF16ToWide(default_prompt_text);
106 callback_ = std::move(callback);
107
108 InstallMessageHook();
109 hook_installed_ = true;
110
111 int dialog_type;
112 if (message_type == content::JAVASCRIPT_DIALOG_TYPE_ALERT)
113 dialog_type = IDD_ALERT;
114 else if (message_type == content::JAVASCRIPT_DIALOG_TYPE_CONFIRM)
115 dialog_type = IDD_CONFIRM;
116 else // JAVASCRIPT_DIALOG_TYPE_PROMPT
117 dialog_type = IDD_PROMPT;
118
119 base::FilePath file_path;
120 HMODULE hModule = NULL;
121
122 // Try to load the dialog from the DLL.
123 if (base::PathService::Get(base::DIR_MODULE, &file_path)) {
124 file_path = file_path.Append(L"libcef.dll");
125 hModule = ::GetModuleHandle(file_path.value().c_str());
126 }
127 if (!hModule)
128 hModule = ::GetModuleHandle(NULL);
129 DCHECK(hModule);
130
131 parent_win_ = GetAncestor(browser->GetWindowHandle(), GA_ROOT);
132 dialog_win_ =
133 CreateDialogParam(hModule, MAKEINTRESOURCE(dialog_type), parent_win_,
134 DialogProc, reinterpret_cast<LPARAM>(this));
135 DCHECK(dialog_win_);
136
137 if (!display_url.empty()) {
138 // Add the display URL to the window title.
139 wchar_t text[64];
140 GetWindowText(dialog_win_, text, sizeof(text) / sizeof(wchar_t));
141
142 std::wstring new_window_text =
143 std::wstring(text) + L" - " + base::UTF16ToWide(display_url);
144 SetWindowText(dialog_win_, new_window_text.c_str());
145 }
146
147 // Disable the parent window so the user can't interact with it.
148 if (IsWindowEnabled(parent_win_))
149 EnableWindow(parent_win_, FALSE);
150
151 ShowWindow(dialog_win_, SW_SHOWNORMAL);
152 }
153
Cancel()154 void CefJavaScriptDialogRunnerWin::Cancel() {
155 // Re-enable the parent before closing the popup to avoid focus/activation/
156 // z-order issues.
157 if (parent_win_ && IsWindow(parent_win_) && !IsWindowEnabled(parent_win_)) {
158 EnableWindow(parent_win_, TRUE);
159 parent_win_ = NULL;
160 }
161
162 if (dialog_win_ && IsWindow(dialog_win_)) {
163 SetWindowLongPtr(dialog_win_, DWLP_USER, NULL);
164 DestroyWindow(dialog_win_);
165 dialog_win_ = NULL;
166 }
167
168 if (hook_installed_) {
169 UninstallMessageHook();
170 hook_installed_ = false;
171 }
172 }
173
CloseDialog(bool success,const std::wstring & user_input)174 void CefJavaScriptDialogRunnerWin::CloseDialog(bool success,
175 const std::wstring& user_input) {
176 // Run the callback first so that RenderProcessHostImpl::IsBlocked is
177 // cleared. Otherwise, RenderWidgetHostImpl::IsIgnoringInputEvents will
178 // return true and RenderWidgetHostViewAura::OnWindowFocused will fail to
179 // re-assign browser focus.
180 std::move(callback_).Run(success, base::WideToUTF16(user_input));
181 Cancel();
182 }
183
184 // static
GetMsgProc(int code,WPARAM wparam,LPARAM lparam)185 LRESULT CALLBACK CefJavaScriptDialogRunnerWin::GetMsgProc(int code,
186 WPARAM wparam,
187 LPARAM lparam) {
188 // Mostly borrowed from http://support.microsoft.com/kb/q187988/
189 // and http://www.codeproject.com/KB/atl/cdialogmessagehook.aspx.
190 LPMSG msg = reinterpret_cast<LPMSG>(lparam);
191 if (code >= 0 && wparam == PM_REMOVE && msg->message >= WM_KEYFIRST &&
192 msg->message <= WM_KEYLAST) {
193 HWND hwnd = GetActiveWindow();
194 if (::IsWindow(hwnd) && ::IsDialogMessage(hwnd, msg)) {
195 // The value returned from this hookproc is ignored, and it cannot
196 // be used to tell Windows the message has been handled. To avoid
197 // further processing, convert the message to WM_NULL before
198 // returning.
199 msg->hwnd = NULL;
200 msg->message = WM_NULL;
201 msg->lParam = 0L;
202 msg->wParam = 0;
203 }
204 }
205
206 // Passes the hook information to the next hook procedure in
207 // the current hook chain.
208 return ::CallNextHookEx(msg_hook_, code, wparam, lparam);
209 }
210
211 // static
InstallMessageHook()212 bool CefJavaScriptDialogRunnerWin::InstallMessageHook() {
213 msg_hook_user_count_++;
214
215 // Make sure we only call this once.
216 if (msg_hook_ != NULL)
217 return true;
218
219 msg_hook_ = ::SetWindowsHookEx(WH_GETMESSAGE,
220 &CefJavaScriptDialogRunnerWin::GetMsgProc,
221 NULL, GetCurrentThreadId());
222 DCHECK(msg_hook_ != NULL);
223 return msg_hook_ != NULL;
224 }
225
226 // static
UninstallMessageHook()227 bool CefJavaScriptDialogRunnerWin::UninstallMessageHook() {
228 msg_hook_user_count_--;
229 DCHECK_GE(msg_hook_user_count_, 0);
230
231 if (msg_hook_user_count_ > 0)
232 return true;
233
234 DCHECK(msg_hook_ != NULL);
235 BOOL result = ::UnhookWindowsHookEx(msg_hook_);
236 DCHECK(result);
237 msg_hook_ = NULL;
238
239 return result != FALSE;
240 }
241