|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "PluginHangUI.h" |
|
8 |
|
9 #include "PluginHangUIChild.h" |
|
10 #include "HangUIDlg.h" |
|
11 |
|
12 #include <assert.h> |
|
13 #include <commctrl.h> |
|
14 #include <windowsx.h> |
|
15 #include <algorithm> |
|
16 #include <sstream> |
|
17 #include <vector> |
|
18 |
|
19 namespace mozilla { |
|
20 namespace plugins { |
|
21 |
|
22 struct WinInfo |
|
23 { |
|
24 WinInfo(HWND aHwnd, POINT& aPos, SIZE& aSize) |
|
25 :hwnd(aHwnd) |
|
26 { |
|
27 pos.x = aPos.x; |
|
28 pos.y = aPos.y; |
|
29 size.cx = aSize.cx; |
|
30 size.cy = aSize.cy; |
|
31 } |
|
32 HWND hwnd; |
|
33 POINT pos; |
|
34 SIZE size; |
|
35 }; |
|
36 typedef std::vector<WinInfo> WinInfoVec; |
|
37 |
|
38 PluginHangUIChild* PluginHangUIChild::sSelf = nullptr; |
|
39 const int PluginHangUIChild::kExpectedMinimumArgc = 10; |
|
40 |
|
41 PluginHangUIChild::PluginHangUIChild() |
|
42 : mResponseBits(0), |
|
43 mParentWindow(nullptr), |
|
44 mDlgHandle(nullptr), |
|
45 mMainThread(nullptr), |
|
46 mParentProcess(nullptr), |
|
47 mRegWaitProcess(nullptr), |
|
48 mIPCTimeoutMs(0) |
|
49 { |
|
50 } |
|
51 |
|
52 PluginHangUIChild::~PluginHangUIChild() |
|
53 { |
|
54 if (mMainThread) { |
|
55 CloseHandle(mMainThread); |
|
56 } |
|
57 if (mRegWaitProcess) { |
|
58 UnregisterWaitEx(mRegWaitProcess, INVALID_HANDLE_VALUE); |
|
59 } |
|
60 if (mParentProcess) { |
|
61 CloseHandle(mParentProcess); |
|
62 } |
|
63 sSelf = nullptr; |
|
64 } |
|
65 |
|
66 bool |
|
67 PluginHangUIChild::Init(int aArgc, wchar_t* aArgv[]) |
|
68 { |
|
69 if (aArgc < kExpectedMinimumArgc) { |
|
70 return false; |
|
71 } |
|
72 unsigned int i = 1; |
|
73 mMessageText = aArgv[i]; |
|
74 mWindowTitle = aArgv[++i]; |
|
75 mWaitBtnText = aArgv[++i]; |
|
76 mKillBtnText = aArgv[++i]; |
|
77 mNoFutureText = aArgv[++i]; |
|
78 std::wistringstream issHwnd(aArgv[++i]); |
|
79 issHwnd >> reinterpret_cast<HANDLE&>(mParentWindow); |
|
80 if (!issHwnd) { |
|
81 return false; |
|
82 } |
|
83 std::wistringstream issProc(aArgv[++i]); |
|
84 issProc >> mParentProcess; |
|
85 if (!issProc) { |
|
86 return false; |
|
87 } |
|
88 // Only set the App User Model ID if it's present in the args |
|
89 if (wcscmp(aArgv[++i], L"-")) { |
|
90 HMODULE shell32 = LoadLibrary(L"shell32.dll"); |
|
91 if (shell32) { |
|
92 SETAPPUSERMODELID fSetAppUserModelID = (SETAPPUSERMODELID) |
|
93 GetProcAddress(shell32, "SetCurrentProcessExplicitAppUserModelID"); |
|
94 if (fSetAppUserModelID) { |
|
95 fSetAppUserModelID(aArgv[i]); |
|
96 } |
|
97 FreeLibrary(shell32); |
|
98 } |
|
99 } |
|
100 std::wistringstream issTimeout(aArgv[++i]); |
|
101 issTimeout >> mIPCTimeoutMs; |
|
102 if (!issTimeout) { |
|
103 return false; |
|
104 } |
|
105 |
|
106 nsresult rv = mMiniShm.Init(this, |
|
107 std::wstring(aArgv[++i]), |
|
108 IsDebuggerPresent() ? INFINITE : mIPCTimeoutMs); |
|
109 if (NS_FAILED(rv)) { |
|
110 return false; |
|
111 } |
|
112 sSelf = this; |
|
113 return true; |
|
114 } |
|
115 |
|
116 void |
|
117 PluginHangUIChild::OnMiniShmEvent(MiniShmBase* aMiniShmObj) |
|
118 { |
|
119 const PluginHangUICommand* cmd = nullptr; |
|
120 nsresult rv = aMiniShmObj->GetReadPtr(cmd); |
|
121 assert(NS_SUCCEEDED(rv)); |
|
122 bool returnStatus = false; |
|
123 if (NS_SUCCEEDED(rv)) { |
|
124 switch (cmd->mCode) { |
|
125 case PluginHangUICommand::HANGUI_CMD_SHOW: |
|
126 returnStatus = RecvShow(); |
|
127 break; |
|
128 case PluginHangUICommand::HANGUI_CMD_CANCEL: |
|
129 returnStatus = RecvCancel(); |
|
130 break; |
|
131 default: |
|
132 break; |
|
133 } |
|
134 } |
|
135 } |
|
136 |
|
137 // static |
|
138 INT_PTR CALLBACK |
|
139 PluginHangUIChild::SHangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, |
|
140 WPARAM aWParam, LPARAM aLParam) |
|
141 { |
|
142 PluginHangUIChild *self = PluginHangUIChild::sSelf; |
|
143 if (self) { |
|
144 return self->HangUIDlgProc(aDlgHandle, aMsgCode, aWParam, aLParam); |
|
145 } |
|
146 return FALSE; |
|
147 } |
|
148 |
|
149 void |
|
150 PluginHangUIChild::ResizeButtons() |
|
151 { |
|
152 // Control IDs are specified right-to-left as they appear in the dialog |
|
153 UINT ids[] = { IDC_STOP, IDC_CONTINUE }; |
|
154 UINT numIds = sizeof(ids)/sizeof(ids[0]); |
|
155 |
|
156 // Pass 1: Compute the ideal size |
|
157 bool needResizing = false; |
|
158 SIZE idealSize = {0}; |
|
159 WinInfoVec winInfo; |
|
160 for (UINT i = 0; i < numIds; ++i) { |
|
161 HWND wnd = GetDlgItem(mDlgHandle, ids[i]); |
|
162 if (!wnd) { |
|
163 return; |
|
164 } |
|
165 |
|
166 // Get the button's dimensions in screen coordinates |
|
167 RECT curRect; |
|
168 if (!GetWindowRect(wnd, &curRect)) { |
|
169 return; |
|
170 } |
|
171 |
|
172 // Get (x,y) position of the button in client coordinates |
|
173 POINT pt; |
|
174 pt.x = curRect.left; |
|
175 pt.y = curRect.top; |
|
176 if (!ScreenToClient(mDlgHandle, &pt)) { |
|
177 return; |
|
178 } |
|
179 |
|
180 // Request the button's text margins |
|
181 RECT margins; |
|
182 if (!Button_GetTextMargin(wnd, &margins)) { |
|
183 return; |
|
184 } |
|
185 |
|
186 // Compute the button's width and height |
|
187 SIZE curSize; |
|
188 curSize.cx = curRect.right - curRect.left; |
|
189 curSize.cy = curRect.bottom - curRect.top; |
|
190 |
|
191 // Request the button's ideal width and height and add in the margins |
|
192 SIZE size = {0}; |
|
193 if (!Button_GetIdealSize(wnd, &size)) { |
|
194 return; |
|
195 } |
|
196 size.cx += margins.left + margins.right; |
|
197 size.cy += margins.top + margins.bottom; |
|
198 |
|
199 // Size all buttons to be the same width as the longest button encountered |
|
200 idealSize.cx = std::max(idealSize.cx, size.cx); |
|
201 idealSize.cy = std::max(idealSize.cy, size.cy); |
|
202 |
|
203 // We won't bother resizing unless we need extra space |
|
204 if (idealSize.cx > curSize.cx) { |
|
205 needResizing = true; |
|
206 } |
|
207 |
|
208 // Save the relevant info for the resize, if any. We do this even if |
|
209 // needResizing is false because another button may trigger a resize later. |
|
210 winInfo.push_back(WinInfo(wnd, pt, curSize)); |
|
211 } |
|
212 |
|
213 if (!needResizing) { |
|
214 return; |
|
215 } |
|
216 |
|
217 // Pass 2: Resize the windows |
|
218 int deltaX = 0; |
|
219 HDWP hwp = BeginDeferWindowPos((int) winInfo.size()); |
|
220 if (!hwp) { |
|
221 return; |
|
222 } |
|
223 for (WinInfoVec::const_iterator itr = winInfo.begin(); |
|
224 itr != winInfo.end(); ++itr) { |
|
225 // deltaX accumulates the size changes so that each button's x coordinate |
|
226 // can compensate for the width increases |
|
227 deltaX += idealSize.cx - itr->size.cx; |
|
228 hwp = DeferWindowPos(hwp, itr->hwnd, nullptr, |
|
229 itr->pos.x - deltaX, itr->pos.y, |
|
230 idealSize.cx, itr->size.cy, |
|
231 SWP_NOZORDER | SWP_NOACTIVATE); |
|
232 if (!hwp) { |
|
233 return; |
|
234 } |
|
235 } |
|
236 EndDeferWindowPos(hwp); |
|
237 } |
|
238 |
|
239 INT_PTR |
|
240 PluginHangUIChild::HangUIDlgProc(HWND aDlgHandle, UINT aMsgCode, WPARAM aWParam, |
|
241 LPARAM aLParam) |
|
242 { |
|
243 mDlgHandle = aDlgHandle; |
|
244 switch (aMsgCode) { |
|
245 case WM_INITDIALOG: { |
|
246 // Register a wait on the Firefox process so that we will be informed |
|
247 // if it dies while the dialog is showing |
|
248 RegisterWaitForSingleObject(&mRegWaitProcess, |
|
249 mParentProcess, |
|
250 &SOnParentProcessExit, |
|
251 this, |
|
252 INFINITE, |
|
253 WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); |
|
254 SetWindowText(aDlgHandle, mWindowTitle); |
|
255 SetDlgItemText(aDlgHandle, IDC_MSG, mMessageText); |
|
256 SetDlgItemText(aDlgHandle, IDC_NOFUTURE, mNoFutureText); |
|
257 SetDlgItemText(aDlgHandle, IDC_CONTINUE, mWaitBtnText); |
|
258 SetDlgItemText(aDlgHandle, IDC_STOP, mKillBtnText); |
|
259 ResizeButtons(); |
|
260 HANDLE icon = LoadImage(nullptr, IDI_QUESTION, IMAGE_ICON, 0, 0, |
|
261 LR_DEFAULTSIZE | LR_SHARED); |
|
262 if (icon) { |
|
263 SendDlgItemMessage(aDlgHandle, IDC_DLGICON, STM_SETICON, (WPARAM)icon, 0); |
|
264 } |
|
265 EnableWindow(mParentWindow, FALSE); |
|
266 return TRUE; |
|
267 } |
|
268 case WM_CLOSE: { |
|
269 mResponseBits |= HANGUI_USER_RESPONSE_CANCEL; |
|
270 EndDialog(aDlgHandle, 0); |
|
271 SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); |
|
272 return TRUE; |
|
273 } |
|
274 case WM_COMMAND: { |
|
275 switch (LOWORD(aWParam)) { |
|
276 case IDC_CONTINUE: |
|
277 if (HIWORD(aWParam) == BN_CLICKED) { |
|
278 mResponseBits |= HANGUI_USER_RESPONSE_CONTINUE; |
|
279 EndDialog(aDlgHandle, 1); |
|
280 SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); |
|
281 return TRUE; |
|
282 } |
|
283 break; |
|
284 case IDC_STOP: |
|
285 if (HIWORD(aWParam) == BN_CLICKED) { |
|
286 mResponseBits |= HANGUI_USER_RESPONSE_STOP; |
|
287 EndDialog(aDlgHandle, 1); |
|
288 SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); |
|
289 return TRUE; |
|
290 } |
|
291 break; |
|
292 case IDC_NOFUTURE: |
|
293 if (HIWORD(aWParam) == BN_CLICKED) { |
|
294 if (Button_GetCheck(GetDlgItem(aDlgHandle, |
|
295 IDC_NOFUTURE)) == BST_CHECKED) { |
|
296 mResponseBits |= HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN; |
|
297 } else { |
|
298 mResponseBits &= |
|
299 ~static_cast<DWORD>(HANGUI_USER_RESPONSE_DONT_SHOW_AGAIN); |
|
300 } |
|
301 SetWindowLongPtr(aDlgHandle, DWLP_MSGRESULT, 0); |
|
302 return TRUE; |
|
303 } |
|
304 break; |
|
305 default: |
|
306 break; |
|
307 } |
|
308 break; |
|
309 } |
|
310 case WM_DESTROY: { |
|
311 EnableWindow(mParentWindow, TRUE); |
|
312 SetForegroundWindow(mParentWindow); |
|
313 break; |
|
314 } |
|
315 default: |
|
316 break; |
|
317 } |
|
318 return FALSE; |
|
319 } |
|
320 |
|
321 // static |
|
322 VOID CALLBACK |
|
323 PluginHangUIChild::SOnParentProcessExit(PVOID aObject, BOOLEAN aIsTimer) |
|
324 { |
|
325 // Simulate a cancel if the parent process died |
|
326 PluginHangUIChild* object = static_cast<PluginHangUIChild*>(aObject); |
|
327 object->RecvCancel(); |
|
328 } |
|
329 |
|
330 bool |
|
331 PluginHangUIChild::RecvShow() |
|
332 { |
|
333 return (QueueUserAPC(&ShowAPC, |
|
334 mMainThread, |
|
335 reinterpret_cast<ULONG_PTR>(this))); |
|
336 } |
|
337 |
|
338 bool |
|
339 PluginHangUIChild::Show() |
|
340 { |
|
341 INT_PTR dlgResult = DialogBox(GetModuleHandle(nullptr), |
|
342 MAKEINTRESOURCE(IDD_HANGUIDLG), |
|
343 nullptr, |
|
344 &SHangUIDlgProc); |
|
345 mDlgHandle = nullptr; |
|
346 assert(dlgResult != -1); |
|
347 bool result = false; |
|
348 if (dlgResult != -1) { |
|
349 PluginHangUIResponse* response = nullptr; |
|
350 nsresult rv = mMiniShm.GetWritePtr(response); |
|
351 if (NS_SUCCEEDED(rv)) { |
|
352 response->mResponseBits = mResponseBits; |
|
353 result = NS_SUCCEEDED(mMiniShm.Send()); |
|
354 } |
|
355 } |
|
356 return result; |
|
357 } |
|
358 |
|
359 // static |
|
360 VOID CALLBACK |
|
361 PluginHangUIChild::ShowAPC(ULONG_PTR aContext) |
|
362 { |
|
363 PluginHangUIChild* object = reinterpret_cast<PluginHangUIChild*>(aContext); |
|
364 object->Show(); |
|
365 } |
|
366 |
|
367 bool |
|
368 PluginHangUIChild::RecvCancel() |
|
369 { |
|
370 if (mDlgHandle) { |
|
371 PostMessage(mDlgHandle, WM_CLOSE, 0, 0); |
|
372 } |
|
373 return true; |
|
374 } |
|
375 |
|
376 bool |
|
377 PluginHangUIChild::WaitForDismissal() |
|
378 { |
|
379 if (!SetMainThread()) { |
|
380 return false; |
|
381 } |
|
382 DWORD waitResult = WaitForSingleObjectEx(mParentProcess, |
|
383 mIPCTimeoutMs, |
|
384 TRUE); |
|
385 return waitResult == WAIT_OBJECT_0 || |
|
386 waitResult == WAIT_IO_COMPLETION; |
|
387 } |
|
388 |
|
389 bool |
|
390 PluginHangUIChild::SetMainThread() |
|
391 { |
|
392 if (mMainThread) { |
|
393 CloseHandle(mMainThread); |
|
394 mMainThread = nullptr; |
|
395 } |
|
396 mMainThread = OpenThread(THREAD_SET_CONTEXT, |
|
397 FALSE, |
|
398 GetCurrentThreadId()); |
|
399 return !(!mMainThread); |
|
400 } |
|
401 |
|
402 } // namespace plugins |
|
403 } // namespace mozilla |
|
404 |
|
405 #ifdef __MINGW32__ |
|
406 extern "C" |
|
407 #endif |
|
408 int |
|
409 wmain(int argc, wchar_t *argv[]) |
|
410 { |
|
411 INITCOMMONCONTROLSEX icc = { sizeof(INITCOMMONCONTROLSEX), |
|
412 ICC_STANDARD_CLASSES }; |
|
413 if (!InitCommonControlsEx(&icc)) { |
|
414 return 1; |
|
415 } |
|
416 mozilla::plugins::PluginHangUIChild hangui; |
|
417 if (!hangui.Init(argc, argv)) { |
|
418 return 1; |
|
419 } |
|
420 if (!hangui.WaitForDismissal()) { |
|
421 return 1; |
|
422 } |
|
423 return 0; |
|
424 } |
|
425 |