widget/windows/winrt/MetroAppShell.cpp

Thu, 15 Jan 2015 15:59:08 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:59:08 +0100
branch
TOR_BUG_9701
changeset 10
ac0c01689b40
permissions
-rw-r--r--

Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "MetroAppShell.h"
     8 #include "mozilla/AutoRestore.h"
     9 #include "mozilla/TimeStamp.h"
    10 #include "mozilla/widget/AudioSession.h"
    12 #include "nsIObserverService.h"
    13 #include "nsIAppStartup.h"
    14 #include "nsToolkitCompsCID.h"
    15 #include "nsIPowerManagerService.h"
    17 #include "nsXULAppAPI.h"
    18 #include "nsServiceManagerUtils.h"
    19 #include "WinUtils.h"
    20 #include "nsWinMetroUtils.h"
    21 #include "MetroUtils.h"
    22 #include "MetroApp.h"
    23 #include "FrameworkView.h"
    24 #include "WakeLockListener.h"
    26 #include <shellapi.h>
    28 using namespace mozilla;
    29 using namespace mozilla::widget;
    30 using namespace mozilla::widget::winrt;
    31 using namespace Microsoft::WRL;
    32 using namespace Microsoft::WRL::Wrappers;
    33 using namespace ABI::Windows::UI::Core;
    34 using namespace ABI::Windows::Foundation;
    36 // ProcessNextNativeEvent message wait timeout, see bug 907410.
    37 #define MSG_WAIT_TIMEOUT 250
    38 // MetroInput will occasionally ask us to flush all input so that the dom is
    39 // up to date. This is the maximum amount of time we'll agree to spend in
    40 // NS_ProcessPendingEvents.
    41 #define PURGE_MAX_TIMEOUT 50
    43 namespace mozilla {
    44 namespace widget {
    45 namespace winrt {
    46 extern ComPtr<MetroApp> sMetroApp;
    47 } } }
    49 namespace mozilla {
    50 namespace widget {
    51 // pulled from win32 app shell
    52 extern UINT sAppShellGeckoMsgId;
    53 } }
    55 static ComPtr<ICoreWindowStatic> sCoreStatic;
    56 static bool sIsDispatching = false;
    57 static bool sShouldPurgeThreadQueue = false;
    58 static bool sBlockNativeEvents = false;
    59 static TimeStamp sPurgeThreadQueueStart;
    61 MetroAppShell::~MetroAppShell()
    62 {
    63   if (mEventWnd) {
    64     SendMessage(mEventWnd, WM_CLOSE, 0, 0);
    65   }
    66 }
    68 nsresult
    69 MetroAppShell::Init()
    70 {
    71   LogFunction();
    73   WNDCLASSW wc;
    74   HINSTANCE module = GetModuleHandle(nullptr);
    76   const char16_t *const kWindowClass = L"nsAppShell:EventWindowClass";
    77   if (!GetClassInfoW(module, kWindowClass, &wc)) {
    78     wc.style         = 0;
    79     wc.lpfnWndProc   = EventWindowProc;
    80     wc.cbClsExtra    = 0;
    81     wc.cbWndExtra    = 0;
    82     wc.hInstance     = module;
    83     wc.hIcon         = nullptr;
    84     wc.hCursor       = nullptr;
    85     wc.hbrBackground = (HBRUSH) nullptr;
    86     wc.lpszMenuName  = (LPCWSTR) nullptr;
    87     wc.lpszClassName = kWindowClass;
    88     RegisterClassW(&wc);
    89   }
    91   mEventWnd = CreateWindowW(kWindowClass, L"nsAppShell:EventWindow",
    92                            0, 0, 0, 10, 10, nullptr, nullptr, module, nullptr);
    93   NS_ENSURE_STATE(mEventWnd);
    95   nsresult rv;
    96   nsCOMPtr<nsIObserverService> observerService = 
    97     do_GetService("@mozilla.org/observer-service;1", &rv);
    98   if (NS_SUCCEEDED(rv)) {
    99     observerService->AddObserver(this, "dl-start", false);
   100     observerService->AddObserver(this, "dl-done", false);
   101     observerService->AddObserver(this, "dl-cancel", false);
   102     observerService->AddObserver(this, "dl-failed", false);
   103   }
   105   return nsBaseAppShell::Init();
   106 }
   108 HRESULT
   109 SHCreateShellItemArrayFromShellItemDynamic(IShellItem *psi, REFIID riid, void **ppv)
   110 {
   111   HMODULE shell32DLL = LoadLibraryW(L"shell32.dll");
   112   if (!shell32DLL) {
   113     return E_FAIL;
   114   }
   116   typedef BOOL (WINAPI* SHFn)(IShellItem *psi, REFIID riid, void **ppv);
   118   HRESULT hr = E_FAIL;
   119   SHFn SHCreateShellItemArrayFromShellItemDynamicPtr =
   120     (SHFn)GetProcAddress(shell32DLL, "SHCreateShellItemArrayFromShellItem");
   121   FreeLibrary(shell32DLL);
   122   if (SHCreateShellItemArrayFromShellItemDynamicPtr) {
   123     hr = SHCreateShellItemArrayFromShellItemDynamicPtr(psi, riid, ppv);
   124   }
   126   FreeLibrary(shell32DLL);
   127   return hr;
   128 }
   130 HRESULT
   131 WinLaunchDeferredMetroFirefox()
   132 {
   133   // Create an instance of the Firefox Metro CEH which is used to launch the browser
   134   const CLSID CLSID_FirefoxMetroDEH = {0x5100FEC1,0x212B, 0x4BF5 ,{0x9B,0xF8, 0x3E,0x65, 0x0F,0xD7,0x94,0xA3}};
   136   nsRefPtr<IExecuteCommand> executeCommand;
   137   HRESULT hr = CoCreateInstance(CLSID_FirefoxMetroDEH,
   138                                 nullptr,
   139                                 CLSCTX_LOCAL_SERVER,
   140                                 IID_IExecuteCommand,
   141                                 getter_AddRefs(executeCommand));
   142   if (FAILED(hr))
   143     return hr;
   145   // Get the currently running exe path
   146   WCHAR exePath[MAX_PATH + 1] = { L'\0' };
   147   if (!::GetModuleFileNameW(0, exePath, MAX_PATH))
   148     return hr;
   150   // Convert the path to a long path since GetModuleFileNameW returns the path
   151   // that was used to launch Firefox which is not necessarily a long path.
   152   if (!::GetLongPathNameW(exePath, exePath, MAX_PATH))
   153     return hr;
   155   // Create an IShellItem for the current browser path
   156   nsRefPtr<IShellItem> shellItem;
   157   hr = WinUtils::SHCreateItemFromParsingName(exePath, nullptr, IID_IShellItem,
   158                                              getter_AddRefs(shellItem));
   159   if (FAILED(hr))
   160     return hr;
   162   // Convert to an IShellItemArray which is used for the path to launch
   163   nsRefPtr<IShellItemArray> shellItemArray;
   164   hr = SHCreateShellItemArrayFromShellItemDynamic(shellItem, IID_IShellItemArray, getter_AddRefs(shellItemArray));
   165   if (FAILED(hr))
   166     return hr;
   168   // Set the path to launch and parameters needed
   169   nsRefPtr<IObjectWithSelection> selection;
   170   hr = executeCommand->QueryInterface(IID_IObjectWithSelection, getter_AddRefs(selection));
   171   if (FAILED(hr))
   172     return hr;
   173   hr = selection->SetSelection(shellItemArray);
   174   if (FAILED(hr))
   175     return hr;
   177   if (nsWinMetroUtils::sUpdatePending) {
   178     hr = executeCommand->SetParameters(L"--metro-update");
   179   } else {
   180     hr = executeCommand->SetParameters(L"--metro-restart");
   181   }
   182   if (FAILED(hr))
   183     return hr;
   185   // Run the default browser through the CEH
   186   return executeCommand->Execute();
   187 }
   189 static WakeLockListener*
   190 InitWakeLock()
   191 {
   192   nsCOMPtr<nsIPowerManagerService> powerManagerService =
   193     do_GetService(POWERMANAGERSERVICE_CONTRACTID);
   194   if (powerManagerService) {
   195     WakeLockListener* pLock = new WakeLockListener();
   196     powerManagerService->AddWakeLockListener(pLock);
   197     return pLock;
   198   }
   199   else {
   200     NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!");
   201   }
   202   return nullptr;
   203 }
   205 static void
   206 ShutdownWakeLock(WakeLockListener* aLock)
   207 {
   208   if (!aLock) {
   209     return;
   210   }
   211   nsCOMPtr<nsIPowerManagerService> powerManagerService =
   212     do_GetService(POWERMANAGERSERVICE_CONTRACTID);
   213   if (powerManagerService) {
   214     powerManagerService->RemoveWakeLockListener(aLock);
   215   }
   216 }
   218 // Called by appstartup->run in xre, which is initiated by a call to
   219 // XRE_metroStartup in MetroApp. This call is on the metro main thread.
   220 NS_IMETHODIMP
   221 MetroAppShell::Run(void)
   222 {
   223   LogFunction();
   224   nsresult rv = NS_OK;
   226   switch(XRE_GetProcessType()) {
   227     case  GeckoProcessType_Content:
   228     case GeckoProcessType_IPDLUnitTest:
   229       mozilla::widget::StartAudioSession();
   230       rv = nsBaseAppShell::Run();
   231       mozilla::widget::StopAudioSession();
   232     break;
   233     case  GeckoProcessType_Plugin:
   234       NS_WARNING("We don't support plugins currently.");
   235       // Just exit
   236       rv = NS_ERROR_NOT_IMPLEMENTED;
   237     break;
   238     case GeckoProcessType_Default: {
   239       {
   240         nsRefPtr<WakeLockListener> wakeLock = InitWakeLock();
   241         mozilla::widget::StartAudioSession();
   242         sMetroApp->ActivateBaseView();
   243         rv = nsBaseAppShell::Run();
   244         mozilla::widget::StopAudioSession();
   245         ShutdownWakeLock(wakeLock);
   246       }
   248       nsCOMPtr<nsIAppStartup> appStartup (do_GetService(NS_APPSTARTUP_CONTRACTID));
   249       bool restartingInMetro = false, restartingInDesktop = false;
   251       if (!appStartup || NS_FAILED(appStartup->GetRestarting(&restartingInDesktop))) {
   252         WinUtils::Log("appStartup->GetRestarting() unsuccessful");
   253       }
   255       if (appStartup && NS_SUCCEEDED(appStartup->GetRestartingTouchEnvironment(&restartingInMetro)) &&
   256           restartingInMetro) {
   257         restartingInDesktop = false;
   258       }
   260       // This calls XRE_metroShutdown() in xre. Shuts down gecko, including
   261       // releasing the profile, and destroys MessagePump.
   262       sMetroApp->Shutdown();
   264       // Handle update restart or browser switch requests
   265       if (restartingInDesktop) {
   266         WinUtils::Log("Relaunching desktop browser");
   267         // We can't call into the ceh to do this. Microsoft prevents switching to
   268         // desktop unless we go through shell execute.
   269         SHELLEXECUTEINFOW sinfo;
   270         memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW));
   271         sinfo.cbSize       = sizeof(SHELLEXECUTEINFOW);
   272         // Per microsoft's metro style enabled desktop browser documentation
   273         // SEE_MASK_FLAG_LOG_USAGE is needed if we want to change from immersive
   274         // mode to desktop mode.
   275         sinfo.fMask        = SEE_MASK_FLAG_LOG_USAGE;
   276         // The ceh will filter out this fake target.
   277         sinfo.lpFile       = L"http://-desktop/";
   278         sinfo.lpVerb       = L"open";
   279         sinfo.lpParameters = L"--desktop-restart";
   280         sinfo.nShow        = SW_SHOWNORMAL;
   281         ShellExecuteEx(&sinfo);
   282       } else if (restartingInMetro) {
   283         HRESULT hresult = WinLaunchDeferredMetroFirefox();
   284         WinUtils::Log("Relaunching metro browser (hr=%X)", hresult);
   285       }
   287       // This will free the real main thread in CoreApplication::Run()
   288       // once winrt cleans up this thread.
   289       sMetroApp->CoreExit();
   290     }
   291     break;
   292   }
   294   return rv;
   295 }
   297 // Called in certain cases where we have async input events in the thread
   298 // queue and need to make sure they get dispatched before the stack unwinds.
   299 void // static
   300 MetroAppShell::MarkEventQueueForPurge()
   301 {
   302   sShouldPurgeThreadQueue = true;
   304   // If we're dispatching native events, wait until the dispatcher is
   305   // off the stack.
   306   if (sIsDispatching) {
   307     return;
   308   }
   310   // Safe to process pending events now
   311   DispatchAllGeckoEvents();
   312 }
   314 // Notification from MetroInput that all events it wanted delivered
   315 // have been dispatched. It is safe to start processing windowing
   316 // events.
   317 void // static
   318 MetroAppShell::InputEventsDispatched()
   319 {
   320   sBlockNativeEvents = false;
   321 }
   323 // static
   324 void
   325 MetroAppShell::DispatchAllGeckoEvents()
   326 {
   327   // Only do this if requested and when we're not shutting down
   328   if (!sShouldPurgeThreadQueue || MetroApp::sGeckoShuttingDown) {
   329     return;
   330   }
   332   NS_ASSERTION(NS_IsMainThread(), "DispatchAllGeckoEvents should be called on the main thread");
   334   sShouldPurgeThreadQueue = false;
   335   sPurgeThreadQueueStart = TimeStamp::Now();
   337   sBlockNativeEvents = true;
   338   nsIThread *thread = NS_GetCurrentThread();
   339   NS_ProcessPendingEvents(thread, PURGE_MAX_TIMEOUT);
   340   sBlockNativeEvents = false;
   341 }
   343 static void
   344 ProcessNativeEvents(CoreProcessEventsOption eventOption)
   345 {
   346   HRESULT hr;
   347   if (!sCoreStatic) {
   348     hr = GetActivationFactory(HStringReference(L"Windows.UI.Core.CoreWindow").Get(), sCoreStatic.GetAddressOf());
   349     NS_ASSERTION(SUCCEEDED(hr), "GetActivationFactory failed?");
   350     AssertHRESULT(hr);
   351   }
   353   ComPtr<ICoreWindow> window;
   354   AssertHRESULT(sCoreStatic->GetForCurrentThread(window.GetAddressOf()));
   355   ComPtr<ICoreDispatcher> dispatcher;
   356   hr = window->get_Dispatcher(&dispatcher);
   357   NS_ASSERTION(SUCCEEDED(hr), "get_Dispatcher failed?");
   358   AssertHRESULT(hr);
   359   dispatcher->ProcessEvents(eventOption);
   360 }
   362 // static
   363 bool
   364 MetroAppShell::ProcessOneNativeEventIfPresent()
   365 {
   366   if (sIsDispatching) {
   367     // Calling into ProcessNativeEvents is harmless, but won't actually process any
   368     // native events. So we log here so we can spot this and get a handle on the
   369     // corner cases where this can happen.
   370     WinUtils::Log("WARNING: Reentrant call into process events detected, returning early.");
   371     return false;
   372   }
   374   {
   375     AutoRestore<bool> dispatching(sIsDispatching);
   376     sIsDispatching = true;
   377     ProcessNativeEvents(CoreProcessEventsOption::CoreProcessEventsOption_ProcessOneIfPresent);
   378   }
   380   DispatchAllGeckoEvents();
   382   return !!HIWORD(::GetQueueStatus(MOZ_QS_ALLEVENT));
   383 }
   385 bool
   386 MetroAppShell::ProcessNextNativeEvent(bool mayWait)
   387 {
   388   // NS_ProcessPendingEvents will process thread events *and* call
   389   // nsBaseAppShell::OnProcessNextEvent to process native events. However
   390   // we do not want native events getting dispatched while we are trying
   391   // to dispatch pending input in DispatchAllGeckoEvents since a native
   392   // event may be a UIA Automation call coming in to check focus.
   393   if (sBlockNativeEvents) {
   394     if ((TimeStamp::Now() - sPurgeThreadQueueStart).ToMilliseconds()
   395         < PURGE_MAX_TIMEOUT) {
   396       return false;
   397     }
   398     sBlockNativeEvents = false;
   399   }
   401   if (ProcessOneNativeEventIfPresent()) {
   402     return true;
   403   }
   404   if (mayWait) {
   405     DWORD result = ::MsgWaitForMultipleObjectsEx(0, nullptr, MSG_WAIT_TIMEOUT,
   406                                                  MOZ_QS_ALLEVENT,
   407                                                  MWMO_INPUTAVAILABLE|MWMO_ALERTABLE);
   408     NS_WARN_IF_FALSE(result != WAIT_FAILED, "Wait failed");
   409   }
   410   return ProcessOneNativeEventIfPresent();
   411 }
   413 void
   414 MetroAppShell::NativeCallback()
   415 {
   416   NS_ASSERTION(NS_IsMainThread(), "Native callbacks must be on the metro main thread");
   418   // We shouldn't process native events during xpcom shutdown - this can
   419   // trigger unexpected xpcom event dispatching for the main thread when
   420   // the thread manager is in the process of shutting down non-main threads,
   421   // resulting in shutdown hangs.
   422   if (MetroApp::sGeckoShuttingDown) {
   423     return;
   424   }
   426   NativeEventCallback();
   427 }
   429 // static
   430 LRESULT CALLBACK
   431 MetroAppShell::EventWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
   432 {
   433   if (uMsg == sAppShellGeckoMsgId) {
   434     MetroAppShell *as = reinterpret_cast<MetroAppShell *>(lParam);
   435     as->NativeCallback();
   436     NS_RELEASE(as);
   437     return TRUE;
   438   }
   439   return DefWindowProc(hwnd, uMsg, wParam, lParam);
   440 }
   442 void
   443 MetroAppShell::ScheduleNativeEventCallback()
   444 {
   445   NS_ADDREF_THIS();
   446   PostMessage(mEventWnd, sAppShellGeckoMsgId, 0, reinterpret_cast<LPARAM>(this));
   447 }
   449 void
   450 MetroAppShell::DoProcessMoreGeckoEvents()
   451 {
   452   ScheduleNativeEventCallback();
   453 }
   455 static HANDLE
   456 PowerCreateRequestDyn(REASON_CONTEXT *context)
   457 {
   458   typedef HANDLE (WINAPI * PowerCreateRequestPtr)(REASON_CONTEXT *context);
   459   static HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
   460   static PowerCreateRequestPtr powerCreateRequest =
   461     (PowerCreateRequestPtr)GetProcAddress(kernel32, "PowerCreateRequest");
   462   if (!powerCreateRequest)
   463     return INVALID_HANDLE_VALUE;
   464   return powerCreateRequest(context);
   465 }
   467 static BOOL
   468 PowerClearRequestDyn(HANDLE powerRequest, POWER_REQUEST_TYPE requestType)
   469 {
   470   typedef BOOL (WINAPI * PowerClearRequestPtr)(HANDLE powerRequest, POWER_REQUEST_TYPE requestType);
   471   static HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
   472   static PowerClearRequestPtr powerClearRequest =
   473     (PowerClearRequestPtr)GetProcAddress(kernel32, "PowerClearRequest");
   474   if (!powerClearRequest)
   475     return FALSE;
   476   return powerClearRequest(powerRequest, requestType);
   477 }
   479 static BOOL
   480 PowerSetRequestDyn(HANDLE powerRequest, POWER_REQUEST_TYPE requestType)
   481 {
   482   typedef BOOL (WINAPI * PowerSetRequestPtr)(HANDLE powerRequest, POWER_REQUEST_TYPE requestType);
   483   static HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
   484   static PowerSetRequestPtr powerSetRequest =
   485     (PowerSetRequestPtr)GetProcAddress(kernel32, "PowerSetRequest");
   486   if (!powerSetRequest)
   487     return FALSE;
   488   return powerSetRequest(powerRequest, requestType);
   489 }
   491 NS_IMETHODIMP
   492 MetroAppShell::Observe(nsISupports *subject, const char *topic,
   493                        const char16_t *data)
   494 {
   495     NS_ENSURE_ARG_POINTER(topic);
   496     if (!strcmp(topic, "dl-start")) {
   497       if (mPowerRequestCount++ == 0) {
   498         WinUtils::Log("Download started - Disallowing suspend");
   499         REASON_CONTEXT context;
   500         context.Version = POWER_REQUEST_CONTEXT_VERSION;
   501         context.Flags = POWER_REQUEST_CONTEXT_SIMPLE_STRING;
   502         context.Reason.SimpleReasonString = L"downloading";
   503         mPowerRequest.own(PowerCreateRequestDyn(&context));
   504         PowerSetRequestDyn(mPowerRequest, PowerRequestExecutionRequired);
   505       }
   506       return NS_OK;
   507     } else if (!strcmp(topic, "dl-done") ||
   508                !strcmp(topic, "dl-cancel") ||
   509                !strcmp(topic, "dl-failed")) {
   510       if (--mPowerRequestCount == 0 && mPowerRequest) {
   511         WinUtils::Log("All downloads ended - Allowing suspend");
   512         PowerClearRequestDyn(mPowerRequest, PowerRequestExecutionRequired); 
   513         mPowerRequest.reset();
   514       }
   515       return NS_OK;
   516     }
   518     return nsBaseAppShell::Observe(subject, topic, data);
   519 }

mercurial