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 +}