michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * vim: sw=2 ts=2 et : michael@0: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "WindowsMessageLoop.h" michael@0: #include "MessageChannel.h" michael@0: michael@0: #include "nsAutoPtr.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsIXULAppInfo.h" michael@0: michael@0: #include "mozilla/PaintTracker.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::ipc; michael@0: using namespace mozilla::ipc::windows; michael@0: michael@0: /** michael@0: * The Windows-only code below exists to solve a general problem with deadlocks michael@0: * that we experience when sending synchronous IPC messages to processes that michael@0: * contain native windows (i.e. HWNDs). Windows (the OS) sends synchronous michael@0: * messages between parent and child HWNDs in multiple circumstances (e.g. michael@0: * WM_PARENTNOTIFY, WM_NCACTIVATE, etc.), even when those HWNDs are controlled michael@0: * by different threads or different processes. Thus we can very easily end up michael@0: * in a deadlock by a call stack like the following: michael@0: * michael@0: * Process A: michael@0: * - CreateWindow(...) creates a "parent" HWND. michael@0: * - SendCreateChildWidget(HWND) is a sync IPC message that sends the "parent" michael@0: * HWND over to Process B. Process A blocks until a response is received michael@0: * from Process B. michael@0: * michael@0: * Process B: michael@0: * - RecvCreateWidget(HWND) gets the "parent" HWND from Process A. michael@0: * - CreateWindow(..., HWND) creates a "child" HWND with the parent from michael@0: * process A. michael@0: * - Windows (the OS) generates a WM_PARENTNOTIFY message that is sent michael@0: * synchronously to Process A. Process B blocks until a response is michael@0: * received from Process A. Process A, however, is blocked and cannot michael@0: * process the message. Both processes are deadlocked. michael@0: * michael@0: * The example above has a few different workarounds (e.g. setting the michael@0: * WS_EX_NOPARENTNOTIFY style on the child window) but the general problem is michael@0: * persists. Once two HWNDs are parented we must not block their owning michael@0: * threads when manipulating either HWND. michael@0: * michael@0: * Windows requires any application that hosts native HWNDs to always process michael@0: * messages or risk deadlock. Given our architecture the only way to meet michael@0: * Windows' requirement and allow for synchronous IPC messages is to pump a michael@0: * miniature message loop during a sync IPC call. We avoid processing any michael@0: * queued messages during the loop (with one exception, see below), but michael@0: * "nonqueued" messages (see michael@0: * http://msdn.microsoft.com/en-us/library/ms644927(VS.85).aspx under the michael@0: * section "Nonqueued messages") cannot be avoided. Those messages are trapped michael@0: * in a special window procedure where we can either ignore the message or michael@0: * process it in some fashion. michael@0: * michael@0: * Queued and "non-queued" messages will be processed during Interrupt calls if michael@0: * modal UI related api calls block an Interrupt in-call in the child. To prevent michael@0: * windows from freezing, and to allow concurrent processing of critical michael@0: * events (such as painting), we spin a native event dispatch loop while michael@0: * these in-calls are blocked. michael@0: */ michael@0: michael@0: #if defined(ACCESSIBILITY) michael@0: // pulled from accessibility's win utils michael@0: extern const wchar_t* kPropNameTabContent; michael@0: #endif michael@0: michael@0: // widget related message id constants we need to defer michael@0: namespace mozilla { michael@0: namespace widget { michael@0: extern UINT sAppShellGeckoMsgId; michael@0: #ifdef MOZ_METRO michael@0: extern UINT sDefaultBrowserMsgId; michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: const wchar_t kOldWndProcProp[] = L"MozillaIPCOldWndProc"; michael@0: michael@0: // This isn't defined before Windows XP. michael@0: enum { WM_XP_THEMECHANGED = 0x031A }; michael@0: michael@0: char16_t gAppMessageWindowName[256] = { 0 }; michael@0: int32_t gAppMessageWindowNameLength = 0; michael@0: michael@0: nsTArray* gNeuteredWindows = nullptr; michael@0: michael@0: typedef nsTArray > DeferredMessageArray; michael@0: DeferredMessageArray* gDeferredMessages = nullptr; michael@0: michael@0: HHOOK gDeferredGetMsgHook = nullptr; michael@0: HHOOK gDeferredCallWndProcHook = nullptr; michael@0: michael@0: DWORD gUIThreadId = 0; michael@0: michael@0: LRESULT CALLBACK michael@0: DeferredMessageHook(int nCode, michael@0: WPARAM wParam, michael@0: LPARAM lParam) michael@0: { michael@0: // XXX This function is called for *both* the WH_CALLWNDPROC hook and the michael@0: // WH_GETMESSAGE hook, but they have different parameters. We don't michael@0: // use any of them except nCode which has the same meaning. michael@0: michael@0: // Only run deferred messages if all of these conditions are met: michael@0: // 1. The |nCode| indicates that this hook should do something. michael@0: // 2. We have deferred messages to run. michael@0: // 3. We're not being called from the PeekMessage within the WaitFor*Notify michael@0: // function (indicated with MessageChannel::IsPumpingMessages). We really michael@0: // only want to run after returning to the main event loop. michael@0: if (nCode >= 0 && gDeferredMessages && !MessageChannel::IsPumpingMessages()) { michael@0: NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook, michael@0: "These hooks must be set if we're being called!"); michael@0: NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!"); michael@0: michael@0: // Unset hooks first, in case we reenter below. michael@0: UnhookWindowsHookEx(gDeferredGetMsgHook); michael@0: UnhookWindowsHookEx(gDeferredCallWndProcHook); michael@0: gDeferredGetMsgHook = 0; michael@0: gDeferredCallWndProcHook = 0; michael@0: michael@0: // Unset the global and make sure we delete it when we're done here. michael@0: nsAutoPtr messages(gDeferredMessages); michael@0: gDeferredMessages = nullptr; michael@0: michael@0: // Run all the deferred messages in order. michael@0: uint32_t count = messages->Length(); michael@0: for (uint32_t index = 0; index < count; index++) { michael@0: messages->ElementAt(index)->Run(); michael@0: } michael@0: } michael@0: michael@0: // Always call the next hook. michael@0: return CallNextHookEx(nullptr, nCode, wParam, lParam); michael@0: } michael@0: michael@0: void michael@0: ScheduleDeferredMessageRun() michael@0: { michael@0: if (gDeferredMessages && michael@0: !(gDeferredGetMsgHook && gDeferredCallWndProcHook)) { michael@0: NS_ASSERTION(gDeferredMessages->Length(), "No deferred messages?!"); michael@0: michael@0: gDeferredGetMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, DeferredMessageHook, michael@0: nullptr, gUIThreadId); michael@0: gDeferredCallWndProcHook = ::SetWindowsHookEx(WH_CALLWNDPROC, michael@0: DeferredMessageHook, nullptr, michael@0: gUIThreadId); michael@0: NS_ASSERTION(gDeferredGetMsgHook && gDeferredCallWndProcHook, michael@0: "Failed to set hooks!"); michael@0: } michael@0: } michael@0: michael@0: LRESULT michael@0: ProcessOrDeferMessage(HWND hwnd, michael@0: UINT uMsg, michael@0: WPARAM wParam, michael@0: LPARAM lParam) michael@0: { michael@0: DeferredMessage* deferred = nullptr; michael@0: michael@0: // Most messages ask for 0 to be returned if the message is processed. michael@0: LRESULT res = 0; michael@0: michael@0: switch (uMsg) { michael@0: // Messages that can be deferred as-is. These must not contain pointers in michael@0: // their wParam or lParam arguments! michael@0: case WM_ACTIVATE: michael@0: case WM_ACTIVATEAPP: michael@0: case WM_CANCELMODE: michael@0: case WM_CAPTURECHANGED: michael@0: case WM_CHILDACTIVATE: michael@0: case WM_DESTROY: michael@0: case WM_ENABLE: michael@0: case WM_IME_NOTIFY: michael@0: case WM_IME_SETCONTEXT: michael@0: case WM_KILLFOCUS: michael@0: case WM_MOUSEWHEEL: michael@0: case WM_NCDESTROY: michael@0: case WM_PARENTNOTIFY: michael@0: case WM_SETFOCUS: michael@0: case WM_SYSCOMMAND: michael@0: case WM_DISPLAYCHANGE: michael@0: case WM_SHOWWINDOW: // Intentional fall-through. michael@0: case WM_XP_THEMECHANGED: { michael@0: deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); michael@0: break; michael@0: } michael@0: michael@0: case WM_DEVICECHANGE: michael@0: case WM_POWERBROADCAST: michael@0: case WM_NCACTIVATE: // Intentional fall-through. michael@0: case WM_SETCURSOR: { michael@0: // Friggin unconventional return value... michael@0: res = TRUE; michael@0: deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); michael@0: break; michael@0: } michael@0: michael@0: case WM_MOUSEACTIVATE: { michael@0: res = MA_NOACTIVATE; michael@0: deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); michael@0: break; michael@0: } michael@0: michael@0: // These messages need to use the RedrawWindow function to generate the michael@0: // right kind of message. We can't simply fake them as the MSDN docs say michael@0: // explicitly that paint messages should not be sent by an application. michael@0: case WM_ERASEBKGND: { michael@0: UINT flags = RDW_INVALIDATE | RDW_ERASE | RDW_NOINTERNALPAINT | michael@0: RDW_NOFRAME | RDW_NOCHILDREN | RDW_ERASENOW; michael@0: deferred = new DeferredRedrawMessage(hwnd, flags); michael@0: break; michael@0: } michael@0: michael@0: // This message will generate a WM_PAINT message if there are invalid michael@0: // areas. michael@0: case WM_PAINT: { michael@0: deferred = new DeferredUpdateMessage(hwnd); michael@0: break; michael@0: } michael@0: michael@0: // This message holds a string in its lParam that we must copy. michael@0: case WM_SETTINGCHANGE: { michael@0: deferred = new DeferredSettingChangeMessage(hwnd, uMsg, wParam, lParam); michael@0: break; michael@0: } michael@0: michael@0: // These messages are faked via a call to SetWindowPos. michael@0: case WM_WINDOWPOSCHANGED: { michael@0: deferred = new DeferredWindowPosMessage(hwnd, lParam); michael@0: break; michael@0: } michael@0: case WM_NCCALCSIZE: { michael@0: deferred = new DeferredWindowPosMessage(hwnd, lParam, true, wParam); michael@0: break; michael@0: } michael@0: michael@0: case WM_COPYDATA: { michael@0: deferred = new DeferredCopyDataMessage(hwnd, uMsg, wParam, lParam); michael@0: res = TRUE; michael@0: break; michael@0: } michael@0: michael@0: case WM_STYLECHANGED: { michael@0: deferred = new DeferredStyleChangeMessage(hwnd, wParam, lParam); michael@0: break; michael@0: } michael@0: michael@0: case WM_SETICON: { michael@0: deferred = new DeferredSetIconMessage(hwnd, uMsg, wParam, lParam); michael@0: break; michael@0: } michael@0: michael@0: // Messages that are safe to pass to DefWindowProc go here. michael@0: case WM_ENTERIDLE: michael@0: case WM_GETICON: michael@0: case WM_NCPAINT: // (never trap nc paint events) michael@0: case WM_GETMINMAXINFO: michael@0: case WM_GETTEXT: michael@0: case WM_NCHITTEST: michael@0: case WM_STYLECHANGING: // Intentional fall-through. michael@0: case WM_WINDOWPOSCHANGING: { michael@0: return DefWindowProc(hwnd, uMsg, wParam, lParam); michael@0: } michael@0: michael@0: // Just return, prevents DefWindowProc from messaging the window michael@0: // syncronously with other events, which may be deferred. Prevents michael@0: // random shutdown of aero composition on the window. michael@0: case WM_SYNCPAINT: michael@0: return 0; michael@0: michael@0: // This message causes QuickTime to make re-entrant calls. michael@0: // Simply discarding it doesn't seem to hurt anything. michael@0: case WM_APP-1: michael@0: return 0; michael@0: michael@0: default: { michael@0: if (uMsg && uMsg == mozilla::widget::sAppShellGeckoMsgId) { michael@0: // Widget's registered native event callback michael@0: deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); michael@0: #ifdef MOZ_METRO michael@0: } else if (uMsg && uMsg == mozilla::widget::sDefaultBrowserMsgId) { michael@0: // Metro widget's system shutdown message michael@0: deferred = new DeferredSendMessage(hwnd, uMsg, wParam, lParam); michael@0: #endif michael@0: } else { michael@0: // Unknown messages only michael@0: #ifdef DEBUG michael@0: nsAutoCString log("Received \"nonqueued\" message "); michael@0: log.AppendInt(uMsg); michael@0: log.AppendLiteral(" during a synchronous IPC message for window "); michael@0: log.AppendInt((int64_t)hwnd); michael@0: michael@0: wchar_t className[256] = { 0 }; michael@0: if (GetClassNameW(hwnd, className, sizeof(className) - 1) > 0) { michael@0: log.AppendLiteral(" (\""); michael@0: log.Append(NS_ConvertUTF16toUTF8((char16_t*)className)); michael@0: log.AppendLiteral("\")"); michael@0: } michael@0: michael@0: log.AppendLiteral(", sending it to DefWindowProc instead of the normal " michael@0: "window procedure."); michael@0: NS_ERROR(log.get()); michael@0: #endif michael@0: return DefWindowProc(hwnd, uMsg, wParam, lParam); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(deferred, "Must have a message here!"); michael@0: michael@0: // Create the deferred message array if it doesn't exist already. michael@0: if (!gDeferredMessages) { michael@0: gDeferredMessages = new nsTArray >(20); michael@0: NS_ASSERTION(gDeferredMessages, "Out of memory!"); michael@0: } michael@0: michael@0: // Save for later. The array takes ownership of |deferred|. michael@0: gDeferredMessages->AppendElement(deferred); michael@0: return res; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: // We need the pointer value of this in PluginInstanceChild. michael@0: LRESULT CALLBACK michael@0: NeuteredWindowProc(HWND hwnd, michael@0: UINT uMsg, michael@0: WPARAM wParam, michael@0: LPARAM lParam) michael@0: { michael@0: WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp); michael@0: if (!oldWndProc) { michael@0: // We should really never ever get here. michael@0: NS_ERROR("No old wndproc!"); michael@0: return DefWindowProc(hwnd, uMsg, wParam, lParam); michael@0: } michael@0: michael@0: // See if we care about this message. We may either ignore it, send it to michael@0: // DefWindowProc, or defer it for later. michael@0: return ProcessOrDeferMessage(hwnd, uMsg, wParam, lParam); michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: static bool michael@0: WindowIsDeferredWindow(HWND hWnd) michael@0: { michael@0: if (!IsWindow(hWnd)) { michael@0: NS_WARNING("Window has died!"); michael@0: return false; michael@0: } michael@0: michael@0: char16_t buffer[256] = { 0 }; michael@0: int length = GetClassNameW(hWnd, (wchar_t*)buffer, sizeof(buffer) - 1); michael@0: if (length <= 0) { michael@0: NS_WARNING("Failed to get class name!"); michael@0: return false; michael@0: } michael@0: michael@0: #if defined(ACCESSIBILITY) michael@0: // Tab content creates a window that responds to accessible WM_GETOBJECT michael@0: // calls. This window can safely be ignored. michael@0: if (::GetPropW(hWnd, kPropNameTabContent)) { michael@0: return false; michael@0: } michael@0: #endif michael@0: michael@0: // Common mozilla windows we must defer messages to. michael@0: nsDependentString className(buffer, length); michael@0: if (StringBeginsWith(className, NS_LITERAL_STRING("Mozilla")) || michael@0: StringBeginsWith(className, NS_LITERAL_STRING("Gecko")) || michael@0: className.EqualsLiteral("nsToolkitClass") || michael@0: className.EqualsLiteral("nsAppShell:EventWindowClass")) { michael@0: return true; michael@0: } michael@0: michael@0: #ifdef MOZ_METRO michael@0: // immersive UI ICoreWindow michael@0: if (className.EqualsLiteral("Windows.UI.Core.CoreWindow")) { michael@0: return true; michael@0: } michael@0: #endif michael@0: michael@0: // Plugin windows that can trigger ipc calls in child: michael@0: // 'ShockwaveFlashFullScreen' - flash fullscreen window michael@0: // 'QTNSHIDDEN' - QuickTime michael@0: // 'AGFullScreenWinClass' - silverlight fullscreen window michael@0: if (className.EqualsLiteral("ShockwaveFlashFullScreen") || michael@0: className.EqualsLiteral("QTNSHIDDEN") || michael@0: className.EqualsLiteral("AGFullScreenWinClass")) { michael@0: return true; michael@0: } michael@0: michael@0: // Google Earth bridging msg window between the plugin instance and a separate michael@0: // earth process. The earth process can trigger a plugin incall on the browser michael@0: // at any time, which is badness if the instance is already making an incall. michael@0: if (className.EqualsLiteral("__geplugin_bridge_window__")) { michael@0: return true; michael@0: } michael@0: michael@0: // nsNativeAppSupport makes a window like "FirefoxMessageWindow" based on the michael@0: // toolkit app's name. It's pretty expensive to calculate this so we only try michael@0: // once. michael@0: if (gAppMessageWindowNameLength == 0) { michael@0: nsCOMPtr appInfo = michael@0: do_GetService("@mozilla.org/xre/app-info;1"); michael@0: if (appInfo) { michael@0: nsAutoCString appName; michael@0: if (NS_SUCCEEDED(appInfo->GetName(appName))) { michael@0: appName.Append("MessageWindow"); michael@0: nsDependentString windowName(gAppMessageWindowName); michael@0: CopyUTF8toUTF16(appName, windowName); michael@0: gAppMessageWindowNameLength = windowName.Length(); michael@0: } michael@0: } michael@0: michael@0: // Don't try again if that failed. michael@0: if (gAppMessageWindowNameLength == 0) { michael@0: gAppMessageWindowNameLength = -1; michael@0: } michael@0: } michael@0: michael@0: if (gAppMessageWindowNameLength != -1 && michael@0: className.Equals(nsDependentString(gAppMessageWindowName, michael@0: gAppMessageWindowNameLength))) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: NeuterWindowProcedure(HWND hWnd) michael@0: { michael@0: if (!WindowIsDeferredWindow(hWnd)) { michael@0: // Some other kind of window, skip. michael@0: return false; michael@0: } michael@0: michael@0: NS_ASSERTION(!GetProp(hWnd, kOldWndProcProp), "This should always be null!"); michael@0: michael@0: // It's possible to get nullptr out of SetWindowLongPtr, and the only way to michael@0: // know if that's a valid old value is to use GetLastError. Clear the error michael@0: // here so we can tell. michael@0: SetLastError(ERROR_SUCCESS); michael@0: michael@0: LONG_PTR currentWndProc = michael@0: SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)NeuteredWindowProc); michael@0: if (!currentWndProc) { michael@0: if (ERROR_SUCCESS == GetLastError()) { michael@0: // No error, so we set something and must therefore reset it. michael@0: SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: NS_ASSERTION(currentWndProc != (LONG_PTR)NeuteredWindowProc, michael@0: "This shouldn't be possible!"); michael@0: michael@0: if (!SetProp(hWnd, kOldWndProcProp, (HANDLE)currentWndProc)) { michael@0: // Cleanup michael@0: NS_WARNING("SetProp failed!"); michael@0: SetWindowLongPtr(hWnd, GWLP_WNDPROC, currentWndProc); michael@0: RemoveProp(hWnd, kOldWndProcProp); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: RestoreWindowProcedure(HWND hWnd) michael@0: { michael@0: NS_ASSERTION(WindowIsDeferredWindow(hWnd), michael@0: "Not a deferred window, this shouldn't be in our list!"); michael@0: LONG_PTR oldWndProc = (LONG_PTR)GetProp(hWnd, kOldWndProcProp); michael@0: if (oldWndProc) { michael@0: NS_ASSERTION(oldWndProc != (LONG_PTR)NeuteredWindowProc, michael@0: "This shouldn't be possible!"); michael@0: michael@0: DebugOnly currentWndProc = michael@0: SetWindowLongPtr(hWnd, GWLP_WNDPROC, oldWndProc); michael@0: NS_ASSERTION(currentWndProc == (LONG_PTR)NeuteredWindowProc, michael@0: "This should never be switched out from under us!"); michael@0: } michael@0: RemoveProp(hWnd, kOldWndProcProp); michael@0: } michael@0: michael@0: LRESULT CALLBACK michael@0: CallWindowProcedureHook(int nCode, michael@0: WPARAM wParam, michael@0: LPARAM lParam) michael@0: { michael@0: if (nCode >= 0) { michael@0: NS_ASSERTION(gNeuteredWindows, "This should never be null!"); michael@0: michael@0: HWND hWnd = reinterpret_cast(lParam)->hwnd; michael@0: michael@0: if (!gNeuteredWindows->Contains(hWnd) && NeuterWindowProcedure(hWnd)) { michael@0: if (!gNeuteredWindows->AppendElement(hWnd)) { michael@0: NS_ERROR("Out of memory!"); michael@0: RestoreWindowProcedure(hWnd); michael@0: } michael@0: } michael@0: } michael@0: return CallNextHookEx(nullptr, nCode, wParam, lParam); michael@0: } michael@0: michael@0: inline void michael@0: AssertWindowIsNotNeutered(HWND hWnd) michael@0: { michael@0: #ifdef DEBUG michael@0: // Make sure our neutered window hook isn't still in place. michael@0: LONG_PTR wndproc = GetWindowLongPtr(hWnd, GWLP_WNDPROC); michael@0: NS_ASSERTION(wndproc != (LONG_PTR)NeuteredWindowProc, "Window is neutered!"); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: UnhookNeuteredWindows() michael@0: { michael@0: if (!gNeuteredWindows) michael@0: return; michael@0: uint32_t count = gNeuteredWindows->Length(); michael@0: for (uint32_t index = 0; index < count; index++) { michael@0: RestoreWindowProcedure(gNeuteredWindows->ElementAt(index)); michael@0: } michael@0: gNeuteredWindows->Clear(); michael@0: } michael@0: michael@0: // This timeout stuff assumes a sane value of mTimeoutMs (less than the overflow michael@0: // value for GetTickCount(), which is something like 50 days). It uses the michael@0: // cheapest (and least accurate) method supported by Windows 2000. michael@0: michael@0: struct TimeoutData michael@0: { michael@0: DWORD startTicks; michael@0: DWORD targetTicks; michael@0: }; michael@0: michael@0: void michael@0: InitTimeoutData(TimeoutData* aData, michael@0: int32_t aTimeoutMs) michael@0: { michael@0: aData->startTicks = GetTickCount(); michael@0: if (!aData->startTicks) { michael@0: // How unlikely is this! michael@0: aData->startTicks++; michael@0: } michael@0: aData->targetTicks = aData->startTicks + aTimeoutMs; michael@0: } michael@0: michael@0: michael@0: bool michael@0: TimeoutHasExpired(const TimeoutData& aData) michael@0: { michael@0: if (!aData.startTicks) { michael@0: return false; michael@0: } michael@0: michael@0: DWORD now = GetTickCount(); michael@0: michael@0: if (aData.targetTicks < aData.startTicks) { michael@0: // Overflow michael@0: return now < aData.startTicks && now >= aData.targetTicks; michael@0: } michael@0: return now >= aData.targetTicks; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: namespace mozilla { michael@0: namespace ipc { michael@0: namespace windows { michael@0: michael@0: void michael@0: InitUIThread() michael@0: { michael@0: // If we aren't setup before a call to NotifyWorkerThread, we'll hang michael@0: // on startup. michael@0: if (!gUIThreadId) { michael@0: gUIThreadId = GetCurrentThreadId(); michael@0: } michael@0: MOZ_ASSERT(gUIThreadId); michael@0: MOZ_ASSERT(gUIThreadId == GetCurrentThreadId(), michael@0: "Called InitUIThread multiple times on different threads!"); michael@0: } michael@0: michael@0: } // namespace windows michael@0: } // namespace ipc michael@0: } // namespace mozilla michael@0: michael@0: // See SpinInternalEventLoop below michael@0: MessageChannel::SyncStackFrame::SyncStackFrame(MessageChannel* channel, bool interrupt) michael@0: : mInterrupt(interrupt) michael@0: , mSpinNestedEvents(false) michael@0: , mListenerNotified(false) michael@0: , mChannel(channel) michael@0: , mPrev(mChannel->mTopFrame) michael@0: , mStaticPrev(sStaticTopFrame) michael@0: { michael@0: // Only track stack frames when Windows message deferral behavior michael@0: // is request for the channel. michael@0: if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { michael@0: return; michael@0: } michael@0: michael@0: mChannel->mTopFrame = this; michael@0: sStaticTopFrame = this; michael@0: michael@0: if (!mStaticPrev) { michael@0: NS_ASSERTION(!gNeuteredWindows, "Should only set this once!"); michael@0: gNeuteredWindows = new nsAutoTArray(); michael@0: NS_ASSERTION(gNeuteredWindows, "Out of memory!"); michael@0: } michael@0: } michael@0: michael@0: MessageChannel::SyncStackFrame::~SyncStackFrame() michael@0: { michael@0: if (!(mChannel->GetChannelFlags() & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(this == mChannel->mTopFrame, michael@0: "Mismatched interrupt stack frames"); michael@0: NS_ASSERTION(this == sStaticTopFrame, michael@0: "Mismatched static Interrupt stack frames"); michael@0: michael@0: mChannel->mTopFrame = mPrev; michael@0: sStaticTopFrame = mStaticPrev; michael@0: michael@0: if (!mStaticPrev) { michael@0: NS_ASSERTION(gNeuteredWindows, "Bad pointer!"); michael@0: delete gNeuteredWindows; michael@0: gNeuteredWindows = nullptr; michael@0: } michael@0: } michael@0: michael@0: MessageChannel::SyncStackFrame* MessageChannel::sStaticTopFrame; michael@0: michael@0: // nsAppShell's notification that gecko events are being processed. michael@0: // If we are here and there is an Interrupt Incall active, we are spinning michael@0: // a nested gecko event loop. In which case the remote process needs michael@0: // to know about it. michael@0: void /* static */ michael@0: MessageChannel::NotifyGeckoEventDispatch() michael@0: { michael@0: // sStaticTopFrame is only valid for Interrupt channels michael@0: if (!sStaticTopFrame || sStaticTopFrame->mListenerNotified) michael@0: return; michael@0: michael@0: sStaticTopFrame->mListenerNotified = true; michael@0: MessageChannel* channel = static_cast(sStaticTopFrame->mChannel); michael@0: channel->Listener()->ProcessRemoteNativeEventsInInterruptCall(); michael@0: } michael@0: michael@0: // invoked by the module that receives the spin event loop michael@0: // message. michael@0: void michael@0: MessageChannel::ProcessNativeEventsInInterruptCall() michael@0: { michael@0: NS_ASSERTION(GetCurrentThreadId() == gUIThreadId, michael@0: "Shouldn't be on a non-main thread in here!"); michael@0: if (!mTopFrame) { michael@0: NS_ERROR("Spin logic error: no Interrupt frame"); michael@0: return; michael@0: } michael@0: michael@0: mTopFrame->mSpinNestedEvents = true; michael@0: } michael@0: michael@0: // Spin loop is called in place of WaitFor*Notify when modal ui is being shown michael@0: // in a child. There are some intricacies in using it however. Spin loop is michael@0: // enabled for a particular Interrupt frame by the client calling michael@0: // MessageChannel::ProcessNativeEventsInInterrupt(). michael@0: // This call can be nested for multiple Interrupt frames in a single plugin or michael@0: // multiple unrelated plugins. michael@0: void michael@0: MessageChannel::SpinInternalEventLoop() michael@0: { michael@0: if (mozilla::PaintTracker::IsPainting()) { michael@0: NS_RUNTIMEABORT("Don't spin an event loop while painting."); michael@0: } michael@0: michael@0: NS_ASSERTION(mTopFrame && mTopFrame->mSpinNestedEvents, michael@0: "Spinning incorrectly"); michael@0: michael@0: // Nested windows event loop we trigger when the child enters into modal michael@0: // event loops. michael@0: michael@0: // Note, when we return, we always reset the notify worker event. So there's michael@0: // no need to reset it on return here. michael@0: michael@0: do { michael@0: MSG msg = { 0 }; michael@0: michael@0: // Don't get wrapped up in here if the child connection dies. michael@0: { michael@0: MonitorAutoLock lock(*mMonitor); michael@0: if (!Connected()) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Retrieve window or thread messages michael@0: if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { michael@0: // The child UI should have been destroyed before the app is closed, in michael@0: // which case, we should never get this here. michael@0: if (msg.message == WM_QUIT) { michael@0: NS_ERROR("WM_QUIT received in SpinInternalEventLoop!"); michael@0: } else { michael@0: TranslateMessage(&msg); michael@0: ::DispatchMessageW(&msg); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Note, give dispatching windows events priority over checking if michael@0: // mEvent is signaled, otherwise heavy ipc traffic can cause jittery michael@0: // playback of video. We'll exit out on each disaptch above, so ipc michael@0: // won't get starved. michael@0: michael@0: // Wait for UI events or a signal from the io thread. michael@0: DWORD result = MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, michael@0: QS_ALLINPUT); michael@0: if (result == WAIT_OBJECT_0) { michael@0: // Our NotifyWorkerThread event was signaled michael@0: return; michael@0: } michael@0: } while (true); michael@0: } michael@0: michael@0: static inline bool michael@0: IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout) michael@0: { michael@0: return (aTimeout != PR_INTERVAL_NO_TIMEOUT) && michael@0: (aTimeout <= (PR_IntervalNow() - aStart)); michael@0: } michael@0: michael@0: bool michael@0: MessageChannel::WaitForSyncNotify() michael@0: { michael@0: mMonitor->AssertCurrentThreadOwns(); michael@0: michael@0: MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!"); michael@0: michael@0: // Use a blocking wait if this channel does not require michael@0: // Windows message deferral behavior. michael@0: if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { michael@0: PRIntervalTime timeout = (kNoTimeout == mTimeoutMs) ? michael@0: PR_INTERVAL_NO_TIMEOUT : michael@0: PR_MillisecondsToInterval(mTimeoutMs); michael@0: // XXX could optimize away this syscall for "no timeout" case if desired michael@0: PRIntervalTime waitStart = 0; michael@0: michael@0: if (timeout != PR_INTERVAL_NO_TIMEOUT) { michael@0: waitStart = PR_IntervalNow(); michael@0: } michael@0: michael@0: mIsSyncWaitingOnNonMainThread = true; michael@0: michael@0: mMonitor->Wait(timeout); michael@0: michael@0: mIsSyncWaitingOnNonMainThread = false; michael@0: michael@0: // If the timeout didn't expire, we know we received an event. The michael@0: // converse is not true. michael@0: return WaitResponse(timeout == PR_INTERVAL_NO_TIMEOUT ? michael@0: false : IsTimeoutExpired(waitStart, timeout)); michael@0: } michael@0: michael@0: NS_ASSERTION(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION, michael@0: "Shouldn't be here for channels that don't use message deferral!"); michael@0: NS_ASSERTION(mTopFrame && !mTopFrame->mInterrupt, michael@0: "Top frame is not a sync frame!"); michael@0: michael@0: MonitorAutoUnlock unlock(*mMonitor); michael@0: michael@0: bool timedout = false; michael@0: michael@0: UINT_PTR timerId = 0; michael@0: TimeoutData timeoutData = { 0 }; michael@0: michael@0: if (mTimeoutMs != kNoTimeout) { michael@0: InitTimeoutData(&timeoutData, mTimeoutMs); michael@0: michael@0: // We only do this to ensure that we won't get stuck in michael@0: // MsgWaitForMultipleObjects below. michael@0: timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr); michael@0: NS_ASSERTION(timerId, "SetTimer failed!"); michael@0: } michael@0: michael@0: // Setup deferred processing of native events while we wait for a response. michael@0: NS_ASSERTION(!MessageChannel::IsPumpingMessages(), michael@0: "Shouldn't be pumping already!"); michael@0: michael@0: MessageChannel::SetIsPumpingMessages(true); michael@0: HHOOK windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook, michael@0: nullptr, gUIThreadId); michael@0: NS_ASSERTION(windowHook, "Failed to set hook!"); michael@0: michael@0: { michael@0: while (1) { michael@0: MSG msg = { 0 }; michael@0: // Don't get wrapped up in here if the child connection dies. michael@0: { michael@0: MonitorAutoLock lock(*mMonitor); michael@0: if (!Connected()) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Wait until we have a message in the queue. MSDN docs are a bit unclear michael@0: // but it seems that windows from two different threads (and it should be michael@0: // noted that a thread in another process counts as a "different thread") michael@0: // will implicitly have their message queues attached if they are parented michael@0: // to one another. This wait call, then, will return for a message michael@0: // delivered to *either* thread. michael@0: DWORD result = MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, michael@0: QS_ALLINPUT); michael@0: if (result == WAIT_OBJECT_0) { michael@0: // Our NotifyWorkerThread event was signaled michael@0: ResetEvent(mEvent); michael@0: break; michael@0: } else michael@0: if (result != (WAIT_OBJECT_0 + 1)) { michael@0: NS_ERROR("Wait failed!"); michael@0: break; michael@0: } michael@0: michael@0: if (TimeoutHasExpired(timeoutData)) { michael@0: // A timeout was specified and we've passed it. Break out. michael@0: timedout = true; michael@0: break; michael@0: } michael@0: michael@0: // The only way to know on which thread the message was delivered is to michael@0: // use some logic on the return values of GetQueueStatus and PeekMessage. michael@0: // PeekMessage will return false if there are no "queued" messages, but it michael@0: // will run all "nonqueued" messages before returning. So if PeekMessage michael@0: // returns false and there are no "nonqueued" messages that were run then michael@0: // we know that the message we woke for was intended for a window on michael@0: // another thread. michael@0: bool haveSentMessagesPending = michael@0: (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0; michael@0: michael@0: // This PeekMessage call will actually process all "nonqueued" messages michael@0: // that are pending before returning. If we have "nonqueued" messages michael@0: // pending then we should have switched out all the window procedures michael@0: // above. In that case this PeekMessage call won't actually cause any michael@0: // mozilla code (or plugin code) to run. michael@0: michael@0: // If the following PeekMessage call fails to return a message for us (and michael@0: // returns false) and we didn't run any "nonqueued" messages then we must michael@0: // have woken up for a message designated for a window in another thread. michael@0: // If we loop immediately then we could enter a tight loop, so we'll give michael@0: // up our time slice here to let the child process its message. michael@0: if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) && michael@0: !haveSentMessagesPending) { michael@0: // Message was for child, we should wait a bit. michael@0: SwitchToThread(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Unhook the neutered window procedure hook. michael@0: UnhookWindowsHookEx(windowHook); michael@0: michael@0: // Unhook any neutered windows procedures so messages can be delivered michael@0: // normally. michael@0: UnhookNeuteredWindows(); michael@0: michael@0: // Before returning we need to set a hook to run any deferred messages that michael@0: // we received during the IPC call. The hook will unset itself as soon as michael@0: // someone else calls GetMessage, PeekMessage, or runs code that generates michael@0: // a "nonqueued" message. michael@0: ScheduleDeferredMessageRun(); michael@0: michael@0: if (timerId) { michael@0: KillTimer(nullptr, timerId); michael@0: } michael@0: michael@0: MessageChannel::SetIsPumpingMessages(false); michael@0: michael@0: return WaitResponse(timedout); michael@0: } michael@0: michael@0: bool michael@0: MessageChannel::WaitForInterruptNotify() michael@0: { michael@0: mMonitor->AssertCurrentThreadOwns(); michael@0: michael@0: MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!"); michael@0: michael@0: // Re-use sync notification wait code if this channel does not require michael@0: // Windows message deferral behavior. michael@0: if (!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION)) { michael@0: return WaitForSyncNotify(); michael@0: } michael@0: michael@0: if (!InterruptStackDepth()) { michael@0: // There is currently no way to recover from this condition. michael@0: NS_RUNTIMEABORT("StackDepth() is 0 in call to MessageChannel::WaitForNotify!"); michael@0: } michael@0: michael@0: NS_ASSERTION(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION, michael@0: "Shouldn't be here for channels that don't use message deferral!"); michael@0: NS_ASSERTION(mTopFrame && mTopFrame->mInterrupt, michael@0: "Top frame is not a sync frame!"); michael@0: michael@0: MonitorAutoUnlock unlock(*mMonitor); michael@0: michael@0: bool timedout = false; michael@0: michael@0: UINT_PTR timerId = 0; michael@0: TimeoutData timeoutData = { 0 }; michael@0: michael@0: // windowHook is used as a flag variable for the loop below: if it is set michael@0: // and we start to spin a nested event loop, we need to clear the hook and michael@0: // process deferred/pending messages. michael@0: // If windowHook is nullptr, MessageChannel::IsPumpingMessages should be false. michael@0: HHOOK windowHook = nullptr; michael@0: michael@0: while (1) { michael@0: NS_ASSERTION((!!windowHook) == MessageChannel::IsPumpingMessages(), michael@0: "windowHook out of sync with reality"); michael@0: michael@0: if (mTopFrame->mSpinNestedEvents) { michael@0: if (windowHook) { michael@0: UnhookWindowsHookEx(windowHook); michael@0: windowHook = nullptr; michael@0: michael@0: if (timerId) { michael@0: KillTimer(nullptr, timerId); michael@0: timerId = 0; michael@0: } michael@0: michael@0: // Used by widget to assert on incoming native events michael@0: MessageChannel::SetIsPumpingMessages(false); michael@0: michael@0: // Unhook any neutered windows procedures so messages can be delievered michael@0: // normally. michael@0: UnhookNeuteredWindows(); michael@0: michael@0: // Send all deferred "nonqueued" message to the intended receiver. michael@0: // We're dropping into SpinInternalEventLoop so we should be fairly michael@0: // certain these will get delivered soohn. michael@0: ScheduleDeferredMessageRun(); michael@0: } michael@0: SpinInternalEventLoop(); michael@0: ResetEvent(mEvent); michael@0: return true; michael@0: } michael@0: michael@0: if (!windowHook) { michael@0: MessageChannel::SetIsPumpingMessages(true); michael@0: windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook, michael@0: nullptr, gUIThreadId); michael@0: NS_ASSERTION(windowHook, "Failed to set hook!"); michael@0: michael@0: NS_ASSERTION(!timerId, "Timer already initialized?"); michael@0: michael@0: if (mTimeoutMs != kNoTimeout) { michael@0: InitTimeoutData(&timeoutData, mTimeoutMs); michael@0: timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr); michael@0: NS_ASSERTION(timerId, "SetTimer failed!"); michael@0: } michael@0: } michael@0: michael@0: MSG msg = { 0 }; michael@0: michael@0: // Don't get wrapped up in here if the child connection dies. michael@0: { michael@0: MonitorAutoLock lock(*mMonitor); michael@0: if (!Connected()) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: DWORD result = MsgWaitForMultipleObjects(1, &mEvent, FALSE, INFINITE, michael@0: QS_ALLINPUT); michael@0: if (result == WAIT_OBJECT_0) { michael@0: // Our NotifyWorkerThread event was signaled michael@0: ResetEvent(mEvent); michael@0: break; michael@0: } else michael@0: if (result != (WAIT_OBJECT_0 + 1)) { michael@0: NS_ERROR("Wait failed!"); michael@0: break; michael@0: } michael@0: michael@0: if (TimeoutHasExpired(timeoutData)) { michael@0: // A timeout was specified and we've passed it. Break out. michael@0: timedout = true; michael@0: break; michael@0: } michael@0: michael@0: // See MessageChannel's WaitFor*Notify for details. michael@0: bool haveSentMessagesPending = michael@0: (HIWORD(GetQueueStatus(QS_SENDMESSAGE)) & QS_SENDMESSAGE) != 0; michael@0: michael@0: // PeekMessage markes the messages as "old" so that they don't wake up michael@0: // MsgWaitForMultipleObjects every time. michael@0: if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) && michael@0: !haveSentMessagesPending) { michael@0: // Message was for child, we should wait a bit. michael@0: SwitchToThread(); michael@0: } michael@0: } michael@0: michael@0: if (windowHook) { michael@0: // Unhook the neutered window procedure hook. michael@0: UnhookWindowsHookEx(windowHook); michael@0: michael@0: // Unhook any neutered windows procedures so messages can be delivered michael@0: // normally. michael@0: UnhookNeuteredWindows(); michael@0: michael@0: // Before returning we need to set a hook to run any deferred messages that michael@0: // we received during the IPC call. The hook will unset itself as soon as michael@0: // someone else calls GetMessage, PeekMessage, or runs code that generates michael@0: // a "nonqueued" message. michael@0: ScheduleDeferredMessageRun(); michael@0: michael@0: if (timerId) { michael@0: KillTimer(nullptr, timerId); michael@0: } michael@0: } michael@0: michael@0: MessageChannel::SetIsPumpingMessages(false); michael@0: michael@0: return WaitResponse(timedout); michael@0: } michael@0: michael@0: void michael@0: MessageChannel::NotifyWorkerThread() michael@0: { michael@0: mMonitor->AssertCurrentThreadOwns(); michael@0: michael@0: if (mIsSyncWaitingOnNonMainThread) { michael@0: mMonitor->Notify(); michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(mEvent, "No signal event to set, this is really bad!"); michael@0: if (!SetEvent(mEvent)) { michael@0: NS_WARNING("Failed to set NotifyWorkerThread event!"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: DeferredSendMessage::Run() michael@0: { michael@0: AssertWindowIsNotNeutered(hWnd); michael@0: if (!IsWindow(hWnd)) { michael@0: NS_ERROR("Invalid window!"); michael@0: return; michael@0: } michael@0: michael@0: WNDPROC wndproc = michael@0: reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); michael@0: if (!wndproc) { michael@0: NS_ERROR("Invalid window procedure!"); michael@0: return; michael@0: } michael@0: michael@0: CallWindowProc(wndproc, hWnd, message, wParam, lParam); michael@0: } michael@0: michael@0: void michael@0: DeferredRedrawMessage::Run() michael@0: { michael@0: AssertWindowIsNotNeutered(hWnd); michael@0: if (!IsWindow(hWnd)) { michael@0: NS_ERROR("Invalid window!"); michael@0: return; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: BOOL ret = michael@0: #endif michael@0: RedrawWindow(hWnd, nullptr, nullptr, flags); michael@0: NS_ASSERTION(ret, "RedrawWindow failed!"); michael@0: } michael@0: michael@0: DeferredUpdateMessage::DeferredUpdateMessage(HWND aHWnd) michael@0: { michael@0: mWnd = aHWnd; michael@0: if (!GetUpdateRect(mWnd, &mUpdateRect, FALSE)) { michael@0: memset(&mUpdateRect, 0, sizeof(RECT)); michael@0: return; michael@0: } michael@0: ValidateRect(mWnd, &mUpdateRect); michael@0: } michael@0: michael@0: void michael@0: DeferredUpdateMessage::Run() michael@0: { michael@0: AssertWindowIsNotNeutered(mWnd); michael@0: if (!IsWindow(mWnd)) { michael@0: NS_ERROR("Invalid window!"); michael@0: return; michael@0: } michael@0: michael@0: InvalidateRect(mWnd, &mUpdateRect, FALSE); michael@0: #ifdef DEBUG michael@0: BOOL ret = michael@0: #endif michael@0: UpdateWindow(mWnd); michael@0: NS_ASSERTION(ret, "UpdateWindow failed!"); michael@0: } michael@0: michael@0: DeferredSettingChangeMessage::DeferredSettingChangeMessage(HWND aHWnd, michael@0: UINT aMessage, michael@0: WPARAM aWParam, michael@0: LPARAM aLParam) michael@0: : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) michael@0: { michael@0: NS_ASSERTION(aMessage == WM_SETTINGCHANGE, "Wrong message type!"); michael@0: if (aLParam) { michael@0: lParamString = _wcsdup(reinterpret_cast(aLParam)); michael@0: lParam = reinterpret_cast(lParamString); michael@0: } michael@0: else { michael@0: lParamString = nullptr; michael@0: lParam = 0; michael@0: } michael@0: } michael@0: michael@0: DeferredSettingChangeMessage::~DeferredSettingChangeMessage() michael@0: { michael@0: free(lParamString); michael@0: } michael@0: michael@0: DeferredWindowPosMessage::DeferredWindowPosMessage(HWND aHWnd, michael@0: LPARAM aLParam, michael@0: bool aForCalcSize, michael@0: WPARAM aWParam) michael@0: { michael@0: if (aForCalcSize) { michael@0: if (aWParam) { michael@0: NCCALCSIZE_PARAMS* arg = reinterpret_cast(aLParam); michael@0: memcpy(&windowPos, arg->lppos, sizeof(windowPos)); michael@0: michael@0: NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!"); michael@0: } michael@0: else { michael@0: RECT* arg = reinterpret_cast(aLParam); michael@0: windowPos.hwnd = aHWnd; michael@0: windowPos.hwndInsertAfter = nullptr; michael@0: windowPos.x = arg->left; michael@0: windowPos.y = arg->top; michael@0: windowPos.cx = arg->right - arg->left; michael@0: windowPos.cy = arg->bottom - arg->top; michael@0: michael@0: NS_ASSERTION(arg->right >= arg->left && arg->bottom >= arg->top, michael@0: "Negative width or height!"); michael@0: } michael@0: windowPos.flags = SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOOWNERZORDER | michael@0: SWP_NOZORDER | SWP_DEFERERASE | SWP_NOSENDCHANGING; michael@0: } michael@0: else { michael@0: // Not for WM_NCCALCSIZE michael@0: WINDOWPOS* arg = reinterpret_cast(aLParam); michael@0: memcpy(&windowPos, arg, sizeof(windowPos)); michael@0: michael@0: NS_ASSERTION(aHWnd == windowPos.hwnd, "Mismatched hwnds!"); michael@0: michael@0: // Windows sends in some private flags sometimes that we can't simply copy. michael@0: // Filter here. michael@0: UINT mask = SWP_ASYNCWINDOWPOS | SWP_DEFERERASE | SWP_DRAWFRAME | michael@0: SWP_FRAMECHANGED | SWP_HIDEWINDOW | SWP_NOACTIVATE | michael@0: SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREDRAW | michael@0: SWP_NOREPOSITION | SWP_NOSENDCHANGING | SWP_NOSIZE | michael@0: SWP_NOZORDER | SWP_SHOWWINDOW; michael@0: windowPos.flags &= mask; michael@0: } michael@0: } michael@0: michael@0: void michael@0: DeferredWindowPosMessage::Run() michael@0: { michael@0: AssertWindowIsNotNeutered(windowPos.hwnd); michael@0: if (!IsWindow(windowPos.hwnd)) { michael@0: NS_ERROR("Invalid window!"); michael@0: return; michael@0: } michael@0: michael@0: if (!IsWindow(windowPos.hwndInsertAfter)) { michael@0: NS_WARNING("ZOrder change cannot be honored"); michael@0: windowPos.hwndInsertAfter = 0; michael@0: windowPos.flags |= SWP_NOZORDER; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: BOOL ret = michael@0: #endif michael@0: SetWindowPos(windowPos.hwnd, windowPos.hwndInsertAfter, windowPos.x, michael@0: windowPos.y, windowPos.cx, windowPos.cy, windowPos.flags); michael@0: NS_ASSERTION(ret, "SetWindowPos failed!"); michael@0: } michael@0: michael@0: DeferredCopyDataMessage::DeferredCopyDataMessage(HWND aHWnd, michael@0: UINT aMessage, michael@0: WPARAM aWParam, michael@0: LPARAM aLParam) michael@0: : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) michael@0: { michael@0: NS_ASSERTION(IsWindow(reinterpret_cast(aWParam)), "Bad window!"); michael@0: michael@0: COPYDATASTRUCT* source = reinterpret_cast(aLParam); michael@0: NS_ASSERTION(source, "Should never be null!"); michael@0: michael@0: copyData.dwData = source->dwData; michael@0: copyData.cbData = source->cbData; michael@0: michael@0: if (source->cbData) { michael@0: copyData.lpData = malloc(source->cbData); michael@0: if (copyData.lpData) { michael@0: memcpy(copyData.lpData, source->lpData, source->cbData); michael@0: } michael@0: else { michael@0: NS_ERROR("Out of memory?!"); michael@0: copyData.cbData = 0; michael@0: } michael@0: } michael@0: else { michael@0: copyData.lpData = nullptr; michael@0: } michael@0: michael@0: lParam = reinterpret_cast(©Data); michael@0: } michael@0: michael@0: DeferredCopyDataMessage::~DeferredCopyDataMessage() michael@0: { michael@0: free(copyData.lpData); michael@0: } michael@0: michael@0: DeferredStyleChangeMessage::DeferredStyleChangeMessage(HWND aHWnd, michael@0: WPARAM aWParam, michael@0: LPARAM aLParam) michael@0: : hWnd(aHWnd) michael@0: { michael@0: index = static_cast(aWParam); michael@0: style = reinterpret_cast(aLParam)->styleNew; michael@0: } michael@0: michael@0: void michael@0: DeferredStyleChangeMessage::Run() michael@0: { michael@0: SetWindowLongPtr(hWnd, index, style); michael@0: } michael@0: michael@0: DeferredSetIconMessage::DeferredSetIconMessage(HWND aHWnd, michael@0: UINT aMessage, michael@0: WPARAM aWParam, michael@0: LPARAM aLParam) michael@0: : DeferredSendMessage(aHWnd, aMessage, aWParam, aLParam) michael@0: { michael@0: NS_ASSERTION(aMessage == WM_SETICON, "Wrong message type!"); michael@0: } michael@0: michael@0: void michael@0: DeferredSetIconMessage::Run() michael@0: { michael@0: AssertWindowIsNotNeutered(hWnd); michael@0: if (!IsWindow(hWnd)) { michael@0: NS_ERROR("Invalid window!"); michael@0: return; michael@0: } michael@0: michael@0: WNDPROC wndproc = michael@0: reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); michael@0: if (!wndproc) { michael@0: NS_ERROR("Invalid window procedure!"); michael@0: return; michael@0: } michael@0: michael@0: HICON hOld = reinterpret_cast( michael@0: CallWindowProc(wndproc, hWnd, message, wParam, lParam)); michael@0: if (hOld) { michael@0: DestroyIcon(hOld); michael@0: } michael@0: }