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