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