|
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/. */ |
|
5 |
|
6 #include "mozilla/ipc/MessageChannel.h" |
|
7 #include "mozilla/ipc/WindowsMessageLoop.h" |
|
8 #include "nsAppShell.h" |
|
9 #include "nsToolkit.h" |
|
10 #include "nsThreadUtils.h" |
|
11 #include "WinUtils.h" |
|
12 #include "WinTaskbar.h" |
|
13 #include "WinMouseScrollHandler.h" |
|
14 #include "nsWindowDefs.h" |
|
15 #include "nsString.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" |
|
25 |
|
26 using namespace mozilla; |
|
27 using namespace mozilla::widget; |
|
28 |
|
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 { |
|
33 public: |
|
34 NS_DECL_ISUPPORTS; |
|
35 |
|
36 private: |
|
37 NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) { |
|
38 bool isLocked = mLockedTopics.Contains(aTopic); |
|
39 bool shouldLock = aState.Equals(NS_LITERAL_STRING("locked-foreground")); |
|
40 if (isLocked == shouldLock) { |
|
41 return NS_OK; |
|
42 } |
|
43 if (shouldLock) { |
|
44 if (!mLockedTopics.Count()) { |
|
45 // This is the first topic to request the screen saver be disabled. |
|
46 // Prevent screen saver. |
|
47 SetThreadExecutionState(ES_DISPLAY_REQUIRED|ES_CONTINUOUS); |
|
48 } |
|
49 mLockedTopics.PutEntry(aTopic); |
|
50 } else { |
|
51 mLockedTopics.RemoveEntry(aTopic); |
|
52 if (!mLockedTopics.Count()) { |
|
53 // No other outstanding topics have requested screen saver be disabled. |
|
54 // Re-enable screen saver. |
|
55 SetThreadExecutionState(ES_CONTINUOUS); |
|
56 } |
|
57 } |
|
58 return NS_OK; |
|
59 } |
|
60 |
|
61 // Keep track of all the topics that have requested a wake lock. When the |
|
62 // number of topics in the hashtable reaches zero, we can uninhibit the |
|
63 // screensaver again. |
|
64 nsTHashtable<nsStringHashKey> mLockedTopics; |
|
65 }; |
|
66 |
|
67 NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener) |
|
68 StaticRefPtr<WinWakeLockListener> sWakeLockListener; |
|
69 |
|
70 static void |
|
71 AddScreenWakeLockListener() |
|
72 { |
|
73 nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); |
|
74 if (sPowerManagerService) { |
|
75 sWakeLockListener = new WinWakeLockListener(); |
|
76 sPowerManagerService->AddWakeLockListener(sWakeLockListener); |
|
77 } else { |
|
78 NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); |
|
79 } |
|
80 } |
|
81 |
|
82 static void |
|
83 RemoveScreenWakeLockListener() |
|
84 { |
|
85 nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); |
|
86 if (sPowerManagerService) { |
|
87 sPowerManagerService->RemoveWakeLockListener(sWakeLockListener); |
|
88 sPowerManagerService = nullptr; |
|
89 sWakeLockListener = nullptr; |
|
90 } |
|
91 } |
|
92 |
|
93 namespace mozilla { |
|
94 namespace widget { |
|
95 // Native event callback message. |
|
96 UINT sAppShellGeckoMsgId = RegisterWindowMessageW(L"nsAppShell:EventID"); |
|
97 } } |
|
98 |
|
99 const wchar_t* kTaskbarButtonEventId = L"TaskbarButtonCreated"; |
|
100 UINT sTaskbarButtonCreatedMsg; |
|
101 |
|
102 /* static */ |
|
103 UINT nsAppShell::GetTaskbarButtonCreatedMessage() { |
|
104 return sTaskbarButtonCreatedMsg; |
|
105 } |
|
106 |
|
107 namespace mozilla { |
|
108 namespace crashreporter { |
|
109 void LSPAnnotate(); |
|
110 } // namespace crashreporter |
|
111 } // namespace mozilla |
|
112 |
|
113 using mozilla::crashreporter::LSPAnnotate; |
|
114 |
|
115 //------------------------------------------------------------------------- |
|
116 |
|
117 /*static*/ LRESULT CALLBACK |
|
118 nsAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) |
|
119 { |
|
120 if (uMsg == sAppShellGeckoMsgId) { |
|
121 nsAppShell *as = reinterpret_cast<nsAppShell *>(lParam); |
|
122 as->NativeEventCallback(); |
|
123 NS_RELEASE(as); |
|
124 return TRUE; |
|
125 } |
|
126 return DefWindowProc(hwnd, uMsg, wParam, lParam); |
|
127 } |
|
128 |
|
129 nsAppShell::~nsAppShell() |
|
130 { |
|
131 if (mEventWnd) { |
|
132 // DestroyWindow doesn't do anything when called from a non UI thread. |
|
133 // Since mEventWnd was created on the UI thread, it must be destroyed on |
|
134 // the UI thread. |
|
135 SendMessage(mEventWnd, WM_CLOSE, 0, 0); |
|
136 } |
|
137 } |
|
138 |
|
139 nsresult |
|
140 nsAppShell::Init() |
|
141 { |
|
142 #ifdef MOZ_CRASHREPORTER |
|
143 LSPAnnotate(); |
|
144 #endif |
|
145 |
|
146 mLastNativeEventScheduled = TimeStamp::NowLoRes(); |
|
147 |
|
148 mozilla::ipc::windows::InitUIThread(); |
|
149 |
|
150 sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId); |
|
151 NS_ASSERTION(sTaskbarButtonCreatedMsg, "Could not register taskbar button creation message"); |
|
152 |
|
153 WNDCLASSW wc; |
|
154 HINSTANCE module = GetModuleHandle(nullptr); |
|
155 |
|
156 const wchar_t *const kWindowClass = L"nsAppShell:EventWindowClass"; |
|
157 if (!GetClassInfoW(module, kWindowClass, &wc)) { |
|
158 wc.style = 0; |
|
159 wc.lpfnWndProc = EventWindowProc; |
|
160 wc.cbClsExtra = 0; |
|
161 wc.cbWndExtra = 0; |
|
162 wc.hInstance = module; |
|
163 wc.hIcon = nullptr; |
|
164 wc.hCursor = nullptr; |
|
165 wc.hbrBackground = (HBRUSH) nullptr; |
|
166 wc.lpszMenuName = (LPCWSTR) nullptr; |
|
167 wc.lpszClassName = kWindowClass; |
|
168 RegisterClassW(&wc); |
|
169 } |
|
170 |
|
171 mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow", |
|
172 0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr); |
|
173 NS_ENSURE_STATE(mEventWnd); |
|
174 |
|
175 return nsBaseAppShell::Init(); |
|
176 } |
|
177 |
|
178 NS_IMETHODIMP |
|
179 nsAppShell::Run(void) |
|
180 { |
|
181 // Ignore failure; failing to start the application is not exactly an |
|
182 // appropriate response to failing to start an audio session. |
|
183 mozilla::widget::StartAudioSession(); |
|
184 |
|
185 // Add an observer that disables the screen saver when requested by Gecko. |
|
186 // For example when we're playing video in the foreground tab. |
|
187 AddScreenWakeLockListener(); |
|
188 |
|
189 nsresult rv = nsBaseAppShell::Run(); |
|
190 |
|
191 RemoveScreenWakeLockListener(); |
|
192 |
|
193 mozilla::widget::StopAudioSession(); |
|
194 |
|
195 return rv; |
|
196 } |
|
197 |
|
198 NS_IMETHODIMP |
|
199 nsAppShell::Exit(void) |
|
200 { |
|
201 return nsBaseAppShell::Exit(); |
|
202 } |
|
203 |
|
204 void |
|
205 nsAppShell::DoProcessMoreGeckoEvents() |
|
206 { |
|
207 // Called by nsBaseAppShell's NativeEventCallback() after it has finished |
|
208 // processing pending gecko events and there are still gecko events pending |
|
209 // for the thread. (This can happen if NS_ProcessPendingEvents reached it's |
|
210 // starvation timeout limit.) The default behavior in nsBaseAppShell is to |
|
211 // call ScheduleNativeEventCallback to post a follow up native event callback |
|
212 // message. This triggers an additional call to NativeEventCallback for more |
|
213 // gecko event processing. |
|
214 |
|
215 // There's a deadlock risk here with certain internal Windows modal loops. In |
|
216 // our dispatch code, we prioritize messages so that input is handled first. |
|
217 // However Windows modal dispatch loops often prioritize posted messages. If |
|
218 // we find ourselves in a tight gecko timer loop where NS_ProcessPendingEvents |
|
219 // takes longer than the timer duration, NS_HasPendingEvents(thread) will |
|
220 // always be true. ScheduleNativeEventCallback will be called on every |
|
221 // NativeEventCallback callback, and in a Windows modal dispatch loop, the |
|
222 // callback message will be processed first -> input gets starved, dead lock. |
|
223 |
|
224 // To avoid, don't post native callback messages from NativeEventCallback |
|
225 // when we're in a modal loop. This gets us back into the Windows modal |
|
226 // dispatch loop dispatching input messages. Once we drop out of the modal |
|
227 // loop, we use mNativeCallbackPending to fire off a final NativeEventCallback |
|
228 // if we need it, which insures NS_ProcessPendingEvents gets called and all |
|
229 // gecko events get processed. |
|
230 if (mEventloopNestingLevel < 2) { |
|
231 OnDispatchedEvent(nullptr); |
|
232 mNativeCallbackPending = false; |
|
233 } else { |
|
234 mNativeCallbackPending = true; |
|
235 } |
|
236 } |
|
237 |
|
238 void |
|
239 nsAppShell::ScheduleNativeEventCallback() |
|
240 { |
|
241 // Post a message to the hidden message window |
|
242 NS_ADDREF_THIS(); // will be released when the event is processed |
|
243 { |
|
244 MutexAutoLock lock(mLastNativeEventScheduledMutex); |
|
245 // Time stamp this event so we can detect cases where the event gets |
|
246 // dropping in sub classes / modal loops we do not control. |
|
247 mLastNativeEventScheduled = TimeStamp::NowLoRes(); |
|
248 } |
|
249 ::PostMessage(mEventWnd, sAppShellGeckoMsgId, 0, reinterpret_cast<LPARAM>(this)); |
|
250 } |
|
251 |
|
252 bool |
|
253 nsAppShell::ProcessNextNativeEvent(bool mayWait) |
|
254 { |
|
255 // Notify ipc we are spinning a (possibly nested) gecko event loop. |
|
256 mozilla::ipc::MessageChannel::NotifyGeckoEventDispatch(); |
|
257 |
|
258 bool gotMessage = false; |
|
259 |
|
260 do { |
|
261 MSG msg; |
|
262 bool uiMessage = false; |
|
263 |
|
264 // For avoiding deadlock between our process and plugin process by |
|
265 // mouse wheel messages, we're handling actually when we receive one of |
|
266 // following internal messages which is posted by native mouse wheel |
|
267 // message handler. Any other events, especially native modifier key |
|
268 // events, should not be handled between native message and posted |
|
269 // internal message because it may make different modifier key state or |
|
270 // mouse cursor position between them. |
|
271 if (mozilla::widget::MouseScrollHandler::IsWaitingInternalMessage()) { |
|
272 gotMessage = WinUtils::PeekMessage(&msg, nullptr, MOZ_WM_MOUSEWHEEL_FIRST, |
|
273 MOZ_WM_MOUSEWHEEL_LAST, PM_REMOVE); |
|
274 NS_ASSERTION(gotMessage, |
|
275 "waiting internal wheel message, but it has not come"); |
|
276 uiMessage = gotMessage; |
|
277 } |
|
278 |
|
279 if (!gotMessage) { |
|
280 gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE); |
|
281 uiMessage = |
|
282 (msg.message >= WM_KEYFIRST && msg.message <= WM_IME_KEYLAST) || |
|
283 (msg.message >= NS_WM_IMEFIRST && msg.message <= NS_WM_IMELAST) || |
|
284 (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST); |
|
285 } |
|
286 |
|
287 if (gotMessage) { |
|
288 if (msg.message == WM_QUIT) { |
|
289 ::PostQuitMessage(msg.wParam); |
|
290 Exit(); |
|
291 } else { |
|
292 // If we had UI activity we would be processing it now so we know we |
|
293 // have either kUIActivity or kActivityNoUIAVail. |
|
294 mozilla::HangMonitor::NotifyActivity( |
|
295 uiMessage ? mozilla::HangMonitor::kUIActivity : |
|
296 mozilla::HangMonitor::kActivityNoUIAVail); |
|
297 |
|
298 if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST && |
|
299 IMEHandler::ProcessRawKeyMessage(msg)) { |
|
300 continue; // the message is consumed. |
|
301 } |
|
302 |
|
303 ::TranslateMessage(&msg); |
|
304 ::DispatchMessageW(&msg); |
|
305 } |
|
306 } else if (mayWait) { |
|
307 // Block and wait for any posted application message |
|
308 mozilla::HangMonitor::Suspend(); |
|
309 { |
|
310 GeckoProfilerSleepRAII profiler_sleep; |
|
311 ::WaitMessage(); |
|
312 } |
|
313 } |
|
314 } while (!gotMessage && mayWait); |
|
315 |
|
316 // See DoProcessNextNativeEvent, mEventloopNestingLevel will be |
|
317 // one when a modal loop unwinds. |
|
318 if (mNativeCallbackPending && mEventloopNestingLevel == 1) |
|
319 DoProcessMoreGeckoEvents(); |
|
320 |
|
321 // Check for starved native callbacks. If we haven't processed one |
|
322 // of these events in NATIVE_EVENT_STARVATION_LIMIT, fire one off. |
|
323 static const mozilla::TimeDuration nativeEventStarvationLimit = |
|
324 mozilla::TimeDuration::FromSeconds(NATIVE_EVENT_STARVATION_LIMIT); |
|
325 |
|
326 TimeDuration timeSinceLastNativeEventScheduled; |
|
327 { |
|
328 MutexAutoLock lock(mLastNativeEventScheduledMutex); |
|
329 timeSinceLastNativeEventScheduled = |
|
330 TimeStamp::NowLoRes() - mLastNativeEventScheduled; |
|
331 } |
|
332 if (timeSinceLastNativeEventScheduled > nativeEventStarvationLimit) { |
|
333 ScheduleNativeEventCallback(); |
|
334 } |
|
335 |
|
336 return gotMessage; |
|
337 } |