|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
|
2 * vim: sw=2 ts=2 et : |
|
3 */ |
|
4 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
5 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
7 |
|
8 #include "mozilla/DebugOnly.h" |
|
9 |
|
10 #include "WindowsMessageLoop.h" |
|
11 #include "MessageChannel.h" |
|
12 |
|
13 #include "nsAutoPtr.h" |
|
14 #include "nsServiceManagerUtils.h" |
|
15 #include "nsString.h" |
|
16 #include "nsIXULAppInfo.h" |
|
17 |
|
18 #include "mozilla/PaintTracker.h" |
|
19 |
|
20 using namespace mozilla; |
|
21 using namespace mozilla::ipc; |
|
22 using namespace mozilla::ipc::windows; |
|
23 |
|
24 /** |
|
25 * The Windows-only code below exists to solve a general problem with deadlocks |
|
26 * that we experience when sending synchronous IPC messages to processes that |
|
27 * contain native windows (i.e. HWNDs). Windows (the OS) sends synchronous |
|
28 * messages between parent and child HWNDs in multiple circumstances (e.g. |
|
29 * WM_PARENTNOTIFY, WM_NCACTIVATE, etc.), even when those HWNDs are controlled |
|
30 * by different threads or different processes. Thus we can very easily end up |
|
31 * in a deadlock by a call stack like the following: |
|
32 * |
|
33 * Process A: |
|
34 * - CreateWindow(...) creates a "parent" HWND. |
|
35 * - SendCreateChildWidget(HWND) is a sync IPC message that sends the "parent" |
|
36 * HWND over to Process B. Process A blocks until a response is received |
|
37 * from Process B. |
|
38 * |
|
39 * Process B: |
|
40 * - RecvCreateWidget(HWND) gets the "parent" HWND from Process A. |
|
41 * - CreateWindow(..., HWND) creates a "child" HWND with the parent from |
|
42 * process A. |
|
43 * - Windows (the OS) generates a WM_PARENTNOTIFY message that is sent |
|
44 * synchronously to Process A. Process B blocks until a response is |
|
45 * received from Process A. Process A, however, is blocked and cannot |
|
46 * process the message. Both processes are deadlocked. |
|
47 * |
|
48 * The example above has a few different workarounds (e.g. setting the |
|
49 * WS_EX_NOPARENTNOTIFY style on the child window) but the general problem is |
|
50 * persists. Once two HWNDs are parented we must not block their owning |
|
51 * threads when manipulating either HWND. |
|
52 * |
|
53 * Windows requires any application that hosts native HWNDs to always process |
|
54 * messages or risk deadlock. Given our architecture the only way to meet |
|
55 * Windows' requirement and allow for synchronous IPC messages is to pump a |
|
56 * miniature message loop during a sync IPC call. We avoid processing any |
|
57 * queued messages during the loop (with one exception, see below), but |
|
58 * "nonqueued" messages (see |
|
59 * http://msdn.microsoft.com/en-us/library/ms644927(VS.85).aspx under the |
|
60 * section "Nonqueued messages") cannot be avoided. Those messages are trapped |
|
61 * in a special window procedure where we can either ignore the message or |
|
62 * process it in some fashion. |
|
63 * |
|
64 * Queued and "non-queued" messages will be processed during Interrupt calls if |
|
65 * modal UI related api calls block an Interrupt in-call in the child. To prevent |
|
66 * windows from freezing, and to allow concurrent processing of critical |
|
67 * events (such as painting), we spin a native event dispatch loop while |
|
68 * these in-calls are blocked. |
|
69 */ |
|
70 |
|
71 #if defined(ACCESSIBILITY) |
|
72 // pulled from accessibility's win utils |
|
73 extern const wchar_t* kPropNameTabContent; |
|
74 #endif |
|
75 |
|
76 // widget related message id constants we need to defer |
|
77 namespace mozilla { |
|
78 namespace widget { |
|
79 extern UINT sAppShellGeckoMsgId; |
|
80 #ifdef MOZ_METRO |
|
81 extern UINT sDefaultBrowserMsgId; |
|
82 #endif |
|
83 } |
|
84 } |
|
85 |
|
86 namespace { |
|
87 |
|
88 const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc"; |
|
89 |
|
90 // This isn't defined before Windows XP. |
|
91 enum { WM_XP_THEMECHANGED = 0x031A }; |
|
92 |
|
93 char16_t gAppMessageWindowName[256] = { 0 }; |
|
94 int32_t gAppMessageWindowNameLength = 0; |
|
95 |
|
96 nsTArray<HWND>* gNeuteredWindows = nullptr; |
|
97 |
|
98 typedef nsTArray<nsAutoPtr<DeferredMessage> > DeferredMessageArray; |
|
99 DeferredMessageArray* gDeferredMessages = nullptr; |
|
100 |
|
101 HHOOK gDeferredGetMsgHook = nullptr; |
|
102 HHOOK gDeferredCallWndProcHook = nullptr; |
|
103 |
|
104 DWORD gUIThreadId = 0; |
|
105 |
|
106 LRESULT CALLBACK |
|
107 DeferredMessageHook(int nCode, |
|
108 WPARAM wParam, |
|
109 LPARAM lParam) |
|
110 { |
|
111 // XXX This function is called for *both* the WH_CALLWNDPROC hook and the |
|
112 // WH_GETMESSAGE hook, but they have different parameters. We don't |
|
113 // use any of them except nCode which has the same meaning. |
|
114 |
|
115 // Only run deferred messages if all of these conditions are met: |
|
116 // 1. The |nCode| indicates that this hook should do something. |
|
117 // 2. We have deferred messages to run. |
|
118 // 3. We're not being called from the PeekMessage within the WaitFor*Notify |
|
119 // function (indicated with MessageChannel::IsPumpingMessages). We really |
|
120 // only want to run after returning to the main event loop. |
|
121 if (nCode >= 0 && gDeferredMessages && !MessageChannel::IsPumpingMessages()) { |
|
122 NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook, |
|
123 "These hooks must be set if we're being called!"); |
|
124 NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!"); |
|
125 |
|
126 // Unset hooks first, in case we reenter below. |
|
127 UnhookWindowsHookEx(gDeferredGetMsgHook); |
|
128 UnhookWindowsHookEx(gDeferredCallWndProcHook); |
|
129 gDeferredGetMsgHook = 0; |
|
130 gDeferredCallWndProcHook = 0; |
|
131 |
|
132 // Unset the global and make sure we delete it when we're done here. |
|
133 nsAutoPtr<DeferredMessageArray> messages(gDeferredMessages); |
|
134 gDeferredMessages = nullptr; |
|
135 |
|
136 // Run all the deferred messages in order. |
|
137 uint32_t count = messages->Length(); |
|
138 for (uint32_t index = 0; index < count; index++) { |
|
139 messages->ElementAt(index)->Run(); |
|
140 } |
|
141 } |
|
142 |
|
143 // Always call the next hook. |
|
144 return CallNextHookEx(nullptr, nCode, wParam, lParam); |
|
145 } |
|
146 |
|
147 void |
|
148 ScheduleDeferredMessageRun() |
|
149 { |
|
150 if (gDeferredMessages && |
|
151 !(gDeferredGetMsgHook && gDeferredCallWndProcHook)) { |
|
152 NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!"); |
|
153 |
|
154 gDeferredGetMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, DeferredMessageHook, |
|
155 nullptr, gUIThreadId); |
|
156 gDeferredCallWndProcHook = ::SetWindowsHookEx(WH_CALLWNDPROC, |
|
157 DeferredMessageHook, nullptr, |
|
158 gUIThreadId); |
|
159 NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook, |
|
160 "Failed to set hooks!"); |
|
161 } |
|
162 } |
|
163 |
|
164 LRESULT |
|
165 ProcessOrDeferMessage(HWND hwnd, |
|
166 UINT uMsg, |
|
167 WPARAM wParam, |
|
168 LPARAM lParam) |
|
169 { |
|
170 DeferredMessage* deferred = nullptr; |
|
171 |
|
172 // Most messages ask for 0 to be returned if the message is processed. |
|
173 LRESULT res = 0; |
|
174 |
|
175 switch (uMsg) { |
|
176 // Messages that can be deferred as-is. These must not contain pointers in |
|
177 // their wParam or lParam arguments! |
|
178 case WM_ACTIVATE: |
|
179 case WM_ACTIVATEAPP: |
|
180 case WM_CANCELMODE: |
|
181 case WM_CAPTURECHANGED: |
|
182 case WM_CHILDACTIVATE: |
|
183 case WM_DESTROY: |
|
184 case WM_ENABLE: |
|
185 case WM_IME_NOTIFY: |
|
186 case WM_IME_SETCONTEXT: |
|
187 case WM_KILLFOCUS: |
|
188 case WM_MOUSEWHEEL: |
|
189 case WM_NCDESTROY: |
|
190 case WM_PARENTNOTIFY: |
|
191 case WM_SETFOCUS: |
|
192 case WM_SYSCOMMAND: |
|
193 case WM_DISPLAYCHANGE: |
|
194 case WM_SHOWWINDOW: // Intentional fall-through. |
|
195 case WM_XP_THEMECHANGED: { |
|
196 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); |
|
197 break; |
|
198 } |
|
199 |
|
200 case WM_DEVICECHANGE: |
|
201 case WM_POWERBROADCAST: |
|
202 case WM_NCACTIVATE: // Intentional fall-through. |
|
203 case WM_SETCURSOR: { |
|
204 // Friggin unconventional return value... |
|
205 res = TRUE; |
|
206 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); |
|
207 break; |
|
208 } |
|
209 |
|
210 case WM_MOUSEACTIVATE: { |
|
211 res = MA_NOACTIVATE; |
|
212 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); |
|
213 break; |
|
214 } |
|
215 |
|
216 // These messages need to use the RedrawWindow function to generate the |
|
217 // right kind of message. We can't simply fake them as the MSDN docs say |
|
218 // explicitly that paint messages should not be sent by an application. |
|
219 case WM_ERASEBKGND: { |
|
220 UINT flags = RDW_INVALIDATE | RDW_ERASE | RDW_NOINTERNALPAINT | |
|
221 RDW_NOFRAME | RDW_NOCHILDREN | RDW_ERASENOW; |
|
222 deferred = new DeferredRedrawMessage(hwnd, flags); |
|
223 break; |
|
224 } |
|
225 |
|
226 // This message will generate a WM_PAINT message if there are invalid |
|
227 // areas. |
|
228 case WM_PAINT: { |
|
229 deferred = new DeferredUpdateMessage(hwnd); |
|
230 break; |
|
231 } |
|
232 |
|
233 // This message holds a string in its lParam that we must copy. |
|
234 case WM_SETTINGCHANGE: { |
|
235 deferred = new DeferredSettingChangeMessage(hwnd, uMsg, wParam, lParam); |
|
236 break; |
|
237 } |
|
238 |
|
239 // These messages are faked via a call to SetWindowPos. |
|
240 case WM_WINDOWPOSCHANGED: { |
|
241 deferred = new DeferredWindowPosMessage(hwnd, lParam); |
|
242 break; |
|
243 } |
|
244 case WM_NCCALCSIZE: { |
|
245 deferred = new DeferredWindowPosMessage(hwnd, lParam, true, wParam); |
|
246 break; |
|
247 } |
|
248 |
|
249 case WM_COPYDATA: { |
|
250 deferred = new DeferredCopyDataMessage(hwnd, uMsg, wParam, lParam); |
|
251 res = TRUE; |
|
252 break; |
|
253 } |
|
254 |
|
255 case WM_STYLECHANGED: { |
|
256 deferred = new DeferredStyleChangeMessage(hwnd, wParam, lParam); |
|
257 break; |
|
258 } |
|
259 |
|
260 case WM_SETICON: { |
|
261 deferred = new DeferredSetIconMessage(hwnd, uMsg, wParam, lParam); |
|
262 break; |
|
263 } |
|
264 |
|
265 // Messages that are safe to pass to DefWindowProc go here. |
|
266 case WM_ENTERIDLE: |
|
267 case WM_GETICON: |
|
268 case WM_NCPAINT: // (never trap nc paint events) |
|
269 case WM_GETMINMAXINFO: |
|
270 case WM_GETTEXT: |
|
271 case WM_NCHITTEST: |
|
272 case WM_STYLECHANGING: // Intentional fall-through. |
|
273 case WM_WINDOWPOSCHANGING: { |
|
274 return DefWindowProc(hwnd, uMsg, wParam, lParam); |
|
275 } |
|
276 |
|
277 // Just return, prevents DefWindowProc from messaging the window |
|
278 // syncronously with other events, which may be deferred. Prevents |
|
279 // random shutdown of aero composition on the window. |
|
280 case WM_SYNCPAINT: |
|
281 return 0; |
|
282 |
|
283 // This message causes QuickTime to make re-entrant calls. |
|
284 // Simply discarding it doesn't seem to hurt anything. |
|
285 case WM_APP-1: |
|
286 return 0; |
|
287 |
|
288 default: { |
|
289 if (uMsg && uMsg == mozilla::widget::sAppShellGeckoMsgId) { |
|
290 // Widget's registered native event callback |
|
291 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); |
|
292 #ifdef MOZ_METRO |
|
293 } else if (uMsg && uMsg == mozilla::widget::sDefaultBrowserMsgId) { |
|
294 // Metro widget's system shutdown message |
|
295 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); |
|
296 #endif |
|
297 } else { |
|
298 // Unknown messages only |
|
299 #ifdef DEBUG |
|
300 nsAutoCString log("Received \"nonqueued\" message "); |
|
301 log.AppendInt(uMsg); |
|
302 log.AppendLiteral(" during a synchronous IPC message for window "); |
|
303 log.AppendInt((int64_t)hwnd); |
|
304 |
|
305 wchar_t className[256] = { 0 }; |
|
306 if (GetClassNameW(hwnd, className, sizeof(className) - 1) > 0) { |
|
307 log.AppendLiteral(" (\""); |
|
308 log.Append(NS_ConvertUTF16toUTF8((char16_t*)className)); |
|
309 log.AppendLiteral("\")"); |
|
310 } |
|
311 |
|
312 log.AppendLiteral(", sending it to DefWindowProc instead of the normal " |
|
313 "window procedure."); |
|
314 NS_ERROR(log.get()); |
|
315 #endif |
|
316 return DefWindowProc(hwnd, uMsg, wParam, lParam); |
|
317 } |
|
318 } |
|
319 } |
|
320 |
|
321 NS_ASSERTION(deferred, "Must have a message here!"); |
|
322 |
|
323 // Create the deferred message array if it doesn't exist already. |
|
324 if (!gDeferredMessages) { |
|
325 gDeferredMessages = new nsTArray<nsAutoPtr<DeferredMessage> >(20); |
|
326 NS_ASSERTION(gDeferredMessages, "Out of memory!"); |
|
327 } |
|
328 |
|
329 // Save for later. The array takes ownership of |deferred|. |
|
330 gDeferredMessages->AppendElement(deferred); |
|
331 return res; |
|
332 } |
|
333 |
|
334 } // anonymous namespace |
|
335 |
|
336 // We need the pointer value of this in PluginInstanceChild. |
|
337 LRESULT CALLBACK |
|
338 NeuteredWindowProc(HWND hwnd, |
|
339 UINT uMsg, |
|
340 WPARAM wParam, |
|
341 LPARAM lParam) |
|
342 { |
|
343 WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp); |
|
344 if (!oldWndProc) { |
|
345 // We should really never ever get here. |
|
346 NS_ERROR("No old wndproc!"); |
|
347 return DefWindowProc(hwnd, uMsg, wParam, lParam); |
|
348 } |
|
349 |
|
350 // See if we care about this message. We may either ignore it, send it to |
|
351 // DefWindowProc, or defer it for later. |
|
352 return ProcessOrDeferMessage(hwnd, uMsg, wParam, lParam); |
|
353 } |
|
354 |
|
355 namespace { |
|
356 |
|
357 static bool |
|
358 WindowIsDeferredWindow(HWND hWnd) |
|
359 { |
|
360 if (!IsWindow(hWnd)) { |
|
361 NS_WARNING("Window has died!"); |
|
362 return false; |
|
363 } |
|
364 |
|
365 char16_t buffer[256] = { 0 }; |
|
366 int length = GetClassNameW(hWnd, (wchar_t*)buffer, sizeof(buffer) - 1); |
|
367 if (length <= 0) { |
|
368 NS_WARNING("Failed to get class name!"); |
|
369 return false; |
|
370 } |
|
371 |
|
372 #if defined(ACCESSIBILITY) |
|
373 // Tab content creates a window that responds to accessible WM_GETOBJECT |
|
374 // calls. This window can safely be ignored. |
|
375 if (::GetPropW(hWnd, kPropNameTabContent)) { |
|
376 return false; |
|
377 } |
|
378 #endif |
|
379 |
|
380 // Common mozilla windows we must defer messages to. |
|
381 nsDependentString className(buffer, length); |
|
382 if (StringBeginsWith(className, NS_LITERAL_STRING("Mozilla")) || |
|
383 StringBeginsWith(className, NS_LITERAL_STRING("Gecko")) || |
|
384 className.EqualsLiteral("nsToolkitClass") || |
|
385 className.EqualsLiteral("nsAppShell:EventWindowClass")) { |
|
386 return true; |
|
387 } |
|
388 |
|
389 #ifdef MOZ_METRO |
|
390 // immersive UI ICoreWindow |
|
391 if (className.EqualsLiteral("Windows.UI.Core.CoreWindow")) { |
|
392 return true; |
|
393 } |
|
394 #endif |
|
395 |
|
396 // Plugin windows that can trigger ipc calls in child: |
|
397 // 'ShockwaveFlashFullScreen' - flash fullscreen window |
|
398 // 'QTNSHIDDEN' - QuickTime |
|
399 // 'AGFullScreenWinClass' - silverlight fullscreen window |
|
400 if (className.EqualsLiteral("ShockwaveFlashFullScreen") || |
|
401 className.EqualsLiteral("QTNSHIDDEN") || |
|
402 className.EqualsLiteral("AGFullScreenWinClass")) { |
|
403 return true; |
|
404 } |
|
405 |
|
406 // Google Earth bridging msg window between the plugin instance and a separate |
|
407 // earth process. The earth process can trigger a plugin incall on the browser |
|
408 // at any time, which is badness if the instance is already making an incall. |
|
409 if (className.EqualsLiteral("__geplugin_bridge_window__")) { |
|
410 return true; |
|
411 } |
|
412 |
|
413 // nsNativeAppSupport makes a window like "FirefoxMessageWindow" based on the |
|
414 // toolkit app's name. It's pretty expensive to calculate this so we only try |
|
415 // once. |
|
416 if (gAppMessageWindowNameLength == 0) { |
|
417 nsCOMPtr<nsIXULAppInfo> appInfo = |
|
418 do_GetService("@mozilla.org/xre/app-info;1"); |
|
419 if (appInfo) { |
|
420 nsAutoCString appName; |
|
421 if (NS_SUCCEEDED(appInfo->GetName(appName))) { |
|
422 appName.Append("MessageWindow"); |
|
423 nsDependentString windowName(gAppMessageWindowName); |
|
424 CopyUTF8toUTF16(appName, windowName); |
|
425 gAppMessageWindowNameLength = windowName.Length(); |
|
426 } |
|
427 } |
|
428 |
|
429 // Don't try again if that failed. |
|
430 if (gAppMessageWindowNameLength == 0) { |
|
431 gAppMessageWindowNameLength = -1; |
|
432 } |
|
433 } |
|
434 |
|
435 if (gAppMessageWindowNameLength != -1 && |
|
436 className.Equals(nsDependentString(gAppMessageWindowName, |
|
437 gAppMessageWindowNameLength))) { |
|
438 return true; |
|
439 } |
|
440 |
|
441 return false; |
|
442 } |
|
443 |
|
444 bool |
|
445 NeuterWindowProcedure(HWND hWnd) |
|
446 { |
|
447 if (!WindowIsDeferredWindow(hWnd)) { |
|
448 // Some other kind of window, skip. |
|
449 return false; |
|
450 } |
|
451 |
|
452 NS_ASSERTION(!GetProp(hWnd, kOldWndProcProp), "This should always be null!"); |
|
453 |
|
454 // It's possible to get nullptr out of SetWindowLongPtr, and the only way to |
|
455 // know if that's a valid old value is to use GetLastError. Clear the error |
|
456 // here so we can tell. |
|
457 SetLastError(ERROR_SUCCESS); |
|
458 |
|
459 LONG_PTR currentWndProc = |
|
460 SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)NeuteredWindowProc); |
|
461 if (!currentWndProc) { |
|
462 if (ERROR_SUCCESS == GetLastError()) { |
|
463 // No error, so we set something and must therefore reset it. |
|
464 SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc); |
|
465 } |
|
466 return false; |
|
467 } |
|
468 |
|
469 NS_ASSERTION(currentWndProc != (LONG_PTR)NeuteredWindowProc, |
|
470 "This shouldn't be possible!"); |
|
471 |
|
472 if (!SetProp(hWnd, kOldWndProcProp, (HANDLE)currentWndProc)) { |
|
473 // Cleanup |
|
474 NS_WARNING("SetProp failed!"); |
|
475 SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc); |
|
476 RemoveProp(hWnd, kOldWndProcProp); |
|
477 return false; |
|
478 } |
|
479 |
|
480 return true; |
|
481 } |
|
482 |
|
483 void |
|
484 RestoreWindowProcedure(HWND hWnd) |
|
485 { |
|
486 NS_ASSERTION(WindowIsDeferredWindow(hWnd), |
|
487 "Not a deferred window, this shouldn't be in our list!"); |
|
488 LONG_PTR oldWndProc = (LONG_PTR)GetProp(hWnd, kOldWndProcProp); |
|
489 if (oldWndProc) { |
|
490 NS_ASSERTION(oldWndProc != (LONG_PTR)NeuteredWindowProc, |
|
491 "This shouldn't be possible!"); |
|
492 |
|
493 DebugOnly<LONG_PTR> currentWndProc = |
|
494 SetWindowLongPtr(hWnd, GWLP_WNDPROC, oldWndProc); |
|
495 NS_ASSERTION(currentWndProc == (LONG_PTR)NeuteredWindowProc, |
|
496 "This should never be switched out from under us!"); |
|
497 } |
|
498 RemoveProp(hWnd, kOldWndProcProp); |
|
499 } |
|
500 |
|
501 LRESULT CALLBACK |
|
502 CallWindowProcedureHook(int nCode, |
|
503 WPARAM wParam, |
|
504 LPARAM lParam) |
|
505 { |
|
506 if (nCode >= 0) { |
|
507 NS_ASSERTION(gNeuteredWindows, "This should never be null!"); |
|
508 |
|
509 HWND hWnd = reinterpret_cast<CWPSTRUCT*>(lParam)->hwnd; |
|
510 |
|
511 if (!gNeuteredWindows->Contains(hWnd) && NeuterWindowProcedure(hWnd)) { |
|
512 if (!gNeuteredWindows->AppendElement(hWnd)) { |
|
513 NS_ERROR("Out of memory!"); |
|
514 RestoreWindowProcedure(hWnd); |
|
515 } |
|
516 } |
|
517 } |
|
518 return CallNextHookEx(nullptr, nCode, wParam, lParam); |
|
519 } |
|
520 |
|
521 inline void |
|
522 AssertWindowIsNotNeutered(HWND hWnd) |
|
523 { |
|
524 #ifdef DEBUG |
|
525 // Make sure our neutered window hook isn't still in place. |
|
526 LONG_PTR wndproc = GetWindowLongPtr(hWnd, GWLP_WNDPROC); |
|
527 NS_ASSERTION(wndproc != (LONG_PTR)NeuteredWindowProc, "Window is neutered!"); |
|
528 #endif |
|
529 } |
|
530 |
|
531 void |
|
532 UnhookNeuteredWindows() |
|
533 { |
|
534 if (!gNeuteredWindows) |
|
535 return; |
|
536 uint32_t count = gNeuteredWindows->Length(); |
|
537 for (uint32_t index = 0; index < count; index++) { |
|
538 RestoreWindowProcedure(gNeuteredWindows->ElementAt(index)); |
|
539 } |
|
540 gNeuteredWindows->Clear(); |
|
541 } |
|
542 |
|
543 // This timeout stuff assumes a sane value of mTimeoutMs (less than the overflow |
|
544 // value for GetTickCount(), which is something like 50 days). It uses the |
|
545 // cheapest (and least accurate) method supported by Windows 2000. |
|
546 |
|
547 struct TimeoutData |
|
548 { |
|
549 DWORD startTicks; |
|
550 DWORD targetTicks; |
|
551 }; |
|
552 |
|
553 void |
|
554 InitTimeoutData(TimeoutData* aData, |
|
555 int32_t aTimeoutMs) |
|
556 { |
|
557 aData->startTicks = GetTickCount(); |
|
558 if (!aData->startTicks) { |
|
559 // How unlikely is this! |
|
560 aData->startTicks++; |
|
561 } |
|
562 aData->targetTicks = aData->startTicks + aTimeoutMs; |
|
563 } |
|
564 |
|
565 |
|
566 bool |
|
567 TimeoutHasExpired(const TimeoutData& aData) |
|
568 { |
|
569 if (!aData.startTicks) { |
|
570 return false; |
|
571 } |
|
572 |
|
573 DWORD now = GetTickCount(); |
|
574 |
|
575 if (aData.targetTicks < aData.startTicks) { |
|
576 // Overflow |
|
577 return now < aData.startTicks && now >= aData.targetTicks; |
|
578 } |
|
579 return now >= aData.targetTicks; |
|
580 } |
|
581 |
|
582 } // anonymous namespace |
|
583 |
|
584 namespace mozilla { |
|
585 namespace ipc { |
|
586 namespace windows { |
|
587 |
|
588 void |
|
589 InitUIThread() |
|
590 { |
|
591 // If we aren't setup before a call to NotifyWorkerThread, we'll hang |
|
592 // on startup. |
|
593 if (!gUIThreadId) { |
|
594 gUIThreadId = GetCurrentThreadId(); |
|
595 } |
|
596 MOZ_ASSERT(gUIThreadId); |
|
597 MOZ_ASSERT(gUIThreadId == GetCurrentThreadId(), |
|
598 "Called InitUIThread multiple times on different threads!"); |
|
599 } |
|
600 |
|
601 } // namespace windows |
|
602 } // namespace ipc |
|
603 } // namespace mozilla |
|
604 |
|
605 // See SpinInternalEventLoop below |
|
606 MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel* channel, bool interrupt) |
|
607 : mInterrupt(interrupt) |
|
608 , mSpinNestedEvents(false) |
|
609 , mListenerNotified(false) |
|
610 , mChannel(channel) |
|
611 , mPrev(mChannel->mTopFrame) |
|
612 , mStaticPrev(sStaticTopFrame) |
|
613 { |
|
614 // Only track stack frames when Windows message deferral behavior |
|
615 // is request for the channel. |
|
616 if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { |
|
617 return; |
|
618 } |
|
619 |
|
620 mChannel->mTopFrame = this; |
|
621 sStaticTopFrame = this; |
|
622 |
|
623 if (!mStaticPrev) { |
|
624 NS_ASSERTION(!gNeuteredWindows, "Should only set this once!"); |
|
625 gNeuteredWindows = new nsAutoTArray<HWND, 20>(); |
|
626 NS_ASSERTION(gNeuteredWindows, "Out of memory!"); |
|
627 } |
|
628 } |
|
629 |
|
630 MessageChannel::SyncStackFrame::~SyncStackFrame() |
|
631 { |
|
632 if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { |
|
633 return; |
|
634 } |
|
635 |
|
636 NS_ASSERTION(this == mChannel->mTopFrame, |
|
637 "Mismatched interrupt stack frames"); |
|
638 NS_ASSERTION(this == sStaticTopFrame, |
|
639 "Mismatched static Interrupt stack frames"); |
|
640 |
|
641 mChannel->mTopFrame = mPrev; |
|
642 sStaticTopFrame = mStaticPrev; |
|
643 |
|
644 if (!mStaticPrev) { |
|
645 NS_ASSERTION(gNeuteredWindows, "Bad pointer!"); |
|
646 delete gNeuteredWindows; |
|
647 gNeuteredWindows = nullptr; |
|
648 } |
|
649 } |
|
650 |
|
651 MessageChannel::SyncStackFrame* MessageChannel::sStaticTopFrame; |
|
652 |
|
653 // nsAppShell's notification that gecko events are being processed. |
|
654 // If we are here and there is an Interrupt Incall active, we are spinning |
|
655 // a nested gecko event loop. In which case the remote process needs |
|
656 // to know about it. |
|
657 void /* static */ |
|
658 MessageChannel::NotifyGeckoEventDispatch() |
|
659 { |
|
660 // sStaticTopFrame is only valid for Interrupt channels |
|
661 if (!sStaticTopFrame || sStaticTopFrame->mListenerNotified) |
|
662 return; |
|
663 |
|
664 sStaticTopFrame->mListenerNotified = true; |
|
665 MessageChannel* channel = static_cast<MessageChannel*>(sStaticTopFrame->mChannel); |
|
666 channel->Listener()->ProcessRemoteNativeEventsInInterruptCall(); |
|
667 } |
|
668 |
|
669 // invoked by the module that receives the spin event loop |
|
670 // message. |
|
671 void |
|
672 MessageChannel::ProcessNativeEventsInInterruptCall() |
|
673 { |
|
674 NS_ASSERTION(GetCurrentThreadId() == gUIThreadId, |
|
675 "Shouldn't be on a non-main thread in here!"); |
|
676 if (!mTopFrame) { |
|
677 NS_ERROR("Spin logic error: no Interrupt frame"); |
|
678 return; |
|
679 } |
|
680 |
|
681 mTopFrame->mSpinNestedEvents = true; |
|
682 } |
|
683 |
|
684 // Spin loop is called in place of WaitFor*Notify when modal ui is being shown |
|
685 // in a child. There are some intricacies in using it however. Spin loop is |
|
686 // enabled for a particular Interrupt frame by the client calling |
|
687 // MessageChannel::ProcessNativeEventsInInterrupt(). |
|
688 // This call can be nested for multiple Interrupt frames in a single plugin or |
|
689 // multiple unrelated plugins. |
|
690 void |
|
691 MessageChannel::SpinInternalEventLoop() |
|
692 { |
|
693 if (mozilla::PaintTracker::IsPainting()) { |
|
694 NS_RUNTIMEABORT("Don't spin an event loop while painting."); |
|
695 } |
|
696 |
|
697 NS_ASSERTION(mTopFrame && mTopFrame->mSpinNestedEvents, |
|
698 "Spinning incorrectly"); |
|
699 |
|
700 // Nested windows event loop we trigger when the child enters into modal |
|
701 // event loops. |
|
702 |
|
703 // Note, when we return, we always reset the notify worker event. So there's |
|
704 // no need to reset it on return here. |
|
705 |
|
706 do { |
|
707 MSG msg = { 0 }; |
|
708 |
|
709 // Don't get wrapped up in here if the child connection dies. |
|
710 { |
|
711 MonitorAutoLock lock(*mMonitor); |
|
712 if (!Connected()) { |
|
713 return; |
|
714 } |
|
715 } |
|
716 |
|
717 // Retrieve window or thread messages |
|
718 if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { |
|
719 // The child UI should have been destroyed before the app is closed, in |
|
720 // which case, we should never get this here. |
|
721 if (msg.message == WM_QUIT) { |
|
722 NS_ERROR("WM_QUIT received in SpinInternalEventLoop!"); |
|
723 } else { |
|
724 TranslateMessage(&msg); |
|
725 ::DispatchMessageW(&msg); |
|
726 return; |
|
727 } |
|
728 } |
|
729 |
|
730 // Note, give dispatching windows events priority over checking if |
|
731 // mEvent is signaled, otherwise heavy ipc traffic can cause jittery |
|
732 // playback of video. We'll exit out on each disaptch above, so ipc |
|
733 // won't get starved. |
|
734 |
|
735 // Wait for UI events or a signal from the io thread. |
|
736 DWORD result = MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, |
|
737 QS_ALLINPUT); |
|
738 if (result == WAIT_OBJECT_0) { |
|
739 // Our NotifyWorkerThread event was signaled |
|
740 return; |
|
741 } |
|
742 } while (true); |
|
743 } |
|
744 |
|
745 static inline bool |
|
746 IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout) |
|
747 { |
|
748 return (aTimeout != PR_INTERVAL_NO_TIMEOUT) && |
|
749 (aTimeout <= (PR_IntervalNow() - aStart)); |
|
750 } |
|
751 |
|
752 bool |
|
753 MessageChannel::WaitForSyncNotify() |
|
754 { |
|
755 mMonitor->AssertCurrentThreadOwns(); |
|
756 |
|
757 MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!"); |
|
758 |
|
759 // Use a blocking wait if this channel does not require |
|
760 // Windows message deferral behavior. |
|
761 if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { |
|
762 PRIntervalTime timeout = (kNoTimeout == mTimeoutMs) ? |
|
763 PR_INTERVAL_NO_TIMEOUT : |
|
764 PR_MillisecondsToInterval(mTimeoutMs); |
|
765 // XXX could optimize away this syscall for "no timeout" case if desired |
|
766 PRIntervalTime waitStart = 0; |
|
767 |
|
768 if (timeout != PR_INTERVAL_NO_TIMEOUT) { |
|
769 waitStart = PR_IntervalNow(); |
|
770 } |
|
771 |
|
772 mIsSyncWaitingOnNonMainThread = true; |
|
773 |
|
774 mMonitor->Wait(timeout); |
|
775 |
|
776 mIsSyncWaitingOnNonMainThread = false; |
|
777 |
|
778 // If the timeout didn't expire, we know we received an event. The |
|
779 // converse is not true. |
|
780 return WaitResponse(timeout == PR_INTERVAL_NO_TIMEOUT ? |
|
781 false : IsTimeoutExpired(waitStart, timeout)); |
|
782 } |
|
783 |
|
784 NS_ASSERTION(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION, |
|
785 "Shouldn't be here for channels that don't use message deferral!"); |
|
786 NS_ASSERTION(mTopFrame && !mTopFrame->mInterrupt, |
|
787 "Top frame is not a sync frame!"); |
|
788 |
|
789 MonitorAutoUnlock unlock(*mMonitor); |
|
790 |
|
791 bool timedout = false; |
|
792 |
|
793 UINT_PTR timerId = 0; |
|
794 TimeoutData timeoutData = { 0 }; |
|
795 |
|
796 if (mTimeoutMs != kNoTimeout) { |
|
797 InitTimeoutData(&timeoutData, mTimeoutMs); |
|
798 |
|
799 // We only do this to ensure that we won't get stuck in |
|
800 // MsgWaitForMultipleObjects below. |
|
801 timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr); |
|
802 NS_ASSERTION(timerId, "SetTimer failed!"); |
|
803 } |
|
804 |
|
805 // Setup deferred processing of native events while we wait for a response. |
|
806 NS_ASSERTION(!MessageChannel::IsPumpingMessages(), |
|
807 "Shouldn't be pumping already!"); |
|
808 |
|
809 MessageChannel::SetIsPumpingMessages(true); |
|
810 HHOOK windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook, |
|
811 nullptr, gUIThreadId); |
|
812 NS_ASSERTION(windowHook, "Failed to set hook!"); |
|
813 |
|
814 { |
|
815 while (1) { |
|
816 MSG msg = { 0 }; |
|
817 // Don't get wrapped up in here if the child connection dies. |
|
818 { |
|
819 MonitorAutoLock lock(*mMonitor); |
|
820 if (!Connected()) { |
|
821 break; |
|
822 } |
|
823 } |
|
824 |
|
825 // Wait until we have a message in the queue. MSDN docs are a bit unclear |
|
826 // but it seems that windows from two different threads (and it should be |
|
827 // noted that a thread in another process counts as a "different thread") |
|
828 // will implicitly have their message queues attached if they are parented |
|
829 // to one another. This wait call, then, will return for a message |
|
830 // delivered to *either* thread. |
|
831 DWORD result = MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, |
|
832 QS_ALLINPUT); |
|
833 if (result == WAIT_OBJECT_0) { |
|
834 // Our NotifyWorkerThread event was signaled |
|
835 ResetEvent(mEvent); |
|
836 break; |
|
837 } else |
|
838 if (result != (WAIT_OBJECT_0 + 1)) { |
|
839 NS_ERROR("Wait failed!"); |
|
840 break; |
|
841 } |
|
842 |
|
843 if (TimeoutHasExpired(timeoutData)) { |
|
844 // A timeout was specified and we've passed it. Break out. |
|
845 timedout = true; |
|
846 break; |
|
847 } |
|
848 |
|
849 // The only way to know on which thread the message was delivered is to |
|
850 // use some logic on the return values of GetQueueStatus and PeekMessage. |
|
851 // PeekMessage will return false if there are no "queued" messages, but it |
|
852 // will run all "nonqueued" messages before returning. So if PeekMessage |
|
853 // returns false and there are no "nonqueued" messages that were run then |
|
854 // we know that the message we woke for was intended for a window on |
|
855 // another thread. |
|
856 bool haveSentMessagesPending = |
|
857 (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0; |
|
858 |
|
859 // This PeekMessage call will actually process all "nonqueued" messages |
|
860 // that are pending before returning. If we have "nonqueued" messages |
|
861 // pending then we should have switched out all the window procedures |
|
862 // above. In that case this PeekMessage call won't actually cause any |
|
863 // mozilla code (or plugin code) to run. |
|
864 |
|
865 // If the following PeekMessage call fails to return a message for us (and |
|
866 // returns false) and we didn't run any "nonqueued" messages then we must |
|
867 // have woken up for a message designated for a window in another thread. |
|
868 // If we loop immediately then we could enter a tight loop, so we'll give |
|
869 // up our time slice here to let the child process its message. |
|
870 if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) && |
|
871 !haveSentMessagesPending) { |
|
872 // Message was for child, we should wait a bit. |
|
873 SwitchToThread(); |
|
874 } |
|
875 } |
|
876 } |
|
877 |
|
878 // Unhook the neutered window procedure hook. |
|
879 UnhookWindowsHookEx(windowHook); |
|
880 |
|
881 // Unhook any neutered windows procedures so messages can be delivered |
|
882 // normally. |
|
883 UnhookNeuteredWindows(); |
|
884 |
|
885 // Before returning we need to set a hook to run any deferred messages that |
|
886 // we received during the IPC call. The hook will unset itself as soon as |
|
887 // someone else calls GetMessage, PeekMessage, or runs code that generates |
|
888 // a "nonqueued" message. |
|
889 ScheduleDeferredMessageRun(); |
|
890 |
|
891 if (timerId) { |
|
892 KillTimer(nullptr, timerId); |
|
893 } |
|
894 |
|
895 MessageChannel::SetIsPumpingMessages(false); |
|
896 |
|
897 return WaitResponse(timedout); |
|
898 } |
|
899 |
|
900 bool |
|
901 MessageChannel::WaitForInterruptNotify() |
|
902 { |
|
903 mMonitor->AssertCurrentThreadOwns(); |
|
904 |
|
905 MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!"); |
|
906 |
|
907 // Re-use sync notification wait code if this channel does not require |
|
908 // Windows message deferral behavior. |
|
909 if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { |
|
910 return WaitForSyncNotify(); |
|
911 } |
|
912 |
|
913 if (!InterruptStackDepth()) { |
|
914 // There is currently no way to recover from this condition. |
|
915 NS_RUNTIMEABORT("StackDepth() is 0 in call to MessageChannel::WaitForNotify!"); |
|
916 } |
|
917 |
|
918 NS_ASSERTION(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION, |
|
919 "Shouldn't be here for channels that don't use message deferral!"); |
|
920 NS_ASSERTION(mTopFrame && mTopFrame->mInterrupt, |
|
921 "Top frame is not a sync frame!"); |
|
922 |
|
923 MonitorAutoUnlock unlock(*mMonitor); |
|
924 |
|
925 bool timedout = false; |
|
926 |
|
927 UINT_PTR timerId = 0; |
|
928 TimeoutData timeoutData = { 0 }; |
|
929 |
|
930 // windowHook is used as a flag variable for the loop below: if it is set |
|
931 // and we start to spin a nested event loop, we need to clear the hook and |
|
932 // process deferred/pending messages. |
|
933 // If windowHook is nullptr, MessageChannel::IsPumpingMessages should be false. |
|
934 HHOOK windowHook = nullptr; |
|
935 |
|
936 while (1) { |
|
937 NS_ASSERTION((!!windowHook) == MessageChannel::IsPumpingMessages(), |
|
938 "windowHook out of sync with reality"); |
|
939 |
|
940 if (mTopFrame->mSpinNestedEvents) { |
|
941 if (windowHook) { |
|
942 UnhookWindowsHookEx(windowHook); |
|
943 windowHook = nullptr; |
|
944 |
|
945 if (timerId) { |
|
946 KillTimer(nullptr, timerId); |
|
947 timerId = 0; |
|
948 } |
|
949 |
|
950 // Used by widget to assert on incoming native events |
|
951 MessageChannel::SetIsPumpingMessages(false); |
|
952 |
|
953 // Unhook any neutered windows procedures so messages can be delievered |
|
954 // normally. |
|
955 UnhookNeuteredWindows(); |
|
956 |
|
957 // Send all deferred "nonqueued" message to the intended receiver. |
|
958 // We're dropping into SpinInternalEventLoop so we should be fairly |
|
959 // certain these will get delivered soohn. |
|
960 ScheduleDeferredMessageRun(); |
|
961 } |
|
962 SpinInternalEventLoop(); |
|
963 ResetEvent(mEvent); |
|
964 return true; |
|
965 } |
|
966 |
|
967 if (!windowHook) { |
|
968 MessageChannel::SetIsPumpingMessages(true); |
|
969 windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook, |
|
970 nullptr, gUIThreadId); |
|
971 NS_ASSERTION(windowHook, "Failed to set hook!"); |
|
972 |
|
973 NS_ASSERTION(!timerId, "Timer already initialized?"); |
|
974 |
|
975 if (mTimeoutMs != kNoTimeout) { |
|
976 InitTimeoutData(&timeoutData, mTimeoutMs); |
|
977 timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr); |
|
978 NS_ASSERTION(timerId, "SetTimer failed!"); |
|
979 } |
|
980 } |
|
981 |
|
982 MSG msg = { 0 }; |
|
983 |
|
984 // Don't get wrapped up in here if the child connection dies. |
|
985 { |
|
986 MonitorAutoLock lock(*mMonitor); |
|
987 if (!Connected()) { |
|
988 break; |
|
989 } |
|
990 } |
|
991 |
|
992 DWORD result = MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, |
|
993 QS_ALLINPUT); |
|
994 if (result == WAIT_OBJECT_0) { |
|
995 // Our NotifyWorkerThread event was signaled |
|
996 ResetEvent(mEvent); |
|
997 break; |
|
998 } else |
|
999 if (result != (WAIT_OBJECT_0 + 1)) { |
|
1000 NS_ERROR("Wait failed!"); |
|
1001 break; |
|
1002 } |
|
1003 |
|
1004 if (TimeoutHasExpired(timeoutData)) { |
|
1005 // A timeout was specified and we've passed it. Break out. |
|
1006 timedout = true; |
|
1007 break; |
|
1008 } |
|
1009 |
|
1010 // See MessageChannel's WaitFor*Notify for details. |
|
1011 bool haveSentMessagesPending = |
|
1012 (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0; |
|
1013 |
|
1014 // PeekMessage markes the messages as "old" so that they don't wake up |
|
1015 // MsgWaitForMultipleObjects every time. |
|
1016 if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) && |
|
1017 !haveSentMessagesPending) { |
|
1018 // Message was for child, we should wait a bit. |
|
1019 SwitchToThread(); |
|
1020 } |
|
1021 } |
|
1022 |
|
1023 if (windowHook) { |
|
1024 // Unhook the neutered window procedure hook. |
|
1025 UnhookWindowsHookEx(windowHook); |
|
1026 |
|
1027 // Unhook any neutered windows procedures so messages can be delivered |
|
1028 // normally. |
|
1029 UnhookNeuteredWindows(); |
|
1030 |
|
1031 // Before returning we need to set a hook to run any deferred messages that |
|
1032 // we received during the IPC call. The hook will unset itself as soon as |
|
1033 // someone else calls GetMessage, PeekMessage, or runs code that generates |
|
1034 // a "nonqueued" message. |
|
1035 ScheduleDeferredMessageRun(); |
|
1036 |
|
1037 if (timerId) { |
|
1038 KillTimer(nullptr, timerId); |
|
1039 } |
|
1040 } |
|
1041 |
|
1042 MessageChannel::SetIsPumpingMessages(false); |
|
1043 |
|
1044 return WaitResponse(timedout); |
|
1045 } |
|
1046 |
|
1047 void |
|
1048 MessageChannel::NotifyWorkerThread() |
|
1049 { |
|
1050 mMonitor->AssertCurrentThreadOwns(); |
|
1051 |
|
1052 if (mIsSyncWaitingOnNonMainThread) { |
|
1053 mMonitor->Notify(); |
|
1054 return; |
|
1055 } |
|
1056 |
|
1057 NS_ASSERTION(mEvent, "No signal event to set, this is really bad!"); |
|
1058 if (!SetEvent(mEvent)) { |
|
1059 NS_WARNING("Failed to set NotifyWorkerThread event!"); |
|
1060 } |
|
1061 } |
|
1062 |
|
1063 void |
|
1064 DeferredSendMessage::Run() |
|
1065 { |
|
1066 AssertWindowIsNotNeutered(hWnd); |
|
1067 if (!IsWindow(hWnd)) { |
|
1068 NS_ERROR("Invalid window!"); |
|
1069 return; |
|
1070 } |
|
1071 |
|
1072 WNDPROC wndproc = |
|
1073 reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); |
|
1074 if (!wndproc) { |
|
1075 NS_ERROR("Invalid window procedure!"); |
|
1076 return; |
|
1077 } |
|
1078 |
|
1079 CallWindowProc(wndproc, hWnd, message, wParam, lParam); |
|
1080 } |
|
1081 |
|
1082 void |
|
1083 DeferredRedrawMessage::Run() |
|
1084 { |
|
1085 AssertWindowIsNotNeutered(hWnd); |
|
1086 if (!IsWindow(hWnd)) { |
|
1087 NS_ERROR("Invalid window!"); |
|
1088 return; |
|
1089 } |
|
1090 |
|
1091 #ifdef DEBUG |
|
1092 BOOL ret = |
|
1093 #endif |
|
1094 RedrawWindow(hWnd, nullptr, nullptr, flags); |
|
1095 NS_ASSERTION(ret, "RedrawWindow failed!"); |
|
1096 } |
|
1097 |
|
1098 DeferredUpdateMessage::DeferredUpdateMessage(HWND aHWnd) |
|
1099 { |
|
1100 mWnd = aHWnd; |
|
1101 if (!GetUpdateRect(mWnd, &mUpdateRect, FALSE)) { |
|
1102 memset(&mUpdateRect, 0, sizeof(RECT)); |
|
1103 return; |
|
1104 } |
|
1105 ValidateRect(mWnd, &mUpdateRect); |
|
1106 } |
|
1107 |
|
1108 void |
|
1109 DeferredUpdateMessage::Run() |
|
1110 { |
|
1111 AssertWindowIsNotNeutered(mWnd); |
|
1112 if (!IsWindow(mWnd)) { |
|
1113 NS_ERROR("Invalid window!"); |
|
1114 return; |
|
1115 } |
|
1116 |
|
1117 InvalidateRect(mWnd, &mUpdateRect, FALSE); |
|
1118 #ifdef DEBUG |
|
1119 BOOL ret = |
|
1120 #endif |
|
1121 UpdateWindow(mWnd); |
|
1122 NS_ASSERTION(ret, "UpdateWindow failed!"); |
|
1123 } |
|
1124 |
|
1125 DeferredSettingChangeMessage::DeferredSettingChangeMessage(HWND aHWnd, |
|
1126 UINT aMessage, |
|
1127 WPARAM aWParam, |
|
1128 LPARAM aLParam) |
|
1129 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) |
|
1130 { |
|
1131 NS_ASSERTION(aMessage == WM_SETTINGCHANGE, "Wrong message type!"); |
|
1132 if (aLParam) { |
|
1133 lParamString = _wcsdup(reinterpret_cast<const wchar_t*>(aLParam)); |
|
1134 lParam = reinterpret_cast<LPARAM>(lParamString); |
|
1135 } |
|
1136 else { |
|
1137 lParamString = nullptr; |
|
1138 lParam = 0; |
|
1139 } |
|
1140 } |
|
1141 |
|
1142 DeferredSettingChangeMessage::~DeferredSettingChangeMessage() |
|
1143 { |
|
1144 free(lParamString); |
|
1145 } |
|
1146 |
|
1147 DeferredWindowPosMessage::DeferredWindowPosMessage(HWND aHWnd, |
|
1148 LPARAM aLParam, |
|
1149 bool aForCalcSize, |
|
1150 WPARAM aWParam) |
|
1151 { |
|
1152 if (aForCalcSize) { |
|
1153 if (aWParam) { |
|
1154 NCCALCSIZE_PARAMS* arg = reinterpret_cast<NCCALCSIZE_PARAMS*>(aLParam); |
|
1155 memcpy(&windowPos, arg->lppos, sizeof(windowPos)); |
|
1156 |
|
1157 NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!"); |
|
1158 } |
|
1159 else { |
|
1160 RECT* arg = reinterpret_cast<RECT*>(aLParam); |
|
1161 windowPos.hwnd = aHWnd; |
|
1162 windowPos.hwndInsertAfter = nullptr; |
|
1163 windowPos.x = arg->left; |
|
1164 windowPos.y = arg->top; |
|
1165 windowPos.cx = arg->right - arg->left; |
|
1166 windowPos.cy = arg->bottom - arg->top; |
|
1167 |
|
1168 NS_ASSERTION(arg->right >= arg->left && arg->bottom >= arg->top, |
|
1169 "Negative width or height!"); |
|
1170 } |
|
1171 windowPos.flags = SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER | |
|
1172 SWP_NOZORDER | SWP_DEFERERASE | SWP_NOSENDCHANGING; |
|
1173 } |
|
1174 else { |
|
1175 // Not for WM_NCCALCSIZE |
|
1176 WINDOWPOS* arg = reinterpret_cast<WINDOWPOS*>(aLParam); |
|
1177 memcpy(&windowPos, arg, sizeof(windowPos)); |
|
1178 |
|
1179 NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!"); |
|
1180 |
|
1181 // Windows sends in some private flags sometimes that we can't simply copy. |
|
1182 // Filter here. |
|
1183 UINT mask = SWP_ASYNCWINDOWPOS | SWP_DEFERERASE | SWP_DRAWFRAME | |
|
1184 SWP_FRAMECHANGED | SWP_HIDEWINDOW | SWP_NOACTIVATE | |
|
1185 SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW | |
|
1186 SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE | |
|
1187 SWP_NOZORDER | SWP_SHOWWINDOW; |
|
1188 windowPos.flags &= mask; |
|
1189 } |
|
1190 } |
|
1191 |
|
1192 void |
|
1193 DeferredWindowPosMessage::Run() |
|
1194 { |
|
1195 AssertWindowIsNotNeutered(windowPos.hwnd); |
|
1196 if (!IsWindow(windowPos.hwnd)) { |
|
1197 NS_ERROR("Invalid window!"); |
|
1198 return; |
|
1199 } |
|
1200 |
|
1201 if (!IsWindow(windowPos.hwndInsertAfter)) { |
|
1202 NS_WARNING("ZOrder change cannot be honored"); |
|
1203 windowPos.hwndInsertAfter = 0; |
|
1204 windowPos.flags |= SWP_NOZORDER; |
|
1205 } |
|
1206 |
|
1207 #ifdef DEBUG |
|
1208 BOOL ret = |
|
1209 #endif |
|
1210 SetWindowPos(windowPos.hwnd, windowPos.hwndInsertAfter, windowPos.x, |
|
1211 windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags); |
|
1212 NS_ASSERTION(ret, "SetWindowPos failed!"); |
|
1213 } |
|
1214 |
|
1215 DeferredCopyDataMessage::DeferredCopyDataMessage(HWND aHWnd, |
|
1216 UINT aMessage, |
|
1217 WPARAM aWParam, |
|
1218 LPARAM aLParam) |
|
1219 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) |
|
1220 { |
|
1221 NS_ASSERTION(IsWindow(reinterpret_cast<HWND>(aWParam)), "Bad window!"); |
|
1222 |
|
1223 COPYDATASTRUCT* source = reinterpret_cast<COPYDATASTRUCT*>(aLParam); |
|
1224 NS_ASSERTION(source, "Should never be null!"); |
|
1225 |
|
1226 copyData.dwData = source->dwData; |
|
1227 copyData.cbData = source->cbData; |
|
1228 |
|
1229 if (source->cbData) { |
|
1230 copyData.lpData = malloc(source->cbData); |
|
1231 if (copyData.lpData) { |
|
1232 memcpy(copyData.lpData, source->lpData, source->cbData); |
|
1233 } |
|
1234 else { |
|
1235 NS_ERROR("Out of memory?!"); |
|
1236 copyData.cbData = 0; |
|
1237 } |
|
1238 } |
|
1239 else { |
|
1240 copyData.lpData = nullptr; |
|
1241 } |
|
1242 |
|
1243 lParam = reinterpret_cast<LPARAM>(©Data); |
|
1244 } |
|
1245 |
|
1246 DeferredCopyDataMessage::~DeferredCopyDataMessage() |
|
1247 { |
|
1248 free(copyData.lpData); |
|
1249 } |
|
1250 |
|
1251 DeferredStyleChangeMessage::DeferredStyleChangeMessage(HWND aHWnd, |
|
1252 WPARAM aWParam, |
|
1253 LPARAM aLParam) |
|
1254 : hWnd(aHWnd) |
|
1255 { |
|
1256 index = static_cast<int>(aWParam); |
|
1257 style = reinterpret_cast<STYLESTRUCT*>(aLParam)->styleNew; |
|
1258 } |
|
1259 |
|
1260 void |
|
1261 DeferredStyleChangeMessage::Run() |
|
1262 { |
|
1263 SetWindowLongPtr(hWnd, index, style); |
|
1264 } |
|
1265 |
|
1266 DeferredSetIconMessage::DeferredSetIconMessage(HWND aHWnd, |
|
1267 UINT aMessage, |
|
1268 WPARAM aWParam, |
|
1269 LPARAM aLParam) |
|
1270 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) |
|
1271 { |
|
1272 NS_ASSERTION(aMessage == WM_SETICON, "Wrong message type!"); |
|
1273 } |
|
1274 |
|
1275 void |
|
1276 DeferredSetIconMessage::Run() |
|
1277 { |
|
1278 AssertWindowIsNotNeutered(hWnd); |
|
1279 if (!IsWindow(hWnd)) { |
|
1280 NS_ERROR("Invalid window!"); |
|
1281 return; |
|
1282 } |
|
1283 |
|
1284 WNDPROC wndproc = |
|
1285 reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); |
|
1286 if (!wndproc) { |
|
1287 NS_ERROR("Invalid window procedure!"); |
|
1288 return; |
|
1289 } |
|
1290 |
|
1291 HICON hOld = reinterpret_cast<HICON>( |
|
1292 CallWindowProc(wndproc, hWnd, message, wParam, lParam)); |
|
1293 if (hOld) { |
|
1294 DestroyIcon(hOld); |
|
1295 } |
|
1296 } |