browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* -*- Mode: C++; tab-width: 2; 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 file,
     4  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "CEHHelper.h"
     9 #include <objbase.h>
    10 #include <combaseapi.h>
    11 #include <atlcore.h>
    12 #include <atlstr.h>
    13 #include <wininet.h>
    14 #include <shlobj.h>
    15 #include <shlwapi.h>
    16 #include <propkey.h>
    17 #include <propvarutil.h>
    18 #include <stdio.h>
    19 #include <stdlib.h>
    20 #include <strsafe.h>
    21 #include <io.h>
    22 #include <shellapi.h>
    24 #ifdef SHOW_CONSOLE
    25 #define DEBUG_DELAY_SHUTDOWN 1
    26 #endif
    28 // Heartbeat timer duration used while waiting for an incoming request.
    29 #define HEARTBEAT_MSEC 250
    30 // Total number of heartbeats we wait before giving up and shutting down.
    31 #define REQUEST_WAIT_TIMEOUT 30
    32 // Pulled from desktop browser's shell
    33 #define APP_REG_NAME L"Firefox"
    35 const WCHAR* kFirefoxExe = L"firefox.exe";
    36 static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL";
    37 static const WCHAR* kMetroRestartCmdLine = L"--metro-restart";
    38 static const WCHAR* kMetroUpdateCmdLine = L"--metro-update";
    39 static const WCHAR* kDesktopRestartCmdLine = L"--desktop-restart";
    40 static const WCHAR* kNsisLaunchCmdLine = L"--launchmetro";
    41 static const WCHAR* kExplorerLaunchCmdLine = L"-Embedding";
    43 static bool GetDefaultBrowserPath(CStringW& aPathBuffer);
    45 /*
    46  * Retrieve our module dir path.
    47  *
    48  * @aPathBuffer Buffer to fill
    49  */
    50 static bool GetModulePath(CStringW& aPathBuffer)
    51 {
    52   WCHAR buffer[MAX_PATH];
    53   memset(buffer, 0, sizeof(buffer));
    55   if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) {
    56     Log(L"GetModuleFileName failed.");
    57     return false;
    58   }
    60   WCHAR* slash = wcsrchr(buffer, '\\');
    61   if (!slash)
    62     return false;
    63   *slash = '\0';
    65   aPathBuffer = buffer;
    66   return true;
    67 }
    70 template <class T>void SafeRelease(T **ppT)
    71 {
    72   if (*ppT) {
    73     (*ppT)->Release();
    74     *ppT = nullptr;
    75   }
    76 }
    78 template <class T> HRESULT SetInterface(T **ppT, IUnknown *punk)
    79 {
    80   SafeRelease(ppT);
    81   return punk ? punk->QueryInterface(ppT) : E_NOINTERFACE;
    82 }
    84 class __declspec(uuid("5100FEC1-212B-4BF5-9BF8-3E650FD794A3"))
    85   CExecuteCommandVerb : public IExecuteCommand,
    86                         public IObjectWithSelection,
    87                         public IInitializeCommand,
    88                         public IObjectWithSite,
    89                         public IExecuteCommandApplicationHostEnvironment
    90 {
    91 public:
    93   CExecuteCommandVerb() :
    94     mRef(0),
    95     mShellItemArray(nullptr),
    96     mUnkSite(nullptr),
    97     mTargetIsFileSystemLink(false),
    98     mTargetIsDefaultBrowser(false),
    99     mTargetIsBrowser(false),
   100     mRequestType(DEFAULT_LAUNCH),
   101     mRequestMet(false),
   102     mDelayedLaunchType(NONE),
   103     mVerb(L"open")
   104   {
   105   }
   107   ~CExecuteCommandVerb()
   108   {
   109   }
   111   bool RequestMet() { return mRequestMet; }
   112   void SetRequestMet();
   113   long RefCount() { return mRef; }
   114   void HeartBeat();
   116   // IUnknown
   117   IFACEMETHODIMP QueryInterface(REFIID aRefID, void **aInt)
   118   {
   119     static const QITAB qit[] = {
   120       QITABENT(CExecuteCommandVerb, IExecuteCommand),
   121       QITABENT(CExecuteCommandVerb, IObjectWithSelection),
   122       QITABENT(CExecuteCommandVerb, IInitializeCommand),
   123       QITABENT(CExecuteCommandVerb, IObjectWithSite),
   124       QITABENT(CExecuteCommandVerb, IExecuteCommandApplicationHostEnvironment),
   125       { 0 },
   126     };
   127     return QISearch(this, qit, aRefID, aInt);
   128   }
   130   IFACEMETHODIMP_(ULONG) AddRef()
   131   {
   132     return InterlockedIncrement(&mRef);
   133   }
   135   IFACEMETHODIMP_(ULONG) Release()
   136   {
   137     long cRef = InterlockedDecrement(&mRef);
   138     if (!cRef) {
   139       delete this;
   140     }
   141     return cRef;
   142   }
   144   // IExecuteCommand
   145   IFACEMETHODIMP SetKeyState(DWORD aKeyState)
   146   {
   147     mKeyState = aKeyState;
   148     return S_OK;
   149   }
   151   IFACEMETHODIMP SetParameters(PCWSTR aParameters)
   152   {
   153     Log(L"SetParameters: '%s'", aParameters);
   155     if (!_wcsicmp(aParameters, kMetroRestartCmdLine)) {
   156       mRequestType = METRO_RESTART;
   157     } else if (_wcsicmp(aParameters, kMetroUpdateCmdLine) == 0) {
   158       mRequestType = METRO_UPDATE;
   159     } else if (_wcsicmp(aParameters, kDesktopRestartCmdLine) == 0) {
   160       mRequestType = DESKTOP_RESTART;
   161     } else {
   162       mParameters = aParameters;
   163     }
   164     return S_OK;
   165   }
   167   IFACEMETHODIMP SetPosition(POINT aPoint)
   168   { return S_OK; }
   170   IFACEMETHODIMP SetShowWindow(int aShowFlag)
   171   { return S_OK; }
   173   IFACEMETHODIMP SetNoShowUI(BOOL aNoUI)
   174   { return S_OK; }
   176   IFACEMETHODIMP SetDirectory(PCWSTR aDirPath)
   177   { return S_OK; }
   179   IFACEMETHODIMP Execute();
   181   // IObjectWithSelection
   182   IFACEMETHODIMP SetSelection(IShellItemArray *aArray)
   183   {
   184     if (!aArray) {
   185       return E_FAIL;
   186     }
   188     SetInterface(&mShellItemArray, aArray);
   190     DWORD count = 0;
   191     aArray->GetCount(&count);
   192     if (!count) {
   193       return E_FAIL;
   194     }
   196 #ifdef SHOW_CONSOLE
   197     Log(L"SetSelection param count: %d", count);
   198     for (DWORD idx = 0; idx < count; idx++) {
   199       IShellItem* item = nullptr;
   200       if (SUCCEEDED(aArray->GetItemAt(idx, &item))) {
   201         LPWSTR str = nullptr;
   202         if (FAILED(item->GetDisplayName(SIGDN_FILESYSPATH, &str))) {
   203           if (FAILED(item->GetDisplayName(SIGDN_URL, &str))) {
   204             Log(L"Failed to get a shell item array item.");
   205             item->Release();
   206             continue;
   207           }
   208         }
   209         item->Release();
   210         Log(L"SetSelection param: '%s'", str);
   211         CoTaskMemFree(str);
   212       }
   213     }
   214 #endif
   216     IShellItem* item = nullptr;
   217     if (FAILED(aArray->GetItemAt(0, &item))) {
   218       return E_FAIL;
   219     }
   221     bool isFileSystem = false;
   222     if (!SetTargetPath(item) || !mTarget.GetLength()) {
   223       Log(L"SetTargetPath failed.");
   224       return E_FAIL;
   225     }
   226     item->Release();
   228     Log(L"SetSelection target: %s", mTarget);
   229     return S_OK;
   230   }
   232   IFACEMETHODIMP GetSelection(REFIID aRefID, void **aInt)
   233   {
   234     *aInt = nullptr;
   235     return mShellItemArray ? mShellItemArray->QueryInterface(aRefID, aInt) : E_FAIL;
   236   }
   238   // IInitializeCommand
   239   IFACEMETHODIMP Initialize(PCWSTR aVerb, IPropertyBag* aPropBag)
   240   {
   241     if (!aVerb)
   242       return E_FAIL;
   243     // 'open', 'edit', etc. Based on our registry settings
   244     Log(L"Initialize(%s)", aVerb);
   245     mVerb = aVerb;
   246     return S_OK;
   247   }
   249   // IObjectWithSite
   250   IFACEMETHODIMP SetSite(IUnknown *aUnkSite)
   251   {
   252     SetInterface(&mUnkSite, aUnkSite);
   253     return S_OK;
   254   }
   256   IFACEMETHODIMP GetSite(REFIID aRefID, void **aInt)
   257   {
   258     *aInt = nullptr;
   259     return mUnkSite ? mUnkSite->QueryInterface(aRefID, aInt) : E_FAIL;
   260   }
   262   // IExecuteCommandApplicationHostEnvironment
   263   IFACEMETHODIMP GetValue(AHE_TYPE *aLaunchType)
   264   {
   265     Log(L"IExecuteCommandApplicationHostEnvironment::GetValue()");
   266     *aLaunchType = GetLaunchType();
   267     return S_OK;
   268   }
   270   /**
   271    * Choose the appropriate launch type based on the user's previously chosen
   272    * host environment, along with system constraints.
   273    *
   274    * AHE_DESKTOP = 0, AHE_IMMERSIVE = 1
   275    */
   276   AHE_TYPE GetLaunchType()
   277   {
   278     AHE_TYPE ahe = GetLastAHE();
   279     Log(L"Previous AHE: %d", ahe);
   281     // Default launch settings from GetLastAHE() can be overriden by
   282     // custom parameter values we receive. 
   283     if (mRequestType == DESKTOP_RESTART) {
   284       Log(L"Restarting in desktop host environment.");
   285       return AHE_DESKTOP;
   286     } else if (mRequestType == METRO_RESTART) {
   287       Log(L"Restarting in metro host environment.");
   288       ahe = AHE_IMMERSIVE;
   289     } else if (mRequestType == METRO_UPDATE) {
   290       // Shouldn't happen from GetValue above, but might from other calls.
   291       ahe = AHE_IMMERSIVE;
   292     }
   294     if (ahe == AHE_IMMERSIVE) {
   295       if (!IsDefaultBrowser()) {
   296         Log(L"returning AHE_DESKTOP because we are not the default browser");
   297         return AHE_DESKTOP;
   298       }
   300       if (!IsDX10Available()) {
   301         Log(L"returning AHE_DESKTOP because DX10 is not available");
   302         return AHE_DESKTOP;
   303       }
   304     }
   305     return ahe;
   306   }
   308   bool DefaultLaunchIsDesktop()
   309   {
   310     return GetLaunchType() == AHE_DESKTOP;
   311   }
   313   bool DefaultLaunchIsMetro()
   314   {
   315     return GetLaunchType() == AHE_IMMERSIVE;
   316   }
   318   /*
   319    * Retrieve the target path if it is the default browser
   320    * or if not default, retreives the target path if it is a firefox browser
   321    * or if the target is not firefox, relies on a hack to get the
   322    * 'module dir path\firefox.exe'
   323    * The reason why it's not good to rely on the CEH path is because there is
   324    * no guarantee win8 will use the CEH at our expected path.  It has an in
   325    * memory cache even if the registry is updated for the CEH path.
   326    *
   327    * @aPathBuffer Buffer to fill
   328    */
   329   bool GetDesktopBrowserPath(CStringW& aPathBuffer)
   330   {
   331     // If the target was the default browser itself then return early.  Otherwise
   332     // rely on a hack to check CEH path and calculate it relative to it.
   334     if (mTargetIsDefaultBrowser || mTargetIsBrowser) {
   335       aPathBuffer = mTarget;
   336       return true;
   337     }
   339     if (!GetModulePath(aPathBuffer))
   340       return false;
   342     // ceh.exe sits in dist/bin root with the desktop browser. Since this
   343     // is a firefox only component, this hardcoded filename is ok.
   344     aPathBuffer.Append(L"\\");
   345     aPathBuffer.Append(kFirefoxExe);
   346     return true;
   347   }
   349   bool IsDefaultBrowser()
   350   {
   351     IApplicationAssociationRegistration* pAAR;
   352     HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
   353                                   nullptr,
   354                                   CLSCTX_INPROC,
   355                                   IID_IApplicationAssociationRegistration,
   356                                   (void**)&pAAR);
   357     if (FAILED(hr))
   358       return false;
   360     BOOL res = FALSE;
   361     hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE,
   362                                     APP_REG_NAME,
   363                                     &res);
   364     Log(L"QueryAppIsDefaultAll: %d", res);
   365     if (!res) {
   366       pAAR->Release();
   367       return false;
   368     }
   369     // Make sure the Prog ID matches what we have
   370     LPWSTR registeredApp;
   371     hr = pAAR->QueryCurrentDefault(L"http", AT_URLPROTOCOL, AL_EFFECTIVE,
   372                                     &registeredApp);
   373     pAAR->Release();
   374     Log(L"QueryCurrentDefault: %X", hr);
   375     if (FAILED(hr))
   376       return false;
   378     Log(L"registeredApp=%s", registeredApp);
   379     bool result = !wcsicmp(registeredApp, kDefaultMetroBrowserIDPathKey);
   380     CoTaskMemFree(registeredApp);
   381     if (!result)
   382       return false;
   384     // If the registry points another browser's path,
   385     // activating the Metro browser will fail. So fallback to the desktop.
   386     CStringW selfPath;
   387     GetDesktopBrowserPath(selfPath);
   388     CStringW browserPath;
   389     GetDefaultBrowserPath(browserPath);
   391     return !selfPath.CompareNoCase(browserPath);
   392   }
   394   /*
   395    * Helper for nsis installer when it wants to launch the
   396    * default metro browser.
   397    */
   398   void CommandLineMetroLaunch()
   399   {
   400     mTargetIsDefaultBrowser = true;
   401     LaunchMetroBrowser();
   402   }
   404 private:
   405   void LaunchDesktopBrowser();
   406   bool LaunchMetroBrowser();
   407   bool SetTargetPath(IShellItem* aItem);
   408   bool TestForUpdateLock();
   410   /*
   411    * Defines the type of startup request we receive.
   412    */
   413   enum RequestType {
   414     DEFAULT_LAUNCH,
   415     DESKTOP_RESTART,
   416     METRO_RESTART,
   417     METRO_UPDATE,
   418   };
   420   RequestType mRequestType;
   422   /*
   423    * Defines the type of delayed launch we might do.
   424    */
   425   enum DelayedLaunchType {
   426     NONE,
   427     DESKTOP,
   428     METRO,
   429   };
   431   DelayedLaunchType mDelayedLaunchType;
   433   long mRef;
   434   IShellItemArray *mShellItemArray;
   435   IUnknown *mUnkSite;
   436   CStringW mVerb;
   437   CStringW mTarget;
   438   CStringW mParameters;
   439   bool mTargetIsFileSystemLink;
   440   bool mTargetIsDefaultBrowser;
   441   bool mTargetIsBrowser;
   442   DWORD mKeyState;
   443   bool mRequestMet;
   444 };
   446 /*
   447  * Retrieve the current default browser's path.
   448  *
   449  * @aPathBuffer Buffer to fill
   450  */
   451 static bool GetDefaultBrowserPath(CStringW& aPathBuffer)
   452 {
   453   WCHAR buffer[MAX_PATH];
   454   DWORD length = MAX_PATH;
   456   if (FAILED(AssocQueryStringW(ASSOCF_NOTRUNCATE | ASSOCF_INIT_IGNOREUNKNOWN,
   457                                ASSOCSTR_EXECUTABLE,
   458                                kDefaultMetroBrowserIDPathKey, nullptr,
   459                                buffer, &length))) {
   460     Log(L"AssocQueryString failed.");
   461     return false;
   462   }
   464   // sanity check
   465   if (lstrcmpiW(PathFindFileNameW(buffer), kFirefoxExe))
   466     return false;
   468   aPathBuffer = buffer;
   469   return true;
   470 }
   472 /*
   473  * Retrieve the app model id of the firefox metro browser.
   474  *
   475  * @aPathBuffer Buffer to fill
   476  * @aCharLength Length of buffer to fill in characters
   477  */
   478 template <size_t N>
   479 static bool GetDefaultBrowserAppModelID(WCHAR (&aIDBuffer)[N])
   480 {
   481   HKEY key;
   482   if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey,
   483                     0, KEY_READ, &key) != ERROR_SUCCESS) {
   484     return false;
   485   }
   486   DWORD len = sizeof(aIDBuffer);
   487   memset(aIDBuffer, 0, len);
   488   if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr,
   489                        (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) {
   490     RegCloseKey(key);
   491     return false;
   492   }
   493   RegCloseKey(key);
   494   return true;
   495 }
   497 namespace {
   498   const FORMATETC kPlainTextFormat =
   499     {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
   500   const FORMATETC kPlainTextWFormat =
   501     {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
   502 }
   504 bool HasPlainText(IDataObject* aDataObj) {
   505   return SUCCEEDED(aDataObj->QueryGetData((FORMATETC*)&kPlainTextWFormat)) ||
   506       SUCCEEDED(aDataObj->QueryGetData((FORMATETC*)&kPlainTextFormat));
   507 }
   509 bool GetPlainText(IDataObject* aDataObj, CStringW& cstrText)
   510 {
   511   if (!HasPlainText(aDataObj))
   512     return false;
   514   STGMEDIUM store;
   516   // unicode text
   517   if (SUCCEEDED(aDataObj->GetData((FORMATETC*)&kPlainTextWFormat, &store))) {
   518     // makes a copy
   519     cstrText = static_cast<LPCWSTR>(GlobalLock(store.hGlobal));
   520     GlobalUnlock(store.hGlobal);
   521     ReleaseStgMedium(&store);
   522     return true;
   523   }
   525   // ascii text
   526   if (SUCCEEDED(aDataObj->GetData((FORMATETC*)&kPlainTextFormat, &store))) {
   527     // makes a copy
   528     cstrText = static_cast<char*>(GlobalLock(store.hGlobal));
   529     GlobalUnlock(store.hGlobal);
   530     ReleaseStgMedium(&store);
   531     return true;
   532   }
   534   return false;
   535 }
   537 /*
   538  * Updates the current target based on the contents of
   539  * a shell item.
   540  */
   541 bool CExecuteCommandVerb::SetTargetPath(IShellItem* aItem)
   542 {
   543   if (!aItem)
   544     return false;
   546   CString cstrText;
   547   CComPtr<IDataObject> object;
   548   // Check the underlying data object first to insure we get
   549   // absolute uri. See chromium bug 157184.
   550   if (SUCCEEDED(aItem->BindToHandler(nullptr, BHID_DataObject,
   551                                      IID_IDataObject,
   552                                      reinterpret_cast<void**>(&object))) &&
   553       GetPlainText(object, cstrText)) {
   554     wchar_t scheme[16];
   555     URL_COMPONENTS components = {0};
   556     components.lpszScheme = scheme;
   557     components.dwSchemeLength = sizeof(scheme)/sizeof(scheme[0]);
   558     components.dwStructSize = sizeof(components);
   559     // note, more advanced use may have issues with paths with spaces.
   560     if (!InternetCrackUrlW(cstrText, 0, 0, &components)) {
   561       Log(L"Failed to identify object text '%s'", cstrText);
   562       return false;
   563     }
   565     mTargetIsFileSystemLink = (components.nScheme == INTERNET_SCHEME_FILE);
   566     mTarget = cstrText;
   568     return true;
   569   }
   571   Log(L"No data object or data object has no text.");
   573   // Use the shell item display name
   574   LPWSTR str = nullptr;
   575   mTargetIsFileSystemLink = true;
   576   if (FAILED(aItem->GetDisplayName(SIGDN_FILESYSPATH, &str))) {
   577     mTargetIsFileSystemLink = false;
   578     if (FAILED(aItem->GetDisplayName(SIGDN_URL, &str))) {
   579       Log(L"Failed to get parameter string.");
   580       return false;
   581     }
   582   }
   583   mTarget = str;
   584   CoTaskMemFree(str);
   586   CStringW defaultPath;
   587   GetDefaultBrowserPath(defaultPath);
   588   mTargetIsDefaultBrowser = !mTarget.CompareNoCase(defaultPath);
   590   size_t browserEXELen = wcslen(kFirefoxExe);
   591   mTargetIsBrowser = mTarget.GetLength() >= browserEXELen &&
   592                      !mTarget.Right(browserEXELen).CompareNoCase(kFirefoxExe);
   594   return true;
   595 }
   597 /*
   598  * Desktop launch - Launch the destop browser to display the current
   599  * target using shellexecute.
   600  */
   601 void LaunchDesktopBrowserWithParams(CStringW& aBrowserPath, CStringW& aVerb,
   602                                     CStringW& aTarget, CStringW& aParameters,
   603                                     bool aTargetIsDefaultBrowser, bool aTargetIsBrowser)
   604 {
   605   // If a taskbar shortcut, link or local file is clicked, the target will
   606   // be the browser exe or file.  Don't pass in -url for the target if the
   607   // target is known to be a browser.  Otherwise, one instance of Firefox
   608   // will try to open another instance.
   609   CStringW params;
   610   if (!aTargetIsDefaultBrowser && !aTargetIsBrowser && !aTarget.IsEmpty()) {
   611     // Fallback to the module path if it failed to get the default browser.
   612     GetDefaultBrowserPath(aBrowserPath);
   613     params += "-url ";
   614     params += "\"";
   615     params += aTarget;
   616     params += "\"";
   617   }
   619   // Tack on any extra parameters we received (for example -profilemanager)
   620   if (!aParameters.IsEmpty()) {
   621     params += " ";
   622     params += aParameters;
   623   }
   625   Log(L"Desktop Launch: verb:'%s' exe:'%s' params:'%s'", aVerb, aBrowserPath, params);
   627   // Relaunch in Desktop mode uses a special URL to trick Windows into
   628   // switching environments. We shouldn't actually try to open this URL.
   629   if (!_wcsicmp(aTarget, L"http://-desktop/")) {
   630     // Ignore any params and just launch on desktop
   631     params.Empty();
   632   }
   634   PROCESS_INFORMATION procInfo;
   635   STARTUPINFO startInfo;
   636   memset(&procInfo, 0, sizeof(PROCESS_INFORMATION));
   637   memset(&startInfo, 0, sizeof(STARTUPINFO));
   639   startInfo.cb = sizeof(STARTUPINFO);
   640   startInfo.dwFlags = STARTF_USESHOWWINDOW;
   641   startInfo.wShowWindow = SW_SHOWNORMAL;
   643   BOOL result =
   644     CreateProcessW(aBrowserPath, static_cast<LPWSTR>(params.GetBuffer()),
   645                    NULL, NULL, FALSE, 0, NULL, NULL, &startInfo, &procInfo);
   646   if (!result) {
   647     Log(L"CreateProcess failed! (%d)", GetLastError());
   648     return;
   649   }
   650   // Hand off foreground/focus rights to the browser we create. If we don't
   651   // do this the ceh will keep ownership causing desktop firefox to launch
   652   // deactivated.
   653   if (!AllowSetForegroundWindow(procInfo.dwProcessId)) {
   654     Log(L"AllowSetForegroundWindow failed! (%d)", GetLastError());
   655   }
   656   CloseHandle(procInfo.hThread);
   657   CloseHandle(procInfo.hProcess);
   658   Log(L"Desktop browser process id: %d", procInfo.dwProcessId);
   659 }
   661 void
   662 CExecuteCommandVerb::LaunchDesktopBrowser()
   663 {
   664   CStringW browserPath;
   665   if (!GetDesktopBrowserPath(browserPath)) {
   666     return;
   667   }
   669   LaunchDesktopBrowserWithParams(browserPath, mVerb, mTarget, mParameters,
   670                                  mTargetIsDefaultBrowser, mTargetIsBrowser);
   671 }
   673 void
   674 CExecuteCommandVerb::HeartBeat()
   675 {
   676   if (mRequestType == METRO_UPDATE && mDelayedLaunchType == DESKTOP &&
   677       !IsMetroProcessRunning()) {
   678     mDelayedLaunchType = NONE;
   679     LaunchDesktopBrowser();
   680     SetRequestMet();
   681   }
   682   if (mDelayedLaunchType == METRO && !TestForUpdateLock()) {
   683     mDelayedLaunchType = NONE;
   684     LaunchMetroBrowser();
   685     SetRequestMet();
   686   }
   687 }
   689 bool
   690 CExecuteCommandVerb::TestForUpdateLock()
   691 {
   692   CStringW browserPath;
   693   if (!GetDefaultBrowserPath(browserPath)) {
   694     return false;
   695   }
   697   HANDLE hFile = CreateFileW(browserPath,
   698                              FILE_EXECUTE, FILE_SHARE_READ|FILE_SHARE_WRITE,
   699                              nullptr, OPEN_EXISTING, 0, nullptr);
   700   if (hFile != INVALID_HANDLE_VALUE) {
   701     CloseHandle(hFile);
   702     return false;
   703   }
   704   return true;
   705 }
   707 bool
   708 CExecuteCommandVerb::LaunchMetroBrowser()
   709 {
   710   HRESULT hr;
   712   CComPtr<IApplicationActivationManager> activateMgr;
   713   hr = activateMgr.CoCreateInstance(CLSID_ApplicationActivationManager,
   714                                     nullptr, CLSCTX_LOCAL_SERVER);
   715   if (FAILED(hr)) {
   716     Log(L"CoCreateInstance failed, launching on desktop.");
   717     return false;
   718   }
   720   // Hand off focus rights to the out-of-process activation server. This will
   721   // fail if we don't have the rights to begin with. Log but don't bail.
   722   hr = CoAllowSetForegroundWindow(activateMgr, nullptr);
   723   if (FAILED(hr)) {
   724     Log(L"CoAllowSetForegroundWindow result %X", hr);
   725   }
   727   WCHAR appModelID[256];
   728   if (!GetDefaultBrowserAppModelID(appModelID)) {
   729     Log(L"GetDefaultBrowserAppModelID failed.");
   730     return false;
   731   }
   733   Log(L"Metro Launch: verb:'%s' appid:'%s' params:'%s'", mVerb, appModelID, mTarget);
   735   // shortcuts to the application
   736   DWORD processID;
   737   if (mTargetIsDefaultBrowser) {
   738     hr = activateMgr->ActivateApplication(appModelID, L"", AO_NONE, &processID);
   739     Log(L"ActivateApplication result %X", hr);
   740   // files
   741   } else if (mTargetIsFileSystemLink) {
   742     hr = activateMgr->ActivateForFile(appModelID, mShellItemArray, mVerb, &processID);
   743     Log(L"ActivateForFile result %X", hr);
   744   // protocols
   745   } else {
   746     hr = activateMgr->ActivateForProtocol(appModelID, mShellItemArray, &processID);
   747     Log(L"ActivateForProtocol result %X", hr);
   748   }
   749   return true;
   750 }
   752 void CExecuteCommandVerb::SetRequestMet()
   753 {
   754   SafeRelease(&mShellItemArray);
   755   SafeRelease(&mUnkSite);
   756   mRequestMet = true;
   757   Log(L"Request met, exiting.");
   758 }
   760 IFACEMETHODIMP CExecuteCommandVerb::Execute()
   761 {
   762   Log(L"Execute()");
   764   if (!mTarget.GetLength()) {
   765     // We shut down when this flips to true
   766     SetRequestMet();
   767     return E_FAIL;
   768   }
   770   if (!IsDX10Available()) {
   771     Log(L"Can't launch in metro due to missing hardware acceleration features.");
   772     mRequestType = DESKTOP_RESTART;
   773   } 
   775   // Deal with metro restart for an update - launch desktop with a command
   776   // that tells it to run updater then launch the metro browser.
   777   if (mRequestType == METRO_UPDATE) {
   778     // We'll complete this in the heart beat callback from the main msg loop.
   779     // We do this because the last browser instance makes this call to Execute
   780     // sync. So we want to make sure it's completely shutdown before we do
   781     // the update.
   782     mParameters = kMetroUpdateCmdLine;
   783     mDelayedLaunchType = DESKTOP;
   784     return S_OK;
   785   }
   787   // Launch on the desktop
   788   if (mRequestType == DESKTOP_RESTART ||
   789       (mRequestType == DEFAULT_LAUNCH && DefaultLaunchIsDesktop())) {
   790     LaunchDesktopBrowser();
   791     SetRequestMet();
   792     return S_OK;
   793   }
   795   // If we have an update in the works, don't try to activate yet,
   796   // delay until the lock is removed.
   797   if (TestForUpdateLock()) {
   798     mDelayedLaunchType = METRO;
   799     return S_OK;
   800   }
   802   LaunchMetroBrowser();
   803   SetRequestMet();
   804   return S_OK;
   805 }
   807 class ClassFactory : public IClassFactory 
   808 {
   809 public:
   810   ClassFactory(IUnknown *punkObject);
   811   ~ClassFactory();
   812   STDMETHODIMP Register(CLSCTX classContent, REGCLS classUse);
   813   STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
   814   STDMETHODIMP_(ULONG) AddRef() { return 2; }
   815   STDMETHODIMP_(ULONG) Release() { return 1; }
   816   STDMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv);
   817   STDMETHODIMP LockServer(BOOL);
   818 private:
   819   IUnknown* mUnkObject;
   820   DWORD mRegID;
   821 };
   823 ClassFactory::ClassFactory(IUnknown* aUnkObj) :
   824   mUnkObject(aUnkObj),
   825   mRegID(0)
   826 {
   827   if (mUnkObject) {
   828     mUnkObject->AddRef();
   829   }
   830 }
   832 ClassFactory::~ClassFactory()
   833 {
   834   if (mRegID) {
   835     CoRevokeClassObject(mRegID);
   836   }
   837   mUnkObject->Release();
   838 }
   840 STDMETHODIMP
   841 ClassFactory::Register(CLSCTX aClass, REGCLS aUse)
   842 {
   843   return CoRegisterClassObject(__uuidof(CExecuteCommandVerb),
   844                                static_cast<IClassFactory *>(this),
   845                                aClass, aUse, &mRegID);
   846 }
   848 STDMETHODIMP
   849 ClassFactory::QueryInterface(REFIID riid, void **ppv)
   850 {
   851   IUnknown *punk = nullptr;
   852   if (riid == IID_IUnknown || riid == IID_IClassFactory) {
   853     punk = static_cast<IClassFactory*>(this);
   854   }
   855   *ppv = punk;
   856   if (punk) {
   857     punk->AddRef();
   858     return S_OK;
   859   } else {
   860     return E_NOINTERFACE;
   861   }
   862 }
   864 STDMETHODIMP
   865 ClassFactory::CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
   866 {
   867   *ppv = nullptr;
   868   if (punkOuter)
   869     return CLASS_E_NOAGGREGATION;
   870   return mUnkObject->QueryInterface(riid, ppv);
   871 }
   873 LONG gObjRefCnt;
   875 STDMETHODIMP
   876 ClassFactory::LockServer(BOOL fLock)
   877 {
   878   if (fLock)
   879     InterlockedIncrement(&gObjRefCnt);
   880   else
   881     InterlockedDecrement(&gObjRefCnt);
   882   Log(L"ClassFactory::LockServer() %d", gObjRefCnt);
   883   return S_OK;
   884 }
   886 int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR pszCmdLine, int)
   887 {
   888 #if defined(SHOW_CONSOLE)
   889   SetupConsole();
   890 #endif
   892   // nsis installer uses this as a helper to launch metro
   893   if (pszCmdLine && StrStrI(pszCmdLine, kNsisLaunchCmdLine))
   894   {
   895     CoInitialize(nullptr);
   896     CExecuteCommandVerb *pHandler = new CExecuteCommandVerb();
   897     if (!pHandler)
   898       return E_OUTOFMEMORY;
   899     pHandler->CommandLineMetroLaunch();
   900     delete pHandler;
   901     CoUninitialize();
   902     return 0;
   903   }
   905   if (!wcslen(pszCmdLine) || StrStrI(pszCmdLine, kExplorerLaunchCmdLine))
   906   {
   907       CoInitialize(nullptr);
   909       CExecuteCommandVerb *pHandler = new CExecuteCommandVerb();
   910       if (!pHandler)
   911         return E_OUTOFMEMORY;
   913       IUnknown* ppi;
   914       pHandler->QueryInterface(IID_IUnknown, (void**)&ppi);
   915       if (!ppi)
   916         return E_FAIL;
   918       ClassFactory classFactory(ppi);
   919       ppi->Release();
   920       ppi = nullptr;
   922       // REGCLS_SINGLEUSE insures we only get used once and then discarded.
   923       if (FAILED(classFactory.Register(CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE)))
   924         return -1;
   926       if (!SetTimer(nullptr, 1, HEARTBEAT_MSEC, nullptr)) {
   927         Log(L"Failed to set timer, can't process request.");
   928         return -1;
   929       }
   931       MSG msg;
   932       long beatCount = 0;
   933       while (GetMessage(&msg, 0, 0, 0) > 0) {
   934         if (msg.message == WM_TIMER) {
   935           pHandler->HeartBeat();
   936           if (++beatCount > REQUEST_WAIT_TIMEOUT ||
   937               (pHandler->RequestMet() && pHandler->RefCount() < 2)) {
   938             break;
   939           }
   940         }
   941         TranslateMessage(&msg);
   942         DispatchMessage(&msg);
   943       }
   945 #ifdef DEBUG_DELAY_SHUTDOWN
   946       Sleep(10000);
   947 #endif
   948       CoUninitialize();
   949       return 0;
   950   }
   951   return 0;
   952 }

mercurial