Bug 1858509 add thread-safety annotations around MediaSourceDemuxer::mMonitor r=alwu
[gecko.git] / ipc / glue / WindowsMessageLoop.cpp
blob2f21c264f1b167cdb6ab811101cb0611c67b475f
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/UniquePtr.h"
24 #include "mozilla/WindowsProcessMitigations.h"
26 using namespace mozilla;
27 using namespace mozilla::ipc;
28 using namespace mozilla::ipc::windows;
30 /**
31 * The Windows-only code below exists to solve a general problem with deadlocks
32 * that we experience when sending synchronous IPC messages to processes that
33 * contain native windows (i.e. HWNDs). Windows (the OS) sends synchronous
34 * messages between parent and child HWNDs in multiple circumstances (e.g.
35 * WM_PARENTNOTIFY, WM_NCACTIVATE, etc.), even when those HWNDs are controlled
36 * by different threads or different processes. Thus we can very easily end up
37 * in a deadlock by a call stack like the following:
39 * Process A:
40 * - CreateWindow(...) creates a "parent" HWND.
41 * - SendCreateChildWidget(HWND) is a sync IPC message that sends the "parent"
42 * HWND over to Process B. Process A blocks until a response is received
43 * from Process B.
45 * Process B:
46 * - RecvCreateWidget(HWND) gets the "parent" HWND from Process A.
47 * - CreateWindow(..., HWND) creates a "child" HWND with the parent from
48 * process A.
49 * - Windows (the OS) generates a WM_PARENTNOTIFY message that is sent
50 * synchronously to Process A. Process B blocks until a response is
51 * received from Process A. Process A, however, is blocked and cannot
52 * process the message. Both processes are deadlocked.
54 * The example above has a few different workarounds (e.g. setting the
55 * WS_EX_NOPARENTNOTIFY style on the child window) but the general problem is
56 * persists. Once two HWNDs are parented we must not block their owning
57 * threads when manipulating either HWND.
59 * Windows requires any application that hosts native HWNDs to always process
60 * messages or risk deadlock. Given our architecture the only way to meet
61 * Windows' requirement and allow for synchronous IPC messages is to pump a
62 * miniature message loop during a sync IPC call. We avoid processing any
63 * queued messages during the loop (with one exception, see below), but
64 * "nonqueued" messages (see
65 * http://msdn.microsoft.com/en-us/library/ms644927(VS.85).aspx under the
66 * section "Nonqueued messages") cannot be avoided. Those messages are trapped
67 * in a special window procedure where we can either ignore the message or
68 * process it in some fashion.
70 * Queued and "non-queued" messages will be processed during Interrupt calls if
71 * modal UI related api calls block an Interrupt in-call in the child. To
72 * prevent windows from freezing, and to allow concurrent processing of critical
73 * events (such as painting), we spin a native event dispatch loop while
74 * these in-calls are blocked.
77 #if defined(ACCESSIBILITY)
78 // pulled from accessibility's win utils
79 extern const wchar_t* kPropNameTabContent;
80 #endif
82 // widget related message id constants we need to defer, see nsAppShell.
83 extern UINT sAppShellGeckoMsgId;
85 namespace {
87 const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc";
89 // This isn't defined before Windows XP.
90 enum { WM_XP_THEMECHANGED = 0x031A };
92 nsTArray<HWND>* gNeuteredWindows = nullptr;
94 typedef nsTArray<UniquePtr<DeferredMessage>> DeferredMessageArray;
95 DeferredMessageArray* gDeferredMessages = nullptr;
97 HHOOK gDeferredGetMsgHook = nullptr;
98 HHOOK gDeferredCallWndProcHook = nullptr;
100 DWORD gUIThreadId = 0;
101 HWND gCOMWindow = 0;
102 // Once initialized, gWinEventHook is never unhooked. We save the handle so
103 // that we can check whether or not the hook is initialized.
104 HWINEVENTHOOK gWinEventHook = nullptr;
105 const wchar_t kCOMWindowClassName[] = L"OleMainThreadWndClass";
107 // WM_GETOBJECT id pulled from uia headers
108 #define MOZOBJID_UIAROOT -25
110 HWND FindCOMWindow() {
111 MOZ_ASSERT(gUIThreadId);
113 HWND last = 0;
114 while (
115 (last = FindWindowExW(HWND_MESSAGE, last, kCOMWindowClassName, NULL))) {
116 if (GetWindowThreadProcessId(last, NULL) == gUIThreadId) {
117 return last;
121 return (HWND)0;
124 void CALLBACK WinEventHook(HWINEVENTHOOK aWinEventHook, DWORD aEvent,
125 HWND aHwnd, LONG aIdObject, LONG aIdChild,
126 DWORD aEventThread, DWORD aMsEventTime) {
127 MOZ_ASSERT(aWinEventHook == gWinEventHook);
128 MOZ_ASSERT(gUIThreadId == aEventThread);
129 switch (aEvent) {
130 case EVENT_OBJECT_CREATE: {
131 if (aIdObject != OBJID_WINDOW || aIdChild != CHILDID_SELF) {
132 // Not an event we're interested in
133 return;
135 wchar_t classBuf[256] = {0};
136 int result = ::GetClassNameW(aHwnd, classBuf, MOZ_ARRAY_LENGTH(classBuf));
137 if (result != (MOZ_ARRAY_LENGTH(kCOMWindowClassName) - 1) ||
138 wcsncmp(kCOMWindowClassName, classBuf, result)) {
139 // Not a class we're interested in
140 return;
142 MOZ_ASSERT(FindCOMWindow() == aHwnd);
143 gCOMWindow = aHwnd;
144 break;
146 case EVENT_OBJECT_DESTROY: {
147 if (aHwnd == gCOMWindow && aIdObject == OBJID_WINDOW) {
148 MOZ_ASSERT(aIdChild == CHILDID_SELF);
149 gCOMWindow = 0;
151 break;
153 default: {
154 return;
159 LRESULT CALLBACK DeferredMessageHook(int nCode, WPARAM wParam, LPARAM lParam) {
160 // XXX This function is called for *both* the WH_CALLWNDPROC hook and the
161 // WH_GETMESSAGE hook, but they have different parameters. We don't
162 // use any of them except nCode which has the same meaning.
164 // Only run deferred messages if all of these conditions are met:
165 // 1. The |nCode| indicates that this hook should do something.
166 // 2. We have deferred messages to run.
167 // 3. We're not being called from the PeekMessage within the WaitFor*Notify
168 // function (indicated with MessageChannel::IsPumpingMessages). We really
169 // only want to run after returning to the main event loop.
170 if (nCode >= 0 && gDeferredMessages && !MessageChannel::IsPumpingMessages()) {
171 NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
172 "These hooks must be set if we're being called!");
173 NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
175 // Unset hooks first, in case we reenter below.
176 UnhookWindowsHookEx(gDeferredGetMsgHook);
177 UnhookWindowsHookEx(gDeferredCallWndProcHook);
178 gDeferredGetMsgHook = 0;
179 gDeferredCallWndProcHook = 0;
181 // Unset the global and make sure we delete it when we're done here.
182 auto messages = WrapUnique(gDeferredMessages);
183 gDeferredMessages = nullptr;
185 // Run all the deferred messages in order.
186 uint32_t count = messages->Length();
187 for (uint32_t index = 0; index < count; index++) {
188 messages->ElementAt(index)->Run();
192 // Always call the next hook.
193 return CallNextHookEx(nullptr, nCode, wParam, lParam);
196 void ScheduleDeferredMessageRun() {
197 if (gDeferredMessages && !(gDeferredGetMsgHook && gDeferredCallWndProcHook)) {
198 NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
200 gDeferredGetMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, DeferredMessageHook,
201 nullptr, gUIThreadId);
202 gDeferredCallWndProcHook = ::SetWindowsHookEx(
203 WH_CALLWNDPROC, DeferredMessageHook, nullptr, gUIThreadId);
204 NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
205 "Failed to set hooks!");
209 static void DumpNeuteredMessage(HWND hwnd, UINT uMsg) {
210 #ifdef DEBUG
211 nsAutoCString log("Received \"nonqueued\" ");
212 // classify messages
213 if (uMsg < WM_USER) {
214 const char* msgText = mozilla::widget::WinUtils::WinEventToEventName(uMsg);
215 if (msgText) {
216 log.AppendPrintf("ui message \"%s\"", msgText);
217 } else {
218 log.AppendPrintf("ui message (0x%X)", uMsg);
220 } else if (uMsg >= WM_USER && uMsg < WM_APP) {
221 log.AppendPrintf("WM_USER message (0x%X)", uMsg);
222 } else if (uMsg >= WM_APP && uMsg < 0xC000) {
223 log.AppendPrintf("WM_APP message (0x%X)", uMsg);
224 } else if (uMsg >= 0xC000 && uMsg < 0x10000) {
225 log.AppendPrintf("registered windows message (0x%X)", uMsg);
226 } else {
227 log.AppendPrintf("system message (0x%X)", uMsg);
230 log.AppendLiteral(" during a synchronous IPC message for window ");
231 log.AppendPrintf("0x%p", hwnd);
233 wchar_t className[256] = {0};
234 if (GetClassNameW(hwnd, className, sizeof(className) - 1) > 0) {
235 log.AppendLiteral(" (\"");
236 log.Append(NS_ConvertUTF16toUTF8((char16_t*)className));
237 log.AppendLiteral("\")");
240 log.AppendLiteral(
241 ", sending it to DefWindowProc instead of the normal "
242 "window procedure.");
243 NS_ERROR(log.get());
244 #endif
247 LRESULT
248 ProcessOrDeferMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
249 UniquePtr<DeferredMessage> deferred;
251 // Most messages ask for 0 to be returned if the message is processed.
252 LRESULT res = 0;
254 switch (uMsg) {
255 // Messages that can be deferred as-is. These must not contain pointers in
256 // their wParam or lParam arguments!
257 case WM_ACTIVATE:
258 case WM_ACTIVATEAPP:
259 case WM_CANCELMODE:
260 case WM_CAPTURECHANGED:
261 case WM_CHILDACTIVATE:
262 case WM_DESTROY:
263 case WM_ENABLE:
264 case WM_IME_NOTIFY:
265 case WM_IME_SETCONTEXT:
266 case WM_KILLFOCUS:
267 case WM_MOUSEWHEEL:
268 case WM_NCDESTROY:
269 case WM_PARENTNOTIFY:
270 case WM_SETFOCUS:
271 case WM_SYSCOMMAND:
272 case WM_DISPLAYCHANGE:
273 case WM_SHOWWINDOW: // Intentional fall-through.
274 case WM_XP_THEMECHANGED: {
275 deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
276 break;
279 case WM_DEVICECHANGE:
280 case WM_POWERBROADCAST:
281 case WM_NCACTIVATE: // Intentional fall-through.
282 case WM_SETCURSOR: {
283 // Friggin unconventional return value...
284 res = TRUE;
285 deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
286 break;
289 case WM_MOUSEACTIVATE: {
290 res = MA_NOACTIVATE;
291 deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
292 break;
295 // These messages need to use the RedrawWindow function to generate the
296 // right kind of message. We can't simply fake them as the MSDN docs say
297 // explicitly that paint messages should not be sent by an application.
298 case WM_ERASEBKGND: {
299 UINT flags = RDW_INVALIDATE | RDW_ERASE | RDW_NOINTERNALPAINT |
300 RDW_NOFRAME | RDW_NOCHILDREN | RDW_ERASENOW;
301 deferred = MakeUnique<DeferredRedrawMessage>(hwnd, flags);
302 break;
305 // This message will generate a WM_PAINT message if there are invalid
306 // areas.
307 case WM_PAINT: {
308 deferred = MakeUnique<DeferredUpdateMessage>(hwnd);
309 break;
312 // This message holds a string in its lParam that we must copy.
313 case WM_SETTINGCHANGE: {
314 deferred =
315 MakeUnique<DeferredSettingChangeMessage>(hwnd, uMsg, wParam, lParam);
316 break;
319 // These messages are faked via a call to SetWindowPos.
320 case WM_WINDOWPOSCHANGED: {
321 deferred = MakeUnique<DeferredWindowPosMessage>(hwnd, lParam);
322 break;
324 case WM_NCCALCSIZE: {
325 deferred =
326 MakeUnique<DeferredWindowPosMessage>(hwnd, lParam, true, wParam);
327 break;
330 case WM_COPYDATA: {
331 deferred =
332 MakeUnique<DeferredCopyDataMessage>(hwnd, uMsg, wParam, lParam);
333 res = TRUE;
334 break;
337 case WM_STYLECHANGED: {
338 deferred = MakeUnique<DeferredStyleChangeMessage>(hwnd, wParam, lParam);
339 break;
342 case WM_SETICON: {
343 deferred = MakeUnique<DeferredSetIconMessage>(hwnd, uMsg, wParam, lParam);
344 break;
347 // Messages that are safe to pass to DefWindowProc go here.
348 case WM_ENTERIDLE:
349 case WM_GETICON:
350 case WM_NCPAINT: // (never trap nc paint events)
351 case WM_GETMINMAXINFO:
352 case WM_GETTEXT:
353 case WM_NCHITTEST:
354 case WM_STYLECHANGING: // Intentional fall-through.
355 case WM_WINDOWPOSCHANGING:
356 case WM_GETTEXTLENGTH: {
357 return DefWindowProc(hwnd, uMsg, wParam, lParam);
360 // Just return, prevents DefWindowProc from messaging the window
361 // syncronously with other events, which may be deferred. Prevents
362 // random shutdown of aero composition on the window.
363 case WM_SYNCPAINT:
364 return 0;
366 // This message causes QuickTime to make re-entrant calls.
367 // Simply discarding it doesn't seem to hurt anything.
368 case WM_APP - 1:
369 return 0;
371 // We only support a query for our IAccessible or UIA pointers.
372 // This should be safe, and needs to be sync.
373 #if defined(ACCESSIBILITY)
374 case WM_GETOBJECT: {
375 LONG objId = static_cast<LONG>(lParam);
376 if (objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT) {
377 WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
378 if (oldWndProc) {
379 return CallWindowProcW(oldWndProc, hwnd, uMsg, wParam, lParam);
382 return DefWindowProc(hwnd, uMsg, wParam, lParam);
384 #endif // ACCESSIBILITY
386 default: {
387 // Unknown messages only are logged in debug builds and sent to
388 // DefWindowProc.
389 if (uMsg && uMsg == sAppShellGeckoMsgId) {
390 // Widget's registered native event callback
391 deferred = MakeUnique<DeferredSendMessage>(hwnd, uMsg, wParam, lParam);
396 // No deferred message was created and we land here, this is an
397 // unhandled message.
398 if (!deferred) {
399 DumpNeuteredMessage(hwnd, uMsg);
400 return DefWindowProc(hwnd, uMsg, wParam, lParam);
403 // Create the deferred message array if it doesn't exist already.
404 if (!gDeferredMessages) {
405 gDeferredMessages = new DeferredMessageArray(20);
408 // Save for later. The array takes ownership of |deferred|.
409 gDeferredMessages->AppendElement(std::move(deferred));
410 return res;
413 } // namespace
415 LRESULT CALLBACK NeuteredWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam,
416 LPARAM lParam) {
417 WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
418 if (!oldWndProc) {
419 // We should really never ever get here.
420 NS_ERROR("No old wndproc!");
421 return DefWindowProc(hwnd, uMsg, wParam, lParam);
424 // See if we care about this message. We may either ignore it, send it to
425 // DefWindowProc, or defer it for later.
426 return ProcessOrDeferMessage(hwnd, uMsg, wParam, lParam);
429 namespace {
431 static bool WindowIsDeferredWindow(HWND hWnd) {
432 if (!IsWindow(hWnd)) {
433 NS_WARNING("Window has died!");
434 return false;
437 char16_t buffer[256] = {0};
438 int length = GetClassNameW(hWnd, (wchar_t*)buffer, sizeof(buffer) - 1);
439 if (length <= 0) {
440 NS_WARNING("Failed to get class name!");
441 return false;
444 #if defined(ACCESSIBILITY)
445 // Tab content creates a window that responds to accessible WM_GETOBJECT
446 // calls. This window can safely be ignored.
447 if (::GetPropW(hWnd, kPropNameTabContent)) {
448 return false;
450 #endif
452 // Common mozilla windows we must defer messages to.
453 nsDependentString className(buffer, length);
454 if (StringBeginsWith(className, u"Mozilla"_ns) ||
455 StringBeginsWith(className, u"Gecko"_ns) ||
456 className.EqualsLiteral("nsToolkitClass") ||
457 className.EqualsLiteral("nsAppShell:EventWindowClass")) {
458 return true;
461 return false;
464 bool NeuterWindowProcedure(HWND hWnd) {
465 if (!WindowIsDeferredWindow(hWnd)) {
466 // Some other kind of window, skip.
467 return false;
470 NS_ASSERTION(!GetProp(hWnd, kOldWndProcProp), "This should always be null!");
472 // It's possible to get nullptr out of SetWindowLongPtr, and the only way to
473 // know if that's a valid old value is to use GetLastError. Clear the error
474 // here so we can tell.
475 SetLastError(ERROR_SUCCESS);
477 LONG_PTR currentWndProc =
478 SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)NeuteredWindowProc);
479 if (!currentWndProc) {
480 if (ERROR_SUCCESS == GetLastError()) {
481 // No error, so we set something and must therefore reset it.
482 SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc);
484 return false;
487 NS_ASSERTION(currentWndProc != (LONG_PTR)NeuteredWindowProc,
488 "This shouldn't be possible!");
490 if (!SetProp(hWnd, kOldWndProcProp, (HANDLE)currentWndProc)) {
491 // Cleanup
492 NS_WARNING("SetProp failed!");
493 SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc);
494 RemovePropW(hWnd, kOldWndProcProp);
495 return false;
498 return true;
501 void RestoreWindowProcedure(HWND hWnd) {
502 NS_ASSERTION(WindowIsDeferredWindow(hWnd),
503 "Not a deferred window, this shouldn't be in our list!");
504 LONG_PTR oldWndProc = (LONG_PTR)GetProp(hWnd, kOldWndProcProp);
505 if (oldWndProc) {
506 NS_ASSERTION(oldWndProc != (LONG_PTR)NeuteredWindowProc,
507 "This shouldn't be possible!");
509 DebugOnly<LONG_PTR> currentWndProc =
510 SetWindowLongPtr(hWnd, GWLP_WNDPROC, oldWndProc);
511 NS_ASSERTION(currentWndProc == (LONG_PTR)NeuteredWindowProc,
512 "This should never be switched out from under us!");
514 RemovePropW(hWnd, kOldWndProcProp);
517 LRESULT CALLBACK CallWindowProcedureHook(int nCode, WPARAM wParam,
518 LPARAM lParam) {
519 if (nCode >= 0) {
520 NS_ASSERTION(gNeuteredWindows, "This should never be null!");
522 HWND hWnd = reinterpret_cast<CWPSTRUCT*>(lParam)->hwnd;
524 if (!gNeuteredWindows->Contains(hWnd) &&
525 !SuppressedNeuteringRegion::IsNeuteringSuppressed() &&
526 NeuterWindowProcedure(hWnd)) {
527 // XXX(Bug 1631371) Check if this should use a fallible operation as it
528 // pretended earlier.
529 gNeuteredWindows->AppendElement(hWnd);
532 return CallNextHookEx(nullptr, nCode, wParam, lParam);
535 inline void AssertWindowIsNotNeutered(HWND hWnd) {
536 #ifdef DEBUG
537 // Make sure our neutered window hook isn't still in place.
538 LONG_PTR wndproc = GetWindowLongPtr(hWnd, GWLP_WNDPROC);
539 NS_ASSERTION(wndproc != (LONG_PTR)NeuteredWindowProc, "Window is neutered!");
540 #endif
543 void UnhookNeuteredWindows() {
544 if (!gNeuteredWindows) return;
545 uint32_t count = gNeuteredWindows->Length();
546 for (uint32_t index = 0; index < count; index++) {
547 RestoreWindowProcedure(gNeuteredWindows->ElementAt(index));
549 gNeuteredWindows->Clear();
552 // This timeout stuff assumes a sane value of mTimeoutMs (less than the overflow
553 // value for GetTickCount(), which is something like 50 days). It uses the
554 // cheapest (and least accurate) method supported by Windows 2000.
556 struct TimeoutData {
557 DWORD startTicks;
558 DWORD targetTicks;
561 void InitTimeoutData(TimeoutData* aData, int32_t aTimeoutMs) {
562 aData->startTicks = GetTickCount();
563 if (!aData->startTicks) {
564 // How unlikely is this!
565 aData->startTicks++;
567 aData->targetTicks = aData->startTicks + aTimeoutMs;
570 bool TimeoutHasExpired(const TimeoutData& aData) {
571 if (!aData.startTicks) {
572 return false;
575 DWORD now = GetTickCount();
577 if (aData.targetTicks < aData.startTicks) {
578 // Overflow
579 return now < aData.startTicks && now >= aData.targetTicks;
581 return now >= aData.targetTicks;
584 } // namespace
586 namespace mozilla {
587 namespace ipc {
588 namespace windows {
590 void InitUIThread() {
591 if (!XRE_UseNativeEventProcessing()) {
592 return;
594 // If we aren't setup before a call to NotifyWorkerThread, we'll hang
595 // on startup.
596 if (!gUIThreadId) {
597 gUIThreadId = GetCurrentThreadId();
600 MOZ_ASSERT(gUIThreadId);
601 MOZ_ASSERT(gUIThreadId == GetCurrentThreadId(),
602 "Called InitUIThread multiple times on different threads!");
604 if (!gWinEventHook && !mscom::IsCurrentThreadMTA()) {
605 gWinEventHook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY,
606 NULL, &WinEventHook, GetCurrentProcessId(),
607 gUIThreadId, WINEVENT_OUTOFCONTEXT);
608 MOZ_ASSERT(gWinEventHook);
610 // We need to execute this after setting the hook in case the OLE window
611 // already existed.
612 gCOMWindow = FindCOMWindow();
616 } // namespace windows
617 } // namespace ipc
618 } // namespace mozilla
620 // See SpinInternalEventLoop below
621 MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel* channel)
622 : mSpinNestedEvents(false),
623 mListenerNotified(false),
624 mChannel(channel),
625 mPrev(mChannel->mTopFrame),
626 mStaticPrev(sStaticTopFrame) {
627 // Only track stack frames when Windows message deferral behavior
628 // is request for the channel.
629 if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
630 return;
633 mChannel->mTopFrame = this;
634 sStaticTopFrame = this;
636 if (!mStaticPrev) {
637 NS_ASSERTION(!gNeuteredWindows, "Should only set this once!");
638 gNeuteredWindows = new AutoTArray<HWND, 20>();
639 NS_ASSERTION(gNeuteredWindows, "Out of memory!");
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 delete gNeuteredWindows;
659 gNeuteredWindows = nullptr;
663 MessageChannel::SyncStackFrame* MessageChannel::sStaticTopFrame;
665 // nsAppShell's notification that gecko events are being processed.
666 // If we are here and there is an Interrupt Incall active, we are spinning
667 // a nested gecko event loop. In which case the remote process needs
668 // to know about it.
669 void /* static */
670 MessageChannel::NotifyGeckoEventDispatch() {
671 // sStaticTopFrame is only valid for Interrupt channels
672 if (!sStaticTopFrame || sStaticTopFrame->mListenerNotified) return;
674 sStaticTopFrame->mListenerNotified = true;
675 MessageChannel* channel =
676 static_cast<MessageChannel*>(sStaticTopFrame->mChannel);
677 channel->Listener()->ProcessRemoteNativeEventsInInterruptCall();
680 // invoked by the module that receives the spin event loop
681 // message.
682 void MessageChannel::ProcessNativeEventsInInterruptCall() {
683 NS_ASSERTION(GetCurrentThreadId() == gUIThreadId,
684 "Shouldn't be on a non-main thread in here!");
685 if (!mTopFrame) {
686 NS_ERROR("Spin logic error: no Interrupt frame");
687 return;
690 mTopFrame->mSpinNestedEvents = true;
693 // Spin loop is called in place of WaitFor*Notify when modal ui is being shown
694 // in a child. There are some intricacies in using it however. Spin loop is
695 // enabled for a particular Interrupt frame by the client calling
696 // MessageChannel::ProcessNativeEventsInInterrupt().
697 // This call can be nested for multiple Interrupt frames in a single plugin or
698 // multiple unrelated plugins.
699 void MessageChannel::SpinInternalEventLoop() {
700 if (mozilla::PaintTracker::IsPainting()) {
701 MOZ_CRASH("Don't spin an event loop while painting.");
704 NS_ASSERTION(mTopFrame && mTopFrame->mSpinNestedEvents,
705 "Spinning incorrectly");
707 // Nested windows event loop we trigger when the child enters into modal
708 // event loops.
710 // Note, when we return, we always reset the notify worker event. So there's
711 // no need to reset it on return here.
713 do {
714 MSG msg = {0};
716 // Don't get wrapped up in here if the child connection dies.
718 MonitorAutoLock lock(*mMonitor);
719 if (!Connected()) {
720 return;
724 // Retrieve window or thread messages
725 if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
726 // The child UI should have been destroyed before the app is closed, in
727 // which case, we should never get this here.
728 if (msg.message == WM_QUIT) {
729 NS_ERROR("WM_QUIT received in SpinInternalEventLoop!");
730 } else {
731 TranslateMessage(&msg);
732 ::DispatchMessageW(&msg);
733 return;
737 // Note, give dispatching windows events priority over checking if
738 // mEvent is signaled, otherwise heavy ipc traffic can cause jittery
739 // playback of video. We'll exit out on each disaptch above, so ipc
740 // won't get starved.
742 // Wait for UI events or a signal from the io thread.
743 DWORD result =
744 MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, QS_ALLINPUT);
745 if (result == WAIT_OBJECT_0) {
746 // Our NotifyWorkerThread event was signaled
747 return;
749 } while (true);
752 static HHOOK gWindowHook;
754 static inline void StartNeutering() {
755 if (!gUIThreadId) {
756 mozilla::ipc::windows::InitUIThread();
758 MOZ_ASSERT(gUIThreadId);
759 MOZ_ASSERT(!gWindowHook);
760 NS_ASSERTION(!MessageChannel::IsPumpingMessages(),
761 "Shouldn't be pumping already!");
762 MessageChannel::SetIsPumpingMessages(true);
763 gWindowHook = ::SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
764 nullptr, gUIThreadId);
765 NS_ASSERTION(gWindowHook, "Failed to set hook!");
768 static void StopNeutering() {
769 MOZ_ASSERT(MessageChannel::IsPumpingMessages());
770 ::UnhookWindowsHookEx(gWindowHook);
771 gWindowHook = NULL;
772 ::UnhookNeuteredWindows();
773 // Before returning we need to set a hook to run any deferred messages that
774 // we received during the IPC call. The hook will unset itself as soon as
775 // someone else calls GetMessage, PeekMessage, or runs code that generates
776 // a "nonqueued" message.
777 ::ScheduleDeferredMessageRun();
778 MessageChannel::SetIsPumpingMessages(false);
781 NeuteredWindowRegion::NeuteredWindowRegion(bool aDoNeuter)
782 : mNeuteredByThis(!gWindowHook && aDoNeuter &&
783 XRE_UseNativeEventProcessing()) {
784 if (mNeuteredByThis) {
785 StartNeutering();
789 NeuteredWindowRegion::~NeuteredWindowRegion() {
790 if (gWindowHook && mNeuteredByThis) {
791 StopNeutering();
795 void NeuteredWindowRegion::PumpOnce() {
796 if (!gWindowHook) {
797 // This should be a no-op if nothing has been neutered.
798 return;
801 MSG msg = {0};
802 // Pump any COM messages so that we don't hang due to STA marshaling.
803 if (gCOMWindow && ::PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
804 ::TranslateMessage(&msg);
805 ::DispatchMessageW(&msg);
807 // Expunge any nonqueued messages on the current thread.
808 ::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE);
811 DeneuteredWindowRegion::DeneuteredWindowRegion()
812 : mReneuter(gWindowHook != NULL) {
813 if (mReneuter) {
814 StopNeutering();
818 DeneuteredWindowRegion::~DeneuteredWindowRegion() {
819 if (mReneuter) {
820 StartNeutering();
824 SuppressedNeuteringRegion::SuppressedNeuteringRegion()
825 : mReenable(::gUIThreadId == ::GetCurrentThreadId() && ::gWindowHook) {
826 if (mReenable) {
827 MOZ_ASSERT(!sSuppressNeutering);
828 sSuppressNeutering = true;
832 SuppressedNeuteringRegion::~SuppressedNeuteringRegion() {
833 if (mReenable) {
834 MOZ_ASSERT(sSuppressNeutering);
835 sSuppressNeutering = false;
839 bool SuppressedNeuteringRegion::sSuppressNeutering = false;
841 bool MessageChannel::WaitForSyncNotify() {
842 mMonitor->AssertCurrentThreadOwns();
844 if (!gUIThreadId) {
845 mozilla::ipc::windows::InitUIThread();
848 // Use a blocking wait if this channel does not require
849 // Windows message deferral behavior.
850 if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
851 TimeDuration timeout = (kNoTimeout == mTimeoutMs)
852 ? TimeDuration::Forever()
853 : TimeDuration::FromMilliseconds(mTimeoutMs);
855 MOZ_ASSERT(!mIsSyncWaitingOnNonMainThread);
856 mIsSyncWaitingOnNonMainThread = true;
858 CVStatus status = mMonitor->Wait(timeout);
860 MOZ_ASSERT(mIsSyncWaitingOnNonMainThread);
861 mIsSyncWaitingOnNonMainThread = false;
863 // If the timeout didn't expire, we know we received an event. The
864 // converse is not true.
865 return WaitResponse(status == CVStatus::Timeout);
868 NS_ASSERTION(
869 mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION,
870 "Shouldn't be here for channels that don't use message deferral!");
871 NS_ASSERTION(mTopFrame, "No top frame!");
873 MonitorAutoUnlock unlock(*mMonitor);
875 bool timedout = false;
877 UINT_PTR timerId = 0;
878 TimeoutData timeoutData = {0};
880 if (mTimeoutMs != kNoTimeout) {
881 InitTimeoutData(&timeoutData, mTimeoutMs);
883 // We only do this to ensure that we won't get stuck in
884 // MsgWaitForMultipleObjects below.
885 timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
886 NS_ASSERTION(timerId, "SetTimer failed!");
889 NeuteredWindowRegion neuteredRgn(true);
892 while (1) {
893 MSG msg = {0};
894 // Don't get wrapped up in here if the child connection dies.
896 MonitorAutoLock lock(*mMonitor);
897 if (!Connected()) {
898 break;
902 // Wait until we have a message in the queue. MSDN docs are a bit unclear
903 // but it seems that windows from two different threads (and it should be
904 // noted that a thread in another process counts as a "different thread")
905 // will implicitly have their message queues attached if they are parented
906 // to one another. This wait call, then, will return for a message
907 // delivered to *either* thread.
908 DWORD result =
909 MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, QS_ALLINPUT);
910 if (result == WAIT_OBJECT_0) {
911 // Our NotifyWorkerThread event was signaled
912 BOOL success = ResetEvent(mEvent);
913 if (!success) {
914 gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle)
915 << "WindowsMessageChannel::WaitForSyncNotify failed to reset "
916 "event. GetLastError: "
917 << GetLastError();
919 break;
920 } else if (result != (WAIT_OBJECT_0 + 1)) {
921 NS_ERROR("Wait failed!");
922 break;
925 if (TimeoutHasExpired(timeoutData)) {
926 // A timeout was specified and we've passed it. Break out.
927 timedout = true;
928 break;
931 // The only way to know on which thread the message was delivered is to
932 // use some logic on the return values of GetQueueStatus and PeekMessage.
933 // PeekMessage will return false if there are no "queued" messages, but it
934 // will run all "nonqueued" messages before returning. So if PeekMessage
935 // returns false and there are no "nonqueued" messages that were run then
936 // we know that the message we woke for was intended for a window on
937 // another thread.
938 bool haveSentMessagesPending =
939 (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
941 // Either of the PeekMessage calls below will actually process all
942 // "nonqueued" messages that are pending before returning. If we have
943 // "nonqueued" messages pending then we should have switched out all the
944 // window procedures above. In that case this PeekMessage call won't
945 // actually cause any mozilla code (or plugin code) to run.
947 // We have to manually pump all COM messages *after* looking at the queue
948 // queue status but before yielding our thread below.
949 if (gCOMWindow) {
950 if (PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
951 TranslateMessage(&msg);
952 ::DispatchMessageW(&msg);
956 // If the following PeekMessage call fails to return a message for us (and
957 // returns false) and we didn't run any "nonqueued" messages then we must
958 // have woken up for a message designated for a window in another thread.
959 // If we loop immediately then we could enter a tight loop, so we'll give
960 // up our time slice here to let the child process its message.
961 if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) &&
962 !haveSentMessagesPending) {
963 // Message was for child, we should wait a bit.
964 SwitchToThread();
969 if (timerId) {
970 KillTimer(nullptr, timerId);
971 timerId = 0;
974 return WaitResponse(timedout);
977 void MessageChannel::NotifyWorkerThread() {
978 mMonitor->AssertCurrentThreadOwns();
980 if (mIsSyncWaitingOnNonMainThread) {
981 mMonitor->Notify();
982 return;
985 MOZ_RELEASE_ASSERT(mEvent, "No signal event to set, this is really bad!");
986 if (!SetEvent(mEvent)) {
987 NS_WARNING("Failed to set NotifyWorkerThread event!");
988 gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle)
989 << "WindowsMessageChannel failed to SetEvent. GetLastError: "
990 << GetLastError();
994 void DeferredSendMessage::Run() {
995 AssertWindowIsNotNeutered(hWnd);
996 if (!IsWindow(hWnd)) {
997 NS_ERROR("Invalid window!");
998 return;
1001 WNDPROC wndproc =
1002 reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
1003 if (!wndproc) {
1004 NS_ERROR("Invalid window procedure!");
1005 return;
1008 CallWindowProc(wndproc, hWnd, message, wParam, lParam);
1011 void DeferredRedrawMessage::Run() {
1012 AssertWindowIsNotNeutered(hWnd);
1013 if (!IsWindow(hWnd)) {
1014 NS_ERROR("Invalid window!");
1015 return;
1018 #ifdef DEBUG
1019 BOOL ret =
1020 #endif
1021 RedrawWindow(hWnd, nullptr, nullptr, flags);
1022 NS_ASSERTION(ret, "RedrawWindow failed!");
1025 DeferredUpdateMessage::DeferredUpdateMessage(HWND aHWnd) {
1026 mWnd = aHWnd;
1027 if (!GetUpdateRect(mWnd, &mUpdateRect, FALSE)) {
1028 memset(&mUpdateRect, 0, sizeof(RECT));
1029 return;
1031 ValidateRect(mWnd, &mUpdateRect);
1034 void DeferredUpdateMessage::Run() {
1035 AssertWindowIsNotNeutered(mWnd);
1036 if (!IsWindow(mWnd)) {
1037 NS_ERROR("Invalid window!");
1038 return;
1041 InvalidateRect(mWnd, &mUpdateRect, FALSE);
1042 #ifdef DEBUG
1043 BOOL ret =
1044 #endif
1045 UpdateWindow(mWnd);
1046 NS_ASSERTION(ret, "UpdateWindow failed!");
1049 DeferredSettingChangeMessage::DeferredSettingChangeMessage(HWND aHWnd,
1050 UINT aMessage,
1051 WPARAM aWParam,
1052 LPARAM aLParam)
1053 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) {
1054 NS_ASSERTION(aMessage == WM_SETTINGCHANGE, "Wrong message type!");
1055 if (aLParam) {
1056 lParamString = _wcsdup(reinterpret_cast<const wchar_t*>(aLParam));
1057 lParam = reinterpret_cast<LPARAM>(lParamString);
1058 } else {
1059 lParamString = nullptr;
1060 lParam = 0;
1064 DeferredSettingChangeMessage::~DeferredSettingChangeMessage() {
1065 free(lParamString);
1068 DeferredWindowPosMessage::DeferredWindowPosMessage(HWND aHWnd, LPARAM aLParam,
1069 bool aForCalcSize,
1070 WPARAM aWParam) {
1071 if (aForCalcSize) {
1072 if (aWParam) {
1073 NCCALCSIZE_PARAMS* arg = reinterpret_cast<NCCALCSIZE_PARAMS*>(aLParam);
1074 memcpy(&windowPos, arg->lppos, sizeof(windowPos));
1076 NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!");
1077 } else {
1078 RECT* arg = reinterpret_cast<RECT*>(aLParam);
1079 windowPos.hwnd = aHWnd;
1080 windowPos.hwndInsertAfter = nullptr;
1081 windowPos.x = arg->left;
1082 windowPos.y = arg->top;
1083 windowPos.cx = arg->right - arg->left;
1084 windowPos.cy = arg->bottom - arg->top;
1086 NS_ASSERTION(arg->right >= arg->left && arg->bottom >= arg->top,
1087 "Negative width or height!");
1089 windowPos.flags = SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER |
1090 SWP_NOZORDER | SWP_DEFERERASE | SWP_NOSENDCHANGING;
1091 } else {
1092 // Not for WM_NCCALCSIZE
1093 WINDOWPOS* arg = reinterpret_cast<WINDOWPOS*>(aLParam);
1094 memcpy(&windowPos, arg, sizeof(windowPos));
1096 NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!");
1098 // Windows sends in some private flags sometimes that we can't simply copy.
1099 // Filter here.
1100 UINT mask = SWP_ASYNCWINDOWPOS | SWP_DEFERERASE | SWP_DRAWFRAME |
1101 SWP_FRAMECHANGED | SWP_HIDEWINDOW | SWP_NOACTIVATE |
1102 SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW |
1103 SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE |
1104 SWP_NOZORDER | SWP_SHOWWINDOW;
1105 windowPos.flags &= mask;
1109 void DeferredWindowPosMessage::Run() {
1110 AssertWindowIsNotNeutered(windowPos.hwnd);
1111 if (!IsWindow(windowPos.hwnd)) {
1112 NS_ERROR("Invalid window!");
1113 return;
1116 if (!IsWindow(windowPos.hwndInsertAfter)) {
1117 NS_WARNING("ZOrder change cannot be honored");
1118 windowPos.hwndInsertAfter = 0;
1119 windowPos.flags |= SWP_NOZORDER;
1122 #ifdef DEBUG
1123 BOOL ret =
1124 #endif
1125 SetWindowPos(windowPos.hwnd, windowPos.hwndInsertAfter, windowPos.x,
1126 windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags);
1127 NS_ASSERTION(ret, "SetWindowPos failed!");
1130 DeferredCopyDataMessage::DeferredCopyDataMessage(HWND aHWnd, UINT aMessage,
1131 WPARAM aWParam, LPARAM aLParam)
1132 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) {
1133 NS_ASSERTION(IsWindow(reinterpret_cast<HWND>(aWParam)), "Bad window!");
1135 COPYDATASTRUCT* source = reinterpret_cast<COPYDATASTRUCT*>(aLParam);
1136 NS_ASSERTION(source, "Should never be null!");
1138 copyData.dwData = source->dwData;
1139 copyData.cbData = source->cbData;
1141 if (source->cbData) {
1142 copyData.lpData = malloc(source->cbData);
1143 if (copyData.lpData) {
1144 memcpy(copyData.lpData, source->lpData, source->cbData);
1145 } else {
1146 NS_ERROR("Out of memory?!");
1147 copyData.cbData = 0;
1149 } else {
1150 copyData.lpData = nullptr;
1153 lParam = reinterpret_cast<LPARAM>(&copyData);
1156 DeferredCopyDataMessage::~DeferredCopyDataMessage() { free(copyData.lpData); }
1158 DeferredStyleChangeMessage::DeferredStyleChangeMessage(HWND aHWnd,
1159 WPARAM aWParam,
1160 LPARAM aLParam)
1161 : hWnd(aHWnd) {
1162 index = static_cast<int>(aWParam);
1163 style = reinterpret_cast<STYLESTRUCT*>(aLParam)->styleNew;
1166 void DeferredStyleChangeMessage::Run() { SetWindowLongPtr(hWnd, index, style); }
1168 DeferredSetIconMessage::DeferredSetIconMessage(HWND aHWnd, UINT aMessage,
1169 WPARAM aWParam, LPARAM aLParam)
1170 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) {
1171 NS_ASSERTION(aMessage == WM_SETICON, "Wrong message type!");
1174 void DeferredSetIconMessage::Run() {
1175 AssertWindowIsNotNeutered(hWnd);
1176 if (!IsWindow(hWnd)) {
1177 NS_ERROR("Invalid window!");
1178 return;
1181 WNDPROC wndproc =
1182 reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
1183 if (!wndproc) {
1184 NS_ERROR("Invalid window procedure!");
1185 return;
1188 HICON hOld = reinterpret_cast<HICON>(
1189 CallWindowProc(wndproc, hWnd, message, wParam, lParam));
1190 if (hOld) {
1191 DestroyIcon(hOld);