Merge pull request #5837 from electron/restart

Add app.relaunch API
This commit is contained in:
Cheng Zhao 2016-06-03 04:00:12 +00:00 committed by GitHub
commit 4ec064d311
19 changed files with 812 additions and 4 deletions

View file

@ -111,6 +111,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) {
params.instance = instance; params.instance = instance;
params.sandbox_info = &sandbox_info; params.sandbox_info = &sandbox_info;
atom::AtomCommandLine::Init(argc, argv); atom::AtomCommandLine::Init(argc, argv);
atom::AtomCommandLine::InitW(argc, wargv);
return content::ContentMain(params); return content::ContentMain(params);
} }

View file

@ -9,6 +9,7 @@
#include "atom/app/atom_content_client.h" #include "atom/app/atom_content_client.h"
#include "atom/browser/atom_browser_client.h" #include "atom/browser/atom_browser_client.h"
#include "atom/browser/relauncher.h"
#include "atom/common/google_api_key.h" #include "atom/common/google_api_key.h"
#include "atom/renderer/atom_renderer_client.h" #include "atom/renderer/atom_renderer_client.h"
#include "atom/utility/atom_content_utility_client.h" #include "atom/utility/atom_content_utility_client.h"
@ -25,6 +26,8 @@ namespace atom {
namespace { namespace {
const char* kRelauncherProcess = "relauncher";
bool IsBrowserProcess(base::CommandLine* cmd) { bool IsBrowserProcess(base::CommandLine* cmd) {
std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType); std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType);
return process_type.empty(); return process_type.empty();
@ -146,6 +149,26 @@ content::ContentUtilityClient* AtomMainDelegate::CreateContentUtilityClient() {
return utility_client_.get(); return utility_client_.get();
} }
int AtomMainDelegate::RunProcess(
const std::string& process_type,
const content::MainFunctionParams& main_function_params) {
if (process_type == kRelauncherProcess)
return relauncher::RelauncherMain(main_function_params);
else
return -1;
}
#if defined(OS_MACOSX)
bool AtomMainDelegate::ShouldSendMachPort(const std::string& process_type) {
return process_type != kRelauncherProcess;
}
bool AtomMainDelegate::DelaySandboxInitialization(
const std::string& process_type) {
return process_type == kRelauncherProcess;
}
#endif
std::unique_ptr<brightray::ContentClient> std::unique_ptr<brightray::ContentClient>
AtomMainDelegate::CreateContentClient() { AtomMainDelegate::CreateContentClient() {
return std::unique_ptr<brightray::ContentClient>(new AtomContentClient); return std::unique_ptr<brightray::ContentClient>(new AtomContentClient);

View file

@ -5,6 +5,8 @@
#ifndef ATOM_APP_ATOM_MAIN_DELEGATE_H_ #ifndef ATOM_APP_ATOM_MAIN_DELEGATE_H_
#define ATOM_APP_ATOM_MAIN_DELEGATE_H_ #define ATOM_APP_ATOM_MAIN_DELEGATE_H_
#include <string>
#include "brightray/common/main_delegate.h" #include "brightray/common/main_delegate.h"
#include "brightray/common/content_client.h" #include "brightray/common/content_client.h"
@ -22,6 +24,13 @@ class AtomMainDelegate : public brightray::MainDelegate {
content::ContentBrowserClient* CreateContentBrowserClient() override; content::ContentBrowserClient* CreateContentBrowserClient() override;
content::ContentRendererClient* CreateContentRendererClient() override; content::ContentRendererClient* CreateContentRendererClient() override;
content::ContentUtilityClient* CreateContentUtilityClient() override; content::ContentUtilityClient* CreateContentUtilityClient() override;
int RunProcess(
const std::string& process_type,
const content::MainFunctionParams& main_function_params) override;
#if defined(OS_MACOSX)
bool ShouldSendMachPort(const std::string& process_type) override;
bool DelaySandboxInitialization(const std::string& process_type) override;
#endif
// brightray::MainDelegate: // brightray::MainDelegate:
std::unique_ptr<brightray::ContentClient> CreateContentClient() override; std::unique_ptr<brightray::ContentClient> CreateContentClient() override;

View file

@ -14,6 +14,8 @@
#include "atom/browser/atom_browser_main_parts.h" #include "atom/browser/atom_browser_main_parts.h"
#include "atom/browser/browser.h" #include "atom/browser/browser.h"
#include "atom/browser/login_handler.h" #include "atom/browser/login_handler.h"
#include "atom/browser/relauncher.h"
#include "atom/common/atom_command_line.h"
#include "atom/common/native_mate_converters/callback.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/file_path_converter.h"
#include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h"
@ -408,6 +410,43 @@ void App::ReleaseSingleInstance() {
} }
} }
bool App::Relaunch(mate::Arguments* js_args) {
// Parse parameters.
bool override_argv = false;
base::FilePath exec_path;
relauncher::StringVector args;
mate::Dictionary options;
if (js_args->GetNext(&options)) {
if (options.Get("execPath", &exec_path) | options.Get("args", &args))
override_argv = true;
}
if (!override_argv) {
#if defined(OS_WIN)
const relauncher::StringVector& argv = atom::AtomCommandLine::wargv();
#else
const relauncher::StringVector& argv = atom::AtomCommandLine::argv();
#endif
return relauncher::RelaunchApp(argv);
}
relauncher::StringVector argv;
argv.reserve(1 + args.size());
if (exec_path.empty()) {
base::FilePath current_exe_path;
PathService::Get(base::FILE_EXE, &current_exe_path);
argv.push_back(current_exe_path.value());
} else {
argv.push_back(exec_path.value());
}
argv.insert(argv.end(), args.begin(), args.end());
return relauncher::RelaunchApp(argv);
}
#if defined(USE_NSS_CERTS) #if defined(USE_NSS_CERTS)
void App::ImportCertificate( void App::ImportCertificate(
const base::DictionaryValue& options, const base::DictionaryValue& options,
@ -488,7 +527,8 @@ void App::BuildPrototype(
.SetMethod("importCertificate", &App::ImportCertificate) .SetMethod("importCertificate", &App::ImportCertificate)
#endif #endif
.SetMethod("makeSingleInstance", &App::MakeSingleInstance) .SetMethod("makeSingleInstance", &App::MakeSingleInstance)
.SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance); .SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance)
.SetMethod("relaunch", &App::Relaunch);
} }
} // namespace api } // namespace api

