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