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"
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/StaticPtr.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
;
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:
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
46 * - RecvCreateWidget(HWND) gets the "parent" HWND from Process A.
47 * - CreateWindow(..., HWND) creates a "child" HWND with the parent from
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
;
82 // widget related message id constants we need to defer, see nsAppShell.
83 extern UINT sAppShellGeckoMsgId
;
87 const wchar_t kOldWndProcProp
[] = L
"MozillaIPCOldWndProc";
89 // This isn't defined before Windows XP.
90 enum { WM_XP_THEMECHANGED
= 0x031A };
92 static StaticAutoPtr
<AutoTArray
<HWND
, 20>> gNeuteredWindows
;
94 typedef nsTArray
<UniquePtr
<DeferredMessage
>> DeferredMessageArray
;
95 DeferredMessageArray
* gDeferredMessages
= nullptr;
97 HHOOK gDeferredGetMsgHook
= nullptr;
98 HHOOK gDeferredCallWndProcHook
= nullptr;
100 DWORD gUIThreadId
= 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
);
115 (last
= FindWindowExW(HWND_MESSAGE
, last
, kCOMWindowClassName
, NULL
))) {
116 if (GetWindowThreadProcessId(last
, NULL
) == gUIThreadId
) {
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
);
130 case EVENT_OBJECT_CREATE
: {
131 if (aIdObject
!= OBJID_WINDOW
|| aIdChild
!= CHILDID_SELF
) {
132 // Not an event we're interested in
135 wchar_t classBuf
[256] = {0};
136 int result
= ::GetClassNameW(aHwnd
, classBuf
, std::size(classBuf
));
137 if (result
!= (std::size(kCOMWindowClassName
) - 1) ||
138 wcsncmp(kCOMWindowClassName
, classBuf
, result
)) {
139 // Not a class we're interested in
142 MOZ_ASSERT(FindCOMWindow() == aHwnd
);
146 case EVENT_OBJECT_DESTROY
: {
147 if (aHwnd
== gCOMWindow
&& aIdObject
== OBJID_WINDOW
) {
148 MOZ_ASSERT(aIdChild
== CHILDID_SELF
);
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
) {
211 nsAutoCString
log("Received \"nonqueued\" ");
213 if (uMsg
< WM_USER
) {
214 const char* msgText
= mozilla::widget::WinUtils::WinEventToEventName(uMsg
);
216 log
.AppendPrintf("ui message \"%s\"", msgText
);
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
);
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("\")");
241 ", sending it to DefWindowProc instead of the normal "
242 "window procedure.");
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.
255 // Messages that can be deferred as-is. These must not contain pointers in
256 // their wParam or lParam arguments!
260 case WM_CAPTURECHANGED
:
261 case WM_CHILDACTIVATE
:
265 case WM_IME_SETCONTEXT
:
269 case WM_PARENTNOTIFY
:
272 case WM_DISPLAYCHANGE
:
273 case WM_SHOWWINDOW
: // Intentional fall-through.
274 case WM_XP_THEMECHANGED
: {
275 deferred
= MakeUnique
<DeferredSendMessage
>(hwnd
, uMsg
, wParam
, lParam
);
279 case WM_DEVICECHANGE
:
280 case WM_POWERBROADCAST
:
281 case WM_NCACTIVATE
: // Intentional fall-through.
283 // Friggin unconventional return value...
285 deferred
= MakeUnique
<DeferredSendMessage
>(hwnd
, uMsg
, wParam
, lParam
);
289 case WM_MOUSEACTIVATE
: {
291 deferred
= MakeUnique
<DeferredSendMessage
>(hwnd
, uMsg
, wParam
, lParam
);
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
);
305 // This message will generate a WM_PAINT message if there are invalid
308 deferred
= MakeUnique
<DeferredUpdateMessage
>(hwnd
);
312 // This message holds a string in its lParam that we must copy.
313 case WM_SETTINGCHANGE
: {
315 MakeUnique
<DeferredSettingChangeMessage
>(hwnd
, uMsg
, wParam
, lParam
);
319 // These messages are faked via a call to SetWindowPos.
320 case WM_WINDOWPOSCHANGED
: {
321 deferred
= MakeUnique
<DeferredWindowPosMessage
>(hwnd
, lParam
);
324 case WM_NCCALCSIZE
: {
326 MakeUnique
<DeferredWindowPosMessage
>(hwnd
, lParam
, true, wParam
);
332 MakeUnique
<DeferredCopyDataMessage
>(hwnd
, uMsg
, wParam
, lParam
);
337 case WM_STYLECHANGED
: {
338 deferred
= MakeUnique
<DeferredStyleChangeMessage
>(hwnd
, wParam
, lParam
);
343 deferred
= MakeUnique
<DeferredSetIconMessage
>(hwnd
, uMsg
, wParam
, lParam
);
347 // Messages that are safe to pass to DefWindowProc go here.
350 case WM_NCPAINT
: // (never trap nc paint events)
351 case WM_GETMINMAXINFO
:
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.
366 // This message causes QuickTime to make re-entrant calls.
367 // Simply discarding it doesn't seem to hurt anything.
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)
375 LONG objId
= static_cast<LONG
>(lParam
);
376 if (objId
== OBJID_CLIENT
|| objId
== MOZOBJID_UIAROOT
) {
377 WNDPROC oldWndProc
= (WNDPROC
)GetProp(hwnd
, kOldWndProcProp
);
379 return CallWindowProcW(oldWndProc
, hwnd
, uMsg
, wParam
, lParam
);
382 return DefWindowProc(hwnd
, uMsg
, wParam
, lParam
);
384 #endif // ACCESSIBILITY
387 // Unknown messages only are logged in debug builds and sent to
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.
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
));
415 LRESULT CALLBACK
NeuteredWindowProc(HWND hwnd
, UINT uMsg
, WPARAM wParam
,
417 WNDPROC oldWndProc
= (WNDPROC
)GetProp(hwnd
, kOldWndProcProp
);
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
);
431 static bool WindowIsDeferredWindow(HWND hWnd
) {
432 if (!IsWindow(hWnd
)) {
433 NS_WARNING("Window has died!");
437 char16_t buffer
[256] = {0};
438 int length
= GetClassNameW(hWnd
, (wchar_t*)buffer
, sizeof(buffer
) - 1);
440 NS_WARNING("Failed to get class name!");
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
)) {
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")) {
464 bool NeuterWindowProcedure(HWND hWnd
) {
465 if (!WindowIsDeferredWindow(hWnd
)) {
466 // Some other kind of window, skip.
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
);
487 NS_ASSERTION(currentWndProc
!= (LONG_PTR
)NeuteredWindowProc
,
488 "This shouldn't be possible!");
490 if (!SetProp(hWnd
, kOldWndProcProp
, (HANDLE
)currentWndProc
)) {
492 NS_WARNING("SetProp failed!");
493 SetWindowLongPtr(hWnd
, GWLP_WNDPROC
, currentWndProc
);
494 RemovePropW(hWnd
, kOldWndProcProp
);
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
);
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
,
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
) {
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!");
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.
561 void InitTimeoutData(TimeoutData
* aData
, int32_t aTimeoutMs
) {
562 aData
->startTicks
= GetTickCount();
563 if (!aData
->startTicks
) {
564 // How unlikely is this!
567 aData
->targetTicks
= aData
->startTicks
+ aTimeoutMs
;
570 bool TimeoutHasExpired(const TimeoutData
& aData
) {
571 if (!aData
.startTicks
) {
575 DWORD now
= GetTickCount();
577 if (aData
.targetTicks
< aData
.startTicks
) {
579 return now
< aData
.startTicks
&& now
>= aData
.targetTicks
;
581 return now
>= aData
.targetTicks
;
590 void InitUIThread() {
591 if (!XRE_UseNativeEventProcessing()) {
594 // If we aren't setup before a call to NotifyWorkerThread, we'll hang
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
612 gCOMWindow
= FindCOMWindow();
616 } // namespace windows
618 } // namespace mozilla
620 MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel
* channel
)
621 : mSpinNestedEvents(false),
622 mListenerNotified(false),
624 mPrev(mChannel
->mTopFrame
),
625 mStaticPrev(sStaticTopFrame
) {
626 // Only track stack frames when Windows message deferral behavior
627 // is request for the channel.
628 if (!(mChannel
->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION
)) {
632 mChannel
->mTopFrame
= this;
633 sStaticTopFrame
= this;
636 NS_ASSERTION(!gNeuteredWindows
, "Should only set this once!");
637 gNeuteredWindows
= new AutoTArray
<HWND
, 20>();
641 MessageChannel::SyncStackFrame::~SyncStackFrame() {
642 if (!(mChannel
->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION
)) {
646 NS_ASSERTION(this == mChannel
->mTopFrame
,
647 "Mismatched interrupt stack frames");
648 NS_ASSERTION(this == sStaticTopFrame
,
649 "Mismatched static Interrupt stack frames");
651 mChannel
->mTopFrame
= mPrev
;
652 sStaticTopFrame
= mStaticPrev
;
655 NS_ASSERTION(gNeuteredWindows
, "Bad pointer!");
656 gNeuteredWindows
= nullptr;
660 MessageChannel::SyncStackFrame
* MessageChannel::sStaticTopFrame
;
662 // nsAppShell's notification that gecko events are being processed.
663 // If we are here and there is an Interrupt Incall active, we are spinning
664 // a nested gecko event loop. In which case the remote process needs
667 MessageChannel::NotifyGeckoEventDispatch() {
668 // sStaticTopFrame is only valid for Interrupt channels
669 if (!sStaticTopFrame
|| sStaticTopFrame
->mListenerNotified
) return;
671 sStaticTopFrame
->mListenerNotified
= true;
672 MessageChannel
* channel
=
673 static_cast<MessageChannel
*>(sStaticTopFrame
->mChannel
);
674 channel
->Listener()->ProcessRemoteNativeEventsInInterruptCall();
677 // invoked by the module that receives the spin event loop
679 void MessageChannel::ProcessNativeEventsInInterruptCall() {
680 NS_ASSERTION(GetCurrentThreadId() == gUIThreadId
,
681 "Shouldn't be on a non-main thread in here!");
683 NS_ERROR("Spin logic error: no Interrupt frame");
687 mTopFrame
->mSpinNestedEvents
= true;
690 static HHOOK gWindowHook
;
692 static inline void StartNeutering() {
694 mozilla::ipc::windows::InitUIThread();
696 MOZ_ASSERT(gUIThreadId
);
697 MOZ_ASSERT(!gWindowHook
);
698 NS_ASSERTION(!MessageChannel::IsPumpingMessages(),
699 "Shouldn't be pumping already!");
700 MessageChannel::SetIsPumpingMessages(true);
701 gWindowHook
= ::SetWindowsHookEx(WH_CALLWNDPROC
, CallWindowProcedureHook
,
702 nullptr, gUIThreadId
);
703 NS_ASSERTION(gWindowHook
, "Failed to set hook!");
706 static void StopNeutering() {
707 MOZ_ASSERT(MessageChannel::IsPumpingMessages());
708 ::UnhookWindowsHookEx(gWindowHook
);
710 ::UnhookNeuteredWindows();
711 // Before returning we need to set a hook to run any deferred messages that
712 // we received during the IPC call. The hook will unset itself as soon as
713 // someone else calls GetMessage, PeekMessage, or runs code that generates
714 // a "nonqueued" message.
715 ::ScheduleDeferredMessageRun();
716 MessageChannel::SetIsPumpingMessages(false);
719 NeuteredWindowRegion::NeuteredWindowRegion(bool aDoNeuter
)
720 : mNeuteredByThis(!gWindowHook
&& aDoNeuter
&&
721 XRE_UseNativeEventProcessing()) {
722 if (mNeuteredByThis
) {
727 NeuteredWindowRegion::~NeuteredWindowRegion() {
728 if (gWindowHook
&& mNeuteredByThis
) {
733 void NeuteredWindowRegion::PumpOnce() {
735 // This should be a no-op if nothing has been neutered.
740 // Pump any COM messages so that we don't hang due to STA marshaling.
741 if (gCOMWindow
&& ::PeekMessageW(&msg
, gCOMWindow
, 0, 0, PM_REMOVE
)) {
742 ::TranslateMessage(&msg
);
743 ::DispatchMessageW(&msg
);
745 // Expunge any nonqueued messages on the current thread.
746 ::PeekMessageW(&msg
, nullptr, 0, 0, PM_NOREMOVE
);
749 DeneuteredWindowRegion::DeneuteredWindowRegion()
750 : mReneuter(gWindowHook
!= NULL
) {
756 DeneuteredWindowRegion::~DeneuteredWindowRegion() {
762 SuppressedNeuteringRegion::SuppressedNeuteringRegion()
763 : mReenable(::gUIThreadId
== ::GetCurrentThreadId() && ::gWindowHook
) {
765 MOZ_ASSERT(!sSuppressNeutering
);
766 sSuppressNeutering
= true;
770 SuppressedNeuteringRegion::~SuppressedNeuteringRegion() {
772 MOZ_ASSERT(sSuppressNeutering
);
773 sSuppressNeutering
= false;
777 bool SuppressedNeuteringRegion::sSuppressNeutering
= false;
779 bool MessageChannel::WaitForSyncNotify() {
780 mMonitor
->AssertCurrentThreadOwns();
783 mozilla::ipc::windows::InitUIThread();
786 // Use a blocking wait if this channel does not require
787 // Windows message deferral behavior.
788 if (!(mFlags
& REQUIRE_DEFERRED_MESSAGE_PROTECTION
)) {
789 TimeDuration timeout
= (kNoTimeout
== mTimeoutMs
)
790 ? TimeDuration::Forever()
791 : TimeDuration::FromMilliseconds(mTimeoutMs
);
793 MOZ_ASSERT(!mIsSyncWaitingOnNonMainThread
);
794 mIsSyncWaitingOnNonMainThread
= true;
796 CVStatus status
= mMonitor
->Wait(timeout
);
798 MOZ_ASSERT(mIsSyncWaitingOnNonMainThread
);
799 mIsSyncWaitingOnNonMainThread
= false;
801 // If the timeout didn't expire, we know we received an event. The
802 // converse is not true.
803 return WaitResponse(status
== CVStatus::Timeout
);
807 mFlags
& REQUIRE_DEFERRED_MESSAGE_PROTECTION
,
808 "Shouldn't be here for channels that don't use message deferral!");
809 NS_ASSERTION(mTopFrame
, "No top frame!");
811 MonitorAutoUnlock
unlock(*mMonitor
);
813 bool timedout
= false;
815 UINT_PTR timerId
= 0;
816 TimeoutData timeoutData
= {0};
818 if (mTimeoutMs
!= kNoTimeout
) {
819 InitTimeoutData(&timeoutData
, mTimeoutMs
);
821 // We only do this to ensure that we won't get stuck in
822 // MsgWaitForMultipleObjects below.
823 timerId
= SetTimer(nullptr, 0, mTimeoutMs
, nullptr);
824 NS_ASSERTION(timerId
, "SetTimer failed!");
827 NeuteredWindowRegion
neuteredRgn(true);
832 // Don't get wrapped up in here if the child connection dies.
834 MonitorAutoLock
lock(*mMonitor
);
840 // Wait until we have a message in the queue. MSDN docs are a bit unclear
841 // but it seems that windows from two different threads (and it should be
842 // noted that a thread in another process counts as a "different thread")
843 // will implicitly have their message queues attached if they are parented
844 // to one another. This wait call, then, will return for a message
845 // delivered to *either* thread.
847 MsgWaitForMultipleObjects(1, &mEvent
, FALSE
, INFINITE
, QS_ALLINPUT
);
848 if (result
== WAIT_OBJECT_0
) {
849 // Our NotifyWorkerThread event was signaled
850 BOOL success
= ResetEvent(mEvent
);
852 gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle
)
853 << "WindowsMessageChannel::WaitForSyncNotify failed to reset "
854 "event. GetLastError: "
858 } else if (result
!= (WAIT_OBJECT_0
+ 1)) {
859 NS_ERROR("Wait failed!");
863 if (TimeoutHasExpired(timeoutData
)) {
864 // A timeout was specified and we've passed it. Break out.
869 // The only way to know on which thread the message was delivered is to
870 // use some logic on the return values of GetQueueStatus and PeekMessage.
871 // PeekMessage will return false if there are no "queued" messages, but it
872 // will run all "nonqueued" messages before returning. So if PeekMessage
873 // returns false and there are no "nonqueued" messages that were run then
874 // we know that the message we woke for was intended for a window on
876 bool haveSentMessagesPending
=
877 (HIWORD(GetQueueStatus(QS_SENDMESSAGE
)) & QS_SENDMESSAGE
) != 0;
879 // Either of the PeekMessage calls below will actually process all
880 // "nonqueued" messages that are pending before returning. If we have
881 // "nonqueued" messages pending then we should have switched out all the
882 // window procedures above. In that case this PeekMessage call won't
883 // actually cause any mozilla code (or plugin code) to run.
885 // We have to manually pump all COM messages *after* looking at the queue
886 // queue status but before yielding our thread below.
888 if (PeekMessageW(&msg
, gCOMWindow
, 0, 0, PM_REMOVE
)) {
889 TranslateMessage(&msg
);
890 ::DispatchMessageW(&msg
);
894 // If the following PeekMessage call fails to return a message for us (and
895 // returns false) and we didn't run any "nonqueued" messages then we must
896 // have woken up for a message designated for a window in another thread.
897 // If we loop immediately then we could enter a tight loop, so we'll give
898 // up our time slice here to let the child process its message.
899 if (!PeekMessageW(&msg
, nullptr, 0, 0, PM_NOREMOVE
) &&
900 !haveSentMessagesPending
) {
901 // Message was for child, we should wait a bit.
908 KillTimer(nullptr, timerId
);
912 return WaitResponse(timedout
);
915 void MessageChannel::NotifyWorkerThread() {
916 mMonitor
->AssertCurrentThreadOwns();
918 if (mIsSyncWaitingOnNonMainThread
) {
923 MOZ_RELEASE_ASSERT(mEvent
, "No signal event to set, this is really bad!");
924 if (!SetEvent(mEvent
)) {
925 NS_WARNING("Failed to set NotifyWorkerThread event!");
926 gfxDevCrash(mozilla::gfx::LogReason::MessageChannelInvalidHandle
)
927 << "WindowsMessageChannel failed to SetEvent. GetLastError: "
932 void DeferredSendMessage::Run() {
933 AssertWindowIsNotNeutered(hWnd
);
934 if (!IsWindow(hWnd
)) {
935 NS_ERROR("Invalid window!");
940 reinterpret_cast<WNDPROC
>(GetWindowLongPtr(hWnd
, GWLP_WNDPROC
));
942 NS_ERROR("Invalid window procedure!");
946 CallWindowProc(wndproc
, hWnd
, message
, wParam
, lParam
);
949 void DeferredRedrawMessage::Run() {
950 AssertWindowIsNotNeutered(hWnd
);
951 if (!IsWindow(hWnd
)) {
952 NS_ERROR("Invalid window!");
959 RedrawWindow(hWnd
, nullptr, nullptr, flags
);
960 NS_ASSERTION(ret
, "RedrawWindow failed!");
963 DeferredUpdateMessage::DeferredUpdateMessage(HWND aHWnd
) {
965 if (!GetUpdateRect(mWnd
, &mUpdateRect
, FALSE
)) {
966 memset(&mUpdateRect
, 0, sizeof(RECT
));
969 ValidateRect(mWnd
, &mUpdateRect
);
972 void DeferredUpdateMessage::Run() {
973 AssertWindowIsNotNeutered(mWnd
);
974 if (!IsWindow(mWnd
)) {
975 NS_ERROR("Invalid window!");
979 InvalidateRect(mWnd
, &mUpdateRect
, FALSE
);
984 NS_ASSERTION(ret
, "UpdateWindow failed!");
987 DeferredSettingChangeMessage::DeferredSettingChangeMessage(HWND aHWnd
,
991 : DeferredSendMessage(aHWnd
, aMessage
, aWParam
, aLParam
) {
992 NS_ASSERTION(aMessage
== WM_SETTINGCHANGE
, "Wrong message type!");
994 lParamString
= _wcsdup(reinterpret_cast<const wchar_t*>(aLParam
));
995 lParam
= reinterpret_cast<LPARAM
>(lParamString
);
997 lParamString
= nullptr;
1002 DeferredSettingChangeMessage::~DeferredSettingChangeMessage() {
1006 DeferredWindowPosMessage::DeferredWindowPosMessage(HWND aHWnd
, LPARAM aLParam
,
1011 NCCALCSIZE_PARAMS
* arg
= reinterpret_cast<NCCALCSIZE_PARAMS
*>(aLParam
);
1012 memcpy(&windowPos
, arg
->lppos
, sizeof(windowPos
));
1014 NS_ASSERTION(aHWnd
== windowPos
.hwnd
, "Mismatched hwnds!");
1016 RECT
* arg
= reinterpret_cast<RECT
*>(aLParam
);
1017 windowPos
.hwnd
= aHWnd
;
1018 windowPos
.hwndInsertAfter
= nullptr;
1019 windowPos
.x
= arg
->left
;
1020 windowPos
.y
= arg
->top
;
1021 windowPos
.cx
= arg
->right
- arg
->left
;
1022 windowPos
.cy
= arg
->bottom
- arg
->top
;
1024 NS_ASSERTION(arg
->right
>= arg
->left
&& arg
->bottom
>= arg
->top
,
1025 "Negative width or height!");
1027 windowPos
.flags
= SWP_FRAMECHANGED
| SWP_NOACTIVATE
| SWP_NOOWNERZORDER
|
1028 SWP_NOZORDER
| SWP_DEFERERASE
| SWP_NOSENDCHANGING
;
1030 // Not for WM_NCCALCSIZE
1031 WINDOWPOS
* arg
= reinterpret_cast<WINDOWPOS
*>(aLParam
);
1032 memcpy(&windowPos
, arg
, sizeof(windowPos
));
1034 NS_ASSERTION(aHWnd
== windowPos
.hwnd
, "Mismatched hwnds!");
1036 // Windows sends in some private flags sometimes that we can't simply copy.
1038 UINT mask
= SWP_ASYNCWINDOWPOS
| SWP_DEFERERASE
| SWP_DRAWFRAME
|
1039 SWP_FRAMECHANGED
| SWP_HIDEWINDOW
| SWP_NOACTIVATE
|
1040 SWP_NOCOPYBITS
| SWP_NOMOVE
| SWP_NOOWNERZORDER
| SWP_NOREDRAW
|
1041 SWP_NOREPOSITION
| SWP_NOSENDCHANGING
| SWP_NOSIZE
|
1042 SWP_NOZORDER
| SWP_SHOWWINDOW
;
1043 windowPos
.flags
&= mask
;
1047 void DeferredWindowPosMessage::Run() {
1048 AssertWindowIsNotNeutered(windowPos
.hwnd
);
1049 if (!IsWindow(windowPos
.hwnd
)) {
1050 NS_ERROR("Invalid window!");
1054 if (!IsWindow(windowPos
.hwndInsertAfter
)) {
1055 NS_WARNING("ZOrder change cannot be honored");
1056 windowPos
.hwndInsertAfter
= 0;
1057 windowPos
.flags
|= SWP_NOZORDER
;
1063 SetWindowPos(windowPos
.hwnd
, windowPos
.hwndInsertAfter
, windowPos
.x
,
1064 windowPos
.y
, windowPos
.cx
, windowPos
.cy
, windowPos
.flags
);
1065 NS_ASSERTION(ret
, "SetWindowPos failed!");
1068 DeferredCopyDataMessage::DeferredCopyDataMessage(HWND aHWnd
, UINT aMessage
,
1069 WPARAM aWParam
, LPARAM aLParam
)
1070 : DeferredSendMessage(aHWnd
, aMessage
, aWParam
, aLParam
) {
1071 NS_ASSERTION(IsWindow(reinterpret_cast<HWND
>(aWParam
)), "Bad window!");
1073 COPYDATASTRUCT
* source
= reinterpret_cast<COPYDATASTRUCT
*>(aLParam
);
1074 NS_ASSERTION(source
, "Should never be null!");
1076 copyData
.dwData
= source
->dwData
;
1077 copyData
.cbData
= source
->cbData
;
1079 if (source
->cbData
) {
1080 copyData
.lpData
= malloc(source
->cbData
);
1081 if (copyData
.lpData
) {
1082 memcpy(copyData
.lpData
, source
->lpData
, source
->cbData
);
1084 NS_ERROR("Out of memory?!");
1085 copyData
.cbData
= 0;
1088 copyData
.lpData
= nullptr;
1091 lParam
= reinterpret_cast<LPARAM
>(©Data
);
1094 DeferredCopyDataMessage::~DeferredCopyDataMessage() { free(copyData
.lpData
); }
1096 DeferredStyleChangeMessage::DeferredStyleChangeMessage(HWND aHWnd
,
1100 index
= static_cast<int>(aWParam
);
1101 style
= reinterpret_cast<STYLESTRUCT
*>(aLParam
)->styleNew
;
1104 void DeferredStyleChangeMessage::Run() { SetWindowLongPtr(hWnd
, index
, style
); }
1106 DeferredSetIconMessage::DeferredSetIconMessage(HWND aHWnd
, UINT aMessage
,
1107 WPARAM aWParam
, LPARAM aLParam
)
1108 : DeferredSendMessage(aHWnd
, aMessage
, aWParam
, aLParam
) {
1109 NS_ASSERTION(aMessage
== WM_SETICON
, "Wrong message type!");
1112 void DeferredSetIconMessage::Run() {
1113 AssertWindowIsNotNeutered(hWnd
);
1114 if (!IsWindow(hWnd
)) {
1115 NS_ERROR("Invalid window!");
1120 reinterpret_cast<WNDPROC
>(GetWindowLongPtr(hWnd
, GWLP_WNDPROC
));
1122 NS_ERROR("Invalid window procedure!");
1126 HICON hOld
= reinterpret_cast<HICON
>(
1127 CallWindowProc(wndproc
, hWnd
, message
, wParam
, lParam
));