View file

@ -106,10 +106,11 @@ class App : public AtomBrowserClient::Delegate,
const base::FilePath& path); const base::FilePath& path);
void SetDesktopName(const std::string& desktop_name); void SetDesktopName(const std::string& desktop_name);
std::string GetLocale();
bool MakeSingleInstance( bool MakeSingleInstance(
const ProcessSingleton::NotificationCallback& callback); const ProcessSingleton::NotificationCallback& callback);
void ReleaseSingleInstance(); void ReleaseSingleInstance();
std::string GetLocale(); bool Relaunch(mate::Arguments* args);
#if defined(USE_NSS_CERTS) #if defined(USE_NSS_CERTS)
void ImportCertificate(const base::DictionaryValue& options, void ImportCertificate(const base::DictionaryValue& options,

192
atom/browser/relauncher.cc Normal file
View file

@ -0,0 +1,192 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/relauncher.h"
#include <string>
#include <vector>
#include "atom/common/atom_command_line.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/main_function_params.h"
#if defined(OS_POSIX)
#include "base/posix/eintr_wrapper.h"
#endif
namespace relauncher {
namespace internal {
#if defined(OS_POSIX)
const int kRelauncherSyncFD = STDERR_FILENO + 1;
#endif
const CharType* kRelauncherTypeArg = FILE_PATH_LITERAL("--type=relauncher");
const CharType* kRelauncherArgSeparator = FILE_PATH_LITERAL("---");
} // namespace internal
bool RelaunchApp(const StringVector& argv) {
// Use the currently-running application's helper process. The automatic
// update feature is careful to leave the currently-running version alone,
// so this is safe even if the relaunch is the result of an update having
// been applied. In fact, it's safer than using the updated version of the
// helper process, because there's no guarantee that the updated version's
// relauncher implementation will be compatible with the running version's.
base::FilePath child_path;
if (!PathService::Get(content::CHILD_PROCESS_EXE, &child_path)) {
LOG(ERROR) << "No CHILD_PROCESS_EXE";
return false;
}
StringVector relauncher_args;
return RelaunchAppWithHelper(child_path, relauncher_args, argv);
}
bool RelaunchAppWithHelper(const base::FilePath& helper,
const StringVector& relauncher_args,
const StringVector& argv) {
StringVector relaunch_argv;
relaunch_argv.push_back(helper.value());
relaunch_argv.push_back(internal::kRelauncherTypeArg);
relaunch_argv.insert(relaunch_argv.end(),
relauncher_args.begin(), relauncher_args.end());
relaunch_argv.push_back(internal::kRelauncherArgSeparator);
relaunch_argv.insert(relaunch_argv.end(), argv.begin(), argv.end());
#if defined(OS_POSIX)
int pipe_fds[2];
if (HANDLE_EINTR(pipe(pipe_fds)) != 0) {
PLOG(ERROR) << "pipe";
return false;
}
// The parent process will only use pipe_read_fd as the read side of the
// pipe. It can close the write side as soon as the relauncher process has
// forked off. The relauncher process will only use pipe_write_fd as the
// write side of the pipe. In that process, the read side will be closed by
// base::LaunchApp because it won't be present in fd_map, and the write side
// will be remapped to kRelauncherSyncFD by fd_map.
base::ScopedFD pipe_read_fd(pipe_fds[0]);
base::ScopedFD pipe_write_fd(pipe_fds[1]);
// Make sure kRelauncherSyncFD is a safe value. base::LaunchProcess will
// preserve these three FDs in forked processes, so kRelauncherSyncFD should
// not conflict with them.
static_assert(internal::kRelauncherSyncFD != STDIN_FILENO &&
internal::kRelauncherSyncFD != STDOUT_FILENO &&
internal::kRelauncherSyncFD != STDERR_FILENO,
"kRelauncherSyncFD must not conflict with stdio fds");
base::FileHandleMappingVector fd_map;
fd_map.push_back(
std::make_pair(pipe_write_fd.get(), internal::kRelauncherSyncFD));
#endif
base::LaunchOptions options;
#if defined(OS_POSIX)
options.fds_to_remap = &fd_map;
base::Process process = base::LaunchProcess(relaunch_argv, options);
#elif defined(OS_WIN)
base::Process process = base::LaunchProcess(
internal::ArgvToCommandLineString(relaunch_argv), options);
#endif
if (!process.IsValid()) {
LOG(ERROR) << "base::LaunchProcess failed";
return false;
}
// The relauncher process is now starting up, or has started up. The
// original parent process continues.
#if defined(OS_WIN)
// Synchronize with the relauncher process.
StringType name = internal::GetWaitEventName(process.Pid());
HANDLE wait_event = ::CreateEventW(NULL, TRUE, FALSE, name.c_str());
if (wait_event != NULL) {
WaitForSingleObject(wait_event, 1000);
CloseHandle(wait_event);
}
#elif defined(OS_POSIX)
pipe_write_fd.reset(); // close(pipe_fds[1]);
// Synchronize with the relauncher process.
char read_char;
int read_result = HANDLE_EINTR(read(pipe_read_fd.get(), &read_char, 1));
if (read_result != 1) {
if (read_result < 0) {
PLOG(ERROR) << "read";
} else {
LOG(ERROR) << "read: unexpected result " << read_result;
}
return false;
}
// Since a byte has been successfully read from the relauncher process, it's
// guaranteed to have set up its kqueue monitoring this process for exit.
// It's safe to exit now.
#endif
return true;
}
int RelauncherMain(const content::MainFunctionParams& main_parameters) {
#if defined(OS_WIN)
const StringVector& argv = atom::AtomCommandLine::wargv();
#else
const StringVector& argv = atom::AtomCommandLine::argv();
#endif
if (argv.size() < 4 || argv[1] != internal::kRelauncherTypeArg) {
LOG(ERROR) << "relauncher process invoked with unexpected arguments";
return 1;
}
internal::RelauncherSynchronizeWithParent();
// Figure out what to execute, what arguments to pass it, and whether to
// start it in the background.
bool in_relauncher_args = false;
StringType relaunch_executable;
StringVector relauncher_args;
StringVector launch_argv;
for (size_t argv_index = 2; argv_index < argv.size(); ++argv_index) {
const StringType& arg(argv[argv_index]);
if (!in_relauncher_args) {
if (arg == internal::kRelauncherArgSeparator) {
in_relauncher_args = true;
} else {
relauncher_args.push_back(arg);
}
} else {
launch_argv.push_back(arg);
}
}
if (launch_argv.empty()) {
LOG(ERROR) << "nothing to relaunch";
return 1;
}
if (internal::LaunchProgram(relauncher_args, launch_argv) != 0) {
LOG(ERROR) << "failed to launch program";
return 1;
}
// The application should have relaunched (or is in the process of
// relaunching). From this point on, only clean-up tasks should occur, and
// failures are tolerable.
return 0;
}
} // namespace relauncher

122
atom/browser/relauncher.h Normal file
View file

@ -0,0 +1,122 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_RELAUNCHER_H_
#define ATOM_BROWSER_RELAUNCHER_H_
// relauncher implements main browser application relaunches across platforms.
// When a browser wants to relaunch itself, it can't simply fork off a new
// process and exec a new browser from within. That leaves open a window
// during which two browser applications might be running concurrently. If
// that happens, each will wind up with a distinct Dock icon, which is
// especially bad if the user expected the Dock icon to be persistent by
// choosing Keep in Dock from the icon's contextual menu.
//
// relauncher approaches this problem by introducing an intermediate
// process (the "relauncher") in between the original browser ("parent") and
// replacement browser ("relaunched"). The helper executable is used for the
// relauncher process; because it's an LSUIElement, it doesn't get a Dock
// icon and isn't visible as a running application at all. The parent will
// start a relauncher process, giving it the "writer" side of a pipe that it
// retains the "reader" end of. When the relauncher starts up, it will
// establish a kqueue to wait for the parent to exit, and will then write to
// the pipe. The parent, upon reading from the pipe, is free to exit. When the
// relauncher is notified via its kqueue that the parent has exited, it
// proceeds, launching the relaunched process. The handshake to synchronize
// the parent with the relauncher is necessary to avoid races: the relauncher
// needs to be sure that it's monitoring the parent and not some other process
// in light of PID reuse, so the parent must remain alive long enough for the
// relauncher to set up its kqueue.
#include <string>
#include <vector>
#include "base/command_line.h"
#if defined(OS_WIN)
#include "base/process/process_handle.h"
#endif
namespace content {
struct MainFunctionParams;
}
namespace relauncher {
using CharType = base::CommandLine::CharType;
using StringType = base::CommandLine::StringType;
using StringVector = base::CommandLine::StringVector;
// Relaunches the application using the helper application associated with the
// currently running instance of Chrome in the parent browser process as the
// executable for the relauncher process. |args| is an argv-style vector of
// command line arguments of the form normally passed to execv. args[0] is
// also the path to the relaunched process. Because the relauncher process
// will ultimately launch the relaunched process via Launch Services, args[0]
// may be either a pathname to an executable file or a pathname to an .app
// bundle directory. The caller should exit soon after RelaunchApp returns
// successfully. Returns true on success, although some failures can occur
// after this function returns true if, for example, they occur within the
// relauncher process. Returns false when the relaunch definitely failed.
bool RelaunchApp(const StringVector& argv);
// Identical to RelaunchApp, but uses |helper| as the path to the relauncher
// process, and allows additional arguments to be supplied to the relauncher
// process in relauncher_args. Unlike args[0], |helper| must be a pathname to
// an executable file. The helper path given must be from the same version of
// Chrome as the running parent browser process, as there are no guarantees
// that the parent and relauncher processes from different versions will be
// able to communicate with one another. This variant can be useful to
// relaunch the same version of Chrome from another location, using that
// location's helper.
bool RelaunchAppWithHelper(const base::FilePath& helper,
const StringVector& relauncher_args,
const StringVector& args);
// The entry point from ChromeMain into the relauncher process.
int RelauncherMain(const content::MainFunctionParams& main_parameters);
namespace internal {
#if defined(OS_POSIX)
// The "magic" file descriptor that the relauncher process' write side of the
// pipe shows up on. Chosen to avoid conflicting with stdin, stdout, and
// stderr.
extern const int kRelauncherSyncFD;
#endif
// The "type" argument identifying a relauncher process ("--type=relauncher").
extern const CharType* kRelauncherTypeArg;
// The argument separating arguments intended for the relauncher process from
// those intended for the relaunched process. "---" is chosen instead of "--"
// because CommandLine interprets "--" as meaning "end of switches", but
// for many purposes, the relauncher process' CommandLine ought to interpret
// arguments intended for the relaunched process, to get the correct settings
// for such things as logging and the user-data-dir in case it affects crash
// reporting.
extern const CharType* kRelauncherArgSeparator;
#if defined(OS_WIN)
StringType GetWaitEventName(base::ProcessId pid);
StringType ArgvToCommandLineString(const StringVector& argv);
#endif
// In the relauncher process, performs the necessary synchronization steps
// with the parent by setting up a kqueue to watch for it to exit, writing a
// byte to the pipe, and then waiting for the exit notification on the kqueue.
// If anything fails, this logs a message and returns immediately. In those
// situations, it can be assumed that something went wrong with the parent
// process and the best recovery approach is to attempt relaunch anyway.
void RelauncherSynchronizeWithParent();
int LaunchProgram(const StringVector& relauncher_args,
const StringVector& argv);
} // namespace internal
} // namespace relauncher
#endif // ATOM_BROWSER_RELAUNCHER_H_

View file

@ -0,0 +1,67 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/relauncher.h"
#include <signal.h>
#include <sys/prctl.h>
#include <sys/signalfd.h>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/launch.h"
namespace relauncher {
namespace internal {
void RelauncherSynchronizeWithParent() {
base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD);
// Don't execute signal handlers of SIGUSR2.
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGUSR2);
if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) {
PLOG(ERROR) << "sigprocmask";
return;
}
// Create a signalfd that watches for SIGUSR2.
int usr2_fd = signalfd(-1, &mask, 0);
if (usr2_fd < 0) {
PLOG(ERROR) << "signalfd";
return;
}
// Send SIGUSR2 to current process when parent process ends.
if (HANDLE_EINTR(prctl(PR_SET_PDEATHSIG, SIGUSR2)) != 0) {
PLOG(ERROR) << "prctl";
return;
}
// Write a '\0' character to the pipe.
if (HANDLE_EINTR(write(relauncher_sync_fd.get(), "", 1)) != 1) {
PLOG(ERROR) << "write";
return;
}
// Wait the SIGUSR2 signal to happen.
struct signalfd_siginfo si;
HANDLE_EINTR(read(usr2_fd, &si, sizeof(si)));
}
int LaunchProgram(const StringVector& relauncher_args,
const StringVector& argv) {
base::LaunchOptions options;
options.allow_new_privs = true;
options.new_process_group = true; // detach
base::Process process = base::LaunchProcess(argv, options);
return process.IsValid() ? 0 : 1;
}
} // namespace internal
} // namespace relauncher

