Merge pull request #3145 from atom/single-instance

Implement Single-Instance for Windows / Linux
This commit is contained in:
Cheng Zhao 2015-10-22 14:18:31 +08:00
commit 04d3eed60e
12 changed files with 1924 additions and 0 deletions

View file

@ -19,6 +19,7 @@
#include "atom/browser/api/atom_api_web_contents.h"
#include "atom/common/native_mate_converters/callback.h"
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "atom/common/native_mate_converters/command_line_converter.h"
#include "atom/common/node_includes.h"
#include "atom/common/options_switches.h"
#include "base/command_line.h"
@ -160,6 +161,14 @@ void App::OnWindowAllClosed() {
void App::OnQuit() {
Emit("quit");
if (process_singleton_.get()) {
if (process_notify_result_ == ProcessSingleton::PROCESS_NONE) {
process_singleton_->Cleanup();
}
process_singleton_.reset();
}
}
void App::OnOpenFile(bool* prevent_default, const std::string& file_path) {
@ -187,6 +196,10 @@ void App::OnFinishLaunching() {
auto handle = Session::CreateFrom(isolate(), browser_context);
default_session_.Reset(isolate(), handle.ToV8());
if (process_singleton_.get()) {
process_singleton_startup_lock_->Unlock();
}
Emit("ready");
}
@ -268,6 +281,39 @@ v8::Local<v8::Value> App::DefaultSession(v8::Isolate* isolate) {
return v8::Local<v8::Value>::New(isolate, default_session_);
}
bool App::MakeSingleInstance(ProcessSingleton::NotificationCallback callback) {
base::FilePath userDir;
PathService::Get(brightray::DIR_USER_DATA, &userDir);
if (!process_singleton_.get()) {
auto browser = Browser::Get();
process_singleton_startup_lock_.reset(
new ProcessSingletonStartupLock(callback));
process_singleton_.reset(
new ProcessSingleton(
userDir,
process_singleton_startup_lock_->AsNotificationCallback()));
if (browser->is_ready()) {
process_singleton_startup_lock_->Unlock();
}
process_notify_result_ = process_singleton_->NotifyOtherProcessOrCreate();
}
switch (process_notify_result_) {
case ProcessSingleton::NotifyResult::PROCESS_NONE:
return false;
case ProcessSingleton::NotifyResult::LOCK_ERROR:
case ProcessSingleton::NotifyResult::PROFILE_IN_USE:
case ProcessSingleton::NotifyResult::PROCESS_NOTIFIED:
return true;
default:
return false;
}
}
mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
auto browser = base::Unretained(Browser::Get());
@ -294,6 +340,7 @@ mate::ObjectTemplateBuilder App::GetObjectTemplateBuilder(
.SetMethod("allowNTLMCredentialsForAllDomains",
&App::AllowNTLMCredentialsForAllDomains)
.SetMethod("getLocale", &App::GetLocale)
.SetMethod("makeSingleInstance", &App::MakeSingleInstance)
.SetProperty("defaultSession", &App::DefaultSession);
}

View file

@ -9,6 +9,9 @@
#include "atom/browser/api/event_emitter.h"
#include "atom/browser/browser_observer.h"
#include "atom/common/native_mate_converters/callback.h"
#include "chrome/browser/process_singleton.h"
#include "chrome/browser/process_singleton_startup_lock.h"
#include "content/public/browser/gpu_data_manager_observer.h"
#include "native_mate/handle.h"
@ -65,13 +68,20 @@ class App : public mate::EventEmitter,
void SetDesktopName(const std::string& desktop_name);
void SetAppUserModelId(const std::string& app_id);
void AllowNTLMCredentialsForAllDomains(bool should_allow);
bool MakeSingleInstance(ProcessSingleton::NotificationCallback callback);
std::string GetLocale();
v8::Local<v8::Value> DefaultSession(v8::Isolate* isolate);
v8::Global<v8::Value> default_session_;
scoped_ptr<ProcessSingleton> process_singleton_;
scoped_ptr<ProcessSingletonStartupLock> process_singleton_startup_lock_;
ProcessSingleton::NotifyResult process_notify_result_;
DISALLOW_COPY_AND_ASSIGN(App);
};

View file

@ -0,0 +1,38 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_COMMAND_LINE_CONVERTER_H_
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_COMMAND_LINE_CONVERTER_H_
#include <string>
#include "atom/common/native_mate_converters/string16_converter.h"
#include "base/command_line.h"
namespace mate {
template<>
struct Converter<base::CommandLine> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const base::CommandLine& val) {
return Converter<base::CommandLine::StringType>::ToV8(
isolate, val.GetCommandLineString());
}
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
base::CommandLine* out) {
base::FilePath::StringType path;
if (Converter<base::FilePath::StringType>::FromV8(isolate, val, &path)) {
*out = base::CommandLine(base::FilePath(path));
return true;
} else {
return false;
}
}
};
} // namespace mate
#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_COMMAND_LINE_CONVERTER_H_

