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