// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <atlbase.h>
#include <atlcom.h>
#include <atlctl.h>
#include <initguid.h>
#include <shellapi.h>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/process/kill.h"
#include "base/strings/string16.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_comptr.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_version.h"
#include "breakpad/src/client/windows/handler/exception_handler.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/installer/util/browser_distribution.h"
#include "win8/delegate_execute/command_execute_impl.h"
#include "win8/delegate_execute/crash_server_init.h"
#include "win8/delegate_execute/delegate_execute_operation.h"
#include "win8/delegate_execute/resource.h"
using namespace ATL;
// Usually classes derived from CAtlExeModuleT, or other types of ATL
// COM module classes statically define their CLSID at compile time through
// the use of various macros, and ATL internals takes care of creating the
// class objects and registering them. However, we need to register the same
// object with different CLSIDs depending on a runtime setting, so we handle
// that logic here, before the main ATL message loop runs.
class DelegateExecuteModule
: public ATL::CAtlExeModuleT< DelegateExecuteModule > {
public :
typedef ATL::CAtlExeModuleT<DelegateExecuteModule> ParentClass;
typedef CComObject<CommandExecuteImpl> ImplType;
DelegateExecuteModule()
: registration_token_(0) {
}
HRESULT PreMessageLoop(int nShowCmd) {
HRESULT hr = S_OK;
base::string16 clsid_string;
GUID clsid;
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
if (!dist->GetCommandExecuteImplClsid(&clsid_string))
return E_FAIL;
hr = ::CLSIDFromString(clsid_string.c_str(), &clsid);
if (FAILED(hr))
return hr;
// We use the same class creation logic as ATL itself. See
// _ATL_OBJMAP_ENTRY::RegisterClassObject() in atlbase.h
hr = ImplType::_ClassFactoryCreatorClass::CreateInstance(
ImplType::_CreatorClass::CreateInstance, IID_IUnknown,
instance_.ReceiveVoid());
if (FAILED(hr))
return hr;
hr = ::CoRegisterClassObject(clsid, instance_, CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED, ®istration_token_);
if (FAILED(hr))
return hr;
return ParentClass::PreMessageLoop(nShowCmd);
}
HRESULT PostMessageLoop() {
if (registration_token_ != 0) {
::CoRevokeClassObject(registration_token_);
registration_token_ = 0;
}
instance_.Release();
return ParentClass::PostMessageLoop();
}
private:
base::win::ScopedComPtr<IUnknown> instance_;
DWORD registration_token_;
};
DelegateExecuteModule _AtlModule;
using delegate_execute::DelegateExecuteOperation;
using base::win::ScopedHandle;
int RelaunchChrome(const DelegateExecuteOperation& operation) {
AtlTrace("Relaunching [%ls] with flags [%ls]\n",
operation.mutex().c_str(), operation.relaunch_flags().c_str());
ScopedHandle mutex(OpenMutexW(SYNCHRONIZE, FALSE, operation.mutex().c_str()));
if (mutex.IsValid()) {
const int kWaitSeconds = 5;
DWORD result = ::WaitForSingleObject(mutex.Get(), kWaitSeconds * 1000);
if (result == WAIT_ABANDONED) {
// This is the normal case. Chrome exits and windows marks the mutex as
// abandoned.
} else if (result == WAIT_OBJECT_0) {
// This is unexpected. Check if somebody is not closing the mutex on
// RelaunchChromehelper, the mutex should not be closed.
AtlTrace("Unexpected release of the relaunch mutex!!\n");
} else if (result == WAIT_TIMEOUT) {
// This could mean that Chrome is hung. Proceed to exterminate.
DWORD pid = operation.GetParentPid();
AtlTrace("%ds timeout. Killing Chrome %d\n", kWaitSeconds, pid);
base::KillProcessById(pid, 0, false);
} else {
AtlTrace("Failed to wait for relaunch mutex, result is 0x%x\n", result);
}
} else {
// It is possible that chrome exits so fast that the mutex is not there.
AtlTrace("No relaunch mutex found\n");
}
// On Windows 8+ to launch Chrome we rely on Windows to use the
// IExecuteCommand interface exposed by delegate_execute to launch Chrome
// into Windows 8 metro mode or desktop.
// On Windows 7 we don't use delegate_execute and instead use plain vanilla
// ShellExecute to launch Chrome into ASH or desktop.
if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
base::win::ScopedCOMInitializer com_initializer;
base::string16 relaunch_flags(operation.relaunch_flags());
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.fMask = SEE_MASK_FLAG_LOG_USAGE;
sei.nShow = SW_SHOWNORMAL;
sei.lpFile = operation.shortcut().value().c_str();
sei.lpParameters = relaunch_flags.c_str();
AtlTrace(L"Relaunching Chrome via shortcut [%ls]\n", sei.lpFile);
if (!::ShellExecuteExW(&sei)) {
int error = HRESULT_FROM_WIN32(::GetLastError());
AtlTrace("ShellExecute returned 0x%08X\n", error);
return error;
}
} else {
base::FilePath chrome_exe_path;
bool found_exe = CommandExecuteImpl::FindChromeExe(&chrome_exe_path);
DCHECK(found_exe);
if (found_exe) {
bool launch_ash = CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceImmersive);
if (launch_ash) {
AtlTrace(L"Relaunching Chrome into Windows ASH on Windows 7\n");
} else {
AtlTrace(L"Relaunching Chrome into Desktop From ASH on Windows 7\n");
}
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.fMask = SEE_MASK_FLAG_LOG_USAGE;
sei.nShow = SW_SHOWNORMAL;
// No point in using the shortcut if we are launching into ASH as any
// additonal command line switches specified in the shortcut will be
// ignored. This is because we don't have a good way to send the command
// line switches from the viewer to the browser process.
sei.lpFile = (launch_ash || operation.shortcut().empty()) ?
chrome_exe_path.value().c_str() :
operation.shortcut().value().c_str();
sei.lpParameters =
launch_ash ? L"-ServerName:DefaultBrowserServer" : NULL;
if (!::ShellExecuteExW(&sei)) {
int error = HRESULT_FROM_WIN32(::GetLastError());
AtlTrace("ShellExecute returned 0x%08X\n", error);
return error;
}
}
}
return S_OK;
}
extern "C" int WINAPI _tWinMain(HINSTANCE , HINSTANCE, LPTSTR, int nShowCmd) {
scoped_ptr<google_breakpad::ExceptionHandler> breakpad =
delegate_execute::InitializeCrashReporting();
base::AtExitManager exit_manager;
AtlTrace("delegate_execute enter\n");
CommandLine::Init(0, NULL);
HRESULT ret_code = E_UNEXPECTED;
DelegateExecuteOperation operation;
if (operation.Init(CommandLine::ForCurrentProcess())) {
switch (operation.operation_type()) {
case DelegateExecuteOperation::DELEGATE_EXECUTE:
ret_code = _AtlModule.WinMain(nShowCmd);
break;
case DelegateExecuteOperation::RELAUNCH_CHROME:
ret_code = RelaunchChrome(operation);
break;
default:
NOTREACHED();
}
}
AtlTrace("delegate_execute exit, code = %d\n", ret_code);
return ret_code;
}