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 29 using 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. 37 class 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, ®istration_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 89 DelegateExecuteModule _AtlModule; 90 91 using delegate_execute::DelegateExecuteOperation; 92 using base::win::ScopedHandle; 93 94 int 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 177 extern "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