Copy some relevant files over from Chromium
This commit is contained in:
parent
285a4789b3
commit
27fa5d880a
5 changed files with 1796 additions and 0 deletions
183
atom/browser/process_singleton.h
Normal file
183
atom/browser/process_singleton.h
Normal file
|
@ -0,0 +1,183 @@
|
|||
// 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.
|
||||
|
||||
#ifndef CHROME_BROWSER_PROCESS_SINGLETON_H_
|
||||
#define CHROME_BROWSER_PROCESS_SINGLETON_H_
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/callback.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/process/process.h"
|
||||
#include "base/threading/non_thread_safe.h"
|
||||
#include "ui/gfx/native_widget_types.h"
|
||||
|
||||
#if defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include "base/win/message_window.h"
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
namespace base {
|
||||
class CommandLine;
|
||||
}
|
||||
|
||||
// ProcessSingleton ----------------------------------------------------------
|
||||
//
|
||||
// This class allows different browser processes to communicate with
|
||||
// each other. It is named according to the user data directory, so
|
||||
// we can be sure that no more than one copy of the application can be
|
||||
// running at once with a given data directory.
|
||||
//
|
||||
// Implementation notes:
|
||||
// - the Windows implementation uses an invisible global message window;
|
||||
// - the Linux implementation uses a Unix domain socket in the user data dir.
|
||||
|
||||
class ProcessSingleton : public base::NonThreadSafe {
|
||||
public:
|
||||
enum NotifyResult {
|
||||
PROCESS_NONE,
|
||||
PROCESS_NOTIFIED,
|
||||
PROFILE_IN_USE,
|
||||
LOCK_ERROR,
|
||||
};
|
||||
|
||||
// Implement this callback to handle notifications from other processes. The
|
||||
// callback will receive the command line and directory with which the other
|
||||
// Chrome process was launched. Return true if the command line will be
|
||||
// handled within the current browser instance or false if the remote process
|
||||
// should handle it (i.e., because the current process is shutting down).
|
||||
using NotificationCallback =
|
||||
base::Callback<bool(const base::CommandLine& command_line,
|
||||
const base::FilePath& current_directory)>;
|
||||
|
||||
ProcessSingleton(const base::FilePath& user_data_dir,
|
||||
const NotificationCallback& notification_callback);
|
||||
~ProcessSingleton();
|
||||
|
||||
// Notify another process, if available. Otherwise sets ourselves as the
|
||||
// singleton instance. Returns PROCESS_NONE if we became the singleton
|
||||
// instance. Callers are guaranteed to either have notified an existing
|
||||
// process or have grabbed the singleton (unless the profile is locked by an
|
||||
// unreachable process).
|
||||
// TODO(brettw): Make the implementation of this method non-platform-specific
|
||||
// by making Linux re-use the Windows implementation.
|
||||
NotifyResult NotifyOtherProcessOrCreate();
|
||||
|
||||
// Sets ourself up as the singleton instance. Returns true on success. If
|
||||
// false is returned, we are not the singleton instance and the caller must
|
||||
// exit.
|
||||
// NOTE: Most callers should generally prefer NotifyOtherProcessOrCreate() to
|
||||
// this method, only callers for whom failure is preferred to notifying
|
||||
// another process should call this directly.
|
||||
bool Create();
|
||||
|
||||
// Clear any lock state during shutdown.
|
||||
void Cleanup();
|
||||
|
||||
#if defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
static void DisablePromptForTesting();
|
||||
#endif
|
||||
#if defined(OS_WIN)
|
||||
// Called to query whether to kill a hung browser process that has visible
|
||||
// windows. Return true to allow killing the hung process.
|
||||
using ShouldKillRemoteProcessCallback = base::Callback<bool()>;
|
||||
void OverrideShouldKillRemoteProcessCallbackForTesting(
|
||||
const ShouldKillRemoteProcessCallback& display_dialog_callback);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// Notify another process, if available.
|
||||
// Returns true if another process was found and notified, false if we should
|
||||
// continue with the current process.
|
||||
// On Windows, Create() has to be called before this.
|
||||
NotifyResult NotifyOtherProcess();
|
||||
|
||||
#if defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
// Exposed for testing. We use a timeout on Linux, and in tests we want
|
||||
// this timeout to be short.
|
||||
NotifyResult NotifyOtherProcessWithTimeout(
|
||||
const base::CommandLine& command_line,
|
||||
int retry_attempts,
|
||||
const base::TimeDelta& timeout,
|
||||
bool kill_unresponsive);
|
||||
NotifyResult NotifyOtherProcessWithTimeoutOrCreate(
|
||||
const base::CommandLine& command_line,
|
||||
int retry_attempts,
|
||||
const base::TimeDelta& timeout);
|
||||
void OverrideCurrentPidForTesting(base::ProcessId pid);
|
||||
void OverrideKillCallbackForTesting(
|
||||
const base::Callback<void(int)>& callback);
|
||||
#endif
|
||||
|
||||
private:
|
||||
NotificationCallback notification_callback_; // Handler for notifications.
|
||||
|
||||
#if defined(OS_WIN)
|
||||
bool EscapeVirtualization(const base::FilePath& user_data_dir);
|
||||
|
||||
HWND remote_window_; // The HWND_MESSAGE of another browser.
|
||||
base::win::MessageWindow window_; // The message-only window.
|
||||
bool is_virtualized_; // Stuck inside Microsoft Softricity VM environment.
|
||||
HANDLE lock_file_;
|
||||
base::FilePath user_data_dir_;
|
||||
ShouldKillRemoteProcessCallback should_kill_remote_process_callback_;
|
||||
#elif defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
// Return true if the given pid is one of our child processes.
|
||||
// Assumes that the current pid is the root of all pids of the current
|
||||
// instance.
|
||||
bool IsSameChromeInstance(pid_t pid);
|
||||
|
||||
// Extract the process's pid from a symbol link path and if it is on
|
||||
// the same host, kill the process, unlink the lock file and return true.
|
||||
// If the process is part of the same chrome instance, unlink the lock file
|
||||
// and return true without killing it.
|
||||
// If the process is on a different host, return false.
|
||||
bool KillProcessByLockPath();
|
||||
|
||||
// Default function to kill a process, overridable by tests.
|
||||
void KillProcess(int pid);
|
||||
|
||||
// Allow overriding for tests.
|
||||
base::ProcessId current_pid_;
|
||||
|
||||
// Function to call when the other process is hung and needs to be killed.
|
||||
// Allows overriding for tests.
|
||||
base::Callback<void(int)> kill_callback_;
|
||||
|
||||
// Path in file system to the socket.
|
||||
base::FilePath socket_path_;
|
||||
|
||||
// Path in file system to the lock.
|
||||
base::FilePath lock_path_;
|
||||
|
||||
// Path in file system to the cookie file.
|
||||
base::FilePath cookie_path_;
|
||||
|
||||
// Temporary directory to hold the socket.
|
||||
base::ScopedTempDir socket_dir_;
|
||||
|
||||
// Helper class for linux specific messages. LinuxWatcher is ref counted
|
||||
// because it posts messages between threads.
|
||||
class LinuxWatcher;
|
||||
scoped_refptr<LinuxWatcher> watcher_;
|
||||
#endif
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProcessSingleton);
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_PROCESS_SINGLETON_H_
|
1062
atom/browser/process_singleton_posix.cc
Normal file
1062
atom/browser/process_singleton_posix.cc
Normal file
File diff suppressed because it is too large
Load diff
53
atom/browser/process_singleton_startup_lock.cc
Normal file
53
atom/browser/process_singleton_startup_lock.cc
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2013 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 "chrome/browser/process_singleton_startup_lock.h"
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/logging.h"
|
||||
|
||||
ProcessSingletonStartupLock::ProcessSingletonStartupLock(
|
||||
const ProcessSingleton::NotificationCallback& original_callback)
|
||||
: locked_(true),
|
||||
original_callback_(original_callback) {}
|
||||
|
||||
ProcessSingletonStartupLock::~ProcessSingletonStartupLock() {}
|
||||
|
||||
ProcessSingleton::NotificationCallback
|
||||
ProcessSingletonStartupLock::AsNotificationCallback() {
|
||||
return base::Bind(&ProcessSingletonStartupLock::NotificationCallbackImpl,
|
||||
base::Unretained(this));
|
||||
}
|
||||
|
||||
void ProcessSingletonStartupLock::Unlock() {
|
||||
DCHECK(CalledOnValidThread());
|
||||
locked_ = false;
|
||||
|
||||
// Replay the command lines of the messages which were received while the
|
||||
// ProcessSingleton was locked. Only replay each message once.
|
||||
std::set<DelayedStartupMessage> replayed_messages;
|
||||
for (std::vector<DelayedStartupMessage>::const_iterator it =
|
||||
saved_startup_messages_.begin();
|
||||
it != saved_startup_messages_.end(); ++it) {
|
||||
if (replayed_messages.find(*it) != replayed_messages.end())
|
||||
continue;
|
||||
original_callback_.Run(base::CommandLine(it->first), it->second);
|
||||
replayed_messages.insert(*it);
|
||||
}
|
||||
saved_startup_messages_.clear();
|
||||
}
|
||||
|
||||
bool ProcessSingletonStartupLock::NotificationCallbackImpl(
|
||||
const base::CommandLine& command_line,
|
||||
const base::FilePath& current_directory) {
|
||||
if (locked_) {
|
||||
// If locked, it means we are not ready to process this message because
|
||||
// we are probably in a first run critical phase.
|
||||
saved_startup_messages_.push_back(
|
||||
std::make_pair(command_line.argv(), current_directory));
|
||||
return true;
|
||||
} else {
|
||||
return original_callback_.Run(command_line, current_directory);
|
||||
}
|
||||
}
|
57
atom/browser/process_singleton_startup_lock.h
Normal file
57
atom/browser/process_singleton_startup_lock.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) 2013 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.
|
||||
|
||||
#ifndef CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_
|
||||
#define CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_
|
||||
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/basictypes.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/threading/non_thread_safe.h"
|
||||
#include "chrome/browser/process_singleton.h"
|
||||
|
||||
// Provides a ProcessSingleton::NotificationCallback that can queue up
|
||||
// command-line invocations during startup and execute them when startup
|
||||
// completes.
|
||||
//
|
||||
// The object starts in a locked state. |Unlock()| must be called
|
||||
// when the process is prepared to handle command-line invocations.
|
||||
//
|
||||
// Once unlocked, notifications are forwarded to a wrapped NotificationCallback.
|
||||
class ProcessSingletonStartupLock : public base::NonThreadSafe {
|
||||
public:
|
||||
explicit ProcessSingletonStartupLock(
|
||||
const ProcessSingleton::NotificationCallback& original_callback);
|
||||
~ProcessSingletonStartupLock();
|
||||
|
||||
// Returns the ProcessSingleton::NotificationCallback.
|
||||
// The callback is only valid during the lifetime of the
|
||||
// ProcessSingletonStartupLock instance.
|
||||
ProcessSingleton::NotificationCallback AsNotificationCallback();
|
||||
|
||||
// Executes previously queued command-line invocations and allows future
|
||||
// invocations to be executed immediately.
|
||||
void Unlock();
|
||||
|
||||
bool locked() { return locked_; }
|
||||
|
||||
private:
|
||||
typedef std::pair<base::CommandLine::StringVector, base::FilePath>
|
||||
DelayedStartupMessage;
|
||||
|
||||
bool NotificationCallbackImpl(const base::CommandLine& command_line,
|
||||
const base::FilePath& current_directory);
|
||||
|
||||
bool locked_;
|
||||
std::vector<DelayedStartupMessage> saved_startup_messages_;
|
||||
ProcessSingleton::NotificationCallback original_callback_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProcessSingletonStartupLock);
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_PROCESS_SINGLETON_STARTUP_LOCK_H_
|
441
atom/browser/process_singleton_win.cc
Normal file
441
atom/browser/process_singleton_win.cc
Normal file
|
@ -0,0 +1,441 @@
|
|||
// 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 "chrome/browser/process_singleton.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
|
||||
#include "base/base_paths.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/process/process.h"
|
||||
#include "base/process/process_info.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/win/metro.h"
|
||||
#include "base/win/registry.h"
|
||||
#include "base/win/scoped_handle.h"
|
||||
#include "base/win/windows_version.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/browser/browser_process_platform_part.h"
|
||||
#include "chrome/browser/chrome_process_finder_win.h"
|
||||
#include "chrome/browser/metro_utils/metro_chrome_win.h"
|
||||
#include "chrome/browser/shell_integration.h"
|
||||
#include "chrome/browser/ui/simple_message_box.h"
|
||||
#include "chrome/common/chrome_constants.h"
|
||||
#include "chrome/common/chrome_paths.h"
|
||||
#include "chrome/common/chrome_paths_internal.h"
|
||||
#include "chrome/common/chrome_switches.h"
|
||||
#include "chrome/grit/chromium_strings.h"
|
||||
#include "chrome/installer/util/wmi.h"
|
||||
#include "content/public/common/result_codes.h"
|
||||
#include "net/base/escape.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/gfx/win/hwnd_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char kLockfile[] = "lockfile";
|
||||
|
||||
const int kMetroChromeActivationTimeoutMs = 3000;
|
||||
|
||||
// A helper class that acquires the given |mutex| while the AutoLockMutex is in
|
||||
// scope.
|
||||
class AutoLockMutex {
|
||||
public:
|
||||
explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) {
|
||||
DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
|
||||
DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
|
||||
}
|
||||
|
||||
~AutoLockMutex() {
|
||||
BOOL released = ::ReleaseMutex(mutex_);
|
||||
DPCHECK(released);
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE mutex_;
|
||||
DISALLOW_COPY_AND_ASSIGN(AutoLockMutex);
|
||||
};
|
||||
|
||||
// A helper class that releases the given |mutex| while the AutoUnlockMutex is
|
||||
// in scope and immediately re-acquires it when going out of scope.
|
||||
class AutoUnlockMutex {
|
||||
public:
|
||||
explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) {
|
||||
BOOL released = ::ReleaseMutex(mutex_);
|
||||
DPCHECK(released);
|
||||
}
|
||||
|
||||
~AutoUnlockMutex() {
|
||||
DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
|
||||
DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE mutex_;
|
||||
DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex);
|
||||
};
|
||||
|
||||
// Checks the visibility of the enumerated window and signals once a visible
|
||||
// window has been found.
|
||||
BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
|
||||
bool* result = reinterpret_cast<bool*>(param);
|
||||
*result = ::IsWindowVisible(window) != 0;
|
||||
// Stops enumeration if a visible window has been found.
|
||||
return !*result;
|
||||
}
|
||||
|
||||
bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
base::CommandLine* parsed_command_line,
|
||||
base::FilePath* current_directory) {
|
||||
// We should have enough room for the shortest command (min_message_size)
|
||||
// and also be a multiple of wchar_t bytes. The shortest command
|
||||
// possible is L"START\0\0" (empty current directory and command line).
|
||||
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;
|
||||
}
|
||||
|
||||
// We split the string into 4 parts on NULLs.
|
||||
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) {
|
||||
// no NULL byte, don't know what to do
|
||||
LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length() <<
|
||||
", first null = " << first_null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode the command, which is everything until the first NULL.
|
||||
if (msg.substr(0, first_null) == L"START") {
|
||||
// Another instance is starting parse the command line & do what it would
|
||||
// have done.
|
||||
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;
|
||||
}
|
||||
|
||||
// Get current directory.
|
||||
*current_directory = base::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";
|
||||
}
|
||||
|
||||
// Get command line.
|
||||
const std::wstring cmd_line =
|
||||
msg.substr(second_null + 1, third_null - second_null);
|
||||
*parsed_command_line = base::CommandLine::FromString(cmd_line);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessLaunchNotification(
|
||||
const ProcessSingleton::NotificationCallback& notification_callback,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam,
|
||||
LRESULT* result) {
|
||||
if (message != WM_COPYDATA)
|
||||
return false;
|
||||
|
||||
// Handle the WM_COPYDATA message from another process.
|
||||
const COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(lparam);
|
||||
|
||||
base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM);
|
||||
base::FilePath current_directory;
|
||||
if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) {
|
||||
*result = TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
*result = notification_callback.Run(parsed_command_line, current_directory) ?
|
||||
TRUE : FALSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true if Chrome needs to be relaunched into Windows 8 immersive mode.
|
||||
// Following conditions apply:-
|
||||
// 1. Windows 8 or greater.
|
||||
// 2. Not in Windows 8 immersive mode.
|
||||
// 3. Chrome is default browser.
|
||||
// 4. Process integrity level is not high.
|
||||
// 5. The profile data directory is the default directory.
|
||||
// 6. Last used mode was immersive/machine is a tablet.
|
||||
// TODO(ananta)
|
||||
// Move this function to a common place as the Windows 8 delegate_execute
|
||||
// handler can possibly use this.
|
||||
bool ShouldLaunchInWindows8ImmersiveMode(const base::FilePath& user_data_dir) {
|
||||
// Returning false from this function doesn't mean we don't launch immersive
|
||||
// mode in Aura. This function is specifically called in case when we need
|
||||
// to relaunch desktop launched chrome into immersive mode through 'relaunch'
|
||||
// menu. In case of Aura, we will use delegate_execute to do the relaunch.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DisplayShouldKillMessageBox() {
|
||||
return 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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Microsoft's Softricity virtualization breaks the sandbox processes.
|
||||
// So, if we detect the Softricity DLL we use WMI Win32_Process.Create to
|
||||
// break out of the virtualization environment.
|
||||
// http://code.google.com/p/chromium/issues/detail?id=43650
|
||||
bool ProcessSingleton::EscapeVirtualization(
|
||||
const base::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;
|
||||
// The new window was spawned from WMI, and won't be in the foreground.
|
||||
// So, first we sleep while the new chrome.exe instance starts (because
|
||||
// WaitForInputIdle doesn't work here). Then we poll for up to two more
|
||||
// seconds and make the window foreground if we find it (or we give up).
|
||||
HWND hwnd = 0;
|
||||
::Sleep(90);
|
||||
for (int tries = 200; tries; --tries) {
|
||||
hwnd = chrome::FindRunningChromeWindow(user_data_dir);
|
||||
if (hwnd) {
|
||||
::SetForegroundWindow(hwnd);
|
||||
break;
|
||||
}
|
||||
::Sleep(10);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ProcessSingleton::ProcessSingleton(
|
||||
const base::FilePath& user_data_dir,
|
||||
const NotificationCallback& notification_callback)
|
||||
: notification_callback_(notification_callback),
|
||||
is_virtualized_(false),
|
||||
lock_file_(INVALID_HANDLE_VALUE),
|
||||
user_data_dir_(user_data_dir),
|
||||
should_kill_remote_process_callback_(
|
||||
base::Bind(&DisplayShouldKillMessageBox)) {
|
||||
}
|
||||
|
||||
ProcessSingleton::~ProcessSingleton() {
|
||||
if (lock_file_ != INVALID_HANDLE_VALUE)
|
||||
::CloseHandle(lock_file_);
|
||||
}
|
||||
|
||||
// Code roughly based on Mozilla.
|
||||
ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
|
||||
if (is_virtualized_)
|
||||
return PROCESS_NOTIFIED; // We already spawned the process in this case.
|
||||
if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) {
|
||||
return LOCK_ERROR;
|
||||
} else if (!remote_window_) {
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
|
||||
switch (chrome::AttemptToNotifyRunningChrome(remote_window_, false)) {
|
||||
case chrome::NOTIFY_SUCCESS:
|
||||
return PROCESS_NOTIFIED;
|
||||
case chrome::NOTIFY_FAILED:
|
||||
remote_window_ = NULL;
|
||||
return PROCESS_NONE;
|
||||
case chrome::NOTIFY_WINDOW_HUNG:
|
||||
// Fall through and potentially terminate the hung browser.
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD process_id = 0;
|
||||
DWORD thread_id = ::GetWindowThreadProcessId(remote_window_, &process_id);
|
||||
if (!thread_id || !process_id) {
|
||||
remote_window_ = NULL;
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
base::Process process = base::Process::Open(process_id);
|
||||
|
||||
// The window is hung. Scan for every window to find a visible one.
|
||||
bool visible_window = false;
|
||||
::EnumThreadWindows(thread_id,
|
||||
&BrowserWindowEnumeration,
|
||||
reinterpret_cast<LPARAM>(&visible_window));
|
||||
|
||||
// If there is a visible browser window, ask the user before killing it.
|
||||
if (visible_window && !should_kill_remote_process_callback_.Run()) {
|
||||
// The user denied. Quit silently.
|
||||
return PROCESS_NOTIFIED;
|
||||
}
|
||||
|
||||
// Time to take action. Kill the browser process.
|
||||
process.Terminate(content::RESULT_CODE_HUNG, true);
|
||||
remote_window_ = NULL;
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
|
||||
ProcessSingleton::NotifyResult
|
||||
ProcessSingleton::NotifyOtherProcessOrCreate() {
|
||||
ProcessSingleton::NotifyResult result = PROCESS_NONE;
|
||||
if (!Create()) {
|
||||
result = NotifyOtherProcess();
|
||||
if (result == PROCESS_NONE)
|
||||
result = PROFILE_IN_USE;
|
||||
} else {
|
||||
g_browser_process->platform_part()->PlatformSpecificCommandLineProcessing(
|
||||
*base::CommandLine::ForCurrentProcess());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Look for a Chrome instance that uses the same profile directory. If there
|
||||
// isn't one, create a message window with its title set to the profile
|
||||
// directory path.
|
||||
bool ProcessSingleton::Create() {
|
||||
static const wchar_t kMutexName[] = L"Local\\ChromeProcessSingletonStartup!";
|
||||
static const wchar_t kMetroActivationEventName[] =
|
||||
L"Local\\ChromeProcessSingletonStartupMetroActivation!";
|
||||
|
||||
remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
|
||||
if (!remote_window_ && !EscapeVirtualization(user_data_dir_)) {
|
||||
// Make sure we will be the one and only process creating the window.
|
||||
// We use a named Mutex since we are protecting against multi-process
|
||||
// access. As documented, it's clearer to NOT request ownership on creation
|
||||
// since it isn't guaranteed we will get it. It is better to create it
|
||||
// without ownership and explicitly get the ownership afterward.
|
||||
base::win::ScopedHandle only_me(::CreateMutex(NULL, FALSE, kMutexName));
|
||||
if (!only_me.IsValid()) {
|
||||
DPLOG(FATAL) << "CreateMutex failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoLockMutex auto_lock_only_me(only_me.Get());
|
||||
|
||||
// We now own the mutex so we are the only process that can create the
|
||||
// window at this time, but we must still check if someone created it
|
||||
// between the time where we looked for it above and the time the mutex
|
||||
// was given to us.
|
||||
remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
|
||||
|
||||
|
||||
// In Win8+, a new Chrome process launched in Desktop mode may need to be
|
||||
// transmuted into Metro Chrome (see ShouldLaunchInWindows8ImmersiveMode for
|
||||
// heuristics). To accomplish this, the current Chrome activates Metro
|
||||
// Chrome, releases the startup mutex, and waits for metro Chrome to take
|
||||
// the singleton. From that point onward, the command line for this Chrome
|
||||
// process will be sent to Metro Chrome by the usual channels.
|
||||
if (!remote_window_ && base::win::GetVersion() >= base::win::VERSION_WIN8 &&
|
||||
!base::win::IsMetroProcess()) {
|
||||
// |metro_activation_event| is created right before activating a Metro
|
||||
// Chrome (note that there can only be one Metro Chrome process; by OS
|
||||
// design); all following Desktop processes will then wait for this event
|
||||
// to be signaled by Metro Chrome which will do so as soon as it grabs
|
||||
// this singleton (should any of the waiting processes timeout waiting for
|
||||
// the signal they will try to grab the singleton for themselves which
|
||||
// will result in a forced Desktop Chrome launch in the worst case).
|
||||
base::win::ScopedHandle metro_activation_event(
|
||||
::OpenEvent(SYNCHRONIZE, FALSE, kMetroActivationEventName));
|
||||
if (!metro_activation_event.IsValid() &&
|
||||
ShouldLaunchInWindows8ImmersiveMode(user_data_dir_)) {
|
||||
// No Metro activation is under way, but the desire is to launch in
|
||||
// Metro mode: activate and rendez-vous with the activated process.
|
||||
metro_activation_event.Set(
|
||||
::CreateEvent(NULL, TRUE, FALSE, kMetroActivationEventName));
|
||||
if (!chrome::ActivateMetroChrome()) {
|
||||
// Failed to launch immersive Chrome, default to launching on Desktop.
|
||||
LOG(ERROR) << "Failed to launch immersive chrome";
|
||||
metro_activation_event.Close();
|
||||
}
|
||||
}
|
||||
|
||||
if (metro_activation_event.IsValid()) {
|
||||
// Release |only_me| (to let Metro Chrome grab this singleton) and wait
|
||||
// until the event is signaled (i.e. Metro Chrome was successfully
|
||||
// activated). Ignore timeout waiting for |metro_activation_event|.
|
||||
{
|
||||
AutoUnlockMutex auto_unlock_only_me(only_me.Get());
|
||||
|
||||
DWORD result = ::WaitForSingleObject(metro_activation_event.Get(),
|
||||
kMetroChromeActivationTimeoutMs);
|
||||
DPCHECK(result == WAIT_OBJECT_0 || result == WAIT_TIMEOUT)
|
||||
<< "Result = " << result;
|
||||
}
|
||||
|
||||
// Check if this singleton was successfully grabbed by another process
|
||||
// (hopefully Metro Chrome). Failing to do so, this process will grab
|
||||
// the singleton and launch in Desktop mode.
|
||||
remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
|
||||
}
|
||||
}
|
||||
|
||||
if (!remote_window_) {
|
||||
// We have to make sure there is no Chrome instance running on another
|
||||
// machine that uses the same profile.
|
||||
base::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) {
|
||||
// Set the window's title to the path of our user data directory so
|
||||
// other Chrome instances can decide if they should forward to us.
|
||||
bool result = window_.CreateNamed(
|
||||
base::Bind(&ProcessLaunchNotification, notification_callback_),
|
||||
user_data_dir_.value());
|
||||
CHECK(result && window_.hwnd());
|
||||
}
|
||||
|
||||
if (base::win::GetVersion() >= base::win::VERSION_WIN8) {
|
||||
// Make sure no one is still waiting on Metro activation whether it
|
||||
// succeeded (i.e., this is the Metro process) or failed.
|
||||
base::win::ScopedHandle metro_activation_event(
|
||||
::OpenEvent(EVENT_MODIFY_STATE, FALSE, kMetroActivationEventName));
|
||||
if (metro_activation_event.IsValid())
|
||||
::SetEvent(metro_activation_event.Get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return window_.hwnd() != NULL;
|
||||
}
|
||||
|
||||
void ProcessSingleton::Cleanup() {
|
||||
}
|
||||
|
||||
void ProcessSingleton::OverrideShouldKillRemoteProcessCallbackForTesting(
|
||||
const ShouldKillRemoteProcessCallback& display_dialog_callback) {
|
||||
should_kill_remote_process_callback_ = display_dialog_callback;
|
||||
}
|
Loading…
Add table
Reference in a new issue