feat: Add data parameter to app.requestSingleInstanceLock()
(#30891)
* WIP * Use serialization * Rebase windows impl of new app requestSingleInstanceLock parameter * Fix test * Implement posix side * Add backwards compatibility test * Apply PR feedback Windows * Fix posix impl * Switch mac impl back to vector * Refactor Windows impl * Use vectors, inline make_span * Use blink converter * fix: ownership across sequences * Fix upstream merge from Chromium Co-authored-by: deepak1556 <hop2deep@gmail.com>
This commit is contained in:
parent
5592652504
commit
db0a152bc1
9 changed files with 442 additions and 22 deletions
|
@ -483,6 +483,7 @@ Returns:
|
|||
* `event` Event
|
||||
* `argv` String[] - An array of the second instance's command line arguments
|
||||
* `workingDirectory` String - The second instance's working directory
|
||||
* `additionalData` unknown - A JSON object of additional data passed from the second instance
|
||||
|
||||
This event will be emitted inside the primary instance of your application
|
||||
when a second instance has been executed and calls `app.requestSingleInstanceLock()`.
|
||||
|
@ -931,6 +932,8 @@ app.setJumpList([
|
|||
|
||||
### `app.requestSingleInstanceLock()`
|
||||
|
||||
* `additionalData` unknown (optional) - A JSON object containing additional data to send to the first instance.
|
||||
|
||||
Returns `Boolean`
|
||||
|
||||
The return value of this method indicates whether or not this instance of your
|
||||
|
@ -956,12 +959,16 @@ starts:
|
|||
const { app } = require('electron')
|
||||
let myWindow = null
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
const additionalData = { myKey: 'myValue' }
|
||||
const gotTheLock = app.requestSingleInstanceLock(additionalData)
|
||||
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory, additionalData) => {
|
||||
// Print out data received from the second instance.
|
||||
console.log(additionalData)
|
||||
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (myWindow) {
|
||||
if (myWindow.isMinimized()) myWindow.restore()
|
||||
|
|
|
@ -106,3 +106,4 @@ feat_expose_raw_response_headers_from_urlloader.patch
|
|||
chore_do_not_use_chrome_windows_in_cryptotoken_webrequestsender.patch
|
||||
process_singleton.patch
|
||||
fix_expose_decrementcapturercount_in_web_contents_impl.patch
|
||||
feat_add_data_parameter_to_processsingleton.patch
|
||||
|
|
|
@ -0,0 +1,343 @@
|
|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Raymond Zhao <raymondzhao@microsoft.com>
|
||||
Date: Tue, 7 Sep 2021 14:54:25 -0700
|
||||
Subject: feat: Add data parameter to ProcessSingleton
|
||||
|
||||
This patch adds an additional_data parameter to the constructor of
|
||||
ProcessSingleton, so that the second instance can send additional
|
||||
data over to the first instance while requesting the ProcessSingleton
|
||||
lock.
|
||||
|
||||
On the Electron side, we then expose an extra parameter to the
|
||||
app.requestSingleInstanceLock API so that users can pass in a JSON
|
||||
object for the second instance to send to the first instance.
|
||||
|
||||
diff --git a/chrome/browser/process_singleton.h b/chrome/browser/process_singleton.h
|
||||
index eec994c4252f17d9c9c41e66d5dae6509ed98a18..e538c9b76da4d4435e10cd3848438446c2cc2cc8 100644
|
||||
--- a/chrome/browser/process_singleton.h
|
||||
+++ b/chrome/browser/process_singleton.h
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/process/process.h"
|
||||
+#include "base/containers/span.h"
|
||||
#include "ui/gfx/native_widget_types.h"
|
||||
|
||||
#if defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
@@ -101,21 +102,24 @@ class ProcessSingleton {
|
||||
// should handle it (i.e., because the current process is shutting down).
|
||||
using NotificationCallback =
|
||||
base::RepeatingCallback<bool(const base::CommandLine& command_line,
|
||||
- const base::FilePath& current_directory)>;
|
||||
+ const base::FilePath& current_directory,
|
||||
+ const std::vector<const uint8_t> additional_data)>;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
ProcessSingleton(const std::string& program_name,
|
||||
const base::FilePath& user_data_dir,
|
||||
+ const base::span<const uint8_t> additional_data,
|
||||
bool is_sandboxed,
|
||||
const NotificationCallback& notification_callback);
|
||||
#else
|
||||
ProcessSingleton(const base::FilePath& user_data_dir,
|
||||
+ const base::span<const uint8_t> additional_data,
|
||||
const NotificationCallback& notification_callback);
|
||||
+#endif
|
||||
|
||||
ProcessSingleton(const ProcessSingleton&) = delete;
|
||||
ProcessSingleton& operator=(const ProcessSingleton&) = delete;
|
||||
|
||||
-#endif
|
||||
~ProcessSingleton();
|
||||
|
||||
// Notify another process, if available. Otherwise sets ourselves as the
|
||||
@@ -179,6 +183,8 @@ class ProcessSingleton {
|
||||
|
||||
private:
|
||||
NotificationCallback notification_callback_; // Handler for notifications.
|
||||
+ // Custom data to pass to the other instance during notify.
|
||||
+ base::span<const uint8_t> additional_data_;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
bool EscapeVirtualization(const base::FilePath& user_data_dir);
|
||||
diff --git a/chrome/browser/process_singleton_posix.cc b/chrome/browser/process_singleton_posix.cc
|
||||
index 05c86df6c871ca7d0926836edc2f6137fcf229cb..01627f6b46c64a24870fa05b9efeaf949203c2ac 100644
|
||||
--- a/chrome/browser/process_singleton_posix.cc
|
||||
+++ b/chrome/browser/process_singleton_posix.cc
|
||||
@@ -564,6 +564,7 @@ class ProcessSingleton::LinuxWatcher
|
||||
// |reader| is for sending back ACK message.
|
||||
void HandleMessage(const std::string& current_dir,
|
||||
const std::vector<std::string>& argv,
|
||||
+ const std::vector<const uint8_t> additional_data,
|
||||
SocketReader* reader);
|
||||
|
||||
private:
|
||||
@@ -620,13 +621,16 @@ void ProcessSingleton::LinuxWatcher::StartListening(int socket) {
|
||||
}
|
||||
|
||||
void ProcessSingleton::LinuxWatcher::HandleMessage(
|
||||
- const std::string& current_dir, const std::vector<std::string>& argv,
|
||||
+ const std::string& current_dir,
|
||||
+ const std::vector<std::string>& argv,
|
||||
+ const std::vector<const uint8_t> additional_data,
|
||||
SocketReader* reader) {
|
||||
DCHECK(ui_task_runner_->BelongsToCurrentThread());
|
||||
DCHECK(reader);
|
||||
|
||||
if (parent_->notification_callback_.Run(base::CommandLine(argv),
|
||||
- base::FilePath(current_dir))) {
|
||||
+ base::FilePath(current_dir),
|
||||
+ std::move(additional_data))) {
|
||||
// Send back "ACK" message to prevent the client process from starting up.
|
||||
reader->FinishWithACK(kACKToken, base::size(kACKToken) - 1);
|
||||
} else {
|
||||
@@ -674,7 +678,8 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
|
||||
}
|
||||
}
|
||||
|
||||
- // Validate the message. The shortest message is kStartToken\0x\0x
|
||||
+ // Validate the message. The shortest message kStartToken\0\00
|
||||
+ // The shortest message with additional data is kStartToken\0\00\00\0.
|
||||
const size_t kMinMessageLength = base::size(kStartToken) + 4;
|
||||
if (bytes_read_ < kMinMessageLength) {
|
||||
buf_[bytes_read_] = 0;
|
||||
@@ -704,10 +709,25 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
|
||||
tokens.erase(tokens.begin());
|
||||
tokens.erase(tokens.begin());
|
||||
|
||||
+ size_t num_args;
|
||||
+ base::StringToSizeT(tokens[0], &num_args);
|
||||
+ std::vector<std::string> command_line(tokens.begin() + 1, tokens.begin() + 1 + num_args);
|
||||
+
|
||||
+ std::vector<const uint8_t> additional_data;
|
||||
+ if (tokens.size() == 3 + num_args) {
|
||||
+ size_t additional_data_size;
|
||||
+ base::StringToSizeT(tokens[1 + num_args], &additional_data_size);
|
||||
+ const uint8_t* additional_data_bits =
|
||||
+ reinterpret_cast<const uint8_t*>(tokens[2 + num_args].c_str());
|
||||
+ additional_data = std::vector<const uint8_t>(additional_data_bits,
|
||||
+ additional_data_bits + additional_data_size);
|
||||
+ }
|
||||
+
|
||||
// Return to the UI thread to handle opening a new browser tab.
|
||||
ui_task_runner_->PostTask(
|
||||
FROM_HERE, base::BindOnce(&ProcessSingleton::LinuxWatcher::HandleMessage,
|
||||
- parent_, current_dir, tokens, this));
|
||||
+ parent_, current_dir, command_line,
|
||||
+ std::move(additional_data), this));
|
||||
fd_watch_controller_.reset();
|
||||
|
||||
// LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader
|
||||
@@ -736,8 +756,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
|
||||
//
|
||||
ProcessSingleton::ProcessSingleton(
|
||||
const base::FilePath& user_data_dir,
|
||||
+ const base::span<const uint8_t> additional_data,
|
||||
const NotificationCallback& notification_callback)
|
||||
: notification_callback_(notification_callback),
|
||||
+ additional_data_(additional_data),
|
||||
current_pid_(base::GetCurrentProcId()),
|
||||
watcher_(new LinuxWatcher(this)) {
|
||||
socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename);
|
||||
@@ -854,7 +876,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
sizeof(socket_timeout));
|
||||
|
||||
// Found another process, prepare our command line
|
||||
- // format is "START\0<current dir>\0<argv[0]>\0...\0<argv[n]>".
|
||||
+ // format is "START\0<current-dir>\0<n-args>\0<argv[0]>\0...\0<argv[n]>
|
||||
+ // \0<additional-data-length>\0<additional-data>".
|
||||
std::string to_send(kStartToken);
|
||||
to_send.push_back(kTokenDelimiter);
|
||||
|
||||
@@ -864,11 +887,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
to_send.append(current_dir.value());
|
||||
|
||||
const std::vector<std::string>& argv = cmd_line.argv();
|
||||
+ to_send.push_back(kTokenDelimiter);
|
||||
+ to_send.append(base::NumberToString(argv.size()));
|
||||
for (auto it = argv.begin(); it != argv.end(); ++it) {
|
||||
to_send.push_back(kTokenDelimiter);
|
||||
to_send.append(*it);
|
||||
}
|
||||
|
||||
+ size_t data_to_send_size = additional_data_.size_bytes();
|
||||
+ if (data_to_send_size) {
|
||||
+ to_send.push_back(kTokenDelimiter);
|
||||
+ to_send.append(base::NumberToString(data_to_send_size));
|
||||
+ to_send.push_back(kTokenDelimiter);
|
||||
+ to_send.append(reinterpret_cast<const char*>(additional_data_.data()), data_to_send_size);
|
||||
+ }
|
||||
+
|
||||
// Send the message
|
||||
if (!WriteToSocket(socket.fd(), to_send.data(), to_send.length())) {
|
||||
// Try to kill the other process, because it might have been dead.
|
||||
diff --git a/chrome/browser/process_singleton_win.cc b/chrome/browser/process_singleton_win.cc
|
||||
index 19d5659d665321da54e05cee01be7da02e0c283b..600ff701b025ba190d05bc30994e3d3e8847df55 100644
|
||||
--- a/chrome/browser/process_singleton_win.cc
|
||||
+++ b/chrome/browser/process_singleton_win.cc
|
||||
@@ -99,10 +99,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
|
||||
|
||||
bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
base::CommandLine* parsed_command_line,
|
||||
- base::FilePath* current_directory) {
|
||||
+ base::FilePath* current_directory,
|
||||
+ std::vector<const uint8_t>* parsed_additional_data) {
|
||||
// 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).
|
||||
+ // possible is L"START\0\0" (empty command line, current directory,
|
||||
+ // and additional data).
|
||||
static const int min_message_size = 7;
|
||||
if (cds->cbData < min_message_size * sizeof(wchar_t) ||
|
||||
cds->cbData % sizeof(wchar_t) != 0) {
|
||||
@@ -152,6 +154,37 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
const std::wstring cmd_line =
|
||||
msg.substr(second_null + 1, third_null - second_null);
|
||||
*parsed_command_line = base::CommandLine::FromString(cmd_line);
|
||||
+
|
||||
+ const std::wstring::size_type fourth_null =
|
||||
+ msg.find_first_of(L'\0', third_null + 1);
|
||||
+ if (fourth_null == std::wstring::npos ||
|
||||
+ fourth_null == msg.length()) {
|
||||
+ // No additional data was provided.
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ // Get length of the additional data.
|
||||
+ const std::wstring additional_data_length_string =
|
||||
+ msg.substr(third_null + 1, fourth_null - third_null);
|
||||
+ size_t additional_data_length;
|
||||
+ base::StringToSizeT(additional_data_length_string, &additional_data_length);
|
||||
+
|
||||
+ const std::wstring::size_type fifth_null =
|
||||
+ msg.find_first_of(L'\0', fourth_null + 1);
|
||||
+ if (fifth_null == std::wstring::npos ||
|
||||
+ fifth_null == msg.length()) {
|
||||
+ LOG(WARNING) << "Invalid format for start command, we need a string in 6 "
|
||||
+ "parts separated by NULLs";
|
||||
+ }
|
||||
+
|
||||
+ // Get the actual additional data.
|
||||
+ const std::wstring additional_data =
|
||||
+ msg.substr(fourth_null + 1, fifth_null - fourth_null);
|
||||
+ const uint8_t* additional_data_bytes =
|
||||
+ reinterpret_cast<const uint8_t*>(additional_data.c_str());
|
||||
+ *parsed_additional_data = std::vector<const uint8_t>(additional_data_bytes,
|
||||
+ additional_data_bytes + additional_data_length);
|
||||
+
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -168,16 +201,16 @@ bool ProcessLaunchNotification(
|
||||
|
||||
// Handle the WM_COPYDATA message from another process.
|
||||
const COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(lparam);
|
||||
-
|
||||
base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM);
|
||||
base::FilePath current_directory;
|
||||
- if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) {
|
||||
+ std::vector<const uint8_t> additional_data;
|
||||
+ if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory, &additional_data)) {
|
||||
*result = TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
- *result = notification_callback.Run(parsed_command_line, current_directory) ?
|
||||
- TRUE : FALSE;
|
||||
+ *result = notification_callback.Run(parsed_command_line,
|
||||
+ current_directory, std::move(additional_data)) ? TRUE : FALSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -274,9 +307,11 @@ bool ProcessSingleton::EscapeVirtualization(
|
||||
ProcessSingleton::ProcessSingleton(
|
||||
const std::string& program_name,
|
||||
const base::FilePath& user_data_dir,
|
||||
+ const base::span<const uint8_t> additional_data,
|
||||
bool is_app_sandboxed,
|
||||
const NotificationCallback& notification_callback)
|
||||
: notification_callback_(notification_callback),
|
||||
+ additional_data_(additional_data),
|
||||
program_name_(program_name),
|
||||
is_app_sandboxed_(is_app_sandboxed),
|
||||
is_virtualized_(false),
|
||||
@@ -301,7 +336,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
|
||||
- switch (chrome::AttemptToNotifyRunningChrome(remote_window_)) {
|
||||
+ switch (chrome::AttemptToNotifyRunningChrome(remote_window_, additional_data_)) {
|
||||
case chrome::NOTIFY_SUCCESS:
|
||||
return PROCESS_NOTIFIED;
|
||||
case chrome::NOTIFY_FAILED:
|
||||
diff --git a/chrome/browser/win/chrome_process_finder.cc b/chrome/browser/win/chrome_process_finder.cc
|
||||
index 788abf9a04f2a3725d67f7f8d84615016b241c8e..6ae6d97708e18c25c59a0b1e3d2d58f27d980ffb 100644
|
||||
--- a/chrome/browser/win/chrome_process_finder.cc
|
||||
+++ b/chrome/browser/win/chrome_process_finder.cc
|
||||
@@ -34,7 +34,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
|
||||
return base::win::MessageWindow::FindWindow(user_data_dir.value());
|
||||
}
|
||||
|
||||
-NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
|
||||
+NotifyChromeResult AttemptToNotifyRunningChrome(
|
||||
+ HWND remote_window,
|
||||
+ const base::span<const uint8_t> additional_data) {
|
||||
DCHECK(remote_window);
|
||||
DWORD process_id = 0;
|
||||
DWORD thread_id = GetWindowThreadProcessId(remote_window, &process_id);
|
||||
@@ -42,7 +44,8 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
|
||||
return NOTIFY_FAILED;
|
||||
|
||||
// Send the command line to the remote chrome window.
|
||||
- // Format is "START\0<<<current directory>>>\0<<<commandline>>>".
|
||||
+ // Format is
|
||||
+ // "START\0<current-directory>\0<command-line>\0<additional-data-length>\0<additional-data>".
|
||||
std::wstring to_send(L"START\0", 6); // want the NULL in the string.
|
||||
base::FilePath cur_dir;
|
||||
if (!base::GetCurrentDirectory(&cur_dir))
|
||||
@@ -53,6 +56,22 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
|
||||
base::CommandLine::ForCurrentProcess()->GetCommandLineString());
|
||||
to_send.append(L"\0", 1); // Null separator.
|
||||
|
||||
+ size_t additional_data_size = additional_data.size_bytes();
|
||||
+ if (additional_data_size) {
|
||||
+ // Send over the size, because the reinterpret cast to wchar_t could
|
||||
+ // add padding.
|
||||
+ to_send.append(base::UTF8ToWide(base::NumberToString(additional_data_size)));
|
||||
+ to_send.append(L"\0", 1); // Null separator.
|
||||
+
|
||||
+ size_t padded_size = additional_data_size / sizeof(wchar_t);
|
||||
+ if (additional_data_size % sizeof(wchar_t) != 0) {
|
||||
+ padded_size++;
|
||||
+ }
|
||||
+ to_send.append(reinterpret_cast<const wchar_t*>(additional_data.data()),
|
||||
+ padded_size);
|
||||
+ 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);
|
||||
diff --git a/chrome/browser/win/chrome_process_finder.h b/chrome/browser/win/chrome_process_finder.h
|
||||
index 5516673cee019f6060077091e59498bf9038cd6e..8edea5079b46c2cba67833114eb9c21d85cfc22d 100644
|
||||
--- a/chrome/browser/win/chrome_process_finder.h
|
||||
+++ b/chrome/browser/win/chrome_process_finder.h
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
+#include "base/containers/span.h"
|
||||
#include "base/time/time.h"
|
||||
|
||||
namespace base {
|
||||
@@ -27,7 +28,9 @@ 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.
|
||||
-NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window);
|
||||
+NotifyChromeResult AttemptToNotifyRunningChrome(
|
||||
+ HWND remote_window,
|
||||
+ const base::span<const uint8_t> additional_data);
|
||||
|
||||
// Changes the notification timeout to |new_timeout|, returns the old timeout.
|
||||
base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout);
|
|
@ -17,6 +17,7 @@
|
|||
#include "base/files/file_util.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/system/sys_info.h"
|
||||
#include "base/values.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/browser/icon_manager.h"
|
||||
#include "chrome/common/chrome_features.h"
|
||||
|
@ -52,6 +53,7 @@
|
|||
#include "shell/common/electron_command_line.h"
|
||||
#include "shell/common/electron_paths.h"
|
||||
#include "shell/common/gin_converters/base_converter.h"
|
||||
#include "shell/common/gin_converters/blink_converter.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
|
@ -63,6 +65,7 @@
|
|||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/options_switches.h"
|
||||
#include "shell/common/platform_util.h"
|
||||
#include "shell/common/v8_value_serializer.h"
|
||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
|
||||
|
@ -513,17 +516,22 @@ int GetPathConstant(const std::string& name) {
|
|||
bool NotificationCallbackWrapper(
|
||||
const base::RepeatingCallback<
|
||||
void(const base::CommandLine& command_line,
|
||||
const base::FilePath& current_directory)>& callback,
|
||||
const base::FilePath& current_directory,
|
||||
const std::vector<const uint8_t> additional_data)>& callback,
|
||||
const base::CommandLine& cmd,
|
||||
const base::FilePath& cwd) {
|
||||
const base::FilePath& cwd,
|
||||
const std::vector<const uint8_t> additional_data) {
|
||||
// Make sure the callback is called after app gets ready.
|
||||
if (Browser::Get()->is_ready()) {
|
||||
callback.Run(cmd, cwd);
|
||||
callback.Run(cmd, cwd, std::move(additional_data));
|
||||
} else {
|
||||
scoped_refptr<base::SingleThreadTaskRunner> task_runner(
|
||||
base::ThreadTaskRunnerHandle::Get());
|
||||
task_runner->PostTask(
|
||||
FROM_HERE, base::BindOnce(base::IgnoreResult(callback), cmd, cwd));
|
||||
|
||||
// Make a copy of the span so that the data isn't lost.
|
||||
task_runner->PostTask(FROM_HERE,
|
||||
base::BindOnce(base::IgnoreResult(callback), cmd, cwd,
|
||||
std::move(additional_data)));
|
||||
}
|
||||
// ProcessSingleton needs to know whether current process is quiting.
|
||||
return !Browser::Get()->is_shutting_down();
|
||||
|
@ -1069,8 +1077,14 @@ std::string App::GetLocaleCountryCode() {
|
|||
}
|
||||
|
||||
void App::OnSecondInstance(const base::CommandLine& cmd,
|
||||
const base::FilePath& cwd) {
|
||||
Emit("second-instance", cmd.argv(), cwd);
|
||||
const base::FilePath& cwd,
|
||||
const std::vector<const uint8_t> additional_data) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::Locker locker(isolate);
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
v8::Local<v8::Value> data_value =
|
||||
DeserializeV8Value(isolate, std::move(additional_data));
|
||||
Emit("second-instance", cmd.argv(), cwd, data_value);
|
||||
}
|
||||
|
||||
bool App::HasSingleInstanceLock() const {
|
||||
|
@ -1079,7 +1093,7 @@ bool App::HasSingleInstanceLock() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool App::RequestSingleInstanceLock() {
|
||||
bool App::RequestSingleInstanceLock(gin::Arguments* args) {
|
||||
if (HasSingleInstanceLock())
|
||||
return true;
|
||||
|
||||
|
@ -1090,15 +1104,18 @@ bool App::RequestSingleInstanceLock() {
|
|||
|
||||
auto cb = base::BindRepeating(&App::OnSecondInstance, base::Unretained(this));
|
||||
|
||||
blink::CloneableMessage additional_data_message;
|
||||
args->GetNext(&additional_data_message);
|
||||
#if defined(OS_WIN)
|
||||
bool app_is_sandboxed =
|
||||
IsSandboxEnabled(base::CommandLine::ForCurrentProcess());
|
||||
process_singleton_ = std::make_unique<ProcessSingleton>(
|
||||
program_name, user_dir, app_is_sandboxed,
|
||||
base::BindRepeating(NotificationCallbackWrapper, cb));
|
||||
program_name, user_dir, additional_data_message.encoded_message,
|
||||
app_is_sandboxed, base::BindRepeating(NotificationCallbackWrapper, cb));
|
||||
#else
|
||||
process_singleton_ = std::make_unique<ProcessSingleton>(
|
||||
user_dir, base::BindRepeating(NotificationCallbackWrapper, cb));
|
||||
user_dir, additional_data_message.encoded_message,
|
||||
base::BindRepeating(NotificationCallbackWrapper, cb));
|
||||
#endif
|
||||
|
||||
switch (process_singleton_->NotifyOtherProcessOrCreate()) {
|
||||
|
|
|
@ -189,9 +189,10 @@ class App : public ElectronBrowserClient::Delegate,
|
|||
std::string GetLocale();
|
||||
std::string GetLocaleCountryCode();
|
||||
void OnSecondInstance(const base::CommandLine& cmd,
|
||||
const base::FilePath& cwd);
|
||||
const base::FilePath& cwd,
|
||||
const std::vector<const uint8_t> additional_data);
|
||||
bool HasSingleInstanceLock() const;
|
||||
bool RequestSingleInstanceLock();
|
||||
bool RequestSingleInstanceLock(gin::Arguments* args);
|
||||
void ReleaseSingleInstanceLock();
|
||||
bool Relaunch(gin::Arguments* args);
|
||||
void DisableHardwareAcceleration(gin_helper::ErrorThrower thrower);
|
||||
|
|
|
@ -207,7 +207,7 @@ describe('app module', () => {
|
|||
describe('app.requestSingleInstanceLock', () => {
|
||||
it('prevents the second launch of app', async function () {
|
||||
this.timeout(120000);
|
||||
const appPath = path.join(fixturesPath, 'api', 'singleton');
|
||||
const appPath = path.join(fixturesPath, 'api', 'singleton-data');
|
||||
const first = cp.spawn(process.execPath, [appPath]);
|
||||
await emittedOnce(first.stdout, 'data');
|
||||
// Start second app when received output.
|
||||
|
@ -218,8 +218,8 @@ describe('app module', () => {
|
|||
expect(code1).to.equal(0);
|
||||
});
|
||||
|
||||
it('passes arguments to the second-instance event', async () => {
|
||||
const appPath = path.join(fixturesPath, 'api', 'singleton');
|
||||
async function testArgumentPassing (fixtureName: string, expectedSecondInstanceData: unknown) {
|
||||
const appPath = path.join(fixturesPath, 'api', fixtureName);
|
||||
const first = cp.spawn(process.execPath, [appPath]);
|
||||
const firstExited = emittedOnce(first, 'exit');
|
||||
|
||||
|
@ -236,14 +236,34 @@ describe('app module', () => {
|
|||
expect(code2).to.equal(1);
|
||||
const [code1] = await firstExited;
|
||||
expect(code1).to.equal(0);
|
||||
const data2 = (await data2Promise)[0].toString('ascii');
|
||||
const secondInstanceArgsReceived: string[] = JSON.parse(data2.toString('ascii'));
|
||||
const received = await data2Promise;
|
||||
const [args, additionalData] = received[0].toString('ascii').split('||');
|
||||
const secondInstanceArgsReceived: string[] = JSON.parse(args.toString('ascii'));
|
||||
const secondInstanceDataReceived = JSON.parse(additionalData.toString('ascii'));
|
||||
|
||||
// Ensure secondInstanceArgs is a subset of secondInstanceArgsReceived
|
||||
for (const arg of secondInstanceArgs) {
|
||||
expect(secondInstanceArgsReceived).to.include(arg,
|
||||
`argument ${arg} is missing from received second args`);
|
||||
}
|
||||
expect(secondInstanceDataReceived).to.be.deep.equal(expectedSecondInstanceData,
|
||||
`received data ${JSON.stringify(secondInstanceDataReceived)} is not equal to expected data ${JSON.stringify(expectedSecondInstanceData)}.`);
|
||||
}
|
||||
|
||||
it('passes arguments to the second-instance event', async () => {
|
||||
const expectedSecondInstanceData = {
|
||||
level: 1,
|
||||
testkey: 'testvalue1',
|
||||
inner: {
|
||||
level: 2,
|
||||
testkey: 'testvalue2'
|
||||
}
|
||||
};
|
||||
await testArgumentPassing('singleton-data', expectedSecondInstanceData);
|
||||
});
|
||||
|
||||
it('passes arguments to the second-instance event no additional data', async () => {
|
||||
await testArgumentPassing('singleton', null);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
26
spec/fixtures/api/singleton-data/main.js
vendored
Normal file
26
spec/fixtures/api/singleton-data/main.js
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
const { app } = require('electron');
|
||||
|
||||
app.whenReady().then(() => {
|
||||
console.log('started'); // ping parent
|
||||
});
|
||||
|
||||
const obj = {
|
||||
level: 1,
|
||||
testkey: 'testvalue1',
|
||||
inner: {
|
||||
level: 2,
|
||||
testkey: 'testvalue2'
|
||||
}
|
||||
};
|
||||
const gotTheLock = app.requestSingleInstanceLock(obj);
|
||||
|
||||
app.on('second-instance', (event, args, workingDirectory, data) => {
|
||||
setImmediate(() => {
|
||||
console.log([JSON.stringify(args), JSON.stringify(data)].join('||'));
|
||||
app.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
if (!gotTheLock) {
|
||||
app.exit(1);
|
||||
}
|
5
spec/fixtures/api/singleton-data/package.json
vendored
Normal file
5
spec/fixtures/api/singleton-data/package.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "electron-app-singleton-data",
|
||||
"main": "main.js"
|
||||
}
|
||||
|
4
spec/fixtures/api/singleton/main.js
vendored
4
spec/fixtures/api/singleton/main.js
vendored
|
@ -6,9 +6,9 @@ app.whenReady().then(() => {
|
|||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
app.on('second-instance', (event, args) => {
|
||||
app.on('second-instance', (event, args, workingDirectory, data) => {
|
||||
setImmediate(() => {
|
||||
console.log(JSON.stringify(args));
|
||||
console.log([JSON.stringify(args), JSON.stringify(data)].join('||'));
|
||||
app.exit(0);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue