widget/windows/nsAppShell.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/widget/windows/nsAppShell.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,337 @@
     1.4 +/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "mozilla/ipc/MessageChannel.h"
    1.10 +#include "mozilla/ipc/WindowsMessageLoop.h"
    1.11 +#include "nsAppShell.h"
    1.12 +#include "nsToolkit.h"
    1.13 +#include "nsThreadUtils.h"
    1.14 +#include "WinUtils.h"
    1.15 +#include "WinTaskbar.h"
    1.16 +#include "WinMouseScrollHandler.h"
    1.17 +#include "nsWindowDefs.h"
    1.18 +#include "nsString.h"
    1.19 +#include "WinIMEHandler.h"
    1.20 +#include "mozilla/widget/AudioSession.h"
    1.21 +#include "mozilla/HangMonitor.h"
    1.22 +#include "nsIDOMWakeLockListener.h"
    1.23 +#include "nsIPowerManagerService.h"
    1.24 +#include "mozilla/StaticPtr.h"
    1.25 +#include "nsTHashtable.h"
    1.26 +#include "nsHashKeys.h"
    1.27 +#include "GeckoProfiler.h"
    1.28 +
    1.29 +using namespace mozilla;
    1.30 +using namespace mozilla::widget;
    1.31 +
    1.32 +// A wake lock listener that disables screen saver when requested by
    1.33 +// Gecko. For example when we're playing video in a foreground tab we
    1.34 +// don't want the screen saver to turn on.
    1.35 +class WinWakeLockListener : public nsIDOMMozWakeLockListener {
    1.36 +public:
    1.37 +  NS_DECL_ISUPPORTS;
    1.38 +
    1.39 +private:
    1.40 +  NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) {
    1.41 +    bool isLocked = mLockedTopics.Contains(aTopic);
    1.42 +    bool shouldLock = aState.Equals(NS_LITERAL_STRING("locked-foreground"));
    1.43 +    if (isLocked == shouldLock) {
    1.44 +      return NS_OK;
    1.45 +    }
    1.46 +    if (shouldLock) {
    1.47 +      if (!mLockedTopics.Count()) {
    1.48 +        // This is the first topic to request the screen saver be disabled.
    1.49 +        // Prevent screen saver.
    1.50 +        SetThreadExecutionState(ES_DISPLAY_REQUIRED|ES_CONTINUOUS);
    1.51 +      }
    1.52 +      mLockedTopics.PutEntry(aTopic);
    1.53 +    } else {
    1.54 +      mLockedTopics.RemoveEntry(aTopic);
    1.55 +      if (!mLockedTopics.Count()) {
    1.56 +        // No other outstanding topics have requested screen saver be disabled.
    1.57 +        // Re-enable screen saver.
    1.58 +        SetThreadExecutionState(ES_CONTINUOUS);
    1.59 +      }
    1.60 +   }
    1.61 +    return NS_OK;
    1.62 +  }
    1.63 +
    1.64 +  // Keep track of all the topics that have requested a wake lock. When the
    1.65 +  // number of topics in the hashtable reaches zero, we can uninhibit the
    1.66 +  // screensaver again.
    1.67 +  nsTHashtable<nsStringHashKey> mLockedTopics;
    1.68 +};
    1.69 +
    1.70 +NS_IMPL_ISUPPORTS(WinWakeLockListener, nsIDOMMozWakeLockListener)
    1.71 +StaticRefPtr<WinWakeLockListener> sWakeLockListener;
    1.72 +
    1.73 +static void
    1.74 +AddScreenWakeLockListener()
    1.75 +{
    1.76 +  nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID);
    1.77 +  if (sPowerManagerService) {
    1.78 +    sWakeLockListener = new WinWakeLockListener();
    1.79 +    sPowerManagerService->AddWakeLockListener(sWakeLockListener);
    1.80 +  } else {
    1.81 +    NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
    1.82 +  }
    1.83 +}
    1.84 +
    1.85 +static void
    1.86 +RemoveScreenWakeLockListener()
    1.87 +{
    1.88 +  nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID);
    1.89 +  if (sPowerManagerService) {
    1.90 +    sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
    1.91 +    sPowerManagerService = nullptr;
    1.92 +    sWakeLockListener = nullptr;
    1.93 +  }
    1.94 +}
    1.95 +
    1.96 +namespace mozilla {
    1.97 +namespace widget {
    1.98 +// Native event callback message.
    1.99 +UINT sAppShellGeckoMsgId = RegisterWindowMessageW(L"nsAppShell:EventID");
   1.100 +} }
   1.101 +
   1.102 +const wchar_t* kTaskbarButtonEventId = L"TaskbarButtonCreated";
   1.103 +UINT sTaskbarButtonCreatedMsg;
   1.104 +
   1.105 +/* static */
   1.106 +UINT nsAppShell::GetTaskbarButtonCreatedMessage() {
   1.107 +	return sTaskbarButtonCreatedMsg;
   1.108 +}
   1.109 +
   1.110 +namespace mozilla {
   1.111 +namespace crashreporter {
   1.112 +void LSPAnnotate();
   1.113 +} // namespace crashreporter
   1.114 +} // namespace mozilla
   1.115 +
   1.116 +using mozilla::crashreporter::LSPAnnotate;
   1.117 +
   1.118 +//-------------------------------------------------------------------------
   1.119 +
   1.120 +/*static*/ LRESULT CALLBACK
   1.121 +nsAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
   1.122 +{
   1.123 +  if (uMsg == sAppShellGeckoMsgId) {
   1.124 +    nsAppShell *as = reinterpret_cast<nsAppShell *>(lParam);
   1.125 +    as->NativeEventCallback();
   1.126 +    NS_RELEASE(as);
   1.127 +    return TRUE;
   1.128 +  }
   1.129 +  return DefWindowProc(hwnd, uMsg, wParam, lParam);
   1.130 +}
   1.131 +
   1.132 +nsAppShell::~nsAppShell()
   1.133 +{
   1.134 +  if (mEventWnd) {
   1.135 +    // DestroyWindow doesn't do anything when called from a non UI thread.
   1.136 +    // Since mEventWnd was created on the UI thread, it must be destroyed on
   1.137 +    // the UI thread.
   1.138 +    SendMessage(mEventWnd, WM_CLOSE, 0, 0);
   1.139 +  }
   1.140 +}
   1.141 +
   1.142 +nsresult
   1.143 +nsAppShell::Init()
   1.144 +{
   1.145 +#ifdef MOZ_CRASHREPORTER
   1.146 +  LSPAnnotate();
   1.147 +#endif
   1.148 +
   1.149 +  mLastNativeEventScheduled = TimeStamp::NowLoRes();
   1.150 +
   1.151 +  mozilla::ipc::windows::InitUIThread();
   1.152 +
   1.153 +  sTaskbarButtonCreatedMsg = ::RegisterWindowMessageW(kTaskbarButtonEventId);
   1.154 +  NS_ASSERTION(sTaskbarButtonCreatedMsg, "Could not register taskbar button creation message");
   1.155 +
   1.156 +  WNDCLASSW wc;
   1.157 +  HINSTANCE module = GetModuleHandle(nullptr);
   1.158 +
   1.159 +  const wchar_t *const kWindowClass = L"nsAppShell:EventWindowClass";
   1.160 +  if (!GetClassInfoW(module, kWindowClass, &wc)) {
   1.161 +    wc.style         = 0;
   1.162 +    wc.lpfnWndProc   = EventWindowProc;
   1.163 +    wc.cbClsExtra    = 0;
   1.164 +    wc.cbWndExtra    = 0;
   1.165 +    wc.hInstance     = module;
   1.166 +    wc.hIcon         = nullptr;
   1.167 +    wc.hCursor       = nullptr;
   1.168 +    wc.hbrBackground = (HBRUSH) nullptr;
   1.169 +    wc.lpszMenuName  = (LPCWSTR) nullptr;
   1.170 +    wc.lpszClassName = kWindowClass;
   1.171 +    RegisterClassW(&wc);
   1.172 +  }
   1.173 +
   1.174 +  mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow",
   1.175 +                           0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr);
   1.176 +  NS_ENSURE_STATE(mEventWnd);
   1.177 +
   1.178 +  return nsBaseAppShell::Init();
   1.179 +}
   1.180 +
   1.181 +NS_IMETHODIMP
   1.182 +nsAppShell::Run(void)
   1.183 +{
   1.184 +  // Ignore failure; failing to start the application is not exactly an
   1.185 +  // appropriate response to failing to start an audio session.
   1.186 +  mozilla::widget::StartAudioSession();
   1.187 +
   1.188 +  // Add an observer that disables the screen saver when requested by Gecko.
   1.189 +  // For example when we're playing video in the foreground tab.
   1.190 +  AddScreenWakeLockListener();
   1.191 +
   1.192 +  nsresult rv = nsBaseAppShell::Run();
   1.193 +
   1.194 +  RemoveScreenWakeLockListener();
   1.195 +
   1.196 +  mozilla::widget::StopAudioSession();
   1.197 +
   1.198 +  return rv;
   1.199 +}
   1.200 +
   1.201 +NS_IMETHODIMP
   1.202 +nsAppShell::Exit(void)
   1.203 +{
   1.204 +  return nsBaseAppShell::Exit();
   1.205 +}
   1.206 +
   1.207 +void
   1.208 +nsAppShell::DoProcessMoreGeckoEvents()
   1.209 +{
   1.210 +  // Called by nsBaseAppShell's NativeEventCallback() after it has finished
   1.211 +  // processing pending gecko events and there are still gecko events pending
   1.212 +  // for the thread. (This can happen if NS_ProcessPendingEvents reached it's
   1.213 +  // starvation timeout limit.) The default behavior in nsBaseAppShell is to
   1.214 +  // call ScheduleNativeEventCallback to post a follow up native event callback
   1.215 +  // message. This triggers an additional call to NativeEventCallback for more
   1.216 +  // gecko event processing.
   1.217 +
   1.218 +  // There's a deadlock risk here with certain internal Windows modal loops. In
   1.219 +  // our dispatch code, we prioritize messages so that input is handled first.
   1.220 +  // However Windows modal dispatch loops often prioritize posted messages. If
   1.221 +  // we find ourselves in a tight gecko timer loop where NS_ProcessPendingEvents
   1.222 +  // takes longer than the timer duration, NS_HasPendingEvents(thread) will
   1.223 +  // always be true. ScheduleNativeEventCallback will be called on every
   1.224 +  // NativeEventCallback callback, and in a Windows modal dispatch loop, the
   1.225 +  // callback message will be processed first -> input gets starved, dead lock.
   1.226 +
   1.227 +  // To avoid, don't post native callback messages from NativeEventCallback
   1.228 +  // when we're in a modal loop. This gets us back into the Windows modal
   1.229 +  // dispatch loop dispatching input messages. Once we drop out of the modal
   1.230 +  // loop, we use mNativeCallbackPending to fire off a final NativeEventCallback
   1.231 +  // if we need it, which insures NS_ProcessPendingEvents gets called and all
   1.232 +  // gecko events get processed.
   1.233 +  if (mEventloopNestingLevel < 2) {
   1.234 +    OnDispatchedEvent(nullptr);
   1.235 +    mNativeCallbackPending = false;
   1.236 +  } else {
   1.237 +    mNativeCallbackPending = true;
   1.238 +  }
   1.239 +}
   1.240 +
   1.241 +void
   1.242 +nsAppShell::ScheduleNativeEventCallback()
   1.243 +{
   1.244 +  // Post a message to the hidden message window
   1.245 +  NS_ADDREF_THIS(); // will be released when the event is processed
   1.246 +  {
   1.247 +    MutexAutoLock lock(mLastNativeEventScheduledMutex);
   1.248 +    // Time stamp this event so we can detect cases where the event gets
   1.249 +    // dropping in sub classes / modal loops we do not control.
   1.250 +    mLastNativeEventScheduled = TimeStamp::NowLoRes();
   1.251 +  }
   1.252 +  ::PostMessage(mEventWnd, sAppShellGeckoMsgId, 0, reinterpret_cast<LPARAM>(this));
   1.253 +}
   1.254 +
   1.255 +bool
   1.256 +nsAppShell::ProcessNextNativeEvent(bool mayWait)
   1.257 +{
   1.258 +  // Notify ipc we are spinning a (possibly nested) gecko event loop.
   1.259 +  mozilla::ipc::MessageChannel::NotifyGeckoEventDispatch();
   1.260 +
   1.261 +  bool gotMessage = false;
   1.262 +
   1.263 +  do {
   1.264 +    MSG msg;
   1.265 +    bool uiMessage = false;
   1.266 +
   1.267 +    // For avoiding deadlock between our process and plugin process by
   1.268 +    // mouse wheel messages, we're handling actually when we receive one of
   1.269 +    // following internal messages which is posted by native mouse wheel
   1.270 +    // message handler. Any other events, especially native modifier key
   1.271 +    // events, should not be handled between native message and posted
   1.272 +    // internal message because it may make different modifier key state or
   1.273 +    // mouse cursor position between them.
   1.274 +    if (mozilla::widget::MouseScrollHandler::IsWaitingInternalMessage()) {
   1.275 +      gotMessage = WinUtils::PeekMessage(&msg, nullptr, MOZ_WM_MOUSEWHEEL_FIRST,
   1.276 +                                         MOZ_WM_MOUSEWHEEL_LAST, PM_REMOVE);
   1.277 +      NS_ASSERTION(gotMessage,
   1.278 +                   "waiting internal wheel message, but it has not come");
   1.279 +      uiMessage = gotMessage;
   1.280 +    }
   1.281 +
   1.282 +    if (!gotMessage) {
   1.283 +      gotMessage = WinUtils::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
   1.284 +      uiMessage =
   1.285 +        (msg.message >= WM_KEYFIRST && msg.message <= WM_IME_KEYLAST) ||
   1.286 +        (msg.message >= NS_WM_IMEFIRST && msg.message <= NS_WM_IMELAST) ||
   1.287 +        (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST);
   1.288 +    }
   1.289 +
   1.290 +    if (gotMessage) {
   1.291 +      if (msg.message == WM_QUIT) {
   1.292 +        ::PostQuitMessage(msg.wParam);
   1.293 +        Exit();
   1.294 +      } else {
   1.295 +        // If we had UI activity we would be processing it now so we know we
   1.296 +        // have either kUIActivity or kActivityNoUIAVail.
   1.297 +        mozilla::HangMonitor::NotifyActivity(
   1.298 +          uiMessage ? mozilla::HangMonitor::kUIActivity :
   1.299 +                      mozilla::HangMonitor::kActivityNoUIAVail);
   1.300 +
   1.301 +        if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST &&
   1.302 +            IMEHandler::ProcessRawKeyMessage(msg)) {
   1.303 +          continue;  // the message is consumed.
   1.304 +        }
   1.305 +
   1.306 +        ::TranslateMessage(&msg);
   1.307 +        ::DispatchMessageW(&msg);
   1.308 +      }
   1.309 +    } else if (mayWait) {
   1.310 +      // Block and wait for any posted application message
   1.311 +      mozilla::HangMonitor::Suspend();
   1.312 +      {
   1.313 +        GeckoProfilerSleepRAII profiler_sleep;
   1.314 +        ::WaitMessage();
   1.315 +      }
   1.316 +    }
   1.317 +  } while (!gotMessage && mayWait);
   1.318 +
   1.319 +  // See DoProcessNextNativeEvent, mEventloopNestingLevel will be
   1.320 +  // one when a modal loop unwinds.
   1.321 +  if (mNativeCallbackPending && mEventloopNestingLevel == 1)
   1.322 +    DoProcessMoreGeckoEvents();
   1.323 +
   1.324 +  // Check for starved native callbacks. If we haven't processed one
   1.325 +  // of these events in NATIVE_EVENT_STARVATION_LIMIT, fire one off.
   1.326 +  static const mozilla::TimeDuration nativeEventStarvationLimit =
   1.327 +    mozilla::TimeDuration::FromSeconds(NATIVE_EVENT_STARVATION_LIMIT);
   1.328 +
   1.329 +  TimeDuration timeSinceLastNativeEventScheduled;
   1.330 +  {
   1.331 +    MutexAutoLock lock(mLastNativeEventScheduledMutex);
   1.332 +    timeSinceLastNativeEventScheduled =
   1.333 +        TimeStamp::NowLoRes() - mLastNativeEventScheduled;
   1.334 +  }
   1.335 +  if (timeSinceLastNativeEventScheduled > nativeEventStarvationLimit) {
   1.336 +    ScheduleNativeEventCallback();
   1.337 +  }
   1.338 +
   1.339 +  return gotMessage;
   1.340 +}

mercurial