Wed, 31 Dec 2014 06:55:50 +0100
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 ®isteredApp);
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 }