michael@0: /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */ 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/ipc/MessageChannel.h" michael@0: #include "mozilla/ipc/WindowsMessageLoop.h" michael@0: #include "nsAppShell.h" michael@0: #include "nsToolkit.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "WinUtils.h" michael@0: #include "WinTaskbar.h" michael@0: #include "WinMouseScrollHandler.h" michael@0: #include "nsWindowDefs.h" michael@0: #include "nsString.h" michael@0: #include "WinIMEHandler.h" michael@0: #include "mozilla/widget/AudioSession.h" michael@0: #include "mozilla/HangMonitor.h" michael@0: #include "nsIDOMWakeLockListener.h" michael@0: #include "nsIPowerManagerService.h" michael@0: #include "mozilla/StaticPtr.h" michael@0: #include "nsTHashtable.h" michael@0: #include "nsHashKeys.h" michael@0: #include "GeckoProfiler.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::widget; michael@0: michael@0: // A wake lock listener that disables screen saver when requested by michael@0: // Gecko. For example when we're playing video in a foreground tab we michael@0: // don't want the screen saver to turn on. michael@0: class WinWakeLockListener : public nsIDOMMozWakeLockListener { michael@0: public: michael@0: NS_DECL_ISUPPORTS; michael@0: michael@0: private: michael@0: NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) { michael@0: bool isLocked = mLockedTopics.Contains(aTopic); michael@0: bool shouldLock = aState.Equals(NS_LITERAL_STRING("locked-foreground")); michael@0: if (isLocked == shouldLock) { michael@0: return NS_OK; michael@0: } michael@0: if (shouldLock) { michael@0: if (!mLockedTopics.Count()) { michael@0: // This is the first topic to request the screen saver be disabled. michael@0: // Prevent screen saver. michael@0: SetThreadExecutionState(ES_DISPLAY_REQUIRED|ES_CONTINUOUS); michael@0: } michael@0: mLockedTopics.PutEntry(aTopic); michael@0: } else { michael@0: mLockedTopics.RemoveEntry(aTopic); michael@0: if (!mLockedTopics.Count()) { michael@0: // No other outstanding topics have requested screen saver be disabled. michael@0: // Re-enable screen saver. michael@0: SetThreadExecutionState(ES_CONTINUOUS); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Keep track of all the topics that have requested a wake lock. When the michael@0: // number of topics in the hashtable reaches zero, we can uninhibit the michael@0: // screensaver again. michael@0: nsTHashtable mLockedTopics; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener) michael@0: StaticRefPtr sWakeLockListener; michael@0: michael@0: static void michael@0: AddScreenWakeLockListener() michael@0: { michael@0: nsCOMPtr sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); michael@0: if (sPowerManagerService) { michael@0: sWakeLockListener = new WinWakeLockListener(); michael@0: sPowerManagerService->AddWakeLockListener(sWakeLockListener); michael@0: } else { michael@0: NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: RemoveScreenWakeLockListener() michael@0: { michael@0: nsCOMPtr sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); michael@0: if (sPowerManagerService) { michael@0: sPowerManagerService->RemoveWakeLockListener(sWakeLockListener); michael@0: sPowerManagerService = nullptr; michael@0: sWakeLockListener = nullptr; michael@0: } michael@0: } michael@0: michael@0: namespace mozilla { michael@0: namespace widget { michael@0: // Native event callback message. michael@0: UINT sAppShellGeckoMsgId = RegisterWindowMessageW(L"nsAppShell:EventID"); michael@0: } } michael@0: michael@0: const wchar_t* kTaskbarButtonEventId = L"TaskbarButtonCreated"; michael@0: UINT sTaskbarButtonCreatedMsg; michael@0: michael@0: /* static */ michael@0: UINT nsAppShell::GetTaskbarButtonCreatedMessage() { michael@0: return sTaskbarButtonCreatedMsg; michael@0: } michael@0: michael@0: namespace mozilla { michael@0: namespace crashreporter { michael@0: void LSPAnnotate(); michael@0: } // namespace crashreporter michael@0: } // namespace mozilla michael@0: michael@0: using mozilla::crashreporter::LSPAnnotate; michael@0: michael@0: //------------------------------------------------------------------------- michael@0: michael@0: /*static*/ LRESULT CALLBACK michael@0: nsAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) michael@0: { michael@0: if (uMsg == sAppShellGeckoMsgId) { michael@0: nsAppShell *as = reinterpret_cast(lParam); michael@0: as->NativeEventCallback(); michael@0: NS_RELEASE(as); michael@0: return TRUE; michael@0: } michael@0: return DefWindowProc(hwnd, uMsg, wParam, lParam); michael@0: } michael@0: michael@0: nsAppShell::~nsAppShell() michael@0: { michael@0: if (mEventWnd) { michael@0: // DestroyWindow doesn't do anything when called from a non UI thread. michael@0: // Since mEventWnd was created on the UI thread, it must be destroyed on michael@0: // the UI thread. michael@0: SendMessage(mEventWnd, WM_CLOSE, 0, 0); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsAppShell::Init() michael@0: { michael@0: #ifdef MOZ_CRASHREPORTER michael@0: LSPAnnotate(); michael@0: #endif michael@0: michael@0: mLastNativeEventScheduled = TimeStamp::NowLoRes(); michael@0: michael@0: mozilla::ipc::windows::InitUIThread(); michael@0: michael@0: sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId); michael@0: NS_ASSERTION(sTaskbarButtonCreatedMsg, "Could not register taskbar button creation message"); michael@0: michael@0: WNDCLASSW wc; michael@0: HINSTANCE module = GetModuleHandle(nullptr); michael@0: michael@0: const wchar_t *const kWindowClass = L"nsAppShell:EventWindowClass"; michael@0: if (!GetClassInfoW(module, kWindowClass, &wc)) { michael@0: wc.style = 0; michael@0: wc.lpfnWndProc = EventWindowProc; michael@0: wc.cbClsExtra = 0; michael@0: wc.cbWndExtra = 0; michael@0: wc.hInstance = module; michael@0: wc.hIcon = nullptr; michael@0: wc.hCursor = nullptr; michael@0: wc.hbrBackground = (HBRUSH) nullptr; michael@0: wc.lpszMenuName = (LPCWSTR) nullptr; michael@0: wc.lpszClassName = kWindowClass; michael@0: RegisterClassW(&wc); michael@0: } michael@0: michael@0: mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow", michael@0: 0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr); michael@0: NS_ENSURE_STATE(mEventWnd); michael@0: michael@0: return nsBaseAppShell::Init(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppShell::Run(void) michael@0: { michael@0: // Ignore failure; failing to start the application is not exactly an michael@0: // appropriate response to failing to start an audio session. michael@0: mozilla::widget::StartAudioSession(); michael@0: michael@0: // Add an observer that disables the screen saver when requested by Gecko. michael@0: // For example when we're playing video in the foreground tab. michael@0: AddScreenWakeLockListener(); michael@0: michael@0: nsresult rv = nsBaseAppShell::Run(); michael@0: michael@0: RemoveScreenWakeLockListener(); michael@0: michael@0: mozilla::widget::StopAudioSession(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsAppShell::Exit(void) michael@0: { michael@0: return nsBaseAppShell::Exit(); michael@0: } michael@0: michael@0: void michael@0: nsAppShell::DoProcessMoreGeckoEvents() michael@0: { michael@0: // Called by nsBaseAppShell's NativeEventCallback() after it has finished michael@0: // processing pending gecko events and there are still gecko events pending michael@0: // for the thread. (This can happen if NS_ProcessPendingEvents reached it's michael@0: // starvation timeout limit.) The default behavior in nsBaseAppShell is to michael@0: // call ScheduleNativeEventCallback to post a follow up native event callback michael@0: // message. This triggers an additional call to NativeEventCallback for more michael@0: // gecko event processing. michael@0: michael@0: // There's a deadlock risk here with certain internal Windows modal loops. In michael@0: // our dispatch code, we prioritize messages so that input is handled first. michael@0: // However Windows modal dispatch loops often prioritize posted messages. If michael@0: // we find ourselves in a tight gecko timer loop where NS_ProcessPendingEvents michael@0: // takes longer than the timer duration, NS_HasPendingEvents(thread) will michael@0: // always be true. ScheduleNativeEventCallback will be called on every michael@0: // NativeEventCallback callback, and in a Windows modal dispatch loop, the michael@0: // callback message will be processed first -> input gets starved, dead lock. michael@0: michael@0: // To avoid, don't post native callback messages from NativeEventCallback michael@0: // when we're in a modal loop. This gets us back into the Windows modal michael@0: // dispatch loop dispatching input messages. Once we drop out of the modal michael@0: // loop, we use mNativeCallbackPending to fire off a final NativeEventCallback michael@0: // if we need it, which insures NS_ProcessPendingEvents gets called and all michael@0: // gecko events get processed. michael@0: if (mEventloopNestingLevel < 2) { michael@0: OnDispatchedEvent(nullptr); michael@0: mNativeCallbackPending = false; michael@0: } else { michael@0: mNativeCallbackPending = true; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsAppShell::ScheduleNativeEventCallback() michael@0: { michael@0: // Post a message to the hidden message window michael@0: NS_ADDREF_THIS(); // will be released when the event is processed michael@0: { michael@0: MutexAutoLock lock(mLastNativeEventScheduledMutex); michael@0: // Time stamp this event so we can detect cases where the event gets michael@0: // dropping in sub classes / modal loops we do not control. michael@0: mLastNativeEventScheduled = TimeStamp::NowLoRes(); michael@0: } michael@0: ::PostMessage(mEventWnd, sAppShellGeckoMsgId, 0, reinterpret_cast(this)); michael@0: } michael@0: michael@0: bool michael@0: nsAppShell::ProcessNextNativeEvent(bool mayWait) michael@0: { michael@0: // Notify ipc we are spinning a (possibly nested) gecko event loop. michael@0: mozilla::ipc::MessageChannel::NotifyGeckoEventDispatch(); michael@0: michael@0: bool gotMessage = false; michael@0: michael@0: do { michael@0: MSG msg; michael@0: bool uiMessage = false; michael@0: michael@0: // For avoiding deadlock between our process and plugin process by michael@0: // mouse wheel messages, we're handling actually when we receive one of michael@0: // following internal messages which is posted by native mouse wheel michael@0: // message handler. Any other events, especially native modifier key michael@0: // events, should not be handled between native message and posted michael@0: // internal message because it may make different modifier key state or michael@0: // mouse cursor position between them. michael@0: if (mozilla::widget::MouseScrollHandler::IsWaitingInternalMessage()) { michael@0: gotMessage = WinUtils::PeekMessage(&msg, nullptr, MOZ_WM_MOUSEWHEEL_FIRST, michael@0: MOZ_WM_MOUSEWHEEL_LAST, PM_REMOVE); michael@0: NS_ASSERTION(gotMessage, michael@0: "waiting internal wheel message, but it has not come"); michael@0: uiMessage = gotMessage; michael@0: } michael@0: michael@0: if (!gotMessage) { michael@0: gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); michael@0: uiMessage = michael@0: (msg.message >= WM_KEYFIRST && msg.message <= WM_IME_KEYLAST) || michael@0: (msg.message >= NS_WM_IMEFIRST && msg.message <= NS_WM_IMELAST) || michael@0: (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST); michael@0: } michael@0: michael@0: if (gotMessage) { michael@0: if (msg.message == WM_QUIT) { michael@0: ::PostQuitMessage(msg.wParam); michael@0: Exit(); michael@0: } else { michael@0: // If we had UI activity we would be processing it now so we know we michael@0: // have either kUIActivity or kActivityNoUIAVail. michael@0: mozilla::HangMonitor::NotifyActivity( michael@0: uiMessage ? mozilla::HangMonitor::kUIActivity : michael@0: mozilla::HangMonitor::kActivityNoUIAVail); michael@0: michael@0: if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST && michael@0: IMEHandler::ProcessRawKeyMessage(msg)) { michael@0: continue; // the message is consumed. michael@0: } michael@0: michael@0: ::TranslateMessage(&msg); michael@0: ::DispatchMessageW(&msg); michael@0: } michael@0: } else if (mayWait) { michael@0: // Block and wait for any posted application message michael@0: mozilla::HangMonitor::Suspend(); michael@0: { michael@0: GeckoProfilerSleepRAII profiler_sleep; michael@0: ::WaitMessage(); michael@0: } michael@0: } michael@0: } while (!gotMessage && mayWait); michael@0: michael@0: // See DoProcessNextNativeEvent, mEventloopNestingLevel will be michael@0: // one when a modal loop unwinds. michael@0: if (mNativeCallbackPending && mEventloopNestingLevel == 1) michael@0: DoProcessMoreGeckoEvents(); michael@0: michael@0: // Check for starved native callbacks. If we haven't processed one michael@0: // of these events in NATIVE_EVENT_STARVATION_LIMIT, fire one off. michael@0: static const mozilla::TimeDuration nativeEventStarvationLimit = michael@0: mozilla::TimeDuration::FromSeconds(NATIVE_EVENT_STARVATION_LIMIT); michael@0: michael@0: TimeDuration timeSinceLastNativeEventScheduled; michael@0: { michael@0: MutexAutoLock lock(mLastNativeEventScheduledMutex); michael@0: timeSinceLastNativeEventScheduled = michael@0: TimeStamp::NowLoRes() - mLastNativeEventScheduled; michael@0: } michael@0: if (timeSinceLastNativeEventScheduled > nativeEventStarvationLimit) { michael@0: ScheduleNativeEventCallback(); michael@0: } michael@0: michael@0: return gotMessage; michael@0: }