ipc/glue/WindowsMessageLoop.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial