1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <atlbase.h>
6#include <atlcom.h>
7#include <atlctl.h>
8#include <initguid.h>
9#include <shellapi.h>
10
11#include "base/at_exit.h"
12#include "base/command_line.h"
13#include "base/files/file_util.h"
14#include "base/memory/scoped_ptr.h"
15#include "base/process/kill.h"
16#include "base/strings/string16.h"
17#include "base/win/scoped_com_initializer.h"
18#include "base/win/scoped_comptr.h"
19#include "base/win/scoped_handle.h"
20#include "base/win/windows_version.h"
21#include "breakpad/src/client/windows/handler/exception_handler.h"
22#include "chrome/common/chrome_switches.h"
23#include "chrome/installer/util/browser_distribution.h"
24#include "win8/delegate_execute/command_execute_impl.h"
25#include "win8/delegate_execute/crash_server_init.h"
26#include "win8/delegate_execute/delegate_execute_operation.h"
27#include "win8/delegate_execute/resource.h"
28
29using namespace ATL;
30
31// Usually classes derived from CAtlExeModuleT, or other types of ATL
32// COM module classes statically define their CLSID at compile time through
33// the use of various macros, and ATL internals takes care of creating the
34// class objects and registering them.  However, we need to register the same
35// object with different CLSIDs depending on a runtime setting, so we handle
36// that logic here, before the main ATL message loop runs.
37class DelegateExecuteModule
38    : public ATL::CAtlExeModuleT< DelegateExecuteModule > {
39 public :
40  typedef ATL::CAtlExeModuleT<DelegateExecuteModule> ParentClass;
41  typedef CComObject<CommandExecuteImpl> ImplType;
42
43  DelegateExecuteModule()
44      : registration_token_(0) {
45  }
46
47  HRESULT PreMessageLoop(int nShowCmd) {
48    HRESULT hr = S_OK;
49    base::string16 clsid_string;
50    GUID clsid;
51    BrowserDistribution* dist = BrowserDistribution::GetDistribution();
52    if (!dist->GetCommandExecuteImplClsid(&clsid_string))
53      return E_FAIL;
54    hr = ::CLSIDFromString(clsid_string.c_str(), &clsid);
55    if (FAILED(hr))
56      return hr;
57
58    // We use the same class creation logic as ATL itself.  See
59    // _ATL_OBJMAP_ENTRY::RegisterClassObject() in atlbase.h
60    hr = ImplType::_ClassFactoryCreatorClass::CreateInstance(
61        ImplType::_CreatorClass::CreateInstance, IID_IUnknown,
62        instance_.ReceiveVoid());
63    if (FAILED(hr))
64      return hr;
65    hr = ::CoRegisterClassObject(clsid, instance_, CLSCTX_LOCAL_SERVER,
66        REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED, &registration_token_);
67    if (FAILED(hr))
68      return hr;
69
70    return ParentClass::PreMessageLoop(nShowCmd);
71  }
72
73  HRESULT PostMessageLoop() {
74    if (registration_token_ != 0) {
75      ::CoRevokeClassObject(registration_token_);
76      registration_token_ = 0;
77    }
78
79    instance_.Release();
80
81    return ParentClass::PostMessageLoop();
82  }
83
84 private:
85  base::win::ScopedComPtr<IUnknown> instance_;
86  DWORD registration_token_;
87};
88
89DelegateExecuteModule _AtlModule;
90
91using delegate_execute::DelegateExecuteOperation;
92using base::win::ScopedHandle;
93
94int RelaunchChrome(const DelegateExecuteOperation& operation) {
95  AtlTrace("Relaunching [%ls] with flags [%ls]\n",
96           operation.mutex().c_str(), operation.relaunch_flags().c_str());
97  ScopedHandle mutex(OpenMutexW(SYNCHRONIZE, FALSE, operation.mutex().c_str()));
98  if (mutex.IsValid()) {
99    const int kWaitSeconds = 5;
100    DWORD result = ::WaitForSingleObject(mutex.Get(), kWaitSeconds * 1000);
101    if (result == WAIT_ABANDONED) {
102      // This is the normal case. Chrome exits and windows marks the mutex as
103      // abandoned.
104    } else if (result == WAIT_OBJECT_0) {
105      // This is unexpected. Check if somebody is not closing the mutex on
106      // RelaunchChromehelper, the mutex should not be closed.
107      AtlTrace("Unexpected release of the relaunch mutex!!\n");
108    } else if (result == WAIT_TIMEOUT) {
109      // This could mean that Chrome is hung. Proceed to exterminate.
110      DWORD pid = operation.GetParentPid();
111      AtlTrace("%ds timeout. Killing Chrome %d\n", kWaitSeconds, pid);
112      base::KillProcessById(pid, 0, false);
113    } else {
114      AtlTrace("Failed to wait for relaunch mutex, result is 0x%x\n", result);
115    }
116  } else {
117    // It is possible that chrome exits so fast that the mutex is not there.
118    AtlTrace("No relaunch mutex found\n");
119  }
120
121  // On Windows 8+ to launch Chrome we rely on Windows to use the
122  // IExecuteCommand interface exposed by delegate_execute to launch Chrome
123  // into Windows 8 metro mode or desktop.
124  // On Windows 7 we don't use delegate_execute and instead use plain vanilla
125  // ShellExecute to launch Chrome into ASH or desktop.
126  if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
127    base::win::ScopedCOMInitializer com_initializer;
128
129    base::string16 relaunch_flags(operation.relaunch_flags());
130    SHELLEXECUTEINFO sei = { sizeof(sei) };
131    sei.fMask = SEE_MASK_FLAG_LOG_USAGE;
132    sei.nShow = SW_SHOWNORMAL;
133    sei.lpFile = operation.shortcut().value().c_str();
134    sei.lpParameters = relaunch_flags.c_str();
135
136    AtlTrace(L"Relaunching Chrome via shortcut [%ls]\n", sei.lpFile);
137
138    if (!::ShellExecuteExW(&sei)) {
139      int error = HRESULT_FROM_WIN32(::GetLastError());
140      AtlTrace("ShellExecute returned 0x%08X\n", error);
141      return error;
142    }
143  } else {
144    base::FilePath chrome_exe_path;
145    bool found_exe = CommandExecuteImpl::FindChromeExe(&chrome_exe_path);
146    DCHECK(found_exe);
147    if (found_exe) {
148      bool launch_ash = CommandLine::ForCurrentProcess()->HasSwitch(
149          switches::kForceImmersive);
150      if (launch_ash) {
151        AtlTrace(L"Relaunching Chrome into Windows ASH on Windows 7\n");
152      } else {
153        AtlTrace(L"Relaunching Chrome into Desktop From ASH on Windows 7\n");
154      }
155      SHELLEXECUTEINFO sei = { sizeof(sei) };
156      sei.fMask = SEE_MASK_FLAG_LOG_USAGE;
157      sei.nShow = SW_SHOWNORMAL;
158      // No point in using the shortcut if we are launching into ASH as any
159      // additonal command line switches specified in the shortcut will be
160      // ignored. This is because we don't have a good way to send the command
161      // line switches from the viewer to the browser process.
162      sei.lpFile = (launch_ash || operation.shortcut().empty()) ?
163            chrome_exe_path.value().c_str() :
164                operation.shortcut().value().c_str();
165      sei.lpParameters =
166          launch_ash ? L"-ServerName:DefaultBrowserServer" : NULL;
167      if (!::ShellExecuteExW(&sei)) {
168        int error = HRESULT_FROM_WIN32(::GetLastError());
169        AtlTrace("ShellExecute returned 0x%08X\n", error);
170        return error;
171      }
172    }
173  }
174  return S_OK;
175}
176
177extern "C" int WINAPI _tWinMain(HINSTANCE , HINSTANCE, LPTSTR, int nShowCmd) {
178  scoped_ptr<google_breakpad::ExceptionHandler> breakpad =
179      delegate_execute::InitializeCrashReporting();
180
181  base::AtExitManager exit_manager;
182  AtlTrace("delegate_execute enter\n");
183
184  CommandLine::Init(0, NULL);
185  HRESULT ret_code = E_UNEXPECTED;
186  DelegateExecuteOperation operation;
187  if (operation.Init(CommandLine::ForCurrentProcess())) {
188    switch (operation.operation_type()) {
189      case DelegateExecuteOperation::DELEGATE_EXECUTE:
190        ret_code = _AtlModule.WinMain(nShowCmd);
191        break;
192      case DelegateExecuteOperation::RELAUNCH_CHROME:
193        ret_code = RelaunchChrome(operation);
194        break;
195      default:
196        NOTREACHED();
197    }
198  }
199  AtlTrace("delegate_execute exit, code = %d\n", ret_code);
200  return ret_code;
201}
202