Bug 1869092 - Fix timeouts in browser_PanelMultiView.js. r=twisniewski,test-only
[gecko.git] / ipc / glue / WindowsMessageLoop.cpp
blob1305ecdea1ac58ad8acb4f40030fb25f83c22d3f
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/. */
7 #include "mozilla/DebugOnly.h"
9 #include "WindowsMessageLoop.h"
10 #include "Neutering.h"
11 #include "MessageChannel.h"
13 #include "nsServiceManagerUtils.h"
14 #include "nsString.h"
15 #include "WinUtils.h"
17 #include "mozilla/ArrayUtils.h"
18 #include "mozilla/dom/JSExecutionManager.h"
19 #include "mozilla/gfx/Logging.h"
20 #include "mozilla/ipc/ProtocolUtils.h"
21 #include "mozilla/mscom/Utils.h"
22 #include "mozilla/PaintTracker.h"
23 #include "mozilla/StaticPtr.h"
24 #include "mozilla/UniquePtr.h"
25 #include "mozilla/WindowsProcessMitigations.h"
27 using namespace mozilla;
28 using namespace mozilla::ipc;
29 using namespace mozilla::ipc::windows;
31 /**
32 * The Windows-only code below exists to solve a general problem with deadlocks
33 * that we experience when sending synchronous IPC messages to processes that
34 * contain native windows (i.e. HWNDs). Windows (the OS) sends synchronous
35 * messages between parent and child HWNDs in multiple circumstances (e.g.
36 * WM_PARENTNOTIFY, WM_NCACTIVATE, etc.), even when those HWNDs are controlled
37 * by different threads or different processes. Thus we can very easily end up
38 * in a deadlock by a call stack like the following:
40 * Process A:
41 * - CreateWindow(...) creates a "parent" HWND.
42 * - SendCreateChildWidget(HWND) is a sync IPC message that sends the "parent"
43 * HWND over to Process B. Process A blocks until a response is received
44 * from Process B.
46 * Process B:
47 * - RecvCreateWidget(HWND) gets the "parent" HWND from Process A.
48 * - CreateWindow(..., HWND) creates a "child" HWND with the parent from
49 * process A.
50 * - Windows (the OS) generates a WM_PARENTNOTIFY message that is sent
51 * synchronously to Process A. Process B blocks until a response is
52 * received from Process A. Process A, however, is blocked and cannot
53 * process the message. Both processes are deadlocked.
55 * The example above has a few different workarounds (e.g. setting the
56 * WS_EX_NOPARENTNOTIFY style on the child window) but the general problem is
57 * persists. Once two HWNDs are parented we must not block their owning
58 * threads when manipulating either HWND.
60 * Windows requires any application that hosts native HWNDs to always process
61 * messages or risk deadlock. Given our architecture the only way to meet
62 * Windows' requirement and allow for synchronous IPC messages is to pump a
63 * miniature message loop during a sync IPC call. We avoid processing any
64 * queued messages during the loop (with one exception, see below), but
65 * "nonqueued" messages (see
66 * http://msdn.microsoft.com/en-us/library/ms644927(VS.85).aspx under the
67 * section "Nonqueued messages") cannot be avoided. Those messages are trapped
68 * in a special window procedure where we can either ignore the message or
69 * process it in some fashion.
71 * Queued and "non-queued" messages will be processed during Interrupt calls if
72 * modal UI related api calls block an Interrupt in-call in the child. To
73 * prevent windows from freezing, and to allow concurrent processing of critical
74 * events (such as painting), we spin a native event dispatch loop while
75 * these in-calls are blocked.
78 #if defined(ACCESSIBILITY)
79 // pulled from accessibility's win utils
80 extern const wchar_t* kPropNameTabContent;
81 #endif
83 // widget related message id constants we need to defer, see nsAppShell.
84 extern UINT sAppShellGeckoMsgId;
86 namespace {
88 const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc";
90 // This isn't defined before Windows XP.
91 enum { WM_XP_THEMECHANGED = 0x031A };
93 static StaticAutoPtr<AutoTArray<HWND, 20>> gNeuteredWindows;
95 typedef nsTArray<UniquePtr<DeferredMessage>> DeferredMessageArray;
96 DeferredMessageArray* gDeferredMessages = nullptr;
98 HHOOK gDeferredGetMsgHook = nullptr;
99 HHOOK gDeferredCallWndProcHook = nullptr;
101 DWORD gUIThreadId = 0;
102 HWND gCOMWindow = 0;
103 // Once initialized, gWinEventHook is never unhooked. We save the handle so
104 // that we can check whether or not the hook is initialized.
105 HWINEVENTHOOK gWinEventHook = nullptr;
106 const wchar_t kCOMWindowClassName[] = L"OleMainThreadWndClass";
108 // WM_GETOBJECT id pulled from uia headers
109 #define MOZOBJID_UIAROOT -25
111 HWND FindCOMWindow() {
112 MOZ_ASSERT(gUIThreadId);
114 HWND last = 0;
115 while (
116 (last = FindWindowExW(HWND_MESSAGE, last, kCOMWindowClassName, NULL))) {
117 if (GetWindowThreadProcessId(last, NULL) == gUIThreadId) {
118 return last;
122 return (HWND)0;
125 void CALLBACK WinEventHook(HWINEVENTHOOK aWinEventHook, DWORD aEvent,
126 HWND aHwnd, LONG aIdObject, LONG aIdChild,
127 DWORD aEventThread, DWORD aMsEventTime) {
128 MOZ_ASSERT(aWinEventHook == gWinEventHook);
129 MOZ_ASSERT(gUIThreadId == aEventThread);
130 switch (aEvent) {
131 case EVENT_OBJECT_CREATE: {
132 if (aIdObject != OBJID_WINDOW || aIdChild != CHILDID_SELF) {
133 // Not an event we're interested in
134 return;
136 wchar_t classBuf[256] = {0};
137 int result = ::GetClassNameW(aHwnd, classBuf, MOZ_ARRAY_LENGTH(classBuf));
138 if (result != (MOZ_ARRAY_LENGTH(kCOMWindowClassName) - 1) ||
139 wcsncmp(kCOMWindowClassName, classBuf, result)) {
140 // Not a class we're interested in
141 return;
143 MOZ_ASSERT(FindCOMWindow() == aHwnd);
144 gCOMWindow = aHwnd;
145 break;
147 case EVENT_OBJECT_DESTROY: {
148 if (aHwnd == gCOMWindow && aIdObject == OBJID_WINDOW) {
149 MOZ_ASSERT(aIdChild == CHILDID_SELF);
150 gCOMWindow = 0;
152 break;
154 default: {
155 return;
160 LRESULT CALLBACK DeferredMessageHook(int nCode, WPARAM wParam, LPARAM lParam) {
161 // XXX This function is called for *both* the WH_CALLWNDPROC hook and the
162 // WH_GETMESSAGE hook, but they have different parameters. We don't
163 // use any of them except nCode which has the same meaning.
165 // Only run deferred messages if all of these conditions are met:
166 // 1. The |nCode| indicates that this hook should do something.
167 // 2. We have deferred messages to run.
168 // 3. We're not being called from the PeekMessage within the WaitFor*Notify
169 // function (indicated with MessageChannel::IsPumpingMessages). We really
170 // only want to run after returning to the main event loop.
171 if (nCode >= 0 && gDeferredMessages && !MessageChannel::IsPumpingMessages()) {
172 NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
173 "These hooks must be set if we're being called!");
174 NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
176 // Unset hooks first, in case we reenter below.
177 UnhookWindowsHookEx(gDeferredGetMsgHook);
178 UnhookWindowsHookEx(gDeferredCallWndProcHook);
179 gDeferredGetMsgHook = 0;
180 gDeferredCallWndProcHook = 0;
182 // Unset the global and make sure we delete it when we're done here.
183 auto messages = WrapUnique(gDeferredMessages);
184 gDeferredMessages = nullptr;
186 // Run all the deferred messages in order.
187 uint32_t count = messages->Length();
188 for (uint32_t index = 0; index < count; index++) {
189 messages->ElementAt(index)->Run();
193 // Always call the next hook.
194 return CallNextHookEx(nullptr, nCode, wParam, lParam);
197 void ScheduleDeferredMessageRun() {
198 if (gDeferredMessages && !(gDeferredGetMsgHook && gDeferredCallWndProcHook)) {
199 NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
201 gDeferredGetMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, DeferredMessageHook,
202 nullptr, gUIThreadId);
203 gDeferredCallWndProcHook = ::SetWindowsHookEx(
204 WH_CALLWNDPROC, DeferredMessageHook, nullptr, gUIThreadId);
205 NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
206 "Failed to set hooks!");
210 static void DumpNeuteredMessage(HWND hwnd, UINT uMsg) {
211 #ifdef DEBUG
212 nsAutoCString log("Received \"nonqueued\" ");
213 // classify messages
214 if (uMsg < WM_USER) {
215 const char* msgText = mozilla::widget::WinUtils::WinEventToEventName(uMsg);
216 if (msgText) {
217 log.AppendPrintf("ui message \"%s\"", msgText);
218 } else {
219 log.AppendPrintf("ui message (0x%X)", uMsg);
221 } else if (uMsg >= WM_USER && uMsg < WM_APP) {
222 log.AppendPrintf("WM_USER message (0x%X)", uMsg);
223 } else if (uMsg >= WM_APP && uMsg < 0xC000) {
224 log.AppendPrintf("WM_APP message (0x%X)", uMsg);
225 } else if (uMsg >= 0xC000 && uMsg < 0x10000) {
226 log.AppendPrintf("registered windows message (0x%X)", uMsg);
227 } else {
228 log.AppendPrintf("system message (0x%X)", uMsg);
231 log.AppendLiteral(" during a synchronous IPC message for window ");
232 log.AppendPrintf("0x%p", hwnd);
234 wchar_t className[256] = {0};
235 if (GetClassNameW(hwnd, className, sizeof(className) - 1) > 0) {
236 log.AppendLiteral(" (\"");
237 log.Append(NS_ConvertUTF16toUTF8((char16_t*)className));
238 log.AppendLiteral("\")");
241 log.AppendLiteral(
242 ", sending it to DefWindowProc instead of the normal "
243 "window procedure.");
244 NS_ERROR(log.get());
245 #endif
248 LRESULT
249 ProcessOrDeferMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
250 UniquePtr<DeferredMessage> deferred;
252 // Most messages ask for 0 to be returned if the message is processed.
253 LRESULT res = 0;
255 switch (uMsg) {
256 // Messages that can be deferred as-is. These must not contain pointers in
257 // their wParam or lParam arguments!
258 case WM_ACTIVATE:
259 case WM_ACTIVATEAPP:
260 case WM_CANCELMODE:
261 case WM_CAPTURECHANGED:
262 case WM_CHILDACTIVATE:
263 case WM_DESTROY:
264 case WM_ENABLE:
265 case WM_IME_NOTIFY:
266 case WM_IME_SETCONTEXT:
267 case WM_KILLFOCUS:
268 case WM_MOUSEWHEEL:
269 case WM_NCDESTROY:
270 case WM_PARENTNOTIFY:
271 case WM_SETFOCUS:
272 case WM_SYSCOMMAND:
273 case WM_DISPLAYCHANGE:
274 case WM_SHOWWINDOW: // Intentional fall-through.
275 case WM_XP_THEMECHANGED: {
276 deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
277 break;
280 case WM_DEVICECHANGE:
281 case WM_POWERBROADCAST:
282 case WM_NCACTIVATE: // Intentional fall-through.
283 case WM_SETCURSOR: {
284 // Friggin unconventional return value...
285 res = TRUE;
286 deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
287 break;
290 case WM_MOUSEACTIVATE: {
291 res = MA_NOACTIVATE;
292 deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
293 break;
296 // These messages need to use the RedrawWindow function to generate the
297 // right kind of message. We can't simply fake them as the MSDN docs say
298 // explicitly that paint messages should not be sent by an application.
299 case WM_ERASEBKGND: {
300 UINT flags = RDW_INVALIDATE | RDW_ERASE | RDW_NOINTERNALPAINT |
301 RDW_NOFRAME | RDW_NOCHILDREN | RDW_ERASENOW;
302 deferred = MakeUnique<DeferredRedrawMessage>(hwnd, flags);
303 break;
306 // This message will generate a WM_PAINT message if there are invalid
307 // areas.
308 case WM_PAINT: {
309 deferred = MakeUnique<DeferredUpdateMessage>(hwnd);
310 break;
313 // This message holds a string in its lParam that we must copy.
314 case WM_SETTINGCHANGE: {
315 deferred =
316 MakeUnique<DeferredSettingChangeMessage>(hwnd, uMsg, wParam, lParam);
317 break;
320 // These messages are faked via a call to SetWindowPos.
321 case WM_WINDOWPOSCHANGED: {
322 deferred = MakeUnique<DeferredWindowPosMessage>(hwnd, lParam);
323 break;
325 case WM_NCCALCSIZE: {
326 deferred =
327 MakeUnique<DeferredWindowPosMessage>(hwnd, lParam, true, wParam);
328 break;
331 case WM_COPYDATA: {
332 deferred =
333 MakeUnique<DeferredCopyDataMessage>(hwnd, uMsg, wParam, lParam);
334 res = TRUE;
335 break;
338 case WM_STYLECHANGED: {
339 deferred = MakeUnique<DeferredStyleChangeMessage>(hwnd, wParam, lParam);
340 break;
343 case WM_SETICON: {
344 deferred = MakeUnique<DeferredSetIconMessage>(hwnd, uMsg, wParam, lParam);
345 break;
348 // Messages that are safe to pass to DefWindowProc go here.
349 case WM_ENTERIDLE:
350 case WM_GETICON:
351 case WM_NCPAINT: // (never trap nc paint events)
352 case WM_GETMINMAXINFO:
353 case WM_GETTEXT:
354 case WM_NCHITTEST:
355 case WM_STYLECHANGING: // Intentional fall-through.
356 case WM_WINDOWPOSCHANGING:
357 case WM_GETTEXTLENGTH: {
358 return DefWindowProc(hwnd, uMsg, wParam, lParam);
361 // Just return, prevents DefWindowProc from messaging the window
362 // syncronously with other events, which may be deferred. Prevents
363 // random shutdown of aero composition on the window.
364 case WM_SYNCPAINT:
365 return 0;
367 // This message causes QuickTime to make re-entrant calls.
368 // Simply discarding it doesn't seem to hurt anything.
369 case WM_APP - 1:
370 return 0;
372 // We only support a query for our IAccessible or UIA pointers.
373 // This should be safe, and needs to be sync.
374 #if defined(ACCESSIBILITY)
375 case WM_GETOBJECT: {
376 LONG objId = static_cast<LONG>(lParam);
377 if (objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT) {
378 WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
379 if (oldWndProc) {
380 return CallWindowProcW(oldWndProc, hwnd, uMsg, wParam, lParam);
383 return DefWindowProc(hwnd, uMsg, wParam, lParam);
385 #endif // ACCESSIBILITY
387 default: {
388 // Unknown messages only are logged in debug builds and sent to
389 // DefWindowProc.
390 if (uMsg && uMsg == sAppShellGeckoMsgId) {
391 // Widget's registered native event callback
392 deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
397 // No deferred message was created and we land here, this is an
398 // unhandled message.
399 if (!deferred) {
400 DumpNeuteredMessage(hwnd, uMsg);
401 return DefWindowProc(hwnd, uMsg, wParam, lParam);
404 // Create the deferred message array if it doesn't exist already.
405 if (!gDeferredMessages) {
406 gDeferredMessages = new DeferredMessageArray(20);
409 // Save for later. The array takes ownership of |deferred|.
410 gDeferredMessages->AppendElement(std::move(deferred));
411 return res;
414 } // namespace
416 LRESULT CALLBACK NeuteredWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
417 LPARAM lParam) {
418 WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
419 if (!oldWndProc) {
420 // We should really never ever get here.
421 NS_ERROR("No old wndproc!");
422 return DefWindowProc(hwnd, uMsg, wParam, lParam);
425 // See if we care about this message. We may either ignore it, send it to
426 // DefWindowProc, or defer it for later.
427 return ProcessOrDeferMessage(hwnd, uMsg, wParam, lParam);
430 namespace {
432 static bool WindowIsDeferredWindow(HWND hWnd) {
433 if (!IsWindow(hWnd)) {
434 NS_WARNING("Window has died!");
435 return false;
438 char16_t buffer[256] = {0};
439 int length = GetClassNameW(hWnd, (wchar_t*)buffer, sizeof(buffer) - 1);
440 if (length <= 0) {
441 NS_WARNING("Failed to get class name!");
442 return false;
445 #if defined(ACCESSIBILITY)
446 // Tab content creates a window that responds to accessible WM_GETOBJECT
447 // calls. This window can safely be ignored.
448 if (::GetPropW(hWnd, kPropNameTabContent)) {
449 return false;
451 #endif
453 // Common mozilla windows we must defer messages to.
454 nsDependentString className(buffer, length);
455 if (StringBeginsWith(className, u"Mozilla"_ns) ||
456 StringBeginsWith(className, u"Gecko"_ns) ||
457 className.EqualsLiteral("nsToolkitClass") ||
458 className.EqualsLiteral("nsAppShell:EventWindowClass")) {
459 return true;
462 return false;
465 bool NeuterWindowProcedure(HWND hWnd) {
466 if (!WindowIsDeferredWindow(hWnd)) {
467 // Some other kind of window, skip.
468 return false;
471 NS_ASSERTION(!GetProp(hWnd, kOldWndProcProp), "This should always be null!");
473 // It's possible to get nullptr out of SetWindowLongPtr, and the only way to
474 // know if that's a valid old value is to use GetLastError. Clear the error
475 // here so we can tell.
476 SetLastError(ERROR_SUCCESS);
478 LONG_PTR currentWndProc =
479 SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)NeuteredWindowProc);
480 if (!currentWndProc) {
481 if (ERROR_SUCCESS == GetLastError()) {
482 // No error, so we set something and must therefore reset it.
483 SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc);
485 return false;
488 NS_ASSERTION(currentWndProc != (LONG_PTR)NeuteredWindowProc,
489 "This shouldn't be possible!");
491 if (!SetProp(hWnd, kOldWndProcProp, (HANDLE)currentWndProc)) {
492 // Cleanup
493 NS_WARNING("SetProp failed!");
494 SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc);
495 RemovePropW(hWnd, kOldWndProcProp);
496 return false;
499 return true;
502 void RestoreWindowProcedure(HWND hWnd) {
503 NS_ASSERTION(WindowIsDeferredWindow(hWnd),
504 "Not a deferred window, this shouldn't be in our list!");
505 LONG_PTR oldWndProc = (LONG_PTR)GetProp(hWnd, kOldWndProcProp);
506 if (oldWndProc) {
507 NS_ASSERTION(oldWndProc != (LONG_PTR)NeuteredWindowProc,
508 "This shouldn't be possible!");
510 DebugOnly<LONG_PTR> currentWndProc =
511 SetWindowLongPtr(hWnd, GWLP_WNDPROC, oldWndProc);
512 NS_ASSERTION(currentWndProc == (LONG_PTR)NeuteredWindowProc,
513 "This should never be switched out from under us!");
515 RemovePropW(hWnd, kOldWndProcProp);
518 LRESULT CALLBACK CallWindowProcedureHook(int nCode, WPARAM wParam,
519 LPARAM lParam) {
520 if (nCode >= 0) {
521 NS_ASSERTION(gNeuteredWindows, "This should never be null!");
523 HWND hWnd = reinterpret_cast<CWPSTRUCT*>(lParam)->hwnd;
525 if (!gNeuteredWindows->Contains(hWnd) &&
526 !SuppressedNeuteringRegion::IsNeuteringSuppressed() &&
527 NeuterWindowProcedure(hWnd)) {
528 // XXX(Bug 1631371) Check if this should use a fallible operation as it
529 // pretended earlier.
530 gNeuteredWindows->AppendElement(hWnd);
533 return CallNextHookEx(nullptr, nCode, wParam, lParam);
536 inline void AssertWindowIsNotNeutered(HWND hWnd) {
537 #ifdef DEBUG
538 // Make sure our neutered window hook isn't still in place.
539 LONG_PTR wndproc = GetWindowLongPtr(hWnd, GWLP_WNDPROC);
540 NS_ASSERTION(wndproc != (LONG_PTR)NeuteredWindowProc, "Window is neutered!");
541 #endif
544 void UnhookNeuteredWindows() {
545 if (!gNeuteredWindows) return;
546 uint32_t count = gNeuteredWindows->Length();
547 for (uint32_t index = 0; index < count; index++) {
548 RestoreWindowProcedure(gNeuteredWindows->ElementAt(index));
550 gNeuteredWindows->Clear();
553 // This timeout stuff assumes a sane value of mTimeoutMs (less than the overflow
554 // value for GetTickCount(), which is something like 50 days). It uses the
555 // cheapest (and least accurate) method supported by Windows 2000.
557 struct TimeoutData {
558 DWORD startTicks;
559 DWORD targetTicks;
562 void InitTimeoutData(TimeoutData* aData, int32_t aTimeoutMs) {
563 aData->startTicks = GetTickCount();
564 if (!aData->startTicks) {
565 // How unlikely is this!
566 aData->startTicks++;
568 aData->targetTicks = aData->startTicks + aTimeoutMs;
571 bool TimeoutHasExpired(const TimeoutData& aData) {
572 if (!aData.startTicks) {
573 return false;
576 DWORD now = GetTickCount();
578 if (aData.targetTicks < aData.startTicks) {
579 // Overflow
580 return now < aData.startTicks && now >= aData.targetTicks;
582 return now >= aData.targetTicks;
585 } // namespace
587 namespace mozilla {
588 namespace ipc {
589 namespace windows {
591 void InitUIThread() {
592 if (!XRE_UseNativeEventProcessing()) {
593 return;
595 // If we aren't setup before a call to NotifyWorkerThread, we'll hang
596 // on startup.
597 if (!gUIThreadId) {
598 gUIThreadId = GetCurrentThreadId();
601 MOZ_ASSERT(gUIThreadId);
602 MOZ_ASSERT(gUIThreadId == GetCurrentThreadId(),
603 "Called InitUIThread multiple times on different threads!");
605 if (!gWinEventHook && !mscom::IsCurrentThreadMTA()) {
606 gWinEventHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY,
607 NULL, &WinEventHook, GetCurrentProcessId(),
608 gUIThreadId, WINEVENT_OUTOFCONTEXT);
609 MOZ_ASSERT(gWinEventHook);
611 // We need to execute this after setting the hook in case the OLE window
612 // already existed.
613 gCOMWindow = FindCOMWindow();
617 } // namespace windows
618 } // namespace ipc
619 } // namespace mozilla
621 // See SpinInternalEventLoop below
622 MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel* channel)
623 : mSpinNestedEvents(false),
624 mListenerNotified(false),
625 mChannel(channel),
626 mPrev(mChannel->mTopFrame),
627 mStaticPrev(sStaticTopFrame) {
628 // Only track stack frames when Windows message deferral behavior
629 // is request for the channel.
630 if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
631 return;
634 mChannel->mTopFrame = this;
635 sStaticTopFrame = this;
637 if (!mStaticPrev) {
638 NS_ASSERTION(!gNeuteredWindows, "Should only set this once!");
639 gNeuteredWindows = new AutoTArray<HWND, 20>();
643 MessageChannel::SyncStackFrame::~SyncStackFrame() {
644 if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
645 return;
648 NS_ASSERTION(this == mChannel->mTopFrame,
649 "Mismatched interrupt stack frames");
650 NS_ASSERTION(this == sStaticTopFrame,
651 "Mismatched static Interrupt stack frames");
653 mChannel->mTopFrame = mPrev;
654 sStaticTopFrame = mStaticPrev;
656 if (!mStaticPrev) {
657 NS_ASSERTION(gNeuteredWindows, "Bad pointer!");
658 gNeuteredWindows = nullptr;
662 MessageChannel::SyncStackFrame* MessageChannel::sStaticTopFrame;
664 // nsAppShell's notification that gecko events are being processed.
665 // If we are here and there is an Interrupt Incall active, we are spinning
666 // a nested gecko event loop. In which case the remote process needs
667 // to know about it.
668 void /* static */
669 MessageChannel::NotifyGeckoEventDispatch() {
670 // sStaticTopFrame is only valid for Interrupt channels
671 if (!sStaticTopFrame || sStaticTopFrame->mListenerNotified) return;
673 sStaticTopFrame->mListenerNotified = true;
674 MessageChannel* channel =
675 static_cast<MessageChannel*>(sStaticTopFrame->mChannel);
676 channel->Listener()->ProcessRemoteNativeEventsInInterruptCall();
679 // invoked by the module that receives the spin event loop
680 // message.
681 void MessageChannel::ProcessNativeEventsInInterruptCall() {
682 NS_ASSERTION(GetCurrentThreadId() == gUIThreadId,
683 "Shouldn't be on a non-main thread in here!");
684 if (!mTopFrame) {
685 NS_ERROR("Spin logic error: no Interrupt frame");
686 return;
689 mTopFrame->mSpinNestedEvents = true;
692 // Spin loop is called in place of WaitFor*Notify when modal ui is being shown
693 // in a child. There are some intricacies in using it however. Spin loop is
694 // enabled for a particular Interrupt frame by the client calling
695 // MessageChannel::ProcessNativeEventsInInterrupt().
696 // This call can be nested for multiple Interrupt frames in a single plugin or
697 // multiple unrelated plugins.
698 void MessageChannel::SpinInternalEventLoop() {
699 if (mozilla::PaintTracker::IsPainting()) {
700 MOZ_CRASH("Don't spin an event loop while painting.");
703 NS_ASSERTION(mTopFrame && mTopFrame->mSpinNestedEvents,
704 "Spinning incorrectly");
706 // Nested windows event loop we trigger when the child enters into modal
707 // event loops.
709 // Note, when we return, we always reset the notify worker event. So there's
710 // no need to reset it on return here.
712 do {
713 MSG msg = {0};
715 // Don't get wrapped up in here if the child connection dies.
717 MonitorAutoLock lock(*mMonitor);
718 if (!Connected()) {
719 return;
723 // Retrieve window or thread messages
724 if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
725 // The child UI should have been destroyed before the app is closed, in
726 // which case, we should never get this here.
727 if (msg.message == WM_QUIT) {
728 NS_ERROR("WM_QUIT received in SpinInternalEventLoop!");
729 } else {
730 TranslateMessage(&msg);
731 ::DispatchMessageW(&msg);
732 return;
736 // Note, give dispatching windows events priority over checking if
737 // mEvent is signaled, otherwise heavy ipc traffic can cause jittery
738 // playback of video. We'll exit out on each disaptch above, so ipc
739 // won't get starved.
741 // Wait for UI events or a signal from the io thread.
742 DWORD result =
743 MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, QS_ALLINPUT);
744 if (result == WAIT_OBJECT_0) {
745 // Our NotifyWorkerThread event was signaled
746 return;
748 } while (true);
751 static HHOOK gWindowHook;
753 static inline void StartNeutering() {
754 if (!gUIThreadId) {
755 mozilla::ipc::windows::InitUIThread();
757 MOZ_ASSERT(gUIThreadId);
758 MOZ_ASSERT(!gWindowHook);
759 NS_ASSERTION(!MessageChannel::IsPumpingMessages(),
760 "Shouldn't be pumping already!");
761 MessageChannel::SetIsPumpingMessages(true);
762 gWindowHook = ::SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
763 nullptr, gUIThreadId);
764 NS_ASSERTION(gWindowHook, "Failed to set hook!");
767 static void StopNeutering() {
768 MOZ_ASSERT(MessageChannel::IsPumpingMessages());
769 ::UnhookWindowsHookEx(gWindowHook);
770 gWindowHook = NULL;
771 ::UnhookNeuteredWindows();
772 // Before returning we need to set a hook to run any deferred messages that
773 // we received during the IPC call. The hook will unset itself as soon as
774 // someone else calls GetMessage, PeekMessage, or runs code that generates
775 // a "nonqueued" message.
776 ::ScheduleDeferredMessageRun();
777 MessageChannel::SetIsPumpingMessages(false);
780 NeuteredWindowRegion::NeuteredWindowRegion(bool aDoNeuter)
781 : mNeuteredByThis(!gWindowHook && aDoNeuter &&
782 XRE_UseNativeEventProcessing()) {
783 if (mNeuteredByThis) {
784 StartNeutering();
788 NeuteredWindowRegion::~NeuteredWindowRegion() {
789 if (gWindowHook && mNeuteredByThis) {
790 StopNeutering();
794 void NeuteredWindowRegion::PumpOnce() {
795 if (!gWindowHook) {
796 // This should be a no-op if nothing has been neutered.
797 return;
800 MSG msg = {0};
801 // Pump any COM messages so that we don't hang due to STA marshaling.
802 if (gCOMWindow && ::PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
803 ::TranslateMessage(&msg);
804 ::DispatchMessageW(&msg);
806 // Expunge any nonqueued messages on the current thread.
807 ::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE);
810 DeneuteredWindowRegion::DeneuteredWindowRegion()
811 : mReneuter(gWindowHook != NULL) {
812 if (mReneuter) {
813 StopNeutering();
817 DeneuteredWindowRegion::~DeneuteredWindowRegion() {
818 if (mReneuter) {
819 StartNeutering();
823 SuppressedNeuteringRegion::SuppressedNeuteringRegion()
824 : mReenable(::gUIThreadId == ::GetCurrentThreadId() && ::gWindowHook) {
825 if (mReenable) {
826 MOZ_ASSERT(!sSuppressNeutering);
827 sSuppressNeutering = true;
831 SuppressedNeuteringRegion::~SuppressedNeuteringRegion() {
832 if (mReenable) {
833 MOZ_ASSERT(sSuppressNeutering);
834 sSuppressNeutering = false;
838 bool SuppressedNeuteringRegion::sSuppressNeutering = false;
840 bool MessageChannel::WaitForSyncNotify() {
841 mMonitor->AssertCurrentThreadOwns();
843 if (!gUIThreadId) {
844 mozilla::ipc::windows::InitUIThread();
847 // Use a blocking wait if this channel does not require
848 // Windows message deferral behavior.
849 if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
850 TimeDuration timeout = (kNoTimeout == mTimeoutMs)
851 ? TimeDuration::Forever()
852 : TimeDuration::FromMilliseconds(mTimeoutMs);
854 MOZ_ASSERT(!mIsSyncWaitingOnNonMainThread);
855 mIsSyncWaitingOnNonMainThread = true;
857 CVStatus status = mMonitor->Wait(timeout);
859 MOZ_ASSERT(mIsSyncWaitingOnNonMainThread);
860 mIsSyncWaitingOnNonMainThread = false;
862 // If the timeout didn't expire, we know we received an event. The
863 // converse is not true.
864 return WaitResponse(status == CVStatus::Timeout);
867 NS_ASSERTION(
868 mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION,
869 "Shouldn't be here for channels that don't use message deferral!");
870 NS_ASSERTION(mTopFrame, "No top frame!");
872 MonitorAutoUnlock unlock(*mMonitor);
874 bool timedout = false;
876 UINT_PTR timerId = 0;
877 TimeoutData timeoutData = {0};
879 if (mTimeoutMs != kNoTimeout) {
880 InitTimeoutData(&timeoutData, mTimeoutMs);
882 // We only do this to ensure that we won't get stuck in
883 // MsgWaitForMultipleObjects below.
884 timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
885 NS_ASSERTION(timerId, "SetTimer failed!");
888 NeuteredWindowRegion neuteredRgn(true);
891 while (1) {
892 MSG msg = {0};
893 // Don't get wrapped up in here if the child connection dies.
895 MonitorAutoLock lock(*mMonitor);
896 if (!Connected()) {
897 break;
901 // Wait until we have a message in the queue. MSDN docs are a bit unclear
902 // but it seems that windows from two different threads (and it should be
903 // noted that a thread in another process counts as a "different thread")
904 // will implicitly have their message queues attached if they are parented
905 // to one another. This wait call, then, will return for a message
906 // delivered to *either* thread.
907 DWORD result =
908 MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, QS_ALLINPUT);
909 if (result == WAIT_OBJECT_0) {
910 // Our NotifyWorkerThread event was signaled
911 BOOL success = ResetEvent(mEvent);
912 if (!success) {
913 gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle)
914 << "WindowsMessageChannel::WaitForSyncNotify failed to reset "
915 "event. GetLastError: "
916 << GetLastError();
918 break;
919 } else if (result != (WAIT_OBJECT_0 + 1)) {
920 NS_ERROR("Wait failed!");
921 break;
924 if (TimeoutHasExpired(timeoutData)) {
925 // A timeout was specified and we've passed it. Break out.
926 timedout = true;
927 break;
930 // The only way to know on which thread the message was delivered is to
931 // use some logic on the return values of GetQueueStatus and PeekMessage.
932 // PeekMessage will return false if there are no "queued" messages, but it
933 // will run all "nonqueued" messages before returning. So if PeekMessage
934 // returns false and there are no "nonqueued" messages that were run then
935 // we know that the message we woke for was intended for a window on
936 // another thread.
937 bool haveSentMessagesPending =
938 (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
940 // Either of the PeekMessage calls below will actually process all
941 // "nonqueued" messages that are pending before returning. If we have
942 // "nonqueued" messages pending then we should have switched out all the
943 // window procedures above. In that case this PeekMessage call won't
944 // actually cause any mozilla code (or plugin code) to run.
946 // We have to manually pump all COM messages *after* looking at the queue
947 // queue status but before yielding our thread below.
948 if (gCOMWindow) {
949 if (PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
950 TranslateMessage(&msg);
951 ::DispatchMessageW(&msg);
955 // If the following PeekMessage call fails to return a message for us (and
956 // returns false) and we didn't run any "nonqueued" messages then we must
957 // have woken up for a message designated for a window in another thread.
958 // If we loop immediately then we could enter a tight loop, so we'll give
959 // up our time slice here to let the child process its message.
960 if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) &&
961 !haveSentMessagesPending) {
962 // Message was for child, we should wait a bit.
963 SwitchToThread();
968 if (timerId) {
969 KillTimer(nullptr, timerId);
970 timerId = 0;
973 return WaitResponse(timedout);
976 void MessageChannel::NotifyWorkerThread() {
977 mMonitor->AssertCurrentThreadOwns();
979 if (mIsSyncWaitingOnNonMainThread) {
980 mMonitor->Notify();
981 return;
984 MOZ_RELEASE_ASSERT(mEvent, "No signal event to set, this is really bad!");
985 if (!SetEvent(mEvent)) {
986 NS_WARNING("Failed to set NotifyWorkerThread event!");
987 gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle)
988 << "WindowsMessageChannel failed to SetEvent. GetLastError: "
989 << GetLastError();
993 void DeferredSendMessage::Run() {
994 AssertWindowIsNotNeutered(hWnd);
995 if (!IsWindow(hWnd)) {
996 NS_ERROR("Invalid window!");
997 return;
1000 WNDPROC wndproc =
1001 reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
1002 if (!wndproc) {
1003 NS_ERROR("Invalid window procedure!");
1004 return;
1007 CallWindowProc(wndproc, hWnd, message, wParam, lParam);
1010 void DeferredRedrawMessage::Run() {
1011 AssertWindowIsNotNeutered(hWnd);
1012 if (!IsWindow(hWnd)) {
1013 NS_ERROR("Invalid window!");
1014 return;
1017 #ifdef DEBUG
1018 BOOL ret =
1019 #endif
1020 RedrawWindow(hWnd, nullptr, nullptr, flags);
1021 NS_ASSERTION(ret, "RedrawWindow failed!");
1024 DeferredUpdateMessage::DeferredUpdateMessage(HWND aHWnd) {
1025 mWnd = aHWnd;
1026 if (!GetUpdateRect(mWnd, &mUpdateRect, FALSE)) {
1027 memset(&mUpdateRect, 0, sizeof(RECT));
1028 return;
1030 ValidateRect(mWnd, &mUpdateRect);
1033 void DeferredUpdateMessage::Run() {
1034 AssertWindowIsNotNeutered(mWnd);
1035 if (!IsWindow(mWnd)) {
1036 NS_ERROR("Invalid window!");
1037 return;
1040 InvalidateRect(mWnd, &mUpdateRect, FALSE);
1041 #ifdef DEBUG
1042 BOOL ret =
1043 #endif
1044 UpdateWindow(mWnd);
1045 NS_ASSERTION(ret, "UpdateWindow failed!");
1048 DeferredSettingChangeMessage::DeferredSettingChangeMessage(HWND aHWnd,
1049 UINT aMessage,
1050 WPARAM aWParam,
1051 LPARAM aLParam)
1052 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) {
1053 NS_ASSERTION(aMessage == WM_SETTINGCHANGE, "Wrong message type!");
1054 if (aLParam) {
1055 lParamString = _wcsdup(reinterpret_cast<const wchar_t*>(aLParam));
1056 lParam = reinterpret_cast<LPARAM>(lParamString);
1057 } else {
1058 lParamString = nullptr;
1059 lParam = 0;
1063 DeferredSettingChangeMessage::~DeferredSettingChangeMessage() {
1064 free(lParamString);
1067 DeferredWindowPosMessage::DeferredWindowPosMessage(HWND aHWnd, LPARAM aLParam,
1068 bool aForCalcSize,
1069 WPARAM aWParam) {
1070 if (aForCalcSize) {
1071 if (aWParam) {
1072 NCCALCSIZE_PARAMS* arg = reinterpret_cast<NCCALCSIZE_PARAMS*>(aLParam);
1073 memcpy(&windowPos, arg->lppos, sizeof(windowPos));
1075 NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!");
1076 } else {
1077 RECT* arg = reinterpret_cast<RECT*>(aLParam);
1078 windowPos.hwnd = aHWnd;
1079 windowPos.hwndInsertAfter = nullptr;
1080 windowPos.x = arg->left;
1081 windowPos.y = arg->top;
1082 windowPos.cx = arg->right - arg->left;
1083 windowPos.cy = arg->bottom - arg->top;
1085 NS_ASSERTION(arg->right >= arg->left && arg->bottom >= arg->top,
1086 "Negative width or height!");
1088 windowPos.flags = SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER |
1089 SWP_NOZORDER | SWP_DEFERERASE | SWP_NOSENDCHANGING;
1090 } else {
1091 // Not for WM_NCCALCSIZE
1092 WINDOWPOS* arg = reinterpret_cast<WINDOWPOS*>(aLParam);
1093 memcpy(&windowPos, arg, sizeof(windowPos));
1095 NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!");
1097 // Windows sends in some private flags sometimes that we can't simply copy.
1098 // Filter here.
1099 UINT mask = SWP_ASYNCWINDOWPOS | SWP_DEFERERASE | SWP_DRAWFRAME |
1100 SWP_FRAMECHANGED | SWP_HIDEWINDOW | SWP_NOACTIVATE |
1101 SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW |
1102 SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE |
1103 SWP_NOZORDER | SWP_SHOWWINDOW;
1104 windowPos.flags &= mask;
1108 void DeferredWindowPosMessage::Run() {
1109 AssertWindowIsNotNeutered(windowPos.hwnd);
1110 if (!IsWindow(windowPos.hwnd)) {
1111 NS_ERROR("Invalid window!");
1112 return;
1115 if (!IsWindow(windowPos.hwndInsertAfter)) {
1116 NS_WARNING("ZOrder change cannot be honored");
1117 windowPos.hwndInsertAfter = 0;
1118 windowPos.flags |= SWP_NOZORDER;
1121 #ifdef DEBUG
1122 BOOL ret =
1123 #endif
1124 SetWindowPos(windowPos.hwnd, windowPos.hwndInsertAfter, windowPos.x,
1125 windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags);
1126 NS_ASSERTION(ret, "SetWindowPos failed!");
1129 DeferredCopyDataMessage::DeferredCopyDataMessage(HWND aHWnd, UINT aMessage,
1130 WPARAM aWParam, LPARAM aLParam)
1131 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) {
1132 NS_ASSERTION(IsWindow(reinterpret_cast<HWND>(aWParam)), "Bad window!");
1134 COPYDATASTRUCT* source = reinterpret_cast<COPYDATASTRUCT*>(aLParam);
1135 NS_ASSERTION(source, "Should never be null!");
1137 copyData.dwData = source->dwData;
1138 copyData.cbData = source->cbData;
1140 if (source->cbData) {
1141 copyData.lpData = malloc(source->cbData);
1142 if (copyData.lpData) {
1143 memcpy(copyData.lpData, source->lpData, source->cbData);
1144 } else {
1145 NS_ERROR("Out of memory?!");
1146 copyData.cbData = 0;
1148 } else {
1149 copyData.lpData = nullptr;
1152 lParam = reinterpret_cast<LPARAM>(&copyData);
1155 DeferredCopyDataMessage::~DeferredCopyDataMessage() { free(copyData.lpData); }
1157 DeferredStyleChangeMessage::DeferredStyleChangeMessage(HWND aHWnd,
1158 WPARAM aWParam,
1159 LPARAM aLParam)
1160 : hWnd(aHWnd) {
1161 index = static_cast<int>(aWParam);
1162 style = reinterpret_cast<STYLESTRUCT*>(aLParam)->styleNew;
1165 void DeferredStyleChangeMessage::Run() { SetWindowLongPtr(hWnd, index, style); }
1167 DeferredSetIconMessage::DeferredSetIconMessage(HWND aHWnd, UINT aMessage,
1168 WPARAM aWParam, LPARAM aLParam)
1169 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) {
1170 NS_ASSERTION(aMessage == WM_SETICON, "Wrong message type!");
1173 void DeferredSetIconMessage::Run() {
1174 AssertWindowIsNotNeutered(hWnd);
1175 if (!IsWindow(hWnd)) {
1176 NS_ERROR("Invalid window!");
1177 return;
1180 WNDPROC wndproc =
1181 reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
1182 if (!wndproc) {
1183 NS_ERROR("Invalid window procedure!");
1184 return;
1187 HICON hOld = reinterpret_cast<HICON>(
1188 CallWindowProc(wndproc, hWnd, message, wParam, lParam));
1189 if (hOld) {
1190 DestroyIcon(hOld);