#include "chrome/browser/process_singleton.h"
#include <shellapi.h>
#include <shobjidl.h>
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/path_service.h"
#include "base/process_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "base/win/metro.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 "base/win/wrapped_window_proc.h"
#include "chrome/browser/ui/simple_message_box.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/installer/util/browser_distribution.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/shell_util.h"
#include "chrome/installer/util/wmi.h"
#include "content/public/common/result_codes.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "net/base/escape.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/win/hwnd_util.h"
namespace {
const char kLockfile[] = "lockfile";
const char kSearchUrl[] =
"http://www.google.com/search?q=%s&sourceid=chrome&ie=UTF-8";
BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
bool* result = reinterpret_cast<bool*>(param);
*result = IsWindowVisible(window) != 0;
return !*result;
}
LRESULT CALLBACK ThunkWndProc(HWND hwnd, UINT message,
WPARAM wparam, LPARAM lparam) {
ProcessSingleton* singleton =
reinterpret_cast<ProcessSingleton*>(ui::GetWindowUserData(hwnd));
if (message == WM_NCCREATE) {
CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
singleton = reinterpret_cast<ProcessSingleton*>(cs->lpCreateParams);
CHECK(singleton);
ui::SetWindowUserData(hwnd, singleton);
} else if (!singleton) {
return ::DefWindowProc(hwnd, message, wparam, lparam);
}
return singleton->WndProc(hwnd, message, wparam, lparam);
}
bool ParseCommandLine(const COPYDATASTRUCT* cds,
CommandLine* parsed_command_line,
FilePath* current_directory) {
static const int min_message_size = 7;
if (cds->cbData < min_message_size * sizeof(wchar_t) ||
cds->cbData % sizeof(wchar_t) != 0) {
LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData;
return false;
}
DCHECK(cds->lpData);
const std::wstring msg(static_cast<wchar_t*>(cds->lpData),
cds->cbData / sizeof(wchar_t));
const std::wstring::size_type first_null = msg.find_first_of(L'\0');
if (first_null == 0 || first_null == std::wstring::npos) {
LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() <<
", first null = " << first_null;
return false;
}
if (msg.substr(0, first_null) == L"START") {
VLOG(1) << "Handling STARTUP request from another process";
const std::wstring::size_type second_null =
msg.find_first_of(L'\0', first_null + 1);
if (second_null == std::wstring::npos ||
first_null == msg.length() - 1 || second_null == msg.length()) {
LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
"parts separated by NULLs";
return false;
}
*current_directory = FilePath(msg.substr(first_null + 1,
second_null - first_null));
const std::wstring::size_type third_null =
msg.find_first_of(L'\0', second_null + 1);
if (third_null == std::wstring::npos ||
third_null == msg.length()) {
LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
"parts separated by NULLs";
}
const std::wstring cmd_line =
msg.substr(second_null + 1, third_null - second_null);
*parsed_command_line = CommandLine::FromString(cmd_line);
return true;
}
return false;
}
bool ActivateMetroChrome() {
FilePath chrome_exe;
if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
NOTREACHED() << "Failed to get chrome exe path";
return false;
}
string16 app_id = ShellUtil::GetBrowserModelId(
BrowserDistribution::GetDistribution(),
InstallUtil::IsPerUserInstall(chrome_exe.value().c_str()));
if (app_id.empty()) {
NOTREACHED() << "Failed to get chrome app user model id.";
return false;
}
base::win::ScopedComPtr<IApplicationActivationManager> activation_manager;
HRESULT hr = activation_manager.CreateInstance(
CLSID_ApplicationActivationManager);
if (!activation_manager) {
NOTREACHED() << "Failed to cocreate activation manager. Error: " << hr;
return false;
}
unsigned long pid = 0;
hr = activation_manager->ActivateApplication(app_id.c_str(),
L"open",
AO_NONE,
&pid);
if (FAILED(hr)) {
NOTREACHED() << "Failed to activate metro chrome. Error: " << hr;
return false;
}
return true;
}
}
bool ProcessSingleton::EscapeVirtualization(const FilePath& user_data_dir) {
if (::GetModuleHandle(L"sftldr_wow64.dll") ||
::GetModuleHandle(L"sftldr.dll")) {
int process_id;
if (!installer::WMIProcess::Launch(GetCommandLineW(), &process_id))
return false;
is_virtualized_ = true;
HWND hwnd = 0;
::Sleep(90);
for (int tries = 200; tries; --tries) {
hwnd = FindWindowEx(HWND_MESSAGE, NULL, chrome::kMessageWindowClass,
user_data_dir.value().c_str());
if (hwnd) {
::SetForegroundWindow(hwnd);
break;
}
::Sleep(10);
}
return true;
}
return false;
}
ProcessSingleton::ProcessSingleton(const FilePath& user_data_dir)
: window_(NULL), locked_(false), foreground_window_(NULL),
is_virtualized_(false), lock_file_(INVALID_HANDLE_VALUE) {
remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
chrome::kMessageWindowClass,
user_data_dir.value().c_str());
if (!remote_window_ && !EscapeVirtualization(user_data_dir)) {
std::wstring mutex_name(L"Local\\ChromeProcessSingletonStartup!");
base::win::ScopedHandle only_me(
CreateMutex(NULL, FALSE, mutex_name.c_str()));
DCHECK(only_me.Get() != NULL) << "GetLastError = " << GetLastError();
DWORD result = WaitForSingleObject(only_me, INFINITE);
DCHECK(result == WAIT_OBJECT_0) << "Result = " << result <<
"GetLastError = " << GetLastError();
remote_window_ = FindWindowEx(HWND_MESSAGE, NULL,
chrome::kMessageWindowClass,
user_data_dir.value().c_str());
if (!remote_window_) {
FilePath lock_file_path = user_data_dir.AppendASCII(kLockfile);
lock_file_ = CreateFile(lock_file_path.value().c_str(),
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
NULL);
DWORD error = GetLastError();
LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE &&
error == ERROR_ALREADY_EXISTS) << "Lock file exists but is writable.";
LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE)
<< "Lock file can not be created! Error code: " << error;
if (lock_file_ != INVALID_HANDLE_VALUE) {
HINSTANCE hinst = base::GetModuleFromAddress(&ThunkWndProc);
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(wc);
wc.lpfnWndProc = base::win::WrappedWindowProc<ThunkWndProc>;
wc.hInstance = hinst;
wc.lpszClassName = chrome::kMessageWindowClass;
ATOM clazz = ::RegisterClassEx(&wc);
DCHECK(clazz);
window_ = ::CreateWindow(MAKEINTATOM(clazz),
user_data_dir.value().c_str(),
0, 0, 0, 0, 0, HWND_MESSAGE, 0, hinst, this);
CHECK(window_);
}
}
BOOL success = ReleaseMutex(only_me);
DCHECK(success) << "GetLastError = " << GetLastError();
}
}
ProcessSingleton::~ProcessSingleton() {
if (window_) {
::DestroyWindow(window_);
::UnregisterClass(chrome::kMessageWindowClass,
base::GetModuleFromAddress(&ThunkWndProc));
}
if (lock_file_ != INVALID_HANDLE_VALUE)
CloseHandle(lock_file_);
}
ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
if (is_virtualized_)
return PROCESS_NOTIFIED;
if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_)
return LOCK_ERROR;
else if (!remote_window_)
return PROCESS_NONE;
DWORD process_id = 0;
DWORD thread_id = GetWindowThreadProcessId(remote_window_, &process_id);
if (!thread_id || !process_id) {
remote_window_ = NULL;
return PROCESS_NONE;
}
if (base::win::IsMetroProcess()) {
string16 terms;
base::win::MetroLaunchType launch = base::win::GetMetroLaunchParams(&terms);
if (launch != base::win::METRO_SEARCH) {
LOG(WARNING) << "In metro mode, but and launch is " << launch;
} else {
std::string query = net::EscapeQueryParamValue(UTF16ToUTF8(terms), true);
std::string url = base::StringPrintf(kSearchUrl, query.c_str());
SHELLEXECUTEINFOA sei = { sizeof(sei) };
sei.fMask = SEE_MASK_FLAG_LOG_USAGE;
sei.nShow = SW_SHOWNORMAL;
sei.lpFile = url.c_str();
OutputDebugStringA(sei.lpFile);
sei.lpDirectory = "";
::ShellExecuteExA(&sei);
}
return PROCESS_NOTIFIED;
}
std::wstring to_send(L"START\0", 6);
FilePath cur_dir;
if (!PathService::Get(base::DIR_CURRENT, &cur_dir))
return PROCESS_NONE;
to_send.append(cur_dir.value());
to_send.append(L"\0", 1);
to_send.append(GetCommandLineW());
to_send.append(L"\0", 1);
AllowSetForegroundWindow(process_id);
COPYDATASTRUCT cds;
cds.dwData = 0;
cds.cbData = static_cast<DWORD>((to_send.length() + 1) * sizeof(wchar_t));
cds.lpData = const_cast<wchar_t*>(to_send.c_str());
DWORD_PTR result = 0;
if (SendMessageTimeout(remote_window_,
WM_COPYDATA,
NULL,
reinterpret_cast<LPARAM>(&cds),
SMTO_ABORTIFHUNG,
kTimeoutInSeconds * 1000,
&result)) {
if (!result) {
remote_window_ = NULL;
return PROCESS_NONE;
}
base::win::ScopedHandle process_handle;
if (base::win::GetVersion() >= base::win::VERSION_WIN8 &&
base::OpenProcessHandleWithAccess(
process_id, PROCESS_QUERY_INFORMATION,
process_handle.Receive())) {
if (base::win::IsProcessImmersive(process_handle.Get()))
ActivateMetroChrome();
}
return PROCESS_NOTIFIED;
}
if (!IsWindow(remote_window_)) {
remote_window_ = NULL;
return PROCESS_NONE;
}
bool visible_window = false;
EnumThreadWindows(thread_id,
&BrowserWindowEnumeration,
reinterpret_cast<LPARAM>(&visible_window));
if (visible_window && chrome::ShowMessageBox(NULL,
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
l10n_util::GetStringUTF16(IDS_BROWSER_HUNGBROWSER_MESSAGE),
chrome::MESSAGE_BOX_TYPE_QUESTION) == chrome::MESSAGE_BOX_RESULT_NO) {
return PROCESS_NOTIFIED;
}
base::KillProcessById(process_id, content::RESULT_CODE_HUNG, true);
remote_window_ = NULL;
return PROCESS_NONE;
}
ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate(
const NotificationCallback& notification_callback) {
NotifyResult result = NotifyOtherProcess();
if (result != PROCESS_NONE)
return result;
return Create(notification_callback) ? PROCESS_NONE : PROFILE_IN_USE;
}
bool ProcessSingleton::Create(
const NotificationCallback& notification_callback) {
DCHECK(!remote_window_);
DCHECK(notification_callback_.is_null());
if (window_ != NULL)
notification_callback_ = notification_callback;
return window_ != NULL;
}
void ProcessSingleton::Cleanup() {
}
LRESULT ProcessSingleton::OnCopyData(HWND hwnd, const COPYDATASTRUCT* cds) {
if (locked_) {
#if defined(USE_AURA)
NOTIMPLEMENTED();
#else
if (foreground_window_ != NULL && IsWindow(foreground_window_)) {
SetForegroundWindow(foreground_window_);
} else {
CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
FilePath current_directory;
if (ParseCommandLine(cds, &parsed_command_line, ¤t_directory))
saved_startup_messages_.push_back(
std::make_pair(parsed_command_line.argv(), current_directory));
}
#endif
return TRUE;
}
CommandLine parsed_command_line(CommandLine::NO_PROGRAM);
FilePath current_directory;
if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory))
return TRUE;
return notification_callback_.Run(parsed_command_line, current_directory) ?
TRUE : FALSE;
}
LRESULT ProcessSingleton::WndProc(HWND hwnd, UINT message,
WPARAM wparam, LPARAM lparam) {
switch (message) {
case WM_COPYDATA:
return OnCopyData(reinterpret_cast<HWND>(wparam),
reinterpret_cast<COPYDATASTRUCT*>(lparam));
default:
break;
}
return ::DefWindowProc(hwnd, message, wparam, lparam);
}