View file

@ -0,0 +1,88 @@
// Copyright 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/chrome_process_finder_win.h"
#include <shellapi.h>
#include <string>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.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/win/message_window.h"
#include "base/win/scoped_handle.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
namespace {
int timeout_in_milliseconds = 20 * 1000;
} // namespace
namespace chrome {
HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
return base::win::MessageWindow::FindWindow(user_data_dir.value());
}
NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window,
bool fast_start) {
DCHECK(remote_window);
DWORD process_id = 0;
DWORD thread_id = GetWindowThreadProcessId(remote_window, &process_id);
if (!thread_id || !process_id)
return NOTIFY_FAILED;
base::CommandLine command_line(*base::CommandLine::ForCurrentProcess());
// Send the command line to the remote chrome window.
// Format is "START\0<<<current directory>>>\0<<<commandline>>>".
std::wstring to_send(L"START\0", 6); // want the NULL in the string.
base::FilePath cur_dir;
if (!base::GetCurrentDirectory(&cur_dir))
return NOTIFY_FAILED;
to_send.append(cur_dir.value());
to_send.append(L"\0", 1); // Null separator.
to_send.append(command_line.GetCommandLineString());
to_send.append(L"\0", 1); // Null separator.
// Allow the current running browser window to make itself the foreground
// window (otherwise it will just flash in the taskbar).
::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,
timeout_in_milliseconds, &result)) {
return result ? NOTIFY_SUCCESS : NOTIFY_FAILED;
}
// It is possible that the process owning this window may have died by now.
if (!::IsWindow(remote_window))
return NOTIFY_FAILED;
// If the window couldn't be notified but still exists, assume it is hung.
return NOTIFY_WINDOW_HUNG;
}
base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout) {
base::TimeDelta old_timeout =
base::TimeDelta::FromMilliseconds(timeout_in_milliseconds);
timeout_in_milliseconds = new_timeout.InMilliseconds();
return old_timeout;
}
} // namespace chrome

View file

@ -0,0 +1,39 @@
// Copyright 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_CHROME_PROCESS_FINDER_WIN_H_
#define CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_
#include <windows.h>
#include "base/time/time.h"
namespace base {
class FilePath;
}
namespace chrome {
enum NotifyChromeResult {
NOTIFY_SUCCESS,
NOTIFY_FAILED,
NOTIFY_WINDOW_HUNG,
};
// Finds an already running Chrome window if it exists.
HWND FindRunningChromeWindow(const base::FilePath& user_data_dir);
// Attempts to send the current command line to an already running instance of
// Chrome via a WM_COPYDATA message.
// Returns true if a running Chrome is found and successfully notified.
// |fast_start| is true when this is being called on the window fast start path.
NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window,
bool fast_start);
// Changes the notification timeout to |new_timeout|, returns the old timeout.
base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout);
} // namespace chrome
#endif // CHROME_BROWSER_CHROME_PROCESS_FINDER_WIN_H_

View file

@ -0,0 +1,179 @@
// 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_
#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)
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_