View file

@ -0,0 +1,90 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/relauncher.h"
#include <sys/event.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/mac/mac_logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/sys_string_conversions.h"
namespace relauncher {
namespace internal {
void RelauncherSynchronizeWithParent() {
base::ScopedFD relauncher_sync_fd(kRelauncherSyncFD);
int parent_pid = getppid();
// PID 1 identifies init. launchd, that is. launchd never starts the
// relauncher process directly, having this parent_pid means that the parent
// already exited and launchd "inherited" the relauncher as its child.
// There's no reason to synchronize with launchd.
if (parent_pid == 1) {
LOG(ERROR) << "unexpected parent_pid";
return;
}
// Set up a kqueue to monitor the parent process for exit.
base::ScopedFD kq(kqueue());
if (!kq.is_valid()) {
PLOG(ERROR) << "kqueue";
return;
}
struct kevent change = { 0 };
EV_SET(&change, parent_pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
if (kevent(kq.get(), &change, 1, NULL, 0, NULL) == -1) {
PLOG(ERROR) << "kevent (add)";
return;
}
// Write a '\0' character to the pipe.
if (HANDLE_EINTR(write(relauncher_sync_fd.get(), "", 1)) != 1) {
PLOG(ERROR) << "write";
return;
}
// Up until now, the parent process was blocked in a read waiting for the
// write above to complete. The parent process is now free to exit. Wait for
// that to happen.
struct kevent event;
int events = kevent(kq.get(), NULL, 0, &event, 1, NULL);
if (events != 1) {
if (events < 0) {
PLOG(ERROR) << "kevent (monitor)";
} else {
LOG(ERROR) << "kevent (monitor): unexpected result " << events;
}
return;
}
if (event.filter != EVFILT_PROC ||
event.fflags != NOTE_EXIT ||
event.ident != static_cast<uintptr_t>(parent_pid)) {
LOG(ERROR) << "kevent (monitor): unexpected event, filter " << event.filter
<< ", fflags " << event.fflags << ", ident " << event.ident;
return;
}
}
int LaunchProgram(const StringVector& relauncher_args,
const StringVector& argv) {
base::LaunchOptions options;
options.new_process_group = true; // detach
base::Process process = base::LaunchProcess(argv, options);
return process.IsValid() ? 0 : 1;
}
} // namespace internal
} // namespace relauncher

View file

@ -0,0 +1,128 @@
// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/relauncher.h"
#include <windows.h>
#include "base/process/launch.h"
#include "base/strings/stringprintf.h"
#include "base/win/scoped_handle.h"
#include "sandbox/win/src/nt_internals.h"
#include "sandbox/win/src/win_utils.h"
#include "ui/base/win/shell.h"
namespace relauncher {
namespace internal {
namespace {
const CharType* kWaitEventName = L"ElectronRelauncherWaitEvent";
HANDLE GetParentProcessHandle(base::ProcessHandle handle) {
NtQueryInformationProcessFunction NtQueryInformationProcess = nullptr;
ResolveNTFunctionPtr("NtQueryInformationProcess", &NtQueryInformationProcess);
if (!NtQueryInformationProcess) {
LOG(ERROR) << "Unable to get NtQueryInformationProcess";
return NULL;
}
PROCESS_BASIC_INFORMATION pbi;
LONG status = NtQueryInformationProcess(
handle, ProcessBasicInformation,
&pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL);
if (!NT_SUCCESS(status)) {
LOG(ERROR) << "NtQueryInformationProcess failed";
return NULL;
}
return ::OpenProcess(PROCESS_ALL_ACCESS, TRUE,
pbi.InheritedFromUniqueProcessId);
}
StringType AddQuoteForArg(const StringType& arg) {
// We follow the quoting rules of CommandLineToArgvW.
// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
std::wstring quotable_chars(L" \\\"");
if (arg.find_first_of(quotable_chars) == std::wstring::npos) {
// No quoting necessary.
return arg;
}
std::wstring out;
out.push_back(L'"');
for (size_t i = 0; i < arg.size(); ++i) {
if (arg[i] == '\\') {
// Find the extent of this run of backslashes.
size_t start = i, end = start + 1;
for (; end < arg.size() && arg[end] == '\\'; ++end) {}
size_t backslash_count = end - start;
// Backslashes are escapes only if the run is followed by a double quote.
// Since we also will end the string with a double quote, we escape for
// either a double quote or the end of the string.
if (end == arg.size() || arg[end] == '"') {
// To quote, we need to output 2x as many backslashes.
backslash_count *= 2;
}
for (size_t j = 0; j < backslash_count; ++j)
out.push_back('\\');
// Advance i to one before the end to balance i++ in loop.
i = end - 1;
} else if (arg[i] == '"') {
out.push_back('\\');
out.push_back('"');
} else {
out.push_back(arg[i]);
}
}
out.push_back('"');
return out;
}
} // namespace
StringType GetWaitEventName(base::ProcessId pid) {
return base::StringPrintf(L"%s-%d", kWaitEventName, static_cast<int>(pid));
}
StringType ArgvToCommandLineString(const StringVector& argv) {
StringType command_line;
for (const StringType& arg : argv) {
if (!command_line.empty())
command_line += L' ';
command_line += AddQuoteForArg(arg);
}
return command_line;
}
void RelauncherSynchronizeWithParent() {
base::Process process = base::Process::Current();
base::win::ScopedHandle parent_process(
GetParentProcessHandle(process.Handle()));
// Notify the parent process that it can quit now.
StringType name = internal::GetWaitEventName(process.Pid());
base::win::ScopedHandle wait_event(
::CreateEventW(NULL, TRUE, FALSE, name.c_str()));
::SetEvent(wait_event.Get());
// Wait for parent process to quit.
WaitForSingleObject(parent_process.Get(), INFINITE);
}
int LaunchProgram(const StringVector& relauncher_args,
const StringVector& argv) {
base::LaunchOptions options;
base::Process process =
base::LaunchProcess(ArgvToCommandLineString(argv), options);
return process.IsValid() ? 0 : 1;
}
} // namespace internal
} // namespace relauncher

View file

@ -12,6 +12,11 @@ namespace atom {
// static // static
std::vector<std::string> AtomCommandLine::argv_; std::vector<std::string> AtomCommandLine::argv_;
#if defined(OS_WIN)
// static
std::vector<std::wstring> AtomCommandLine::wargv_;
#endif
// static // static
void AtomCommandLine::Init(int argc, const char* const* argv) { void AtomCommandLine::Init(int argc, const char* const* argv) {
// Hack around with the argv pointer. Used for process.title = "blah" // Hack around with the argv pointer. Used for process.title = "blah"
@ -21,6 +26,15 @@ void AtomCommandLine::Init(int argc, const char* const* argv) {
} }
} }
#if defined(OS_WIN)
// static
void AtomCommandLine::InitW(int argc, const wchar_t* const* argv) {
for (int i = 0; i < argc; ++i) {
wargv_.push_back(argv[i]);
}
}
#endif
#if defined(OS_LINUX) #if defined(OS_LINUX)
// static // static
void AtomCommandLine::InitializeFromCommandLine() { void AtomCommandLine::InitializeFromCommandLine() {

View file

@ -19,6 +19,11 @@ class AtomCommandLine {
static void Init(int argc, const char* const* argv); static void Init(int argc, const char* const* argv);
static std::vector<std::string> argv() { return argv_; } static std::vector<std::string> argv() { return argv_; }
#if defined(OS_WIN)
static void InitW(int argc, const wchar_t* const* argv);
static std::vector<std::wstring> wargv() { return wargv_; }
#endif
#if defined(OS_LINUX) #if defined(OS_LINUX)
// On Linux the command line has to be read from base::CommandLine since // On Linux the command line has to be read from base::CommandLine since
// it is using zygote. // it is using zygote.
@ -28,6 +33,10 @@ class AtomCommandLine {
private: private:
static std::vector<std::string> argv_; static std::vector<std::string> argv_;
#if defined(OS_WIN)
static std::vector<std::wstring> wargv_;
#endif
DISALLOW_IMPLICIT_CONSTRUCTORS(AtomCommandLine); DISALLOW_IMPLICIT_CONSTRUCTORS(AtomCommandLine);
}; };

View file

@ -276,6 +276,33 @@ Exits immediately with `exitCode`.
All windows will be closed immediately without asking user and the `before-quit` All windows will be closed immediately without asking user and the `before-quit`
and `will-quit` events will not be emitted. and `will-quit` events will not be emitted.
### `app.relaunch([options])`
* `options` Object (optional)
* `args` Array (optional)
* `execPath` String (optional)
Relaunches the app when current instance exits.
By default the new instance will use the same working directory and command line
arguments with current instance. When `args` is specified, the `args` will be
passed as command line arguments instead. When `execPath` is specified, the
`execPath` will be executed for relaunch instead of current app.
Note that this method does not quit the app when executed, you have to call
`app.quit` or `app.exit` after calling `app.relaunch` to make the app restart.
When `app.relaunch` is called for multiple times, multiple instances will be
started after current instance exited.
An example of restarting current instance immediately and adding a new command
line argument to the new instance:
```javascript
app.relaunch({args: process.argv.slice(1) + ['--relaunch']})
app.exit(0)
```
### `app.focus()` ### `app.focus()`
On Linux, focuses on the first visible window. On OS X, makes the application On Linux, focuses on the first visible window. On OS X, makes the application

View file

@ -225,6 +225,11 @@
'atom/browser/net/url_request_fetch_job.h', 'atom/browser/net/url_request_fetch_job.h',
'atom/browser/node_debugger.cc', 'atom/browser/node_debugger.cc',
'atom/browser/node_debugger.h', 'atom/browser/node_debugger.h',
'atom/browser/relauncher_linux.cc',
'atom/browser/relauncher_mac.cc',
'atom/browser/relauncher_win.cc',
'atom/browser/relauncher.cc',
'atom/browser/relauncher.h',
'atom/browser/render_process_preferences.cc', 'atom/browser/render_process_preferences.cc',
'atom/browser/render_process_preferences.h', 'atom/browser/render_process_preferences.h',
'atom/browser/ui/accelerator_util.cc', 'atom/browser/ui/accelerator_util.cc',

View file

@ -1,6 +1,7 @@
const assert = require('assert') const assert = require('assert')
const ChildProcess = require('child_process') const ChildProcess = require('child_process')
const https = require('https') const https = require('https')
const net = require('net')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const {remote} = require('electron') const {remote} = require('electron')
@ -108,6 +109,55 @@ describe('app module', function () {
}) })
}) })
describe('app.relaunch', function () {
let server = null
const socketPath = process.platform === 'win32' ?
'\\\\.\\pipe\\electron-app-relaunch' :
'/tmp/electron-app-relaunch'
beforeEach(function (done) {
fs.unlink(socketPath, (error) => {
server = net.createServer()
server.listen(socketPath)
done()
})
})
afterEach(function (done) {
server.close(() => {
if (process.platform === 'win32') {
done()
} else {
fs.unlink(socketPath, (error) => {
done()
})
}
})
})
it('relaunches the app', function (done) {
this.timeout(100000)
let state = 'none'
server.once('error', (error) => {
done(error)
})
server.on('connection', (client) => {
client.once('data', function (data) {
if (String(data) === 'false' && state === 'none') {
state = 'first-launch'
} else if (String(data) === 'true' && state === 'first-launch') {
done()
} else {
done(`Unexpected state: ${state}`)
}
})
})
const appPath = path.join(__dirname, 'fixtures', 'api', 'relaunch')
ChildProcess.spawn(remote.process.execPath, [appPath])
})
})
describe('app.setUserActivity(type, userInfo)', function () { describe('app.setUserActivity(type, userInfo)', function () {
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
return return

View file

@ -1,4 +1,4 @@
{ {
"name": "quit-app", "name": "electron-quit-app",
"main": "main.js" "main": "main.js"
} }

25
spec/fixtures/api/relaunch/main.js vendored Normal file
View file

@ -0,0 +1,25 @@
const {app, dialog} = require('electron')
const net = require('net')
const socketPath = process.platform === 'win32' ?
'\\\\.\\pipe\\electron-app-relaunch' :
'/tmp/electron-app-relaunch'
process.on('uncaughtException', () => {
app.exit(1)
})
app.once('ready', () => {
let lastArg = process.argv[process.argv.length - 1]
const client = net.connect(socketPath)
client.once('connect', () => {
client.end(String(lastArg === '--second'))
})
client.once('end', () => {
app.exit(0)
})
if (lastArg !== '--second') {
app.relaunch({args: process.argv.slice(1).concat('--second')})
}
})

View file

@ -0,0 +1,5 @@
{
"name": "electron-app-relaunch",
"main": "main.js"
}

View file

@ -13,7 +13,7 @@
"temp": "0.8.1", "temp": "0.8.1",
"walkdir": "0.0.7", "walkdir": "0.0.7",
"ws": "0.7.2", "ws": "0.7.2",
"yargs": "^3.31.0" "yargs": "^4.7.1"
}, },
"optionalDependencies": { "optionalDependencies": {
"ffi": "2.0.0", "ffi": "2.0.0",