commit
4ec064d311
19 changed files with 812 additions and 4 deletions
|
@ -111,6 +111,7 @@ int APIENTRY wWinMain(HINSTANCE instance, HINSTANCE, wchar_t* cmd, int) {
|
|||
params.instance = instance;
|
||||
params.sandbox_info = &sandbox_info;
|
||||
atom::AtomCommandLine::Init(argc, argv);
|
||||
atom::AtomCommandLine::InitW(argc, wargv);
|
||||
return content::ContentMain(params);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "atom/app/atom_content_client.h"
|
||||
#include "atom/browser/atom_browser_client.h"
|
||||
#include "atom/browser/relauncher.h"
|
||||
#include "atom/common/google_api_key.h"
|
||||
#include "atom/renderer/atom_renderer_client.h"
|
||||
#include "atom/utility/atom_content_utility_client.h"
|
||||
|
@ -25,6 +26,8 @@ namespace atom {
|
|||
|
||||
namespace {
|
||||
|
||||
const char* kRelauncherProcess = "relauncher";
|
||||
|
||||
bool IsBrowserProcess(base::CommandLine* cmd) {
|
||||
std::string process_type = cmd->GetSwitchValueASCII(switches::kProcessType);
|
||||
return process_type.empty();
|
||||
|
@ -146,6 +149,26 @@ content::ContentUtilityClient* AtomMainDelegate::CreateContentUtilityClient() {
|
|||
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>
|
||||
AtomMainDelegate::CreateContentClient() {
|
||||
return std::unique_ptr<brightray::ContentClient>(new AtomContentClient);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#ifndef ATOM_APP_ATOM_MAIN_DELEGATE_H_
|
||||
#define ATOM_APP_ATOM_MAIN_DELEGATE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "brightray/common/main_delegate.h"
|
||||
#include "brightray/common/content_client.h"
|
||||
|
||||
|
@ -22,6 +24,13 @@ class AtomMainDelegate : public brightray::MainDelegate {
|
|||
content::ContentBrowserClient* CreateContentBrowserClient() override;
|
||||
content::ContentRendererClient* CreateContentRendererClient() 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:
|
||||
std::unique_ptr<brightray::ContentClient> CreateContentClient() override;
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include "atom/browser/atom_browser_main_parts.h"
|
||||
#include "atom/browser/browser.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/file_path_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, ¤t_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)
|
||||
void App::ImportCertificate(
|
||||
const base::DictionaryValue& options,
|
||||
|
@ -488,7 +527,8 @@ void App::BuildPrototype(
|
|||
.SetMethod("importCertificate", &App::ImportCertificate)
|
||||
#endif
|
||||
.SetMethod("makeSingleInstance", &App::MakeSingleInstance)
|
||||
.SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance);
|
||||
.SetMethod("releaseSingleInstance", &App::ReleaseSingleInstance)
|
||||
.SetMethod("relaunch", &App::Relaunch);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
|
|
|
@ -106,10 +106,11 @@ class App : public AtomBrowserClient::Delegate,
|
|||
const base::FilePath& path);
|
||||
|
||||
void SetDesktopName(const std::string& desktop_name);
|
||||
std::string GetLocale();
|
||||
bool MakeSingleInstance(
|
||||
const ProcessSingleton::NotificationCallback& callback);
|
||||
void ReleaseSingleInstance();
|
||||
std::string GetLocale();
|
||||
bool Relaunch(mate::Arguments* args);
|
||||
|
||||
#if defined(USE_NSS_CERTS)
|
||||
void ImportCertificate(const base::DictionaryValue& options,
|
||||
|
|
192
atom/browser/relauncher.cc
Normal file
192
atom/browser/relauncher.cc
Normal 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
122
atom/browser/relauncher.h
Normal 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_
|
67
atom/browser/relauncher_linux.cc
Normal file
67
atom/browser/relauncher_linux.cc
Normal 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
|
90
atom/browser/relauncher_mac.cc
Normal file
90
atom/browser/relauncher_mac.cc
Normal 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
|
128
atom/browser/relauncher_win.cc
Normal file
128
atom/browser/relauncher_win.cc
Normal 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
|
|
@ -12,6 +12,11 @@ namespace atom {
|
|||
// static
|
||||
std::vector<std::string> AtomCommandLine::argv_;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// static
|
||||
std::vector<std::wstring> AtomCommandLine::wargv_;
|
||||
#endif
|
||||
|
||||
// static
|
||||
void AtomCommandLine::Init(int argc, const char* const* argv) {
|
||||
// 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)
|
||||
// static
|
||||
void AtomCommandLine::InitializeFromCommandLine() {
|
||||
|
|
|
@ -19,6 +19,11 @@ class AtomCommandLine {
|
|||
static void Init(int argc, const char* const* 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)
|
||||
// On Linux the command line has to be read from base::CommandLine since
|
||||
// it is using zygote.
|
||||
|
@ -28,6 +33,10 @@ class AtomCommandLine {
|
|||
private:
|
||||
static std::vector<std::string> argv_;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
static std::vector<std::wstring> wargv_;
|
||||
#endif
|
||||
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(AtomCommandLine);
|
||||
};
|
||||
|
||||
|
|
|
@ -276,6 +276,33 @@ Exits immediately with `exitCode`.
|
|||
All windows will be closed immediately without asking user and the `before-quit`
|
||||
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()`
|
||||
|
||||
On Linux, focuses on the first visible window. On OS X, makes the application
|
||||
|
|
|
@ -225,6 +225,11 @@
|
|||
'atom/browser/net/url_request_fetch_job.h',
|
||||
'atom/browser/node_debugger.cc',
|
||||
'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.h',
|
||||
'atom/browser/ui/accelerator_util.cc',
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const assert = require('assert')
|
||||
const ChildProcess = require('child_process')
|
||||
const https = require('https')
|
||||
const net = require('net')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
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 () {
|
||||
if (process.platform !== 'darwin') {
|
||||
return
|
||||
|
|
2
spec/fixtures/api/quit-app/package.json
vendored
2
spec/fixtures/api/quit-app/package.json
vendored
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"name": "quit-app",
|
||||
"name": "electron-quit-app",
|
||||
"main": "main.js"
|
||||
}
|
||||
|
|
25
spec/fixtures/api/relaunch/main.js
vendored
Normal file
25
spec/fixtures/api/relaunch/main.js
vendored
Normal 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')})
|
||||
}
|
||||
})
|
5
spec/fixtures/api/relaunch/package.json
vendored
Normal file
5
spec/fixtures/api/relaunch/package.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "electron-app-relaunch",
|
||||
"main": "main.js"
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
"temp": "0.8.1",
|
||||
"walkdir": "0.0.7",
|
||||
"ws": "0.7.2",
|
||||
"yargs": "^3.31.0"
|
||||
"yargs": "^4.7.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"ffi": "2.0.0",
|
||||
|
|
Loading…
Reference in a new issue