widget/windows/nsAppShell.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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 }

mercurial