1 /* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/ipc/MessageChannel.h"
7 #include "mozilla/ipc/WindowsMessageLoop.h"
8 #include "nsAppShell.h"
10 #include "nsThreadUtils.h"
12 #include "WinTaskbar.h"
13 #include "WinMouseScrollHandler.h"
14 #include "nsWindowDefs.h"
16 #include "WinIMEHandler.h"
17 #include "mozilla/widget/AudioSession.h"
18 #include "mozilla/HangMonitor.h"
19 #include "nsIDOMWakeLockListener.h"
20 #include "nsIPowerManagerService.h"
21 #include "mozilla/StaticPtr.h"
22 #include "nsTHashtable.h"
23 #include "nsHashKeys.h"
24 #include "GeckoProfiler.h"
26 using namespace mozilla
;
27 using namespace mozilla::widget
;
29 // A wake lock listener that disables screen saver when requested by
30 // Gecko. For example when we're playing video in a foreground tab we
31 // don't want the screen saver to turn on.
32 class WinWakeLockListener
: public nsIDOMMozWakeLockListener
{
37 ~WinWakeLockListener() {}
39 NS_IMETHOD
Callback(const nsAString
& aTopic
, const nsAString
& aState
) {
40 bool isLocked
= mLockedTopics
.Contains(aTopic
);
41 bool shouldLock
= aState
.EqualsLiteral("locked-foreground");
42 if (isLocked
== shouldLock
) {
46 if (!mLockedTopics
.Count()) {
47 // This is the first topic to request the screen saver be disabled.
48 // Prevent screen saver.
49 SetThreadExecutionState(ES_DISPLAY_REQUIRED
|ES_CONTINUOUS
);
51 mLockedTopics
.PutEntry(aTopic
);
53 mLockedTopics
.RemoveEntry(aTopic
);
54 if (!mLockedTopics
.Count()) {
55 // No other outstanding topics have requested screen saver be disabled.
56 // Re-enable screen saver.
57 SetThreadExecutionState(ES_CONTINUOUS
);
63 // Keep track of all the topics that have requested a wake lock. When the
64 // number of topics in the hashtable reaches zero, we can uninhibit the
66 nsTHashtable
<nsStringHashKey
> mLockedTopics
;
69 NS_IMPL_ISUPPORTS(WinWakeLockListener
, nsIDOMMozWakeLockListener
)
70 StaticRefPtr
<WinWakeLockListener
> sWakeLockListener
;
73 AddScreenWakeLockListener()
75 nsCOMPtr
<nsIPowerManagerService
> sPowerManagerService
= do_GetService(POWERMANAGERSERVICE_CONTRACTID
);
76 if (sPowerManagerService
) {
77 sWakeLockListener
= new WinWakeLockListener();
78 sPowerManagerService
->AddWakeLockListener(sWakeLockListener
);
80 NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
85 RemoveScreenWakeLockListener()
87 nsCOMPtr
<nsIPowerManagerService
> sPowerManagerService
= do_GetService(POWERMANAGERSERVICE_CONTRACTID
);
88 if (sPowerManagerService
) {
89 sPowerManagerService
->RemoveWakeLockListener(sWakeLockListener
);
90 sPowerManagerService
= nullptr;
91 sWakeLockListener
= nullptr;
97 // Native event callback message.
98 UINT sAppShellGeckoMsgId
= RegisterWindowMessageW(L
"nsAppShell:EventID");
101 const wchar_t* kTaskbarButtonEventId
= L
"TaskbarButtonCreated";
102 UINT sTaskbarButtonCreatedMsg
;
105 UINT
nsAppShell::GetTaskbarButtonCreatedMessage() {
106 return sTaskbarButtonCreatedMsg
;
110 namespace crashreporter
{
112 } // namespace crashreporter
113 } // namespace mozilla
115 using mozilla::crashreporter::LSPAnnotate
;
117 //-------------------------------------------------------------------------
119 /*static*/ LRESULT CALLBACK
120 nsAppShell::EventWindowProc(HWND hwnd
, UINT uMsg
, WPARAM wParam
, LPARAM lParam
)
122 if (uMsg
== sAppShellGeckoMsgId
) {
123 nsAppShell
*as
= reinterpret_cast<nsAppShell
*>(lParam
);
124 as
->NativeEventCallback();
128 return DefWindowProc(hwnd
, uMsg
, wParam
, lParam
);
131 nsAppShell::~nsAppShell()
134 // DestroyWindow doesn't do anything when called from a non UI thread.
135 // Since mEventWnd was created on the UI thread, it must be destroyed on
137 SendMessage(mEventWnd
, WM_CLOSE
, 0, 0);
144 #ifdef MOZ_CRASHREPORTER
148 mLastNativeEventScheduled
= TimeStamp::NowLoRes();
150 mozilla::ipc::windows::InitUIThread();
152 sTaskbarButtonCreatedMsg
= ::RegisterWindowMessageW(kTaskbarButtonEventId
);
153 NS_ASSERTION(sTaskbarButtonCreatedMsg
, "Could not register taskbar button creation message");
156 HINSTANCE module
= GetModuleHandle(nullptr);
158 const wchar_t *const kWindowClass
= L
"nsAppShell:EventWindowClass";
159 if (!GetClassInfoW(module
, kWindowClass
, &wc
)) {
161 wc
.lpfnWndProc
= EventWindowProc
;
164 wc
.hInstance
= module
;
166 wc
.hCursor
= nullptr;
167 wc
.hbrBackground
= (HBRUSH
) nullptr;
168 wc
.lpszMenuName
= (LPCWSTR
) nullptr;
169 wc
.lpszClassName
= kWindowClass
;
173 mEventWnd
= CreateWindowW(kWindowClass
, L
"nsAppShell:EventWindow",
174 0, 0, 0, 10, 10, nullptr, nullptr, module
, nullptr);
175 NS_ENSURE_STATE(mEventWnd
);
177 return nsBaseAppShell::Init();
181 nsAppShell::Run(void)
183 // Ignore failure; failing to start the application is not exactly an
184 // appropriate response to failing to start an audio session.
185 mozilla::widget::StartAudioSession();
187 // Add an observer that disables the screen saver when requested by Gecko.
188 // For example when we're playing video in the foreground tab.
189 AddScreenWakeLockListener();
191 nsresult rv
= nsBaseAppShell::Run();
193 RemoveScreenWakeLockListener();
195 mozilla::widget::StopAudioSession();
201 nsAppShell::Exit(void)
203 return nsBaseAppShell::Exit();
207 nsAppShell::DoProcessMoreGeckoEvents()
209 // Called by nsBaseAppShell's NativeEventCallback() after it has finished
210 // processing pending gecko events and there are still gecko events pending
211 // for the thread. (This can happen if NS_ProcessPendingEvents reached it's
212 // starvation timeout limit.) The default behavior in nsBaseAppShell is to
213 // call ScheduleNativeEventCallback to post a follow up native event callback
214 // message. This triggers an additional call to NativeEventCallback for more
215 // gecko event processing.
217 // There's a deadlock risk here with certain internal Windows modal loops. In
218 // our dispatch code, we prioritize messages so that input is handled first.
219 // However Windows modal dispatch loops often prioritize posted messages. If
220 // we find ourselves in a tight gecko timer loop where NS_ProcessPendingEvents
221 // takes longer than the timer duration, NS_HasPendingEvents(thread) will
222 // always be true. ScheduleNativeEventCallback will be called on every
223 // NativeEventCallback callback, and in a Windows modal dispatch loop, the
224 // callback message will be processed first -> input gets starved, dead lock.
226 // To avoid, don't post native callback messages from NativeEventCallback
227 // when we're in a modal loop. This gets us back into the Windows modal
228 // dispatch loop dispatching input messages. Once we drop out of the modal
229 // loop, we use mNativeCallbackPending to fire off a final NativeEventCallback
230 // if we need it, which insures NS_ProcessPendingEvents gets called and all
231 // gecko events get processed.
232 if (mEventloopNestingLevel
< 2) {
233 OnDispatchedEvent(nullptr);
234 mNativeCallbackPending
= false;
236 mNativeCallbackPending
= true;
241 nsAppShell::ScheduleNativeEventCallback()
243 // Post a message to the hidden message window
244 NS_ADDREF_THIS(); // will be released when the event is processed
246 MutexAutoLock
lock(mLastNativeEventScheduledMutex
);
247 // Time stamp this event so we can detect cases where the event gets
248 // dropping in sub classes / modal loops we do not control.
249 mLastNativeEventScheduled
= TimeStamp::NowLoRes();
251 ::PostMessage(mEventWnd
, sAppShellGeckoMsgId
, 0, reinterpret_cast<LPARAM
>(this));
255 nsAppShell::ProcessNextNativeEvent(bool mayWait
)
257 // Notify ipc we are spinning a (possibly nested) gecko event loop.
258 mozilla::ipc::MessageChannel::NotifyGeckoEventDispatch();
260 bool gotMessage
= false;
264 bool uiMessage
= false;
266 // For avoiding deadlock between our process and plugin process by
267 // mouse wheel messages, we're handling actually when we receive one of
268 // following internal messages which is posted by native mouse wheel
269 // message handler. Any other events, especially native modifier key
270 // events, should not be handled between native message and posted
271 // internal message because it may make different modifier key state or
272 // mouse cursor position between them.
273 if (mozilla::widget::MouseScrollHandler::IsWaitingInternalMessage()) {
274 gotMessage
= WinUtils::PeekMessage(&msg
, nullptr, MOZ_WM_MOUSEWHEEL_FIRST
,
275 MOZ_WM_MOUSEWHEEL_LAST
, PM_REMOVE
);
276 NS_ASSERTION(gotMessage
,
277 "waiting internal wheel message, but it has not come");
278 uiMessage
= gotMessage
;
282 gotMessage
= WinUtils::PeekMessage(&msg
, nullptr, 0, 0, PM_REMOVE
);
284 (msg
.message
>= WM_KEYFIRST
&& msg
.message
<= WM_IME_KEYLAST
) ||
285 (msg
.message
>= NS_WM_IMEFIRST
&& msg
.message
<= NS_WM_IMELAST
) ||
286 (msg
.message
>= WM_MOUSEFIRST
&& msg
.message
<= WM_MOUSELAST
);
290 if (msg
.message
== WM_QUIT
) {
291 ::PostQuitMessage(msg
.wParam
);
294 // If we had UI activity we would be processing it now so we know we
295 // have either kUIActivity or kActivityNoUIAVail.
296 mozilla::HangMonitor::NotifyActivity(
297 uiMessage
? mozilla::HangMonitor::kUIActivity
:
298 mozilla::HangMonitor::kActivityNoUIAVail
);
300 if (msg
.message
>= WM_KEYFIRST
&& msg
.message
<= WM_KEYLAST
&&
301 IMEHandler::ProcessRawKeyMessage(msg
)) {
302 continue; // the message is consumed.
305 ::TranslateMessage(&msg
);
306 ::DispatchMessageW(&msg
);
308 } else if (mayWait
) {
309 // Block and wait for any posted application message
310 mozilla::HangMonitor::Suspend();
312 GeckoProfilerSleepRAII profiler_sleep
;
313 WinUtils::WaitForMessage();
316 } while (!gotMessage
&& mayWait
);
318 // See DoProcessNextNativeEvent, mEventloopNestingLevel will be
319 // one when a modal loop unwinds.
320 if (mNativeCallbackPending
&& mEventloopNestingLevel
== 1)
321 DoProcessMoreGeckoEvents();
323 // Check for starved native callbacks. If we haven't processed one
324 // of these events in NATIVE_EVENT_STARVATION_LIMIT, fire one off.
325 static const mozilla::TimeDuration nativeEventStarvationLimit
=
326 mozilla::TimeDuration::FromSeconds(NATIVE_EVENT_STARVATION_LIMIT
);
328 TimeDuration timeSinceLastNativeEventScheduled
;
330 MutexAutoLock
lock(mLastNativeEventScheduledMutex
);
331 timeSinceLastNativeEventScheduled
=
332 TimeStamp::NowLoRes() - mLastNativeEventScheduled
;
334 if (timeSinceLastNativeEventScheduled
> nativeEventStarvationLimit
) {
335 ScheduleNativeEventCallback();