widget/windows/nsAppShell.cpp

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

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 }

mercurial