Bug 882142 - Add support to CEH to restart Metro Firefox when it isn't running. r=jimm
authorBrian R. Bondy <netzen@gmail.com>
Tue, 10 Sep 2013 10:24:56 -0400
changeset 146350 2cf4ecefa258051a4aa87580a936a00e215cdb08
parent 146349 225eb3f0434917485a84874148a5a2cb9063fc95
child 146351 f8f04499e62a0acc9801655d35c3ca844e1322dc
push id25255
push userbbondy@mozilla.com
push dateTue, 10 Sep 2013 14:25:16 +0000
treeherdermozilla-central@4d152d0220be [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimm
bugs882142
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 882142 - Add support to CEH to restart Metro Firefox when it isn't running. r=jimm
browser/metro/shell/commandexecutehandler/CEHHelper.cpp
browser/metro/shell/commandexecutehandler/CEHHelper.h
browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp
--- a/browser/metro/shell/commandexecutehandler/CEHHelper.cpp
+++ b/browser/metro/shell/commandexecutehandler/CEHHelper.cpp
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; 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 "CEHHelper.h"
+#include <tlhelp32.h>
 
 #ifdef SHOW_CONSOLE
 #include <io.h> // _open_osfhandle
 #endif
 
 HANDLE sCon;
 LPCWSTR metroDX10Available = L"MetroD3DAvailable";
 
@@ -53,16 +54,63 @@ SetupConsole()
   int fd = _open_osfhandle(reinterpret_cast<intptr_t>(sCon), 0);
   fp = _fdopen(fd, "w");
   *stdout = *fp;
   setvbuf(stdout, nullptr, _IONBF, 0);
 }
 #endif
 
 bool
+IsImmersiveProcessDynamic(HANDLE process)
+{
+  HMODULE user32DLL = LoadLibraryW(L"user32.dll");
+  if (!user32DLL) {
+    return false;
+  }
+
+  typedef BOOL (WINAPI* IsImmersiveProcessFunc)(HANDLE process);
+  IsImmersiveProcessFunc IsImmersiveProcessPtr =
+    (IsImmersiveProcessFunc)GetProcAddress(user32DLL,
+                                           "IsImmersiveProcess");
+  if (!IsImmersiveProcessPtr) {
+    FreeLibrary(user32DLL);
+    return false;
+  }
+
+  BOOL bImmersiveProcess = IsImmersiveProcessPtr(process);
+  FreeLibrary(user32DLL);
+  return bImmersiveProcess;
+}
+
+bool
+IsImmersiveProcessRunning(const wchar_t *processName)
+{
+  bool exists = false;
+  PROCESSENTRY32W entry;
+  entry.dwSize = sizeof(PROCESSENTRY32W);
+
+  HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
+
+  if (Process32First(snapshot, &entry)) {
+    while (!exists && Process32Next(snapshot, &entry)) {
+      if (!_wcsicmp(entry.szExeFile, processName)) {
+        HANDLE process = OpenProcess(GENERIC_READ, FALSE, entry.th32ProcessID);
+        if (IsImmersiveProcessDynamic(process)) {
+          exists = true;
+        }
+        CloseHandle(process);
+      }
+    }
+  }
+
+  CloseHandle(snapshot);
+  return exists;
+}
+
+bool
 IsDX10Available()
 {
   DWORD isDX10Available;
   if (GetDWORDRegKey(metroDX10Available, isDX10Available)) {
     return isDX10Available;
   }
 
   HMODULE dxgiModule = LoadLibraryA("dxgi.dll");
--- a/browser/metro/shell/commandexecutehandler/CEHHelper.h
+++ b/browser/metro/shell/commandexecutehandler/CEHHelper.h
@@ -22,8 +22,10 @@ void Log(const wchar_t *fmt, ...);
 
 #if defined(SHOW_CONSOLE)
 void SetupConsole();
 #endif
 
 bool IsDX10Available();
 bool GetDWORDRegKey(LPCWSTR name, DWORD &value);
 bool SetDWORDRegKey(LPCWSTR name, DWORD value);
+bool IsImmersiveProcessDynamic(HANDLE process);
+bool IsImmersiveProcessRunning(const wchar_t *processName);
--- a/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp
+++ b/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp
@@ -28,19 +28,27 @@
 
 // Heartbeat timer duration used while waiting for an incoming request.
 #define HEARTBEAT_MSEC 1000
 // Total number of heartbeats we wait before giving up and shutting down.
 #define REQUEST_WAIT_TIMEOUT 30
 // Pulled from desktop browser's shell
 #define APP_REG_NAME L"Firefox"
 
+// If we have a restart request, attempt to wait up to RESTART_WAIT_TIMEOUT
+// until the previous instance closes.  We don't want to wait too long
+// because the browser could appear to randomly start for the user. We want
+// it to also be long enough so the browser has time to close.
+#define RESTART_WAIT_PER_RETRY 50
+#define RESTART_WAIT_TIMEOUT 38000
+
 static const WCHAR* kFirefoxExe = L"firefox.exe";
 static const WCHAR* kMetroFirefoxExe = L"firefox.exe";
 static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL";
+static const WCHAR* kMetroRestartCmdLine = L"--metro-restart";
 
 static bool GetDefaultBrowserPath(CStringW& aPathBuffer);
 
 /*
  * Retrieve our module dir path.
  *
  * @aPathBuffer Buffer to fill
  */
@@ -90,16 +98,17 @@ public:
   CExecuteCommandVerb() :
     mRef(1),
     mShellItemArray(nullptr),
     mUnkSite(nullptr),
     mTargetIsFileSystemLink(false),
     mTargetIsDefaultBrowser(false),
     mTargetIsBrowser(false),
     mIsDesktopRequest(true),
+    mIsRestartMetroRequest(false),
     mRequestMet(false)
   {
   }
 
   bool RequestMet() { return mRequestMet; }
   long RefCount() { return mRef; }
 
   // IUnknown
@@ -135,17 +144,22 @@ public:
   {
     mKeyState = aKeyState;
     return S_OK;
   }
 
   IFACEMETHODIMP SetParameters(PCWSTR aParameters)
   {
     Log(L"SetParameters: '%s'", aParameters);
-    mParameters = aParameters;
+
+    if (_wcsicmp(aParameters, kMetroRestartCmdLine) == 0) {
+      mIsRestartMetroRequest = true;
+    } else {
+      mParameters = aParameters;
+    }
     return S_OK;
   }
 
   IFACEMETHODIMP SetPosition(POINT aPoint)
   { return S_OK; }
 
   IFACEMETHODIMP SetShowWindow(int aShowFlag)
   { return S_OK; }
@@ -396,16 +410,17 @@ private:
   CStringW mVerb;
   CStringW mTarget;
   CStringW mParameters;
   bool mTargetIsFileSystemLink;
   bool mTargetIsDefaultBrowser;
   bool mTargetIsBrowser;
   DWORD mKeyState;
   bool mIsDesktopRequest;
+  bool mIsRestartMetroRequest;
   bool mRequestMet;
 };
 
 /*
  * Retrieve the current default browser's path.
  *
  * @aPathBuffer Buffer to fill
  */
