gecko-dev/widget/windows/winrt/MetroAppShell.cpp
Rodrigo Silveira 61a09809fe Bug 952835 - screen goes to sleep while video still playing in Metro Firefox r=jimm
--HG--
extra : rebase_source : 07926a411418a61e20df832ebb032fff5b218a77
2014-01-15 16:11:36 -08:00

511 lines
16 KiB
C++

/* -*- 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 "nsXULAppAPI.h"
#include "mozilla/widget/AudioSession.h"
#include "MetroUtils.h"
#include "MetroApp.h"
#include "FrameworkView.h"
#include "nsIObserverService.h"
#include "nsServiceManagerUtils.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/TimeStamp.h"
#include "WinUtils.h"
#include "nsIAppStartup.h"
#include "nsToolkitCompsCID.h"
#include <shellapi.h>
#include "nsIDOMWakeLockListener.h"
#include "nsIPowerManagerService.h"
#include "mozilla/StaticPtr.h"
#include <windows.system.display.h>
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<MetroApp> sMetroApp;
extern ComPtr<FrameworkView> sFrameworkView;
} } }
namespace mozilla {
namespace widget {
// pulled from win32 app shell
extern UINT sAppShellGeckoMsgId;
} }
class WakeLockListener : public nsIDOMMozWakeLockListener {
public:
NS_DECL_ISUPPORTS;
private:
ComPtr<ABI::Windows::System::Display::IDisplayRequest> mDisplayRequest;
NS_IMETHOD Callback(const nsAString& aTopic, const nsAString& aState) {
if (!mDisplayRequest) {
if (FAILED(ActivateGenericInstance(RuntimeClass_Windows_System_Display_DisplayRequest, mDisplayRequest))) {
NS_WARNING("Failed to instantiate IDisplayRequest, wakelocks will be broken!");
return NS_OK;
}
}
if (aState.Equals(NS_LITERAL_STRING("locked-foreground"))) {
mDisplayRequest->RequestActive();
} else {
mDisplayRequest->RequestRelease();
}
return NS_OK;
}
};
NS_IMPL_ISUPPORTS1(WakeLockListener, nsIDOMMozWakeLockListener)
StaticRefPtr<WakeLockListener> sWakeLockListener;
static ComPtr<ICoreWindowStatic> 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<nsIObserverService> 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 DEH which is used to launch the browser
const CLSID CLSID_FirefoxMetroDEH = {0x5100FEC1,0x212B, 0x4BF5 ,{0x9B,0xF8, 0x3E,0x65, 0x0F,0xD7,0x94,0xA3}};
nsRefPtr<IExecuteCommand> 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<IShellItem> 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<IShellItemArray> shellItemArray;
hr = SHCreateShellItemArrayFromShellItemDynamic(shellItem, IID_IShellItemArray, getter_AddRefs(shellItemArray));
if (FAILED(hr))
return hr;
// Set the path to launch and parameters needed
nsRefPtr<IObjectWithSelection> selection;
hr = executeCommand->QueryInterface(IID_IObjectWithSelection, getter_AddRefs(selection));
if (FAILED(hr))
return hr;
hr = selection->SetSelection(shellItemArray);
if (FAILED(hr))
return hr;
hr = executeCommand->SetParameters(L"--metro-restart");
if (FAILED(hr))
return hr;
// Run the default browser through the DEH
return executeCommand->Execute();
}
// 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: {
nsCOMPtr<nsIPowerManagerService> sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID);
if (sPowerManagerService) {
sWakeLockListener = new WakeLockListener();
sPowerManagerService->AddWakeLockListener(sWakeLockListener);
}
else {
NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
}
mozilla::widget::StartAudioSession();
sFrameworkView->ActivateView();
rv = nsBaseAppShell::Run();
mozilla::widget::StopAudioSession();
if (sPowerManagerService) {
sPowerManagerService->RemoveWakeLockListener(sWakeLockListener);
sPowerManagerService = nullptr;
sWakeLockListener = nullptr;
}
nsCOMPtr<nsIAppStartup> 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->ShutdownXPCOM();
// Handle update restart or browser switch requests
if (restartingInDesktop) {
WinUtils::Log("Relaunching desktop browser");
SHELLEXECUTEINFOW sinfo;
memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
sinfo.cbSize = sizeof(SHELLEXECUTEINFOW);
// Per the Metro style enabled desktop browser, for some reason,
// SEE_MASK_FLAG_LOG_USAGE is needed to change from immersive mode
// to desktop.
sinfo.fMask = SEE_MASK_FLAG_LOG_USAGE;
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
if (!sShouldPurgeThreadQueue) {
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<ICoreWindow> window;
AssertHRESULT(sCoreStatic->GetForCurrentThread(window.GetAddressOf()));
ComPtr<ICoreDispatcher> 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<bool> 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");
NativeEventCallback();
}
// static
LRESULT CALLBACK
MetroAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == sAppShellGeckoMsgId) {
MetroAppShell *as = reinterpret_cast<MetroAppShell *>(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<LPARAM>(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);
}