Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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"
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"
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 {
33 public:
34 NS_DECL_ISUPPORTS;
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 }
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 };
67 NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener)
68 StaticRefPtr<WinWakeLockListener> sWakeLockListener;
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 }
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 }
93 namespace mozilla {
94 namespace widget {
95 // Native event callback message.
96 UINT sAppShellGeckoMsgId = RegisterWindowMessageW(L"nsAppShell:EventID");
97 } }
99 const wchar_t* kTaskbarButtonEventId = L"TaskbarButtonCreated";
100 UINT sTaskbarButtonCreatedMsg;
102 /* static */
103 UINT nsAppShell::GetTaskbarButtonCreatedMessage() {
104 return sTaskbarButtonCreatedMsg;
105 }
107 namespace mozilla {
108 namespace crashreporter {
109 void LSPAnnotate();
110 } // namespace crashreporter
111 } // namespace mozilla
113 using mozilla::crashreporter::LSPAnnotate;
115 //-------------------------------------------------------------------------
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 }
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 }
139 nsresult
140 nsAppShell::Init()
141 {
142 #ifdef MOZ_CRASHREPORTER
143 LSPAnnotate();
144 #endif
146 mLastNativeEventScheduled = TimeStamp::NowLoRes();
148 mozilla::ipc::windows::InitUIThread();
150 sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId);
151 NS_ASSERTION(sTaskbarButtonCreatedMsg, "Could not register taskbar button creation message");
153 WNDCLASSW wc;
154 HINSTANCE module = GetModuleHandle(nullptr);
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 }
171 mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow",
172 0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr);
173 NS_ENSURE_STATE(mEventWnd);
175 return nsBaseAppShell::Init();
176 }
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();
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();
189 nsresult rv = nsBaseAppShell::Run();
191 RemoveScreenWakeLockListener();
193 mozilla::widget::StopAudioSession();
195 return rv;
196 }
198 NS_IMETHODIMP
199 nsAppShell::Exit(void)
200 {
201 return nsBaseAppShell::Exit();
202 }
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.
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.
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 }
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 }
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();
258 bool gotMessage = false;
260 do {
261 MSG msg;
262 bool uiMessage = false;
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 }
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 }
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);
298 if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST &&
299 IMEHandler::ProcessRawKeyMessage(msg)) {
300 continue; // the message is consumed.
301 }
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);
316 // See DoProcessNextNativeEvent, mEventloopNestingLevel will be
317 // one when a modal loop unwinds.
318 if (mNativeCallbackPending && mEventloopNestingLevel == 1)
319 DoProcessMoreGeckoEvents();
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);
326 TimeDuration timeSinceLastNativeEventScheduled;
327 {
328 MutexAutoLock lock(mLastNativeEventScheduledMutex);
329 timeSinceLastNativeEventScheduled =
330 TimeStamp::NowLoRes() - mLastNativeEventScheduled;
331 }
332 if (timeSinceLastNativeEventScheduled > nativeEventStarvationLimit) {
333 ScheduleNativeEventCallback();
334 }
336 return gotMessage;
337 }