File diff suppressed because it is too large Load diff

View 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);
}
}

View 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_

View file

@ -0,0 +1,315 @@
// 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/chrome_process_finder_win.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";
// 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, &current_directory)) {
*result = TRUE;
return true;
}
*result = notification_callback.Run(parsed_command_line, current_directory) ?
TRUE : FALSE;
return true;
}
bool TerminateAppWithError() {
// TODO: This is called when the secondary process can't ping the primary
// process. Need to find out what to do here.
return false;
}
} // namespace
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(&TerminateAppWithError)) {
}
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;
}
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\\AtomProcessSingletonStartup!";
remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
if (!remote_window_) {
// 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_);
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());
// NB: Ensure that if the primary app gets started as elevated
// admin inadvertently, secondary windows running not as elevated
// will still be able to send messages
::ChangeWindowMessageFilterEx(window_.hwnd(), WM_COPYDATA, MSGFLT_ALLOW, NULL);
CHECK(result && window_.hwnd());
}
}
}
return window_.hwnd() != NULL;
}
void ProcessSingleton::Cleanup() {
}
void ProcessSingleton::OverrideShouldKillRemoteProcessCallbackForTesting(
const ShouldKillRemoteProcessCallback& display_dialog_callback) {
should_kill_remote_process_callback_ = display_dialog_callback;
}

View file

@ -290,6 +290,51 @@ URLs that fall under "Local Intranet" sites (i.e. are in the same domain as you)
However, this detection often fails when corporate networks are badly configured,
so this lets you co-opt this behavior and enable it for all URLs.
### `app.makeSingleInstance(callback)`
* `callback` Function
This method makes your application a Single Instance Application - instead of
allowing multiple instances of your app to run, this will ensure that only a
single instance of your app is running, and other instances signal this
instance and exit.
`callback` is called when a second instance has been executed, and provides
the command-line (including Chromium flags) and the working directory of the
secondary instance. Usually applications respond to this by making their
primary window focused and non-minimized.
`callback` should return `true` if the message was successfully handled, or
`false` if the secondary process should retry sending it or it failed.
```js
app.on('ready', function() {
var myWindow;
var shouldQuit = app.makeSingleInstance(function(command_line, working_directory) {
// Someone tried to run a second instance, we should focus our window
if (myWindow) {
if (myWindow.isMinimized()) myWindow.restore();
myWindow.focus();
}
// We successfully handled the command line
return true;
});
if (shouldQuit) {
app.quit();
return;
}
// Create myWindow, load the rest of the app, etc...
});
```
Returns a Boolean - if `false`, your process is the primary instance of the
application and your app should continue loading. If `true`, your process has
sent its parameters to another instance, and you should immediately quit.
### `app.commandLine.appendSwitch(switch[, value])`
Append a switch (with optional `value`) to Chromium's command line.

View file

@ -346,6 +346,7 @@
'atom/utility/atom_content_utility_client.h',
'chromium_src/chrome/browser/browser_process.cc',
'chromium_src/chrome/browser/browser_process.h',
'chromium_src/chrome/browser/chrome_process_finder_win.cc',
'chromium_src/chrome/browser/chrome_notification_types.h',
'chromium_src/chrome/browser/extensions/global_shortcut_listener.cc',
'chromium_src/chrome/browser/extensions/global_shortcut_listener.h',
@ -374,6 +375,9 @@
'chromium_src/chrome/browser/printing/printing_message_filter.h',
'chromium_src/chrome/browser/printing/print_preview_message_handler.cc',
'chromium_src/chrome/browser/printing/print_preview_message_handler.h',
'chromium_src/chrome/browser/process_singleton_posix.cc',
'chromium_src/chrome/browser/process_singleton_startup_lock.cc',
'chromium_src/chrome/browser/process_singleton_win.cc',
'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.cc',
'chromium_src/chrome/browser/renderer_host/pepper/chrome_browser_pepper_host_factory.h',
'chromium_src/chrome/browser/renderer_host/pepper/pepper_broker_message_filter.cc',