diff -r 000000000000 -r 6474c204b198 widget/windows/winrt/MetroAppShell.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widget/windows/winrt/MetroAppShell.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,519 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MetroAppShell.h" + +#include "mozilla/AutoRestore.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/widget/AudioSession.h" + +#include "nsIObserverService.h" +#include "nsIAppStartup.h" +#include "nsToolkitCompsCID.h" +#include "nsIPowerManagerService.h" + +#include "nsXULAppAPI.h" +#include "nsServiceManagerUtils.h" +#include "WinUtils.h" +#include "nsWinMetroUtils.h" +#include "MetroUtils.h" +#include "MetroApp.h" +#include "FrameworkView.h" +#include "WakeLockListener.h" + +#include + +using namespace mozilla; +using namespace mozilla::widget; +using namespace mozilla::widget::winrt; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::UI::Core; +using namespace ABI::Windows::Foundation; + +// ProcessNextNativeEvent message wait timeout, see bug 907410. +#define MSG_WAIT_TIMEOUT 250 +// MetroInput will occasionally ask us to flush all input so that the dom is +// up to date. This is the maximum amount of time we'll agree to spend in +// NS_ProcessPendingEvents. +#define PURGE_MAX_TIMEOUT 50 + +namespace mozilla { +namespace widget { +namespace winrt { +extern ComPtr sMetroApp; +} } } + +namespace mozilla { +namespace widget { +// pulled from win32 app shell +extern UINT sAppShellGeckoMsgId; +} } + +static ComPtr sCoreStatic; +static bool sIsDispatching = false; +static bool sShouldPurgeThreadQueue = false; +static bool sBlockNativeEvents = false; +static TimeStamp sPurgeThreadQueueStart; + +MetroAppShell::~MetroAppShell() +{ + if (mEventWnd) { + SendMessage(mEventWnd, WM_CLOSE, 0, 0); + } +} + +nsresult +MetroAppShell::Init() +{ + LogFunction(); + + WNDCLASSW wc; + HINSTANCE module = GetModuleHandle(nullptr); + + const char16_t *const kWindowClass = L"nsAppShell:EventWindowClass"; + if (!GetClassInfoW(module, kWindowClass, &wc)) { + wc.style = 0; + wc.lpfnWndProc = EventWindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = module; + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = (HBRUSH) nullptr; + wc.lpszMenuName = (LPCWSTR) nullptr; + wc.lpszClassName = kWindowClass; + RegisterClassW(&wc); + } + + mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow", + 0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr); + NS_ENSURE_STATE(mEventWnd); + + nsresult rv; + nsCOMPtr observerService = + do_GetService("@mozilla.org/observer-service;1", &rv); + if (NS_SUCCEEDED(rv)) { + observerService->AddObserver(this, "dl-start", false); + observerService->AddObserver(this, "dl-done", false); + observerService->AddObserver(this, "dl-cancel", false); + observerService->AddObserver(this, "dl-failed", false); + } + + return nsBaseAppShell::Init(); +} + +HRESULT +SHCreateShellItemArrayFromShellItemDynamic(IShellItem *psi, REFIID riid, void **ppv) +{ + HMODULE shell32DLL = LoadLibraryW(L"shell32.dll"); + if (!shell32DLL) { + return E_FAIL; + } + + typedef BOOL (WINAPI* SHFn)(IShellItem *psi, REFIID riid, void **ppv); + + HRESULT hr = E_FAIL; + SHFn SHCreateShellItemArrayFromShellItemDynamicPtr = + (SHFn)GetProcAddress(shell32DLL, "SHCreateShellItemArrayFromShellItem"); + FreeLibrary(shell32DLL); + if (SHCreateShellItemArrayFromShellItemDynamicPtr) { + hr = SHCreateShellItemArrayFromShellItemDynamicPtr(psi, riid, ppv); + } + + FreeLibrary(shell32DLL); + return hr; +} + +HRESULT +WinLaunchDeferredMetroFirefox() +{ + // Create an instance of the Firefox Metro CEH which is used to launch the browser + const CLSID CLSID_FirefoxMetroDEH = {0x5100FEC1,0x212B, 0x4BF5 ,{0x9B,0xF8, 0x3E,0x65, 0x0F,0xD7,0x94,0xA3}}; + + nsRefPtr executeCommand; + HRESULT hr = CoCreateInstance(CLSID_FirefoxMetroDEH, + nullptr, + CLSCTX_LOCAL_SERVER, + IID_IExecuteCommand, + getter_AddRefs(executeCommand)); + if (FAILED(hr)) + return hr; + + // Get the currently running exe path + WCHAR exePath[MAX_PATH + 1] = { L'\0' }; + if (!::GetModuleFileNameW(0, exePath, MAX_PATH)) + return hr; + + // Convert the path to a long path since GetModuleFileNameW returns the path + // that was used to launch Firefox which is not necessarily a long path. + if (!::GetLongPathNameW(exePath, exePath, MAX_PATH)) + return hr; + + // Create an IShellItem for the current browser path + nsRefPtr shellItem; + hr = WinUtils::SHCreateItemFromParsingName(exePath, nullptr, IID_IShellItem, + getter_AddRefs(shellItem)); + if (FAILED(hr)) + return hr; + + // Convert to an IShellItemArray which is used for the path to launch + nsRefPtr shellItemArray; + hr = SHCreateShellItemArrayFromShellItemDynamic(shellItem, IID_IShellItemArray, getter_AddRefs(shellItemArray)); + if (FAILED(hr)) + return hr; + + // Set the path to launch and parameters needed + nsRefPtr selection; + hr = executeCommand->QueryInterface(IID_IObjectWithSelection, getter_AddRefs(selection)); + if (FAILED(hr)) + return hr; + hr = selection->SetSelection(shellItemArray); + if (FAILED(hr)) + return hr; + + if (nsWinMetroUtils::sUpdatePending) { + hr = executeCommand->SetParameters(L"--metro-update"); + } else { + hr = executeCommand->SetParameters(L"--metro-restart"); + } + if (FAILED(hr)) + return hr; + + // Run the default browser through the CEH + return executeCommand->Execute(); +} + +static WakeLockListener* +InitWakeLock() +{ + nsCOMPtr powerManagerService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + if (powerManagerService) { + WakeLockListener* pLock = new WakeLockListener(); + powerManagerService->AddWakeLockListener(pLock); + return pLock; + } + else { + NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); + } + return nullptr; +} + +static void +ShutdownWakeLock(WakeLockListener* aLock) +{ + if (!aLock) { + return; + } + nsCOMPtr powerManagerService = + do_GetService(POWERMANAGERSERVICE_CONTRACTID); + if (powerManagerService) { + powerManagerService->RemoveWakeLockListener(aLock); + } +} + +// Called by appstartup->run in xre, which is initiated by a call to +// XRE_metroStartup in MetroApp. This call is on the metro main thread. +NS_IMETHODIMP +MetroAppShell::Run(void) +{ + LogFunction(); + nsresult rv = NS_OK; + + switch(XRE_GetProcessType()) { + case GeckoProcessType_Content: + case GeckoProcessType_IPDLUnitTest: + mozilla::widget::StartAudioSession(); + rv = nsBaseAppShell::Run(); + mozilla::widget::StopAudioSession(); + break; + case GeckoProcessType_Plugin: + NS_WARNING("We don't support plugins currently."); + // Just exit + rv = NS_ERROR_NOT_IMPLEMENTED; + break; + case GeckoProcessType_Default: { + { + nsRefPtr wakeLock = InitWakeLock(); + mozilla::widget::StartAudioSession(); + sMetroApp->ActivateBaseView(); + rv = nsBaseAppShell::Run(); + mozilla::widget::StopAudioSession(); + ShutdownWakeLock(wakeLock); + } + + nsCOMPtr appStartup (do_GetService(NS_APPSTARTUP_CONTRACTID)); + bool restartingInMetro = false, restartingInDesktop = false; + + if (!appStartup || NS_FAILED(appStartup->GetRestarting(&restartingInDesktop))) { + WinUtils::Log("appStartup->GetRestarting() unsuccessful"); + } + + if (appStartup && NS_SUCCEEDED(appStartup->GetRestartingTouchEnvironment(&restartingInMetro)) && + restartingInMetro) { + restartingInDesktop = false; + } + + // This calls XRE_metroShutdown() in xre. Shuts down gecko, including + // releasing the profile, and destroys MessagePump. + sMetroApp->Shutdown(); + + // Handle update restart or browser switch requests + if (restartingInDesktop) { + WinUtils::Log("Relaunching desktop browser"); + // We can't call into the ceh to do this. Microsoft prevents switching to + // desktop unless we go through shell execute. + SHELLEXECUTEINFOW sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + // Per microsoft's metro style enabled desktop browser documentation + // SEE_MASK_FLAG_LOG_USAGE is needed if we want to change from immersive + // mode to desktop mode. + sinfo.fMask = SEE_MASK_FLAG_LOG_USAGE; + // The ceh will filter out this fake target. + sinfo.lpFile = L"http://-desktop/"; + sinfo.lpVerb = L"open"; + sinfo.lpParameters = L"--desktop-restart"; + sinfo.nShow = SW_SHOWNORMAL; + ShellExecuteEx(&sinfo); + } else if (restartingInMetro) { + HRESULT hresult = WinLaunchDeferredMetroFirefox(); + WinUtils::Log("Relaunching metro browser (hr=%X)", hresult); + } + + // This will free the real main thread in CoreApplication::Run() + // once winrt cleans up this thread. + sMetroApp->CoreExit(); + } + break; + } + + return rv; +} + +// Called in certain cases where we have async input events in the thread +// queue and need to make sure they get dispatched before the stack unwinds. +void // static +MetroAppShell::MarkEventQueueForPurge() +{ + sShouldPurgeThreadQueue = true; + + // If we're dispatching native events, wait until the dispatcher is + // off the stack. + if (sIsDispatching) { + return; + } + + // Safe to process pending events now + DispatchAllGeckoEvents(); +} + +// Notification from MetroInput that all events it wanted delivered +// have been dispatched. It is safe to start processing windowing +// events. +void // static +MetroAppShell::InputEventsDispatched() +{ + sBlockNativeEvents = false; +} + +// static +void +MetroAppShell::DispatchAllGeckoEvents() +{ + // Only do this if requested and when we're not shutting down + if (!sShouldPurgeThreadQueue || MetroApp::sGeckoShuttingDown) { + return; + } + + NS_ASSERTION(NS_IsMainThread(), "DispatchAllGeckoEvents should be called on the main thread"); + + sShouldPurgeThreadQueue = false; + sPurgeThreadQueueStart = TimeStamp::Now(); + + sBlockNativeEvents = true; + nsIThread *thread = NS_GetCurrentThread(); + NS_ProcessPendingEvents(thread, PURGE_MAX_TIMEOUT); + sBlockNativeEvents = false; +} + +static void +ProcessNativeEvents(CoreProcessEventsOption eventOption) +{ + HRESULT hr; + if (!sCoreStatic) { + hr = GetActivationFactory(HStringReference(L"Windows.UI.Core.CoreWindow").Get(), sCoreStatic.GetAddressOf()); + NS_ASSERTION(SUCCEEDED(hr), "GetActivationFactory failed?"); + AssertHRESULT(hr); + } + + ComPtr window; + AssertHRESULT(sCoreStatic->GetForCurrentThread(window.GetAddressOf())); + ComPtr dispatcher; + hr = window->get_Dispatcher(&dispatcher); + NS_ASSERTION(SUCCEEDED(hr), "get_Dispatcher failed?"); + AssertHRESULT(hr); + dispatcher->ProcessEvents(eventOption); +} + +// static +bool +MetroAppShell::ProcessOneNativeEventIfPresent() +{ + if (sIsDispatching) { + // Calling into ProcessNativeEvents is harmless, but won't actually process any + // native events. So we log here so we can spot this and get a handle on the + // corner cases where this can happen. + WinUtils::Log("WARNING: Reentrant call into process events detected, returning early."); + return false; + } + + { + AutoRestore dispatching(sIsDispatching); + sIsDispatching = true; + ProcessNativeEvents(CoreProcessEventsOption::CoreProcessEventsOption_ProcessOneIfPresent); + } + + DispatchAllGeckoEvents(); + + return !!HIWORD(::GetQueueStatus(MOZ_QS_ALLEVENT)); +} + +bool +MetroAppShell::ProcessNextNativeEvent(bool mayWait) +{ + // NS_ProcessPendingEvents will process thread events *and* call + // nsBaseAppShell::OnProcessNextEvent to process native events. However + // we do not want native events getting dispatched while we are trying + // to dispatch pending input in DispatchAllGeckoEvents since a native + // event may be a UIA Automation call coming in to check focus. + if (sBlockNativeEvents) { + if ((TimeStamp::Now() - sPurgeThreadQueueStart).ToMilliseconds() + < PURGE_MAX_TIMEOUT) { + return false; + } + sBlockNativeEvents = false; + } + + if (ProcessOneNativeEventIfPresent()) { + return true; + } + if (mayWait) { + DWORD result = ::MsgWaitForMultipleObjectsEx(0, nullptr, MSG_WAIT_TIMEOUT, + MOZ_QS_ALLEVENT, + MWMO_INPUTAVAILABLE|MWMO_ALERTABLE); + NS_WARN_IF_FALSE(result != WAIT_FAILED, "Wait failed"); + } + return ProcessOneNativeEventIfPresent(); +} + +void +MetroAppShell::NativeCallback() +{ + NS_ASSERTION(NS_IsMainThread(), "Native callbacks must be on the metro main thread"); + + // We shouldn't process native events during xpcom shutdown - this can + // trigger unexpected xpcom event dispatching for the main thread when + // the thread manager is in the process of shutting down non-main threads, + // resulting in shutdown hangs. + if (MetroApp::sGeckoShuttingDown) { + return; + } + + NativeEventCallback(); +} + +// static +LRESULT CALLBACK +MetroAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + if (uMsg == sAppShellGeckoMsgId) { + MetroAppShell *as = reinterpret_cast(lParam); + as->NativeCallback(); + NS_RELEASE(as); + return TRUE; + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +void +MetroAppShell::ScheduleNativeEventCallback() +{ + NS_ADDREF_THIS(); + PostMessage(mEventWnd, sAppShellGeckoMsgId, 0, reinterpret_cast(this)); +} + +void +MetroAppShell::DoProcessMoreGeckoEvents() +{ + ScheduleNativeEventCallback(); +} + +static HANDLE +PowerCreateRequestDyn(REASON_CONTEXT *context) +{ + typedef HANDLE (WINAPI * PowerCreateRequestPtr)(REASON_CONTEXT *context); + static HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + static PowerCreateRequestPtr powerCreateRequest = + (PowerCreateRequestPtr)GetProcAddress(kernel32, "PowerCreateRequest"); + if (!powerCreateRequest) + return INVALID_HANDLE_VALUE; + return powerCreateRequest(context); +} + +static BOOL +PowerClearRequestDyn(HANDLE powerRequest, POWER_REQUEST_TYPE requestType) +{ + typedef BOOL (WINAPI * PowerClearRequestPtr)(HANDLE powerRequest, POWER_REQUEST_TYPE requestType); + static HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + static PowerClearRequestPtr powerClearRequest = + (PowerClearRequestPtr)GetProcAddress(kernel32, "PowerClearRequest"); + if (!powerClearRequest) + return FALSE; + return powerClearRequest(powerRequest, requestType); +} + +static BOOL +PowerSetRequestDyn(HANDLE powerRequest, POWER_REQUEST_TYPE requestType) +{ + typedef BOOL (WINAPI * PowerSetRequestPtr)(HANDLE powerRequest, POWER_REQUEST_TYPE requestType); + static HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + static PowerSetRequestPtr powerSetRequest = + (PowerSetRequestPtr)GetProcAddress(kernel32, "PowerSetRequest"); + if (!powerSetRequest) + return FALSE; + return powerSetRequest(powerRequest, requestType); +} + +NS_IMETHODIMP +MetroAppShell::Observe(nsISupports *subject, const char *topic, + const char16_t *data) +{ + NS_ENSURE_ARG_POINTER(topic); + if (!strcmp(topic, "dl-start")) { + if (mPowerRequestCount++ == 0) { + WinUtils::Log("Download started - Disallowing suspend"); + REASON_CONTEXT context; + context.Version = POWER_REQUEST_CONTEXT_VERSION; + context.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING; + context.Reason.SimpleReasonString = L"downloading"; + mPowerRequest.own(PowerCreateRequestDyn(&context)); + PowerSetRequestDyn(mPowerRequest, PowerRequestExecutionRequired); + } + return NS_OK; + } else if (!strcmp(topic, "dl-done") || + !strcmp(topic, "dl-cancel") || + !strcmp(topic, "dl-failed")) { + if (--mPowerRequestCount == 0 && mPowerRequest) { + WinUtils::Log("All downloads ended - Allowing suspend"); + PowerClearRequestDyn(mPowerRequest, PowerRequestExecutionRequired); + mPowerRequest.reset(); + } + return NS_OK; + } + + return nsBaseAppShell::Observe(subject, topic, data); +}