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 + ®isteredApp); 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 +}