browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,952 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +
    1.10 +#include "CEHHelper.h"
    1.11 +
    1.12 +#include <objbase.h>
    1.13 +#include <combaseapi.h>
    1.14 +#include <atlcore.h>
    1.15 +#include <atlstr.h>
    1.16 +#include <wininet.h>
    1.17 +#include <shlobj.h>
    1.18 +#include <shlwapi.h>
    1.19 +#include <propkey.h>
    1.20 +#include <propvarutil.h>
    1.21 +#include <stdio.h>
    1.22 +#include <stdlib.h>
    1.23 +#include <strsafe.h>
    1.24 +#include <io.h>
    1.25 +#include <shellapi.h>
    1.26 +
    1.27 +#ifdef SHOW_CONSOLE
    1.28 +#define DEBUG_DELAY_SHUTDOWN 1
    1.29 +#endif
    1.30 +
    1.31 +// Heartbeat timer duration used while waiting for an incoming request.
    1.32 +#define HEARTBEAT_MSEC 250
    1.33 +// Total number of heartbeats we wait before giving up and shutting down.
    1.34 +#define REQUEST_WAIT_TIMEOUT 30
    1.35 +// Pulled from desktop browser's shell
    1.36 +#define APP_REG_NAME L"Firefox"
    1.37 +
    1.38 +const WCHAR* kFirefoxExe = L"firefox.exe";
    1.39 +static const WCHAR* kDefaultMetroBrowserIDPathKey = L"FirefoxURL";
    1.40 +static const WCHAR* kMetroRestartCmdLine = L"--metro-restart";
    1.41 +static const WCHAR* kMetroUpdateCmdLine = L"--metro-update";
    1.42 +static const WCHAR* kDesktopRestartCmdLine = L"--desktop-restart";
    1.43 +static const WCHAR* kNsisLaunchCmdLine = L"--launchmetro";
    1.44 +static const WCHAR* kExplorerLaunchCmdLine = L"-Embedding";
    1.45 +
    1.46 +static bool GetDefaultBrowserPath(CStringW& aPathBuffer);
    1.47 +
    1.48 +/*
    1.49 + * Retrieve our module dir path.
    1.50 + *
    1.51 + * @aPathBuffer Buffer to fill
    1.52 + */
    1.53 +static bool GetModulePath(CStringW& aPathBuffer)
    1.54 +{
    1.55 +  WCHAR buffer[MAX_PATH];
    1.56 +  memset(buffer, 0, sizeof(buffer));
    1.57 +
    1.58 +  if (!GetModuleFileName(nullptr, buffer, MAX_PATH)) {
    1.59 +    Log(L"GetModuleFileName failed.");
    1.60 +    return false;
    1.61 +  }
    1.62 +
    1.63 +  WCHAR* slash = wcsrchr(buffer, '\\');
    1.64 +  if (!slash)
    1.65 +    return false;
    1.66 +  *slash = '\0';
    1.67 +
    1.68 +  aPathBuffer = buffer;
    1.69 +  return true;
    1.70 +}
    1.71 +
    1.72 +
    1.73 +template <class T>void SafeRelease(T **ppT)
    1.74 +{
    1.75 +  if (*ppT) {
    1.76 +    (*ppT)->Release();
    1.77 +    *ppT = nullptr;
    1.78 +  }
    1.79 +}
    1.80 +
    1.81 +template <class T> HRESULT SetInterface(T **ppT, IUnknown *punk)
    1.82 +{
    1.83 +  SafeRelease(ppT);
    1.84 +  return punk ? punk->QueryInterface(ppT) : E_NOINTERFACE;
    1.85 +}
    1.86 +
    1.87 +class __declspec(uuid("5100FEC1-212B-4BF5-9BF8-3E650FD794A3"))
    1.88 +  CExecuteCommandVerb : public IExecuteCommand,
    1.89 +                        public IObjectWithSelection,
    1.90 +                        public IInitializeCommand,
    1.91 +                        public IObjectWithSite,
    1.92 +                        public IExecuteCommandApplicationHostEnvironment
    1.93 +{
    1.94 +public:
    1.95 +
    1.96 +  CExecuteCommandVerb() :
    1.97 +    mRef(0),
    1.98 +    mShellItemArray(nullptr),
    1.99 +    mUnkSite(nullptr),
   1.100 +    mTargetIsFileSystemLink(false),
   1.101 +    mTargetIsDefaultBrowser(false),
   1.102 +    mTargetIsBrowser(false),
   1.103 +    mRequestType(DEFAULT_LAUNCH),
   1.104 +    mRequestMet(false),
   1.105 +    mDelayedLaunchType(NONE),
   1.106 +    mVerb(L"open")
   1.107 +  {
   1.108 +  }
   1.109 +
   1.110 +  ~CExecuteCommandVerb()
   1.111 +  {
   1.112 +  }
   1.113 +
   1.114 +  bool RequestMet() { return mRequestMet; }
   1.115 +  void SetRequestMet();
   1.116 +  long RefCount() { return mRef; }
   1.117 +  void HeartBeat();
   1.118 +
   1.119 +  // IUnknown
   1.120 +  IFACEMETHODIMP QueryInterface(REFIID aRefID, void **aInt)
   1.121 +  {
   1.122 +    static const QITAB qit[] = {
   1.123 +      QITABENT(CExecuteCommandVerb, IExecuteCommand),
   1.124 +      QITABENT(CExecuteCommandVerb, IObjectWithSelection),
   1.125 +      QITABENT(CExecuteCommandVerb, IInitializeCommand),
   1.126 +      QITABENT(CExecuteCommandVerb, IObjectWithSite),
   1.127 +      QITABENT(CExecuteCommandVerb, IExecuteCommandApplicationHostEnvironment),
   1.128 +      { 0 },
   1.129 +    };
   1.130 +    return QISearch(this, qit, aRefID, aInt);
   1.131 +  }
   1.132 +
   1.133 +  IFACEMETHODIMP_(ULONG) AddRef()
   1.134 +  {
   1.135 +    return InterlockedIncrement(&mRef);
   1.136 +  }
   1.137 +
   1.138 +  IFACEMETHODIMP_(ULONG) Release()
   1.139 +  {
   1.140 +    long cRef = InterlockedDecrement(&mRef);
   1.141 +    if (!cRef) {
   1.142 +      delete this;
   1.143 +    }
   1.144 +    return cRef;
   1.145 +  }
   1.146 +
   1.147 +  // IExecuteCommand
   1.148 +  IFACEMETHODIMP SetKeyState(DWORD aKeyState)
   1.149 +  {
   1.150 +    mKeyState = aKeyState;
   1.151 +    return S_OK;
   1.152 +  }
   1.153 +
   1.154 +  IFACEMETHODIMP SetParameters(PCWSTR aParameters)
   1.155 +  {
   1.156 +    Log(L"SetParameters: '%s'", aParameters);
   1.157 +
   1.158 +    if (!_wcsicmp(aParameters, kMetroRestartCmdLine)) {
   1.159 +      mRequestType = METRO_RESTART;
   1.160 +    } else if (_wcsicmp(aParameters, kMetroUpdateCmdLine) == 0) {
   1.161 +      mRequestType = METRO_UPDATE;
   1.162 +    } else if (_wcsicmp(aParameters, kDesktopRestartCmdLine) == 0) {
   1.163 +      mRequestType = DESKTOP_RESTART;
   1.164 +    } else {
   1.165 +      mParameters = aParameters;
   1.166 +    }
   1.167 +    return S_OK;
   1.168 +  }
   1.169 +
   1.170 +  IFACEMETHODIMP SetPosition(POINT aPoint)
   1.171 +  { return S_OK; }
   1.172 +
   1.173 +  IFACEMETHODIMP SetShowWindow(int aShowFlag)
   1.174 +  { return S_OK; }
   1.175 +
   1.176 +  IFACEMETHODIMP SetNoShowUI(BOOL aNoUI)
   1.177 +  { return S_OK; }
   1.178 +
   1.179 +  IFACEMETHODIMP SetDirectory(PCWSTR aDirPath)
   1.180 +  { return S_OK; }
   1.181 +
   1.182 +  IFACEMETHODIMP Execute();
   1.183 +
   1.184 +  // IObjectWithSelection
   1.185 +  IFACEMETHODIMP SetSelection(IShellItemArray *aArray)
   1.186 +  {
   1.187 +    if (!aArray) {
   1.188 +      return E_FAIL;
   1.189 +    }
   1.190 +
   1.191 +    SetInterface(&mShellItemArray, aArray);
   1.192 +
   1.193 +    DWORD count = 0;
   1.194 +    aArray->GetCount(&count);
   1.195 +    if (!count) {
   1.196 +      return E_FAIL;
   1.197 +    }
   1.198 +
   1.199 +#ifdef SHOW_CONSOLE
   1.200 +    Log(L"SetSelection param count: %d", count);
   1.201 +    for (DWORD idx = 0; idx < count; idx++) {
   1.202 +      IShellItem* item = nullptr;
   1.203 +      if (SUCCEEDED(aArray->GetItemAt(idx, &item))) {
   1.204 +        LPWSTR str = nullptr;
   1.205 +        if (FAILED(item->GetDisplayName(SIGDN_FILESYSPATH, &str))) {
   1.206 +          if (FAILED(item->GetDisplayName(SIGDN_URL, &str))) {
   1.207 +            Log(L"Failed to get a shell item array item.");
   1.208 +            item->Release();
   1.209 +            continue;
   1.210 +          }
   1.211 +        }
   1.212 +        item->Release();
   1.213 +        Log(L"SetSelection param: '%s'", str);
   1.214 +        CoTaskMemFree(str);
   1.215 +      }
   1.216 +    }
   1.217 +#endif
   1.218 +
   1.219 +    IShellItem* item = nullptr;
   1.220 +    if (FAILED(aArray->GetItemAt(0, &item))) {
   1.221 +      return E_FAIL;
   1.222 +    }
   1.223 +
   1.224 +    bool isFileSystem = false;
   1.225 +    if (!SetTargetPath(item) || !mTarget.GetLength()) {
   1.226 +      Log(L"SetTargetPath failed.");
   1.227 +      return E_FAIL;
   1.228 +    }
   1.229 +    item->Release();
   1.230 +
   1.231 +    Log(L"SetSelection target: %s", mTarget);
   1.232 +    return S_OK;
   1.233 +  }
   1.234 +
   1.235 +  IFACEMETHODIMP GetSelection(REFIID aRefID, void **aInt)
   1.236 +  {
   1.237 +    *aInt = nullptr;
   1.238 +    return mShellItemArray ? mShellItemArray->QueryInterface(aRefID, aInt) : E_FAIL;
   1.239 +  }
   1.240 +
   1.241 +  // IInitializeCommand
   1.242 +  IFACEMETHODIMP Initialize(PCWSTR aVerb, IPropertyBag* aPropBag)
   1.243 +  {
   1.244 +    if (!aVerb)
   1.245 +      return E_FAIL;
   1.246 +    // 'open', 'edit', etc. Based on our registry settings
   1.247 +    Log(L"Initialize(%s)", aVerb);
   1.248 +    mVerb = aVerb;
   1.249 +    return S_OK;
   1.250 +  }
   1.251 +
   1.252 +  // IObjectWithSite
   1.253 +  IFACEMETHODIMP SetSite(IUnknown *aUnkSite)
   1.254 +  {
   1.255 +    SetInterface(&mUnkSite, aUnkSite);
   1.256 +    return S_OK;
   1.257 +  }
   1.258 +
   1.259 +  IFACEMETHODIMP GetSite(REFIID aRefID, void **aInt)
   1.260 +  {
   1.261 +    *aInt = nullptr;
   1.262 +    return mUnkSite ? mUnkSite->QueryInterface(aRefID, aInt) : E_FAIL;
   1.263 +  }
   1.264 +
   1.265 +  // IExecuteCommandApplicationHostEnvironment
   1.266 +  IFACEMETHODIMP GetValue(AHE_TYPE *aLaunchType)
   1.267 +  {
   1.268 +    Log(L"IExecuteCommandApplicationHostEnvironment::GetValue()");
   1.269 +    *aLaunchType = GetLaunchType();
   1.270 +    return S_OK;
   1.271 +  }
   1.272 +
   1.273 +  /**
   1.274 +   * Choose the appropriate launch type based on the user's previously chosen
   1.275 +   * host environment, along with system constraints.
   1.276 +   *
   1.277 +   * AHE_DESKTOP = 0, AHE_IMMERSIVE = 1
   1.278 +   */
   1.279 +  AHE_TYPE GetLaunchType()
   1.280 +  {
   1.281 +    AHE_TYPE ahe = GetLastAHE();
   1.282 +    Log(L"Previous AHE: %d", ahe);
   1.283 +
   1.284 +    // Default launch settings from GetLastAHE() can be overriden by
   1.285 +    // custom parameter values we receive. 
   1.286 +    if (mRequestType == DESKTOP_RESTART) {
   1.287 +      Log(L"Restarting in desktop host environment.");
   1.288 +      return AHE_DESKTOP;
   1.289 +    } else if (mRequestType == METRO_RESTART) {
   1.290 +      Log(L"Restarting in metro host environment.");
   1.291 +      ahe = AHE_IMMERSIVE;
   1.292 +    } else if (mRequestType == METRO_UPDATE) {
   1.293 +      // Shouldn't happen from GetValue above, but might from other calls.
   1.294 +      ahe = AHE_IMMERSIVE;
   1.295 +    }
   1.296 +
   1.297 +    if (ahe == AHE_IMMERSIVE) {
   1.298 +      if (!IsDefaultBrowser()) {
   1.299 +        Log(L"returning AHE_DESKTOP because we are not the default browser");
   1.300 +        return AHE_DESKTOP;
   1.301 +      }
   1.302 +
   1.303 +      if (!IsDX10Available()) {
   1.304 +        Log(L"returning AHE_DESKTOP because DX10 is not available");
   1.305 +        return AHE_DESKTOP;
   1.306 +      }
   1.307 +    }
   1.308 +    return ahe;
   1.309 +  }
   1.310 +
   1.311 +  bool DefaultLaunchIsDesktop()
   1.312 +  {
   1.313 +    return GetLaunchType() == AHE_DESKTOP;
   1.314 +  }
   1.315 +
   1.316 +  bool DefaultLaunchIsMetro()
   1.317 +  {
   1.318 +    return GetLaunchType() == AHE_IMMERSIVE;
   1.319 +  }
   1.320 +
   1.321 +  /*
   1.322 +   * Retrieve the target path if it is the default browser
   1.323 +   * or if not default, retreives the target path if it is a firefox browser
   1.324 +   * or if the target is not firefox, relies on a hack to get the
   1.325 +   * 'module dir path\firefox.exe'
   1.326 +   * The reason why it's not good to rely on the CEH path is because there is
   1.327 +   * no guarantee win8 will use the CEH at our expected path.  It has an in
   1.328 +   * memory cache even if the registry is updated for the CEH path.
   1.329 +   *
   1.330 +   * @aPathBuffer Buffer to fill
   1.331 +   */
   1.332 +  bool GetDesktopBrowserPath(CStringW& aPathBuffer)
   1.333 +  {
   1.334 +    // If the target was the default browser itself then return early.  Otherwise
   1.335 +    // rely on a hack to check CEH path and calculate it relative to it.
   1.336 +
   1.337 +    if (mTargetIsDefaultBrowser || mTargetIsBrowser) {
   1.338 +      aPathBuffer = mTarget;
   1.339 +      return true;
   1.340 +    }
   1.341 +
   1.342 +    if (!GetModulePath(aPathBuffer))
   1.343 +      return false;
   1.344 +
   1.345 +    // ceh.exe sits in dist/bin root with the desktop browser. Since this
   1.346 +    // is a firefox only component, this hardcoded filename is ok.
   1.347 +    aPathBuffer.Append(L"\\");
   1.348 +    aPathBuffer.Append(kFirefoxExe);
   1.349 +    return true;
   1.350 +  }
   1.351 +
   1.352 +  bool IsDefaultBrowser()
   1.353 +  {
   1.354 +    IApplicationAssociationRegistration* pAAR;
   1.355 +    HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
   1.356 +                                  nullptr,
   1.357 +                                  CLSCTX_INPROC,
   1.358 +                                  IID_IApplicationAssociationRegistration,
   1.359 +                                  (void**)&pAAR);
   1.360 +    if (FAILED(hr))
   1.361 +      return false;
   1.362 +
   1.363 +    BOOL res = FALSE;
   1.364 +    hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE,
   1.365 +                                    APP_REG_NAME,
   1.366 +                                    &res);
   1.367 +    Log(L"QueryAppIsDefaultAll: %d", res);
   1.368 +    if (!res) {
   1.369 +      pAAR->Release();
   1.370 +      return false;
   1.371 +    }
   1.372 +    // Make sure the Prog ID matches what we have
   1.373 +    LPWSTR registeredApp;
   1.374 +    hr = pAAR->QueryCurrentDefault(L"http", AT_URLPROTOCOL, AL_EFFECTIVE,
   1.375 +                                    &registeredApp);
   1.376 +    pAAR->Release();
   1.377 +    Log(L"QueryCurrentDefault: %X", hr);
   1.378 +    if (FAILED(hr))
   1.379 +      return false;
   1.380 +
   1.381 +    Log(L"registeredApp=%s", registeredApp);
   1.382 +    bool result = !wcsicmp(registeredApp, kDefaultMetroBrowserIDPathKey);
   1.383 +    CoTaskMemFree(registeredApp);
   1.384 +    if (!result)
   1.385 +      return false;
   1.386 +
   1.387 +    // If the registry points another browser's path,
   1.388 +    // activating the Metro browser will fail. So fallback to the desktop.
   1.389 +    CStringW selfPath;
   1.390 +    GetDesktopBrowserPath(selfPath);
   1.391 +    CStringW browserPath;
   1.392 +    GetDefaultBrowserPath(browserPath);
   1.393 +
   1.394 +    return !selfPath.CompareNoCase(browserPath);
   1.395 +  }
   1.396 +
   1.397 +  /*
   1.398 +   * Helper for nsis installer when it wants to launch the
   1.399 +   * default metro browser.
   1.400 +   */
   1.401 +  void CommandLineMetroLaunch()
   1.402 +  {
   1.403 +    mTargetIsDefaultBrowser = true;
   1.404 +    LaunchMetroBrowser();
   1.405 +  }
   1.406 +
   1.407 +private:
   1.408 +  void LaunchDesktopBrowser();
   1.409 +  bool LaunchMetroBrowser();
   1.410 +  bool SetTargetPath(IShellItem* aItem);
   1.411 +  bool TestForUpdateLock();
   1.412 +
   1.413 +  /*
   1.414 +   * Defines the type of startup request we receive.
   1.415 +   */
   1.416 +  enum RequestType {
   1.417 +    DEFAULT_LAUNCH,
   1.418 +    DESKTOP_RESTART,
   1.419 +    METRO_RESTART,
   1.420 +    METRO_UPDATE,
   1.421 +  };
   1.422 +
   1.423 +  RequestType mRequestType;
   1.424 +
   1.425 +  /*
   1.426 +   * Defines the type of delayed launch we might do.
   1.427 +   */
   1.428 +  enum DelayedLaunchType {
   1.429 +    NONE,
   1.430 +    DESKTOP,
   1.431 +    METRO,
   1.432 +  };
   1.433 +
   1.434 +  DelayedLaunchType mDelayedLaunchType;
   1.435 +
   1.436 +  long mRef;
   1.437 +  IShellItemArray *mShellItemArray;
   1.438 +  IUnknown *mUnkSite;
   1.439 +  CStringW mVerb;
   1.440 +  CStringW mTarget;
   1.441 +  CStringW mParameters;
   1.442 +  bool mTargetIsFileSystemLink;
   1.443 +  bool mTargetIsDefaultBrowser;
   1.444 +  bool mTargetIsBrowser;
   1.445 +  DWORD mKeyState;
   1.446 +  bool mRequestMet;
   1.447 +};
   1.448 +
   1.449 +/*
   1.450 + * Retrieve the current default browser's path.
   1.451 + *
   1.452 + * @aPathBuffer Buffer to fill
   1.453 + */
   1.454 +static bool GetDefaultBrowserPath(CStringW& aPathBuffer)
   1.455 +{
   1.456 +  WCHAR buffer[MAX_PATH];
   1.457 +  DWORD length = MAX_PATH;
   1.458 +
   1.459 +  if (FAILED(AssocQueryStringW(ASSOCF_NOTRUNCATE | ASSOCF_INIT_IGNOREUNKNOWN,
   1.460 +                               ASSOCSTR_EXECUTABLE,
   1.461 +                               kDefaultMetroBrowserIDPathKey, nullptr,
   1.462 +                               buffer, &length))) {
   1.463 +    Log(L"AssocQueryString failed.");
   1.464 +    return false;
   1.465 +  }
   1.466 +
   1.467 +  // sanity check
   1.468 +  if (lstrcmpiW(PathFindFileNameW(buffer), kFirefoxExe))
   1.469 +    return false;
   1.470 +
   1.471 +  aPathBuffer = buffer;
   1.472 +  return true;
   1.473 +}
   1.474 +
   1.475 +/*
   1.476 + * Retrieve the app model id of the firefox metro browser.
   1.477 + *
   1.478 + * @aPathBuffer Buffer to fill
   1.479 + * @aCharLength Length of buffer to fill in characters
   1.480 + */
   1.481 +template <size_t N>
   1.482 +static bool GetDefaultBrowserAppModelID(WCHAR (&aIDBuffer)[N])
   1.483 +{
   1.484 +  HKEY key;
   1.485 +  if (RegOpenKeyExW(HKEY_CLASSES_ROOT, kDefaultMetroBrowserIDPathKey,
   1.486 +                    0, KEY_READ, &key) != ERROR_SUCCESS) {
   1.487 +    return false;
   1.488 +  }
   1.489 +  DWORD len = sizeof(aIDBuffer);
   1.490 +  memset(aIDBuffer, 0, len);
   1.491 +  if (RegQueryValueExW(key, L"AppUserModelID", nullptr, nullptr,
   1.492 +                       (LPBYTE)aIDBuffer, &len) != ERROR_SUCCESS || !len) {
   1.493 +    RegCloseKey(key);
   1.494 +    return false;
   1.495 +  }
   1.496 +  RegCloseKey(key);
   1.497 +  return true;
   1.498 +}
   1.499 +
   1.500 +namespace {
   1.501 +  const FORMATETC kPlainTextFormat =
   1.502 +    {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
   1.503 +  const FORMATETC kPlainTextWFormat =
   1.504 +    {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
   1.505 +}
   1.506 +
   1.507 +bool HasPlainText(IDataObject* aDataObj) {
   1.508 +  return SUCCEEDED(aDataObj->QueryGetData((FORMATETC*)&kPlainTextWFormat)) ||
   1.509 +      SUCCEEDED(aDataObj->QueryGetData((FORMATETC*)&kPlainTextFormat));
   1.510 +}
   1.511 +
   1.512 +bool GetPlainText(IDataObject* aDataObj, CStringW& cstrText)
   1.513 +{
   1.514 +  if (!HasPlainText(aDataObj))
   1.515 +    return false;
   1.516 +
   1.517 +  STGMEDIUM store;
   1.518 +
   1.519 +  // unicode text
   1.520 +  if (SUCCEEDED(aDataObj->GetData((FORMATETC*)&kPlainTextWFormat, &store))) {
   1.521 +    // makes a copy
   1.522 +    cstrText = static_cast<LPCWSTR>(GlobalLock(store.hGlobal));
   1.523 +    GlobalUnlock(store.hGlobal);
   1.524 +    ReleaseStgMedium(&store);
   1.525 +    return true;
   1.526 +  }
   1.527 +
   1.528 +  // ascii text
   1.529 +  if (SUCCEEDED(aDataObj->GetData((FORMATETC*)&kPlainTextFormat, &store))) {
   1.530 +    // makes a copy
   1.531 +    cstrText = static_cast<char*>(GlobalLock(store.hGlobal));
   1.532 +    GlobalUnlock(store.hGlobal);
   1.533 +    ReleaseStgMedium(&store);
   1.534 +    return true;
   1.535 +  }
   1.536 +
   1.537 +  return false;
   1.538 +}
   1.539 +
   1.540 +/*
   1.541 + * Updates the current target based on the contents of
   1.542 + * a shell item.
   1.543 + */
   1.544 +bool CExecuteCommandVerb::SetTargetPath(IShellItem* aItem)
   1.545 +{
   1.546 +  if (!aItem)
   1.547 +    return false;
   1.548 +
   1.549 +  CString cstrText;
   1.550 +  CComPtr<IDataObject> object;
   1.551 +  // Check the underlying data object first to insure we get
   1.552 +  // absolute uri. See chromium bug 157184.
   1.553 +  if (SUCCEEDED(aItem->BindToHandler(nullptr, BHID_DataObject,
   1.554 +                                     IID_IDataObject,
   1.555 +                                     reinterpret_cast<void**>(&object))) &&
   1.556 +      GetPlainText(object, cstrText)) {
   1.557 +    wchar_t scheme[16];
   1.558 +    URL_COMPONENTS components = {0};
   1.559 +    components.lpszScheme = scheme;
   1.560 +    components.dwSchemeLength = sizeof(scheme)/sizeof(scheme[0]);
   1.561 +    components.dwStructSize = sizeof(components);
   1.562 +    // note, more advanced use may have issues with paths with spaces.
   1.563 +    if (!InternetCrackUrlW(cstrText, 0, 0, &components)) {
   1.564 +      Log(L"Failed to identify object text '%s'", cstrText);
   1.565 +      return false;
   1.566 +    }
   1.567 +
   1.568 +    mTargetIsFileSystemLink = (components.nScheme == INTERNET_SCHEME_FILE);
   1.569 +    mTarget = cstrText;
   1.570 +
   1.571 +    return true;
   1.572 +  }
   1.573 +
   1.574 +  Log(L"No data object or data object has no text.");
   1.575 +
   1.576 +  // Use the shell item display name
   1.577 +  LPWSTR str = nullptr;
   1.578 +  mTargetIsFileSystemLink = true;
   1.579 +  if (FAILED(aItem->GetDisplayName(SIGDN_FILESYSPATH, &str))) {
   1.580 +    mTargetIsFileSystemLink = false;
   1.581 +    if (FAILED(aItem->GetDisplayName(SIGDN_URL, &str))) {
   1.582 +      Log(L"Failed to get parameter string.");
   1.583 +      return false;
   1.584 +    }
   1.585 +  }
   1.586 +  mTarget = str;
   1.587 +  CoTaskMemFree(str);
   1.588 +
   1.589 +  CStringW defaultPath;
   1.590 +  GetDefaultBrowserPath(defaultPath);
   1.591 +  mTargetIsDefaultBrowser = !mTarget.CompareNoCase(defaultPath);
   1.592 +
   1.593 +  size_t browserEXELen = wcslen(kFirefoxExe);
   1.594 +  mTargetIsBrowser = mTarget.GetLength() >= browserEXELen &&
   1.595 +                     !mTarget.Right(browserEXELen).CompareNoCase(kFirefoxExe);
   1.596 +
   1.597 +  return true;
   1.598 +}
   1.599 +
   1.600 +/*
   1.601 + * Desktop launch - Launch the destop browser to display the current
   1.602 + * target using shellexecute.
   1.603 + */
   1.604 +void LaunchDesktopBrowserWithParams(CStringW& aBrowserPath, CStringW& aVerb,
   1.605 +                                    CStringW& aTarget, CStringW& aParameters,
   1.606 +                                    bool aTargetIsDefaultBrowser, bool aTargetIsBrowser)
   1.607 +{
   1.608 +  // If a taskbar shortcut, link or local file is clicked, the target will
   1.609 +  // be the browser exe or file.  Don't pass in -url for the target if the
   1.610 +  // target is known to be a browser.  Otherwise, one instance of Firefox
   1.611 +  // will try to open another instance.
   1.612 +  CStringW params;
   1.613 +  if (!aTargetIsDefaultBrowser && !aTargetIsBrowser && !aTarget.IsEmpty()) {
   1.614 +    // Fallback to the module path if it failed to get the default browser.
   1.615 +    GetDefaultBrowserPath(aBrowserPath);
   1.616 +    params += "-url ";
   1.617 +    params += "\"";
   1.618 +    params += aTarget;
   1.619 +    params += "\"";
   1.620 +  }
   1.621 +
   1.622 +  // Tack on any extra parameters we received (for example -profilemanager)
   1.623 +  if (!aParameters.IsEmpty()) {
   1.624 +    params += " ";
   1.625 +    params += aParameters;
   1.626 +  }
   1.627 +
   1.628 +  Log(L"Desktop Launch: verb:'%s' exe:'%s' params:'%s'", aVerb, aBrowserPath, params);
   1.629 +
   1.630 +  // Relaunch in Desktop mode uses a special URL to trick Windows into
   1.631 +  // switching environments. We shouldn't actually try to open this URL.
   1.632 +  if (!_wcsicmp(aTarget, L"http://-desktop/")) {
   1.633 +    // Ignore any params and just launch on desktop
   1.634 +    params.Empty();
   1.635 +  }
   1.636 +
   1.637 +  PROCESS_INFORMATION procInfo;
   1.638 +  STARTUPINFO startInfo;
   1.639 +  memset(&procInfo, 0, sizeof(PROCESS_INFORMATION));
   1.640 +  memset(&startInfo, 0, sizeof(STARTUPINFO));
   1.641 +
   1.642 +  startInfo.cb = sizeof(STARTUPINFO);
   1.643 +  startInfo.dwFlags = STARTF_USESHOWWINDOW;
   1.644 +  startInfo.wShowWindow = SW_SHOWNORMAL;
   1.645 +
   1.646 +  BOOL result =
   1.647 +    CreateProcessW(aBrowserPath, static_cast<LPWSTR>(params.GetBuffer()),
   1.648 +                   NULL, NULL, FALSE, 0, NULL, NULL, &startInfo, &procInfo);
   1.649 +  if (!result) {
   1.650 +    Log(L"CreateProcess failed! (%d)", GetLastError());
   1.651 +    return;
   1.652 +  }
   1.653 +  // Hand off foreground/focus rights to the browser we create. If we don't
   1.654 +  // do this the ceh will keep ownership causing desktop firefox to launch
   1.655 +  // deactivated.
   1.656 +  if (!AllowSetForegroundWindow(procInfo.dwProcessId)) {
   1.657 +    Log(L"AllowSetForegroundWindow failed! (%d)", GetLastError());
   1.658 +  }
   1.659 +  CloseHandle(procInfo.hThread);
   1.660 +  CloseHandle(procInfo.hProcess);
   1.661 +  Log(L"Desktop browser process id: %d", procInfo.dwProcessId);
   1.662 +}
   1.663 +
   1.664 +void
   1.665 +CExecuteCommandVerb::LaunchDesktopBrowser()
   1.666 +{
   1.667 +  CStringW browserPath;
   1.668 +  if (!GetDesktopBrowserPath(browserPath)) {
   1.669 +    return;
   1.670 +  }
   1.671 +
   1.672 +  LaunchDesktopBrowserWithParams(browserPath, mVerb, mTarget, mParameters,
   1.673 +                                 mTargetIsDefaultBrowser, mTargetIsBrowser);
   1.674 +}
   1.675 +
   1.676 +void
   1.677 +CExecuteCommandVerb::HeartBeat()
   1.678 +{
   1.679 +  if (mRequestType == METRO_UPDATE && mDelayedLaunchType == DESKTOP &&
   1.680 +      !IsMetroProcessRunning()) {
   1.681 +    mDelayedLaunchType = NONE;
   1.682 +    LaunchDesktopBrowser();
   1.683 +    SetRequestMet();
   1.684 +  }
   1.685 +  if (mDelayedLaunchType == METRO && !TestForUpdateLock()) {
   1.686 +    mDelayedLaunchType = NONE;
   1.687 +    LaunchMetroBrowser();
   1.688 +    SetRequestMet();
   1.689 +  }
   1.690 +}
   1.691 +
   1.692 +bool
   1.693 +CExecuteCommandVerb::TestForUpdateLock()
   1.694 +{
   1.695 +  CStringW browserPath;
   1.696 +  if (!GetDefaultBrowserPath(browserPath)) {
   1.697 +    return false;
   1.698 +  }
   1.699 +
   1.700 +  HANDLE hFile = CreateFileW(browserPath,
   1.701 +                             FILE_EXECUTE, FILE_SHARE_READ|FILE_SHARE_WRITE,
   1.702 +                             nullptr, OPEN_EXISTING, 0, nullptr);
   1.703 +  if (hFile != INVALID_HANDLE_VALUE) {
   1.704 +    CloseHandle(hFile);
   1.705 +    return false;
   1.706 +  }
   1.707 +  return true;
   1.708 +}
   1.709 +
   1.710 +bool
   1.711 +CExecuteCommandVerb::LaunchMetroBrowser()
   1.712 +{
   1.713 +  HRESULT hr;
   1.714 +
   1.715 +  CComPtr<IApplicationActivationManager> activateMgr;
   1.716 +  hr = activateMgr.CoCreateInstance(CLSID_ApplicationActivationManager,
   1.717 +                                    nullptr, CLSCTX_LOCAL_SERVER);
   1.718 +  if (FAILED(hr)) {
   1.719 +    Log(L"CoCreateInstance failed, launching on desktop.");
   1.720 +    return false;
   1.721 +  }
   1.722 +
   1.723 +  // Hand off focus rights to the out-of-process activation server. This will
   1.724 +  // fail if we don't have the rights to begin with. Log but don't bail.
   1.725 +  hr = CoAllowSetForegroundWindow(activateMgr, nullptr);
   1.726 +  if (FAILED(hr)) {
   1.727 +    Log(L"CoAllowSetForegroundWindow result %X", hr);
   1.728 +  }
   1.729 +
   1.730 +  WCHAR appModelID[256];
   1.731 +  if (!GetDefaultBrowserAppModelID(appModelID)) {
   1.732 +    Log(L"GetDefaultBrowserAppModelID failed.");
   1.733 +    return false;
   1.734 +  }
   1.735 +
   1.736 +  Log(L"Metro Launch: verb:'%s' appid:'%s' params:'%s'", mVerb, appModelID, mTarget);
   1.737 +
   1.738 +  // shortcuts to the application
   1.739 +  DWORD processID;
   1.740 +  if (mTargetIsDefaultBrowser) {
   1.741 +    hr = activateMgr->ActivateApplication(appModelID, L"", AO_NONE, &processID);
   1.742 +    Log(L"ActivateApplication result %X", hr);
   1.743 +  // files
   1.744 +  } else if (mTargetIsFileSystemLink) {
   1.745 +    hr = activateMgr->ActivateForFile(appModelID, mShellItemArray, mVerb, &processID);
   1.746 +    Log(L"ActivateForFile result %X", hr);
   1.747 +  // protocols
   1.748 +  } else {
   1.749 +    hr = activateMgr->ActivateForProtocol(appModelID, mShellItemArray, &processID);
   1.750 +    Log(L"ActivateForProtocol result %X", hr);
   1.751 +  }
   1.752 +  return true;
   1.753 +}
   1.754 +
   1.755 +void CExecuteCommandVerb::SetRequestMet()
   1.756 +{
   1.757 +  SafeRelease(&mShellItemArray);
   1.758 +  SafeRelease(&mUnkSite);
   1.759 +  mRequestMet = true;
   1.760 +  Log(L"Request met, exiting.");
   1.761 +}
   1.762 +
   1.763 +IFACEMETHODIMP CExecuteCommandVerb::Execute()
   1.764 +{
   1.765 +  Log(L"Execute()");
   1.766 +
   1.767 +  if (!mTarget.GetLength()) {
   1.768 +    // We shut down when this flips to true
   1.769 +    SetRequestMet();
   1.770 +    return E_FAIL;
   1.771 +  }
   1.772 +
   1.773 +  if (!IsDX10Available()) {
   1.774 +    Log(L"Can't launch in metro due to missing hardware acceleration features.");
   1.775 +    mRequestType = DESKTOP_RESTART;
   1.776 +  } 
   1.777 +
   1.778 +  // Deal with metro restart for an update - launch desktop with a command
   1.779 +  // that tells it to run updater then launch the metro browser.
   1.780 +  if (mRequestType == METRO_UPDATE) {
   1.781 +    // We'll complete this in the heart beat callback from the main msg loop.
   1.782 +    // We do this because the last browser instance makes this call to Execute
   1.783 +    // sync. So we want to make sure it's completely shutdown before we do
   1.784 +    // the update.
   1.785 +    mParameters = kMetroUpdateCmdLine;
   1.786 +    mDelayedLaunchType = DESKTOP;
   1.787 +    return S_OK;
   1.788 +  }
   1.789 +
   1.790 +  // Launch on the desktop
   1.791 +  if (mRequestType == DESKTOP_RESTART ||
   1.792 +      (mRequestType == DEFAULT_LAUNCH && DefaultLaunchIsDesktop())) {
   1.793 +    LaunchDesktopBrowser();
   1.794 +    SetRequestMet();
   1.795 +    return S_OK;
   1.796 +  }
   1.797 +
   1.798 +  // If we have an update in the works, don't try to activate yet,
   1.799 +  // delay until the lock is removed.
   1.800 +  if (TestForUpdateLock()) {
   1.801 +    mDelayedLaunchType = METRO;
   1.802 +    return S_OK;
   1.803 +  }
   1.804 +
   1.805 +  LaunchMetroBrowser();
   1.806 +  SetRequestMet();
   1.807 +  return S_OK;
   1.808 +}
   1.809 +
   1.810 +class ClassFactory : public IClassFactory 
   1.811 +{
   1.812 +public:
   1.813 +  ClassFactory(IUnknown *punkObject);
   1.814 +  ~ClassFactory();
   1.815 +  STDMETHODIMP Register(CLSCTX classContent, REGCLS classUse);
   1.816 +  STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
   1.817 +  STDMETHODIMP_(ULONG) AddRef() { return 2; }
   1.818 +  STDMETHODIMP_(ULONG) Release() { return 1; }
   1.819 +  STDMETHODIMP CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv);
   1.820 +  STDMETHODIMP LockServer(BOOL);
   1.821 +private:
   1.822 +  IUnknown* mUnkObject;
   1.823 +  DWORD mRegID;
   1.824 +};
   1.825 +
   1.826 +ClassFactory::ClassFactory(IUnknown* aUnkObj) :
   1.827 +  mUnkObject(aUnkObj),
   1.828 +  mRegID(0)
   1.829 +{
   1.830 +  if (mUnkObject) {
   1.831 +    mUnkObject->AddRef();
   1.832 +  }
   1.833 +}
   1.834 +
   1.835 +ClassFactory::~ClassFactory()
   1.836 +{
   1.837 +  if (mRegID) {
   1.838 +    CoRevokeClassObject(mRegID);
   1.839 +  }
   1.840 +  mUnkObject->Release();
   1.841 +}
   1.842 +
   1.843 +STDMETHODIMP
   1.844 +ClassFactory::Register(CLSCTX aClass, REGCLS aUse)
   1.845 +{
   1.846 +  return CoRegisterClassObject(__uuidof(CExecuteCommandVerb),
   1.847 +                               static_cast<IClassFactory *>(this),
   1.848 +                               aClass, aUse, &mRegID);
   1.849 +}
   1.850 +
   1.851 +STDMETHODIMP
   1.852 +ClassFactory::QueryInterface(REFIID riid, void **ppv)
   1.853 +{
   1.854 +  IUnknown *punk = nullptr;
   1.855 +  if (riid == IID_IUnknown || riid == IID_IClassFactory) {
   1.856 +    punk = static_cast<IClassFactory*>(this);
   1.857 +  }
   1.858 +  *ppv = punk;
   1.859 +  if (punk) {
   1.860 +    punk->AddRef();
   1.861 +    return S_OK;
   1.862 +  } else {
   1.863 +    return E_NOINTERFACE;
   1.864 +  }
   1.865 +}
   1.866 +
   1.867 +STDMETHODIMP
   1.868 +ClassFactory::CreateInstance(IUnknown *punkOuter, REFIID riid, void **ppv)
   1.869 +{
   1.870 +  *ppv = nullptr;
   1.871 +  if (punkOuter)
   1.872 +    return CLASS_E_NOAGGREGATION;
   1.873 +  return mUnkObject->QueryInterface(riid, ppv);
   1.874 +}
   1.875 +
   1.876 +LONG gObjRefCnt;
   1.877 +
   1.878 +STDMETHODIMP
   1.879 +ClassFactory::LockServer(BOOL fLock)
   1.880 +{
   1.881 +  if (fLock)
   1.882 +    InterlockedIncrement(&gObjRefCnt);
   1.883 +  else
   1.884 +    InterlockedDecrement(&gObjRefCnt);
   1.885 +  Log(L"ClassFactory::LockServer() %d", gObjRefCnt);
   1.886 +  return S_OK;
   1.887 +}
   1.888 +
   1.889 +int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR pszCmdLine, int)
   1.890 +{
   1.891 +#if defined(SHOW_CONSOLE)
   1.892 +  SetupConsole();
   1.893 +#endif
   1.894 +
   1.895 +  // nsis installer uses this as a helper to launch metro
   1.896 +  if (pszCmdLine && StrStrI(pszCmdLine, kNsisLaunchCmdLine))
   1.897 +  {
   1.898 +    CoInitialize(nullptr);
   1.899 +    CExecuteCommandVerb *pHandler = new CExecuteCommandVerb();
   1.900 +    if (!pHandler)
   1.901 +      return E_OUTOFMEMORY;
   1.902 +    pHandler->CommandLineMetroLaunch();
   1.903 +    delete pHandler;
   1.904 +    CoUninitialize();
   1.905 +    return 0;
   1.906 +  }
   1.907 +
   1.908 +  if (!wcslen(pszCmdLine) || StrStrI(pszCmdLine, kExplorerLaunchCmdLine))
   1.909 +  {
   1.910 +      CoInitialize(nullptr);
   1.911 +
   1.912 +      CExecuteCommandVerb *pHandler = new CExecuteCommandVerb();
   1.913 +      if (!pHandler)
   1.914 +        return E_OUTOFMEMORY;
   1.915 +
   1.916 +      IUnknown* ppi;
   1.917 +      pHandler->QueryInterface(IID_IUnknown, (void**)&ppi);
   1.918 +      if (!ppi)
   1.919 +        return E_FAIL;
   1.920 +
   1.921 +      ClassFactory classFactory(ppi);
   1.922 +      ppi->Release();
   1.923 +      ppi = nullptr;
   1.924 +
   1.925 +      // REGCLS_SINGLEUSE insures we only get used once and then discarded.
   1.926 +      if (FAILED(classFactory.Register(CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE)))
   1.927 +        return -1;
   1.928 +
   1.929 +      if (!SetTimer(nullptr, 1, HEARTBEAT_MSEC, nullptr)) {
   1.930 +        Log(L"Failed to set timer, can't process request.");
   1.931 +        return -1;
   1.932 +      }
   1.933 +
   1.934 +      MSG msg;
   1.935 +      long beatCount = 0;
   1.936 +      while (GetMessage(&msg, 0, 0, 0) > 0) {
   1.937 +        if (msg.message == WM_TIMER) {
   1.938 +          pHandler->HeartBeat();
   1.939 +          if (++beatCount > REQUEST_WAIT_TIMEOUT ||
   1.940 +              (pHandler->RequestMet() && pHandler->RefCount() < 2)) {
   1.941 +            break;
   1.942 +          }
   1.943 +        }
   1.944 +        TranslateMessage(&msg);
   1.945 +        DispatchMessage(&msg);
   1.946 +      }
   1.947 +
   1.948 +#ifdef DEBUG_DELAY_SHUTDOWN
   1.949 +      Sleep(10000);
   1.950 +#endif
   1.951 +      CoUninitialize();
   1.952 +      return 0;
   1.953 +  }
   1.954 +  return 0;
   1.955 +}

mercurial