@@ -431,30 +446,25 @@ static bool GetDefaultBrowserPath(CStrin
 }
 
 /*
  * Retrieve the app model id of the firefox metro browser.
  *
  * @aPathBuffer Buffer to fill
  * @aCharLength Length of buffer to fill in characters
  */
-static bool GetDefaultBrowserAppModelID(WCHAR* aIDBuffer,
-                                        long aCharLength)
+template <size_t N>
+static bool GetDefaultBrowserAppModelID(WCHAR (&aIDBuffer)[N])
 {
-  if (!aIDBuffer || aCharLength <= 0)
-    return false;
-
-  memset(aIDBuffer, 0, (sizeof(WCHAR)*aCharLength));
-
   HKEY key;
   if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey,
                     0, KEY_READ, &key) != ERROR_SUCCESS) {
     return false;
   }
-  DWORD len = aCharLength * sizeof(WCHAR);
+  DWORD len = sizeof(aIDBuffer);
   memset(aIDBuffer, 0, len);
   if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr,
                        (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) {
     RegCloseKey(key);
     return false;
   }
   RegCloseKey(key);
   return true;
@@ -612,79 +622,132 @@ class AutoSetRequestMet
 public:
   explicit AutoSetRequestMet(bool* aFlag) :
     mFlag(aFlag) {}
   ~AutoSetRequestMet() { if (mFlag) *mFlag = true; }
 private:
   bool* mFlag;
 };
 
