Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 et :
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "mozilla/DebugOnly.h"
10 #include "WindowsMessageLoop.h"
11 #include "MessageChannel.h"
13 #include "nsAutoPtr.h"
14 #include "nsServiceManagerUtils.h"
15 #include "nsString.h"
16 #include "nsIXULAppInfo.h"
18 #include "mozilla/PaintTracker.h"
20 using namespace mozilla;
21 using namespace mozilla::ipc;
22 using namespace mozilla::ipc::windows;
24 /**
25 * The Windows-only code below exists to solve a general problem with deadlocks
26 * that we experience when sending synchronous IPC messages to processes that
27 * contain native windows (i.e. HWNDs). Windows (the OS) sends synchronous
28 * messages between parent and child HWNDs in multiple circumstances (e.g.
29 * WM_PARENTNOTIFY, WM_NCACTIVATE, etc.), even when those HWNDs are controlled
30 * by different threads or different processes. Thus we can very easily end up
31 * in a deadlock by a call stack like the following:
32 *
33 * Process A:
34 * - CreateWindow(...) creates a "parent" HWND.
35 * - SendCreateChildWidget(HWND) is a sync IPC message that sends the "parent"
36 * HWND over to Process B. Process A blocks until a response is received
37 * from Process B.
38 *
39 * Process B:
40 * - RecvCreateWidget(HWND) gets the "parent" HWND from Process A.
41 * - CreateWindow(..., HWND) creates a "child" HWND with the parent from
42 * process A.
43 * - Windows (the OS) generates a WM_PARENTNOTIFY message that is sent
44 * synchronously to Process A. Process B blocks until a response is
45 * received from Process A. Process A, however, is blocked and cannot
46 * process the message. Both processes are deadlocked.
47 *
48 * The example above has a few different workarounds (e.g. setting the
49 * WS_EX_NOPARENTNOTIFY style on the child window) but the general problem is
50 * persists. Once two HWNDs are parented we must not block their owning
51 * threads when manipulating either HWND.
52 *
53 * Windows requires any application that hosts native HWNDs to always process
54 * messages or risk deadlock. Given our architecture the only way to meet
55 * Windows' requirement and allow for synchronous IPC messages is to pump a
56 * miniature message loop during a sync IPC call. We avoid processing any
57 * queued messages during the loop (with one exception, see below), but
58 * "nonqueued" messages (see
59 * http://msdn.microsoft.com/en-us/library/ms644927(VS.85).aspx under the
60 * section "Nonqueued messages") cannot be avoided. Those messages are trapped
61 * in a special window procedure where we can either ignore the message or
62 * process it in some fashion.
63 *
64 * Queued and "non-queued" messages will be processed during Interrupt calls if
65 * modal UI related api calls block an Interrupt in-call in the child. To prevent
66 * windows from freezing, and to allow concurrent processing of critical
67 * events (such as painting), we spin a native event dispatch loop while
68 * these in-calls are blocked.
69 */
71 #if defined(ACCESSIBILITY)
72 // pulled from accessibility's win utils
73 extern const wchar_t* kPropNameTabContent;
74 #endif
76 // widget related message id constants we need to defer
77 namespace mozilla {
78 namespace widget {
79 extern UINT sAppShellGeckoMsgId;
80 #ifdef MOZ_METRO
81 extern UINT sDefaultBrowserMsgId;
82 #endif
83 }
84 }
86 namespace {
88 const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc";
90 // This isn't defined before Windows XP.
91 enum { WM_XP_THEMECHANGED = 0x031A };
93 char16_t gAppMessageWindowName[256] = { 0 };
94 int32_t gAppMessageWindowNameLength = 0;
96 nsTArray<HWND>* gNeuteredWindows = nullptr;
98 typedef nsTArray<nsAutoPtr<DeferredMessage> > DeferredMessageArray;
99 DeferredMessageArray* gDeferredMessages = nullptr;
101 HHOOK gDeferredGetMsgHook = nullptr;
102 HHOOK gDeferredCallWndProcHook = nullptr;
104 DWORD gUIThreadId = 0;
106 LRESULT CALLBACK
107 DeferredMessageHook(int nCode,
108 WPARAM wParam,
109 LPARAM lParam)
110 {
111 // XXX This function is called for *both* the WH_CALLWNDPROC hook and the
112 // WH_GETMESSAGE hook, but they have different parameters. We don't
113 // use any of them except nCode which has the same meaning.
115 // Only run deferred messages if all of these conditions are met:
116 // 1. The |nCode| indicates that this hook should do something.
117 // 2. We have deferred messages to run.
118 // 3. We're not being called from the PeekMessage within the WaitFor*Notify
119 // function (indicated with MessageChannel::IsPumpingMessages). We really
120 // only want to run after returning to the main event loop.
121 if (nCode >= 0 && gDeferredMessages && !MessageChannel::IsPumpingMessages()) {
122 NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
123 "These hooks must be set if we're being called!");
124 NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
126 // Unset hooks first, in case we reenter below.
127 UnhookWindowsHookEx(gDeferredGetMsgHook);
128 UnhookWindowsHookEx(gDeferredCallWndProcHook);
129 gDeferredGetMsgHook = 0;
130 gDeferredCallWndProcHook = 0;
132 // Unset the global and make sure we delete it when we're done here.
133 nsAutoPtr<DeferredMessageArray> messages(gDeferredMessages);
134 gDeferredMessages = nullptr;
136 // Run all the deferred messages in order.
137 uint32_t count = messages->Length();
138 for (uint32_t index = 0; index < count; index++) {
139 messages->ElementAt(index)->Run();
140 }
141 }
143 // Always call the next hook.
144 return CallNextHookEx(nullptr, nCode, wParam, lParam);
145 }
147 void
148 ScheduleDeferredMessageRun()
149 {
150 if (gDeferredMessages &&
151 !(gDeferredGetMsgHook && gDeferredCallWndProcHook)) {
152 NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!");
154 gDeferredGetMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, DeferredMessageHook,
155 nullptr, gUIThreadId);
156 gDeferredCallWndProcHook = ::SetWindowsHookEx(WH_CALLWNDPROC,
157 DeferredMessageHook, nullptr,
158 gUIThreadId);
159 NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook,
160 "Failed to set hooks!");
161 }
162 }
164 LRESULT
165 ProcessOrDeferMessage(HWND hwnd,
166 UINT uMsg,
167 WPARAM wParam,
168 LPARAM lParam)
169 {
170 DeferredMessage* deferred = nullptr;
172 // Most messages ask for 0 to be returned if the message is processed.
173 LRESULT res = 0;
175 switch (uMsg) {
176 // Messages that can be deferred as-is. These must not contain pointers in
177 // their wParam or lParam arguments!
178 case WM_ACTIVATE:
179 case WM_ACTIVATEAPP:
180 case WM_CANCELMODE:
181 case WM_CAPTURECHANGED:
182 case WM_CHILDACTIVATE:
183 case WM_DESTROY:
184 case WM_ENABLE:
185 case WM_IME_NOTIFY:
186 case WM_IME_SETCONTEXT:
187 case WM_KILLFOCUS:
188 case WM_MOUSEWHEEL:
189 case WM_NCDESTROY:
190 case WM_PARENTNOTIFY:
191 case WM_SETFOCUS:
192 case WM_SYSCOMMAND:
193 case WM_DISPLAYCHANGE:
194 case WM_SHOWWINDOW: // Intentional fall-through.
195 case WM_XP_THEMECHANGED: {
196 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam);
197 break;
198 }
200 case WM_DEVICECHANGE:
201 case WM_POWERBROADCAST:
202 case WM_NCACTIVATE: // Intentional fall-through.
203 case WM_SETCURSOR: {
204 // Friggin unconventional return value...
205 res = TRUE;
206 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam);
207 break;
208 }
210 case WM_MOUSEACTIVATE: {
211 res = MA_NOACTIVATE;
212 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam);
213 break;
214 }
216 // These messages need to use the RedrawWindow function to generate the
217 // right kind of message. We can't simply fake them as the MSDN docs say
218 // explicitly that paint messages should not be sent by an application.
219 case WM_ERASEBKGND: {
220 UINT flags = RDW_INVALIDATE | RDW_ERASE | RDW_NOINTERNALPAINT |
221 RDW_NOFRAME | RDW_NOCHILDREN | RDW_ERASENOW;
222 deferred = new DeferredRedrawMessage(hwnd, flags);
223 break;
224 }
226 // This message will generate a WM_PAINT message if there are invalid
227 // areas.
228 case WM_PAINT: {
229 deferred = new DeferredUpdateMessage(hwnd);
230 break;
231 }
233 // This message holds a string in its lParam that we must copy.
234 case WM_SETTINGCHANGE: {
235 deferred = new DeferredSettingChangeMessage(hwnd, uMsg, wParam, lParam);
236 break;
237 }
239 // These messages are faked via a call to SetWindowPos.
240 case WM_WINDOWPOSCHANGED: {
241 deferred = new DeferredWindowPosMessage(hwnd, lParam);
242 break;
243 }
244 case WM_NCCALCSIZE: {
245 deferred = new DeferredWindowPosMessage(hwnd, lParam, true, wParam);
246 break;
247 }
249 case WM_COPYDATA: {
250 deferred = new DeferredCopyDataMessage(hwnd, uMsg, wParam, lParam);
251 res = TRUE;
252 break;
253 }
255 case WM_STYLECHANGED: {
256 deferred = new DeferredStyleChangeMessage(hwnd, wParam, lParam);
257 break;
258 }
260 case WM_SETICON: {
261 deferred = new DeferredSetIconMessage(hwnd, uMsg, wParam, lParam);
262 break;
263 }
265 // Messages that are safe to pass to DefWindowProc go here.
266 case WM_ENTERIDLE:
267 case WM_GETICON:
268 case WM_NCPAINT: // (never trap nc paint events)
269 case WM_GETMINMAXINFO:
270 case WM_GETTEXT:
271 case WM_NCHITTEST:
272 case WM_STYLECHANGING: // Intentional fall-through.
273 case WM_WINDOWPOSCHANGING: {
274 return DefWindowProc(hwnd, uMsg, wParam, lParam);
275 }
277 // Just return, prevents DefWindowProc from messaging the window
278 // syncronously with other events, which may be deferred. Prevents
279 // random shutdown of aero composition on the window.
280 case WM_SYNCPAINT:
281 return 0;
283 // This message causes QuickTime to make re-entrant calls.
284 // Simply discarding it doesn't seem to hurt anything.
285 case WM_APP-1:
286 return 0;
288 default: {
289 if (uMsg && uMsg == mozilla::widget::sAppShellGeckoMsgId) {
290 // Widget's registered native event callback
291 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam);
292 #ifdef MOZ_METRO
293 } else if (uMsg && uMsg == mozilla::widget::sDefaultBrowserMsgId) {
294 // Metro widget's system shutdown message
295 deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam);
296 #endif
297 } else {
298 // Unknown messages only
299 #ifdef DEBUG
300 nsAutoCString log("Received \"nonqueued\" message ");
301 log.AppendInt(uMsg);
302 log.AppendLiteral(" during a synchronous IPC message for window ");
303 log.AppendInt((int64_t)hwnd);
305 wchar_t className[256] = { 0 };
306 if (GetClassNameW(hwnd, className, sizeof(className) - 1) > 0) {
307 log.AppendLiteral(" (\"");
308 log.Append(NS_ConvertUTF16toUTF8((char16_t*)className));
309 log.AppendLiteral("\")");
310 }
312 log.AppendLiteral(", sending it to DefWindowProc instead of the normal "
313 "window procedure.");
314 NS_ERROR(log.get());
315 #endif
316 return DefWindowProc(hwnd, uMsg, wParam, lParam);
317 }
318 }
319 }
321 NS_ASSERTION(deferred, "Must have a message here!");
323 // Create the deferred message array if it doesn't exist already.
324 if (!gDeferredMessages) {
325 gDeferredMessages = new nsTArray<nsAutoPtr<DeferredMessage> >(20);
326 NS_ASSERTION(gDeferredMessages, "Out of memory!");
327 }
329 // Save for later. The array takes ownership of |deferred|.
330 gDeferredMessages->AppendElement(deferred);
331 return res;
332 }
334 } // anonymous namespace
336 // We need the pointer value of this in PluginInstanceChild.
337 LRESULT CALLBACK
338 NeuteredWindowProc(HWND hwnd,
339 UINT uMsg,
340 WPARAM wParam,
341 LPARAM lParam)
342 {
343 WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
344 if (!oldWndProc) {
345 // We should really never ever get here.
346 NS_ERROR("No old wndproc!");
347 return DefWindowProc(hwnd, uMsg, wParam, lParam);
348 }
350 // See if we care about this message. We may either ignore it, send it to
351 // DefWindowProc, or defer it for later.
352 return ProcessOrDeferMessage(hwnd, uMsg, wParam, lParam);
353 }
355 namespace {
357 static bool
358 WindowIsDeferredWindow(HWND hWnd)
359 {
360 if (!IsWindow(hWnd)) {
361 NS_WARNING("Window has died!");
362 return false;
363 }
365 char16_t buffer[256] = { 0 };
366 int length = GetClassNameW(hWnd, (wchar_t*)buffer, sizeof(buffer) - 1);
367 if (length <= 0) {
368 NS_WARNING("Failed to get class name!");
369 return false;
370 }
372 #if defined(ACCESSIBILITY)
373 // Tab content creates a window that responds to accessible WM_GETOBJECT
374 // calls. This window can safely be ignored.
375 if (::GetPropW(hWnd, kPropNameTabContent)) {
376 return false;
377 }
378 #endif
380 // Common mozilla windows we must defer messages to.
381 nsDependentString className(buffer, length);
382 if (StringBeginsWith(className, NS_LITERAL_STRING("Mozilla")) ||
383 StringBeginsWith(className, NS_LITERAL_STRING("Gecko")) ||
384 className.EqualsLiteral("nsToolkitClass") ||
385 className.EqualsLiteral("nsAppShell:EventWindowClass")) {
386 return true;
387 }
389 #ifdef MOZ_METRO
390 // immersive UI ICoreWindow
391 if (className.EqualsLiteral("Windows.UI.Core.CoreWindow")) {
392 return true;
393 }
394 #endif
396 // Plugin windows that can trigger ipc calls in child:
397 // 'ShockwaveFlashFullScreen' - flash fullscreen window
398 // 'QTNSHIDDEN' - QuickTime
399 // 'AGFullScreenWinClass' - silverlight fullscreen window
400 if (className.EqualsLiteral("ShockwaveFlashFullScreen") ||
401 className.EqualsLiteral("QTNSHIDDEN") ||
402 className.EqualsLiteral("AGFullScreenWinClass")) {
403 return true;
404 }
406 // Google Earth bridging msg window between the plugin instance and a separate
407 // earth process. The earth process can trigger a plugin incall on the browser
408 // at any time, which is badness if the instance is already making an incall.
409 if (className.EqualsLiteral("__geplugin_bridge_window__")) {
410 return true;
411 }
413 // nsNativeAppSupport makes a window like "FirefoxMessageWindow" based on the
414 // toolkit app's name. It's pretty expensive to calculate this so we only try
415 // once.
416 if (gAppMessageWindowNameLength == 0) {
417 nsCOMPtr<nsIXULAppInfo> appInfo =
418 do_GetService("@mozilla.org/xre/app-info;1");
419 if (appInfo) {
420 nsAutoCString appName;
421 if (NS_SUCCEEDED(appInfo->GetName(appName))) {
422 appName.Append("MessageWindow");
423 nsDependentString windowName(gAppMessageWindowName);
424 CopyUTF8toUTF16(appName, windowName);
425 gAppMessageWindowNameLength = windowName.Length();
426 }
427 }
429 // Don't try again if that failed.
430 if (gAppMessageWindowNameLength == 0) {
431 gAppMessageWindowNameLength = -1;
432 }
433 }
435 if (gAppMessageWindowNameLength != -1 &&
436 className.Equals(nsDependentString(gAppMessageWindowName,
437 gAppMessageWindowNameLength))) {
438 return true;
439 }
441 return false;
442 }
444 bool
445 NeuterWindowProcedure(HWND hWnd)
446 {
447 if (!WindowIsDeferredWindow(hWnd)) {
448 // Some other kind of window, skip.
449 return false;
450 }
452 NS_ASSERTION(!GetProp(hWnd, kOldWndProcProp), "This should always be null!");
454 // It's possible to get nullptr out of SetWindowLongPtr, and the only way to
455 // know if that's a valid old value is to use GetLastError. Clear the error
456 // here so we can tell.
457 SetLastError(ERROR_SUCCESS);
459 LONG_PTR currentWndProc =
460 SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)NeuteredWindowProc);
461 if (!currentWndProc) {
462 if (ERROR_SUCCESS == GetLastError()) {
463 // No error, so we set something and must therefore reset it.
464 SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc);
465 }
466 return false;
467 }
469 NS_ASSERTION(currentWndProc != (LONG_PTR)NeuteredWindowProc,
470 "This shouldn't be possible!");
472 if (!SetProp(hWnd, kOldWndProcProp, (HANDLE)currentWndProc)) {
473 // Cleanup
474 NS_WARNING("SetProp failed!");
475 SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc);
476 RemoveProp(hWnd, kOldWndProcProp);
477 return false;
478 }
480 return true;
481 }
483 void
484 RestoreWindowProcedure(HWND hWnd)
485 {
486 NS_ASSERTION(WindowIsDeferredWindow(hWnd),
487 "Not a deferred window, this shouldn't be in our list!");
488 LONG_PTR oldWndProc = (LONG_PTR)GetProp(hWnd, kOldWndProcProp);
489 if (oldWndProc) {
490 NS_ASSERTION(oldWndProc != (LONG_PTR)NeuteredWindowProc,
491 "This shouldn't be possible!");
493 DebugOnly<LONG_PTR> currentWndProc =
494 SetWindowLongPtr(hWnd, GWLP_WNDPROC, oldWndProc);
495 NS_ASSERTION(currentWndProc == (LONG_PTR)NeuteredWindowProc,
496 "This should never be switched out from under us!");
497 }
498 RemoveProp(hWnd, kOldWndProcProp);
499 }
501 LRESULT CALLBACK
502 CallWindowProcedureHook(int nCode,
503 WPARAM wParam,
504 LPARAM lParam)
505 {
506 if (nCode >= 0) {
507 NS_ASSERTION(gNeuteredWindows, "This should never be null!");
509 HWND hWnd = reinterpret_cast<CWPSTRUCT*>(lParam)->hwnd;
511 if (!gNeuteredWindows->Contains(hWnd) && NeuterWindowProcedure(hWnd)) {
512 if (!gNeuteredWindows->AppendElement(hWnd)) {
513 NS_ERROR("Out of memory!");
514 RestoreWindowProcedure(hWnd);
515 }
516 }
517 }
518 return CallNextHookEx(nullptr, nCode, wParam, lParam);
519 }
521 inline void
522 AssertWindowIsNotNeutered(HWND hWnd)
523 {
524 #ifdef DEBUG
525 // Make sure our neutered window hook isn't still in place.
526 LONG_PTR wndproc = GetWindowLongPtr(hWnd, GWLP_WNDPROC);
527 NS_ASSERTION(wndproc != (LONG_PTR)NeuteredWindowProc, "Window is neutered!");
528 #endif
529 }
531 void
532 UnhookNeuteredWindows()
533 {
534 if (!gNeuteredWindows)
535 return;
536 uint32_t count = gNeuteredWindows->Length();
537 for (uint32_t index = 0; index < count; index++) {
538 RestoreWindowProcedure(gNeuteredWindows->ElementAt(index));
539 }
540 gNeuteredWindows->Clear();
541 }
543 // This timeout stuff assumes a sane value of mTimeoutMs (less than the overflow
544 // value for GetTickCount(), which is something like 50 days). It uses the
545 // cheapest (and least accurate) method supported by Windows 2000.
547 struct TimeoutData
548 {
549 DWORD startTicks;
550 DWORD targetTicks;
551 };
553 void
554 InitTimeoutData(TimeoutData* aData,
555 int32_t aTimeoutMs)
556 {
557 aData->startTicks = GetTickCount();
558 if (!aData->startTicks) {
559 // How unlikely is this!
560 aData->startTicks++;
561 }
562 aData->targetTicks = aData->startTicks + aTimeoutMs;
563 }
566 bool
567 TimeoutHasExpired(const TimeoutData& aData)
568 {
569 if (!aData.startTicks) {
570 return false;
571 }
573 DWORD now = GetTickCount();
575 if (aData.targetTicks < aData.startTicks) {
576 // Overflow
577 return now < aData.startTicks && now >= aData.targetTicks;
578 }
579 return now >= aData.targetTicks;
580 }
582 } // anonymous namespace
584 namespace mozilla {
585 namespace ipc {
586 namespace windows {
588 void
589 InitUIThread()
590 {
591 // If we aren't setup before a call to NotifyWorkerThread, we'll hang
592 // on startup.
593 if (!gUIThreadId) {
594 gUIThreadId = GetCurrentThreadId();
595 }
596 MOZ_ASSERT(gUIThreadId);
597 MOZ_ASSERT(gUIThreadId == GetCurrentThreadId(),
598 "Called InitUIThread multiple times on different threads!");
599 }
601 } // namespace windows
602 } // namespace ipc
603 } // namespace mozilla
605 // See SpinInternalEventLoop below
606 MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel* channel, bool interrupt)
607 : mInterrupt(interrupt)
608 , mSpinNestedEvents(false)
609 , mListenerNotified(false)
610 , mChannel(channel)
611 , mPrev(mChannel->mTopFrame)
612 , mStaticPrev(sStaticTopFrame)
613 {
614 // Only track stack frames when Windows message deferral behavior
615 // is request for the channel.
616 if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
617 return;
618 }
620 mChannel->mTopFrame = this;
621 sStaticTopFrame = this;
623 if (!mStaticPrev) {
624 NS_ASSERTION(!gNeuteredWindows, "Should only set this once!");
625 gNeuteredWindows = new nsAutoTArray<HWND, 20>();
626 NS_ASSERTION(gNeuteredWindows, "Out of memory!");
627 }
628 }
630 MessageChannel::SyncStackFrame::~SyncStackFrame()
631 {
632 if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
633 return;
634 }
636 NS_ASSERTION(this == mChannel->mTopFrame,
637 "Mismatched interrupt stack frames");
638 NS_ASSERTION(this == sStaticTopFrame,
639 "Mismatched static Interrupt stack frames");
641 mChannel->mTopFrame = mPrev;
642 sStaticTopFrame = mStaticPrev;
644 if (!mStaticPrev) {
645 NS_ASSERTION(gNeuteredWindows, "Bad pointer!");
646 delete gNeuteredWindows;
647 gNeuteredWindows = nullptr;
648 }
649 }
651 MessageChannel::SyncStackFrame* MessageChannel::sStaticTopFrame;
653 // nsAppShell's notification that gecko events are being processed.
654 // If we are here and there is an Interrupt Incall active, we are spinning
655 // a nested gecko event loop. In which case the remote process needs
656 // to know about it.
657 void /* static */
658 MessageChannel::NotifyGeckoEventDispatch()
659 {
660 // sStaticTopFrame is only valid for Interrupt channels
661 if (!sStaticTopFrame || sStaticTopFrame->mListenerNotified)
662 return;
664 sStaticTopFrame->mListenerNotified = true;
665 MessageChannel* channel = static_cast<MessageChannel*>(sStaticTopFrame->mChannel);
666 channel->Listener()->ProcessRemoteNativeEventsInInterruptCall();
667 }
669 // invoked by the module that receives the spin event loop
670 // message.
671 void
672 MessageChannel::ProcessNativeEventsInInterruptCall()
673 {
674 NS_ASSERTION(GetCurrentThreadId() == gUIThreadId,
675 "Shouldn't be on a non-main thread in here!");
676 if (!mTopFrame) {
677 NS_ERROR("Spin logic error: no Interrupt frame");
678 return;
679 }
681 mTopFrame->mSpinNestedEvents = true;
682 }
684 // Spin loop is called in place of WaitFor*Notify when modal ui is being shown
685 // in a child. There are some intricacies in using it however. Spin loop is
686 // enabled for a particular Interrupt frame by the client calling
687 // MessageChannel::ProcessNativeEventsInInterrupt().
688 // This call can be nested for multiple Interrupt frames in a single plugin or
689 // multiple unrelated plugins.
690 void
691 MessageChannel::SpinInternalEventLoop()
692 {
693 if (mozilla::PaintTracker::IsPainting()) {
694 NS_RUNTIMEABORT("Don't spin an event loop while painting.");
695 }
697 NS_ASSERTION(mTopFrame && mTopFrame->mSpinNestedEvents,
698 "Spinning incorrectly");
700 // Nested windows event loop we trigger when the child enters into modal
701 // event loops.
703 // Note, when we return, we always reset the notify worker event. So there's
704 // no need to reset it on return here.
706 do {
707 MSG msg = { 0 };
709 // Don't get wrapped up in here if the child connection dies.
710 {
711 MonitorAutoLock lock(*mMonitor);
712 if (!Connected()) {
713 return;
714 }
715 }
717 // Retrieve window or thread messages
718 if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
719 // The child UI should have been destroyed before the app is closed, in
720 // which case, we should never get this here.
721 if (msg.message == WM_QUIT) {
722 NS_ERROR("WM_QUIT received in SpinInternalEventLoop!");
723 } else {
724 TranslateMessage(&msg);
725 ::DispatchMessageW(&msg);
726 return;
727 }
728 }
730 // Note, give dispatching windows events priority over checking if
731 // mEvent is signaled, otherwise heavy ipc traffic can cause jittery
732 // playback of video. We'll exit out on each disaptch above, so ipc
733 // won't get starved.
735 // Wait for UI events or a signal from the io thread.
736 DWORD result = MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE,
737 QS_ALLINPUT);
738 if (result == WAIT_OBJECT_0) {
739 // Our NotifyWorkerThread event was signaled
740 return;
741 }
742 } while (true);
743 }
745 static inline bool
746 IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout)
747 {
748 return (aTimeout != PR_INTERVAL_NO_TIMEOUT) &&
749 (aTimeout <= (PR_IntervalNow() - aStart));
750 }
752 bool
753 MessageChannel::WaitForSyncNotify()
754 {
755 mMonitor->AssertCurrentThreadOwns();
757 MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!");
759 // Use a blocking wait if this channel does not require
760 // Windows message deferral behavior.
761 if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
762 PRIntervalTime timeout = (kNoTimeout == mTimeoutMs) ?
763 PR_INTERVAL_NO_TIMEOUT :
764 PR_MillisecondsToInterval(mTimeoutMs);
765 // XXX could optimize away this syscall for "no timeout" case if desired
766 PRIntervalTime waitStart = 0;
768 if (timeout != PR_INTERVAL_NO_TIMEOUT) {
769 waitStart = PR_IntervalNow();
770 }
772 mIsSyncWaitingOnNonMainThread = true;
774 mMonitor->Wait(timeout);
776 mIsSyncWaitingOnNonMainThread = false;
778 // If the timeout didn't expire, we know we received an event. The
779 // converse is not true.
780 return WaitResponse(timeout == PR_INTERVAL_NO_TIMEOUT ?
781 false : IsTimeoutExpired(waitStart, timeout));
782 }
784 NS_ASSERTION(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION,
785 "Shouldn't be here for channels that don't use message deferral!");
786 NS_ASSERTION(mTopFrame && !mTopFrame->mInterrupt,
787 "Top frame is not a sync frame!");
789 MonitorAutoUnlock unlock(*mMonitor);
791 bool timedout = false;
793 UINT_PTR timerId = 0;
794 TimeoutData timeoutData = { 0 };
796 if (mTimeoutMs != kNoTimeout) {
797 InitTimeoutData(&timeoutData, mTimeoutMs);
799 // We only do this to ensure that we won't get stuck in
800 // MsgWaitForMultipleObjects below.
801 timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
802 NS_ASSERTION(timerId, "SetTimer failed!");
803 }
805 // Setup deferred processing of native events while we wait for a response.
806 NS_ASSERTION(!MessageChannel::IsPumpingMessages(),
807 "Shouldn't be pumping already!");
809 MessageChannel::SetIsPumpingMessages(true);
810 HHOOK windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
811 nullptr, gUIThreadId);
812 NS_ASSERTION(windowHook, "Failed to set hook!");
814 {
815 while (1) {
816 MSG msg = { 0 };
817 // Don't get wrapped up in here if the child connection dies.
818 {
819 MonitorAutoLock lock(*mMonitor);
820 if (!Connected()) {
821 break;
822 }
823 }
825 // Wait until we have a message in the queue. MSDN docs are a bit unclear
826 // but it seems that windows from two different threads (and it should be
827 // noted that a thread in another process counts as a "different thread")
828 // will implicitly have their message queues attached if they are parented
829 // to one another. This wait call, then, will return for a message
830 // delivered to *either* thread.
831 DWORD result = MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE,
832 QS_ALLINPUT);
833 if (result == WAIT_OBJECT_0) {
834 // Our NotifyWorkerThread event was signaled
835 ResetEvent(mEvent);
836 break;
837 } else
838 if (result != (WAIT_OBJECT_0 + 1)) {
839 NS_ERROR("Wait failed!");
840 break;
841 }
843 if (TimeoutHasExpired(timeoutData)) {
844 // A timeout was specified and we've passed it. Break out.
845 timedout = true;
846 break;
847 }
849 // The only way to know on which thread the message was delivered is to
850 // use some logic on the return values of GetQueueStatus and PeekMessage.
851 // PeekMessage will return false if there are no "queued" messages, but it
852 // will run all "nonqueued" messages before returning. So if PeekMessage
853 // returns false and there are no "nonqueued" messages that were run then
854 // we know that the message we woke for was intended for a window on
855 // another thread.
856 bool haveSentMessagesPending =
857 (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
859 // This PeekMessage call will actually process all "nonqueued" messages
860 // that are pending before returning. If we have "nonqueued" messages
861 // pending then we should have switched out all the window procedures
862 // above. In that case this PeekMessage call won't actually cause any
863 // mozilla code (or plugin code) to run.
865 // If the following PeekMessage call fails to return a message for us (and
866 // returns false) and we didn't run any "nonqueued" messages then we must
867 // have woken up for a message designated for a window in another thread.
868 // If we loop immediately then we could enter a tight loop, so we'll give
869 // up our time slice here to let the child process its message.
870 if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) &&
871 !haveSentMessagesPending) {
872 // Message was for child, we should wait a bit.
873 SwitchToThread();
874 }
875 }
876 }
878 // Unhook the neutered window procedure hook.
879 UnhookWindowsHookEx(windowHook);
881 // Unhook any neutered windows procedures so messages can be delivered
882 // normally.
883 UnhookNeuteredWindows();
885 // Before returning we need to set a hook to run any deferred messages that
886 // we received during the IPC call. The hook will unset itself as soon as
887 // someone else calls GetMessage, PeekMessage, or runs code that generates
888 // a "nonqueued" message.
889 ScheduleDeferredMessageRun();
891 if (timerId) {
892 KillTimer(nullptr, timerId);
893 }
895 MessageChannel::SetIsPumpingMessages(false);
897 return WaitResponse(timedout);
898 }
900 bool
901 MessageChannel::WaitForInterruptNotify()
902 {
903 mMonitor->AssertCurrentThreadOwns();
905 MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!");
907 // Re-use sync notification wait code if this channel does not require
908 // Windows message deferral behavior.
909 if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) {
910 return WaitForSyncNotify();
911 }
913 if (!InterruptStackDepth()) {
914 // There is currently no way to recover from this condition.
915 NS_RUNTIMEABORT("StackDepth() is 0 in call to MessageChannel::WaitForNotify!");
916 }
918 NS_ASSERTION(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION,
919 "Shouldn't be here for channels that don't use message deferral!");
920 NS_ASSERTION(mTopFrame && mTopFrame->mInterrupt,
921 "Top frame is not a sync frame!");
923 MonitorAutoUnlock unlock(*mMonitor);
925 bool timedout = false;
927 UINT_PTR timerId = 0;
928 TimeoutData timeoutData = { 0 };
930 // windowHook is used as a flag variable for the loop below: if it is set
931 // and we start to spin a nested event loop, we need to clear the hook and
932 // process deferred/pending messages.
933 // If windowHook is nullptr, MessageChannel::IsPumpingMessages should be false.
934 HHOOK windowHook = nullptr;
936 while (1) {
937 NS_ASSERTION((!!windowHook) == MessageChannel::IsPumpingMessages(),
938 "windowHook out of sync with reality");
940 if (mTopFrame->mSpinNestedEvents) {
941 if (windowHook) {
942 UnhookWindowsHookEx(windowHook);
943 windowHook = nullptr;
945 if (timerId) {
946 KillTimer(nullptr, timerId);
947 timerId = 0;
948 }
950 // Used by widget to assert on incoming native events
951 MessageChannel::SetIsPumpingMessages(false);
953 // Unhook any neutered windows procedures so messages can be delievered
954 // normally.
955 UnhookNeuteredWindows();
957 // Send all deferred "nonqueued" message to the intended receiver.
958 // We're dropping into SpinInternalEventLoop so we should be fairly
959 // certain these will get delivered soohn.
960 ScheduleDeferredMessageRun();
961 }
962 SpinInternalEventLoop();
963 ResetEvent(mEvent);
964 return true;
965 }
967 if (!windowHook) {
968 MessageChannel::SetIsPumpingMessages(true);
969 windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
970 nullptr, gUIThreadId);
971 NS_ASSERTION(windowHook, "Failed to set hook!");
973 NS_ASSERTION(!timerId, "Timer already initialized?");
975 if (mTimeoutMs != kNoTimeout) {
976 InitTimeoutData(&timeoutData, mTimeoutMs);
977 timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
978 NS_ASSERTION(timerId, "SetTimer failed!");
979 }
980 }
982 MSG msg = { 0 };
984 // Don't get wrapped up in here if the child connection dies.
985 {
986 MonitorAutoLock lock(*mMonitor);
987 if (!Connected()) {
988 break;
989 }
990 }
992 DWORD result = MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE,
993 QS_ALLINPUT);
994 if (result == WAIT_OBJECT_0) {
995 // Our NotifyWorkerThread event was signaled
996 ResetEvent(mEvent);
997 break;
998 } else
999 if (result != (WAIT_OBJECT_0 + 1)) {
1000 NS_ERROR("Wait failed!");
1001 break;
1002 }
1004 if (TimeoutHasExpired(timeoutData)) {
1005 // A timeout was specified and we've passed it. Break out.
1006 timedout = true;
1007 break;
1008 }
1010 // See MessageChannel's WaitFor*Notify for details.
1011 bool haveSentMessagesPending =
1012 (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0;
1014 // PeekMessage markes the messages as "old" so that they don't wake up
1015 // MsgWaitForMultipleObjects every time.
1016 if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) &&
1017 !haveSentMessagesPending) {
1018 // Message was for child, we should wait a bit.
1019 SwitchToThread();
1020 }
1021 }
1023 if (windowHook) {
1024 // Unhook the neutered window procedure hook.
1025 UnhookWindowsHookEx(windowHook);
1027 // Unhook any neutered windows procedures so messages can be delivered
1028 // normally.
1029 UnhookNeuteredWindows();
1031 // Before returning we need to set a hook to run any deferred messages that
1032 // we received during the IPC call. The hook will unset itself as soon as
1033 // someone else calls GetMessage, PeekMessage, or runs code that generates
1034 // a "nonqueued" message.
1035 ScheduleDeferredMessageRun();
1037 if (timerId) {
1038 KillTimer(nullptr, timerId);
1039 }
1040 }
1042 MessageChannel::SetIsPumpingMessages(false);
1044 return WaitResponse(timedout);
1045 }
1047 void
1048 MessageChannel::NotifyWorkerThread()
1049 {
1050 mMonitor->AssertCurrentThreadOwns();
1052 if (mIsSyncWaitingOnNonMainThread) {
1053 mMonitor->Notify();
1054 return;
1055 }
1057 NS_ASSERTION(mEvent, "No signal event to set, this is really bad!");
1058 if (!SetEvent(mEvent)) {
1059 NS_WARNING("Failed to set NotifyWorkerThread event!");
1060 }
1061 }
1063 void
1064 DeferredSendMessage::Run()
1065 {
1066 AssertWindowIsNotNeutered(hWnd);
1067 if (!IsWindow(hWnd)) {
1068 NS_ERROR("Invalid window!");
1069 return;
1070 }
1072 WNDPROC wndproc =
1073 reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
1074 if (!wndproc) {
1075 NS_ERROR("Invalid window procedure!");
1076 return;
1077 }
1079 CallWindowProc(wndproc, hWnd, message, wParam, lParam);
1080 }
1082 void
1083 DeferredRedrawMessage::Run()
1084 {
1085 AssertWindowIsNotNeutered(hWnd);
1086 if (!IsWindow(hWnd)) {
1087 NS_ERROR("Invalid window!");
1088 return;
1089 }
1091 #ifdef DEBUG
1092 BOOL ret =
1093 #endif
1094 RedrawWindow(hWnd, nullptr, nullptr, flags);
1095 NS_ASSERTION(ret, "RedrawWindow failed!");
1096 }
1098 DeferredUpdateMessage::DeferredUpdateMessage(HWND aHWnd)
1099 {
1100 mWnd = aHWnd;
1101 if (!GetUpdateRect(mWnd, &mUpdateRect, FALSE)) {
1102 memset(&mUpdateRect, 0, sizeof(RECT));
1103 return;
1104 }
1105 ValidateRect(mWnd, &mUpdateRect);
1106 }
1108 void
1109 DeferredUpdateMessage::Run()
1110 {
1111 AssertWindowIsNotNeutered(mWnd);
1112 if (!IsWindow(mWnd)) {
1113 NS_ERROR("Invalid window!");
1114 return;
1115 }
1117 InvalidateRect(mWnd, &mUpdateRect, FALSE);
1118 #ifdef DEBUG
1119 BOOL ret =
1120 #endif
1121 UpdateWindow(mWnd);
1122 NS_ASSERTION(ret, "UpdateWindow failed!");
1123 }
1125 DeferredSettingChangeMessage::DeferredSettingChangeMessage(HWND aHWnd,
1126 UINT aMessage,
1127 WPARAM aWParam,
1128 LPARAM aLParam)
1129 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam)
1130 {
1131 NS_ASSERTION(aMessage == WM_SETTINGCHANGE, "Wrong message type!");
1132 if (aLParam) {
1133 lParamString = _wcsdup(reinterpret_cast<const wchar_t*>(aLParam));
1134 lParam = reinterpret_cast<LPARAM>(lParamString);
1135 }
1136 else {
1137 lParamString = nullptr;
1138 lParam = 0;
1139 }
1140 }
1142 DeferredSettingChangeMessage::~DeferredSettingChangeMessage()
1143 {
1144 free(lParamString);
1145 }
1147 DeferredWindowPosMessage::DeferredWindowPosMessage(HWND aHWnd,
1148 LPARAM aLParam,
1149 bool aForCalcSize,
1150 WPARAM aWParam)
1151 {
1152 if (aForCalcSize) {
1153 if (aWParam) {
1154 NCCALCSIZE_PARAMS* arg = reinterpret_cast<NCCALCSIZE_PARAMS*>(aLParam);
1155 memcpy(&windowPos, arg->lppos, sizeof(windowPos));
1157 NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!");
1158 }
1159 else {
1160 RECT* arg = reinterpret_cast<RECT*>(aLParam);
1161 windowPos.hwnd = aHWnd;
1162 windowPos.hwndInsertAfter = nullptr;
1163 windowPos.x = arg->left;
1164 windowPos.y = arg->top;
1165 windowPos.cx = arg->right - arg->left;
1166 windowPos.cy = arg->bottom - arg->top;
1168 NS_ASSERTION(arg->right >= arg->left && arg->bottom >= arg->top,
1169 "Negative width or height!");
1170 }
1171 windowPos.flags = SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER |
1172 SWP_NOZORDER | SWP_DEFERERASE | SWP_NOSENDCHANGING;
1173 }
1174 else {
1175 // Not for WM_NCCALCSIZE
1176 WINDOWPOS* arg = reinterpret_cast<WINDOWPOS*>(aLParam);
1177 memcpy(&windowPos, arg, sizeof(windowPos));
1179 NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!");
1181 // Windows sends in some private flags sometimes that we can't simply copy.
1182 // Filter here.
1183 UINT mask = SWP_ASYNCWINDOWPOS | SWP_DEFERERASE | SWP_DRAWFRAME |
1184 SWP_FRAMECHANGED | SWP_HIDEWINDOW | SWP_NOACTIVATE |
1185 SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW |
1186 SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE |
1187 SWP_NOZORDER | SWP_SHOWWINDOW;
1188 windowPos.flags &= mask;
1189 }
1190 }
1192 void
1193 DeferredWindowPosMessage::Run()
1194 {
1195 AssertWindowIsNotNeutered(windowPos.hwnd);
1196 if (!IsWindow(windowPos.hwnd)) {
1197 NS_ERROR("Invalid window!");
1198 return;
1199 }
1201 if (!IsWindow(windowPos.hwndInsertAfter)) {
1202 NS_WARNING("ZOrder change cannot be honored");
1203 windowPos.hwndInsertAfter = 0;
1204 windowPos.flags |= SWP_NOZORDER;
1205 }
1207 #ifdef DEBUG
1208 BOOL ret =
1209 #endif
1210 SetWindowPos(windowPos.hwnd, windowPos.hwndInsertAfter, windowPos.x,
1211 windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags);
1212 NS_ASSERTION(ret, "SetWindowPos failed!");
1213 }
1215 DeferredCopyDataMessage::DeferredCopyDataMessage(HWND aHWnd,
1216 UINT aMessage,
1217 WPARAM aWParam,
1218 LPARAM aLParam)
1219 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam)
1220 {
1221 NS_ASSERTION(IsWindow(reinterpret_cast<HWND>(aWParam)), "Bad window!");
1223 COPYDATASTRUCT* source = reinterpret_cast<COPYDATASTRUCT*>(aLParam);
1224 NS_ASSERTION(source, "Should never be null!");
1226 copyData.dwData = source->dwData;
1227 copyData.cbData = source->cbData;
1229 if (source->cbData) {
1230 copyData.lpData = malloc(source->cbData);
1231 if (copyData.lpData) {
1232 memcpy(copyData.lpData, source->lpData, source->cbData);
1233 }
1234 else {
1235 NS_ERROR("Out of memory?!");
1236 copyData.cbData = 0;
1237 }
1238 }
1239 else {
1240 copyData.lpData = nullptr;
1241 }
1243 lParam = reinterpret_cast<LPARAM>(©Data);
1244 }
1246 DeferredCopyDataMessage::~DeferredCopyDataMessage()
1247 {
1248 free(copyData.lpData);
1249 }
1251 DeferredStyleChangeMessage::DeferredStyleChangeMessage(HWND aHWnd,
1252 WPARAM aWParam,
1253 LPARAM aLParam)
1254 : hWnd(aHWnd)
1255 {
1256 index = static_cast<int>(aWParam);
1257 style = reinterpret_cast<STYLESTRUCT*>(aLParam)->styleNew;
1258 }
1260 void
1261 DeferredStyleChangeMessage::Run()
1262 {
1263 SetWindowLongPtr(hWnd, index, style);
1264 }
1266 DeferredSetIconMessage::DeferredSetIconMessage(HWND aHWnd,
1267 UINT aMessage,
1268 WPARAM aWParam,
1269 LPARAM aLParam)
1270 : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam)
1271 {
1272 NS_ASSERTION(aMessage == WM_SETICON, "Wrong message type!");
1273 }
1275 void
1276 DeferredSetIconMessage::Run()
1277 {
1278 AssertWindowIsNotNeutered(hWnd);
1279 if (!IsWindow(hWnd)) {
1280 NS_ERROR("Invalid window!");
1281 return;
1282 }
1284 WNDPROC wndproc =
1285 reinterpret_cast<WNDPROC>(GetWindowLongPtr(hWnd, GWLP_WNDPROC));
1286 if (!wndproc) {
1287 NS_ERROR("Invalid window procedure!");
1288 return;
1289 }
1291 HICON hOld = reinterpret_cast<HICON>(
1292 CallWindowProc(wndproc, hWnd, message, wParam, lParam));
1293 if (hOld) {
1294 DestroyIcon(hOld);
1295 }
1296 }