+static HRESULT
+PrepareActivationManager(CComPtr<IApplicationActivationManager> &activateMgr)
+{
+  HRESULT hr = activateMgr.CoCreateInstance(CLSID_ApplicationActivationManager, NULL, CLSCTX_LOCAL_SERVER);
+  if (FAILED(hr)) {
+    Log(L"CoCreateInstance failed, launching on desktop.");
+    return E_FAIL;
+  }
+
+  // Hand off focus rights to the out-of-process activation server. Without
+  // this the metro interface won't launch.
+  hr = CoAllowSetForegroundWindow(activateMgr, nullptr);
+  if (FAILED(hr)) {
+    Log(L"CoAllowSetForegroundWindow result %X", hr);
+    return E_FAIL;
+  }
+
+  return S_OK;
+}
+
+DWORD WINAPI
+DelayedExecuteThread(LPVOID param)
+{
+  Log(L"Starting delayed execute thread...");
+  bool &bRequestMet(*(bool*)param);
+  AutoSetRequestMet asrm(&bRequestMet);
+
+  CoInitialize(NULL);
+
+  CComPtr<IApplicationActivationManager> activateMgr;
+  if (FAILED(PrepareActivationManager(activateMgr))) {
+      Log(L"Warning: Could not prepare activation manager");
+  }
+
+  size_t currentWaitTime = 0;
+  while(currentWaitTime < RESTART_WAIT_TIMEOUT) {
+    if (!IsImmersiveProcessRunning(kMetroFirefoxExe))
+      break;
+    currentWaitTime += RESTART_WAIT_PER_RETRY;
+    Sleep(RESTART_WAIT_PER_RETRY);
+  }
+
+  Log(L"Done waiting, getting app ID");
+  // Activate the application as long as we can obtian the appModelID
+  WCHAR appModelID[256];
+  if (GetDefaultBrowserAppModelID(appModelID)) {
+    Log(L"Activating application");
+    DWORD processID;
+    HRESULT hr = activateMgr->ActivateApplication(appModelID, L"", AO_NONE, &processID);
+    if (SUCCEEDED(hr)) {
+      Log(L"Activate application succeeded");
+    } else {
+      Log(L"Activate application failed! (%x)", hr);
+    }
+  }
+
+  CoUninitialize();
+  return 0;
+}
+
 IFACEMETHODIMP CExecuteCommandVerb::Execute()
 {
   Log(L"Execute()");
 
+  if (!mTarget.GetLength()) {
+    // We shut down when this flips to true
+    mRequestMet = true;
+    return E_FAIL;
+  }
+
+  if (mIsRestartMetroRequest) {
+    HANDLE thread = CreateThread(NULL, 0, DelayedExecuteThread, &mRequestMet, 0, NULL);
+    CloseHandle(thread);
+    return S_OK;
+  }
+
   // We shut down when this flips to true
   AutoSetRequestMet asrm(&mRequestMet);
 
-  if (!mTarget.GetLength()) {
-    return E_FAIL;
-  }
-
   // Launch on the desktop
   if (mIsDesktopRequest) {
     LaunchDesktopBrowser();
     return S_OK;
   }
 
-  // Launch into Metro
-  IApplicationActivationManager* activateMgr = nullptr;
-  DWORD processID;
-  if (FAILED(CoCreateInstance(CLSID_ApplicationActivationManager, nullptr,
-                              CLSCTX_LOCAL_SERVER,
-                              IID_IApplicationActivationManager,
-                              (void**)&activateMgr))) {
-    Log(L"CoCreateInstance failed, launching on desktop.");
-    LaunchDesktopBrowser();
-    return S_OK;
+  CComPtr<IApplicationActivationManager> activateMgr;
+  if (!PrepareActivationManager(activateMgr)) {
+      LaunchDesktopBrowser();
+      return S_OK;
   }
-  
+
   HRESULT hr;
   WCHAR appModelID[256];
-  if (!GetDefaultBrowserAppModelID(appModelID, (sizeof(appModelID)/sizeof(WCHAR)))) {
+  if (!GetDefaultBrowserAppModelID(appModelID)) {
     Log(L"GetDefaultBrowserAppModelID failed, launching on desktop.");
-    activateMgr->Release();
     LaunchDesktopBrowser();
     return S_OK;
   }
 
-  // Hand off focus rights to the out-of-process activation server. Without
-  // this the metro interface won't launch.
-  hr = CoAllowSetForegroundWindow(activateMgr, nullptr);
-  if (FAILED(hr)) {
-    Log(L"CoAllowSetForegroundWindow result %X", hr);
-    activateMgr->Release();
-    return false;
-  }
-
   Log(L"Metro Launch: verb:%s appid:%s params:%s", mVerb, appModelID, mTarget); 
 
+
   // shortcuts to the application
+  DWORD processID;
   if (mTargetIsDefaultBrowser) {
     hr = activateMgr->ActivateApplication(appModelID, L"", AO_NONE, &processID);
     Log(L"ActivateApplication result %X", hr);
   // files
   } else if (mTargetIsFileSystemLink) {
     hr = activateMgr->ActivateForFile(appModelID, mShellItemArray, mVerb, &processID);
     Log(L"ActivateForFile result %X", hr);
   // protocols
   } else {
     hr = activateMgr->ActivateForProtocol(appModelID, mShellItemArray, &processID);
     Log(L"ActivateForProtocol result %X", hr);
   }
-  activateMgr->Release();
   return S_OK;
 }
 
 class ClassFactory : public IClassFactory 
 {
 public:
   ClassFactory(IUnknown *punkObject);
   ~ClassFactory();