From c2218f09d2844ef8c4ccbcdea1aa86c5521bc48b Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Thu, 12 Aug 2021 16:23:59 -0700 Subject: [PATCH] feat: Add onFirstInstanceAck event for requestSingleInstanceLock --- docs/api/app.md | 19 + patches/chromium/.patches | 2 +- ...ansfer_to_requestsingleinstancelock.patch} | 328 ++++++++++++++++-- shell/browser/api/electron_api_app.cc | 70 +++- shell/browser/api/electron_api_app.h | 9 +- 5 files changed, 373 insertions(+), 55 deletions(-) rename patches/chromium/{feat_add_data_parameter_to_processsingleton.patch => feat_add_data_transfer_to_requestsingleinstancelock.patch} (53%) diff --git a/docs/api/app.md b/docs/api/app.md index 005ab2fe94756..c713b25d6fe75 100755 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -484,6 +484,7 @@ Returns: * `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 +* `ackCallback` unknown - A function that can be used to send data back to 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()`. @@ -495,12 +496,30 @@ non-minimized. **Note:** If the second instance is started by a different user than the first, the `argv` array will not include the arguments. +**Note:** `ackCallback` allows the user to send data back to the +second instance during the `app.requestSingleInstanceLock()` flow. +This callback can be used for cases where the second instance +needs to obtain additional information from the first instance +before quitting. +In order to call the callback, `event.preventDefault()` must be called +Then, the `ackCallback` callback can be called. +If the callback is not called in either case, `null` will be sent back. + This event is guaranteed to be emitted after the `ready` event of `app` gets emitted. **Note:** Extra command line arguments might be added by Chromium, such as `--original-process-start-time`. +### Event: 'first-instance-ack' + +Returns: + +* `event` Event +* `additionalData` unknown - A JSON object of additional data passed from the first instance, in response to the first instance's `second-instance` event. + +This event will be emitted within the second instance during the call to `app.requestSingleInstanceLock()`, when the first instance calls the `ackCallback` provided by the `second-instance` event handler. + ## Methods The `app` object has the following methods: diff --git a/patches/chromium/.patches b/patches/chromium/.patches index 562d477b5d746..b7e021f4cc854 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -106,6 +106,6 @@ 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 mas_gate_private_enterprise_APIs load_v8_snapshot_in_browser_process.patch +feat_add_data_transfer_to_requestsingleinstancelock.patch diff --git a/patches/chromium/feat_add_data_parameter_to_processsingleton.patch b/patches/chromium/feat_add_data_transfer_to_requestsingleinstancelock.patch similarity index 53% rename from patches/chromium/feat_add_data_parameter_to_processsingleton.patch rename to patches/chromium/feat_add_data_transfer_to_requestsingleinstancelock.patch index 447f7ed42f856..06b7854e87299 100644 --- a/patches/chromium/feat_add_data_parameter_to_processsingleton.patch +++ b/patches/chromium/feat_add_data_transfer_to_requestsingleinstancelock.patch @@ -1,19 +1,25 @@ From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Raymond Zhao Date: Tue, 7 Sep 2021 14:54:25 -0700 -Subject: feat: Add data parameter to ProcessSingleton +Subject: feat: Add data transfer mechanism to requestSingleInstanceLock flow -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. +This patch adds code that allows for the second instance to send +additional data to the first instance, and for the first instance +to send additional data back to the second instance, during the +app.requestSingleInstanceLock call. -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. +Firstly, 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. + +Then, we add additional processing to the second-instance event, both +so the first instance can receive additional data from the second +instance, but also so the second instance can send back additional +data to the first instance if needed. diff --git a/chrome/browser/process_singleton.h b/chrome/browser/process_singleton.h -index eec994c4252f17d9c9c41e66d5dae6509ed98a18..e538c9b76da4d4435e10cd3848438446c2cc2cc8 100644 +index eec994c4252f17d9c9c41e66d5dae6509ed98a18..c16343259158493b914c794f5ec5ae287048ec19 100644 --- a/chrome/browser/process_singleton.h +++ b/chrome/browser/process_singleton.h @@ -19,6 +19,7 @@ @@ -24,24 +30,39 @@ index eec994c4252f17d9c9c41e66d5dae6509ed98a18..e538c9b76da4d4435e10cd3848438446 #include "ui/gfx/native_widget_types.h" #if defined(OS_POSIX) && !defined(OS_ANDROID) -@@ -101,21 +102,24 @@ class ProcessSingleton { +@@ -94,6 +95,9 @@ class ProcessSingleton { + + static constexpr int kNumNotifyResults = LAST_VALUE + 1; + ++ using NotificationAckCallback = ++ base::RepeatingCallback* ack_data)>; ++ + // Implement this callback to handle notifications from other processes. The + // callback will receive the command line and directory with which the other + // Chrome process was launched. Return true if the command line will be +@@ -101,21 +105,27 @@ class ProcessSingleton { // should handle it (i.e., because the current process is shutting down). using NotificationCallback = base::RepeatingCallback; + const base::FilePath& current_directory, -+ const std::vector additional_data)>; ++ const std::vector additional_data, ++ const NotificationAckCallback& ack_callback)>; #if defined(OS_WIN) ProcessSingleton(const std::string& program_name, const base::FilePath& user_data_dir, + const base::span additional_data, bool is_sandboxed, - const NotificationCallback& notification_callback); +- const NotificationCallback& notification_callback); ++ const NotificationCallback& notification_callback, ++ const NotificationAckCallback& ack_notification_callback); #else ProcessSingleton(const base::FilePath& user_data_dir, +- const NotificationCallback& notification_callback); + const base::span additional_data, - const NotificationCallback& notification_callback); ++ const NotificationCallback& notification_callback, ++ const NotificationAckCallback& ack_notification_callback); +#endif ProcessSingleton(const ProcessSingleton&) = delete; @@ -51,19 +72,42 @@ index eec994c4252f17d9c9c41e66d5dae6509ed98a18..e538c9b76da4d4435e10cd3848438446 ~ProcessSingleton(); // Notify another process, if available. Otherwise sets ourselves as the -@@ -179,6 +183,8 @@ class ProcessSingleton { +@@ -178,7 +188,13 @@ class ProcessSingleton { + #endif private: - NotificationCallback notification_callback_; // Handler for notifications. +- NotificationCallback notification_callback_; // Handler for notifications. ++ // A callback to run when the first instance receives data from the second. ++ NotificationCallback notification_callback_; ++ // A callback to run when the second instance ++ // receives an acknowledgement from the first. ++ NotificationAckCallback notification_ack_callback_; + // Custom data to pass to the other instance during notify. + base::span additional_data_; #if defined(OS_WIN) bool EscapeVirtualization(const base::FilePath& user_data_dir); +@@ -191,6 +207,7 @@ class ProcessSingleton { + HANDLE lock_file_; + base::FilePath user_data_dir_; + ShouldKillRemoteProcessCallback should_kill_remote_process_callback_; ++ HANDLE ack_pipe_; + #elif defined(OS_POSIX) && !defined(OS_ANDROID) + // Return true if the given pid is one of our child processes. + // Assumes that the current pid is the root of all pids of the current diff --git a/chrome/browser/process_singleton_posix.cc b/chrome/browser/process_singleton_posix.cc -index 727333dd6abec99643e31bc77ed2cc8f3d5a0a0b..e5361397f78636816507355e7db6a9e55e6ed234 100644 +index 727333dd6abec99643e31bc77ed2cc8f3d5a0a0b..0572a34812ea0588f21ec9a03fbaabc3fe870616 100644 --- a/chrome/browser/process_singleton_posix.cc +++ b/chrome/browser/process_singleton_posix.cc +@@ -122,7 +122,7 @@ const char kACKToken[] = "ACK"; + const char kShutdownToken[] = "SHUTDOWN"; + const char kTokenDelimiter = '\0'; + const int kMaxMessageLength = 32 * 1024; +-const int kMaxACKMessageLength = base::size(kShutdownToken) - 1; ++const int kMaxACKMessageLength = kMaxMessageLength; + + bool g_disable_prompt = false; + bool g_skip_is_chrome_process_check = false; @@ -567,6 +567,7 @@ class ProcessSingleton::LinuxWatcher // |reader| is for sending back ACK message. void HandleMessage(const std::string& current_dir, @@ -72,7 +116,17 @@ index 727333dd6abec99643e31bc77ed2cc8f3d5a0a0b..e5361397f78636816507355e7db6a9e5 SocketReader* reader); private: -@@ -621,13 +622,16 @@ void ProcessSingleton::LinuxWatcher::StartListening(int socket) { +@@ -591,6 +592,9 @@ class ProcessSingleton::LinuxWatcher + // The ProcessSingleton that owns us. + ProcessSingleton* const parent_; + ++ bool ack_callback_called_ = false; ++ void AckCallback(SocketReader* reader, const base::span* response); ++ + std::set, base::UniquePtrComparator> readers_; + }; + +@@ -621,16 +625,21 @@ void ProcessSingleton::LinuxWatcher::StartListening(int socket) { } void ProcessSingleton::LinuxWatcher::HandleMessage( @@ -84,14 +138,46 @@ index 727333dd6abec99643e31bc77ed2cc8f3d5a0a0b..e5361397f78636816507355e7db6a9e5 DCHECK(ui_task_runner_->BelongsToCurrentThread()); DCHECK(reader); - if (parent_->notification_callback_.Run(base::CommandLine(argv), +- if (parent_->notification_callback_.Run(base::CommandLine(argv), - base::FilePath(current_dir))) { +- // Send back "ACK" message to prevent the client process from starting up. +- reader->FinishWithACK(kACKToken, base::size(kACKToken) - 1); +- } else { ++ auto wrapped_ack_callback = ++ base::BindRepeating(&ProcessSingleton::LinuxWatcher::AckCallback, ++ base::Unretained(this), reader); ++ ack_callback_called_ = false; ++ if (!parent_->notification_callback_.Run(base::CommandLine(argv), + 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 { -@@ -675,7 +679,8 @@ void ProcessSingleton::LinuxWatcher::SocketReader:: ++ std::move(additional_data), ++ wrapped_ack_callback)) { + LOG(WARNING) << "Not handling interprocess notification as browser" + " is shutting down"; + // Send back "SHUTDOWN" message, so that the client process can start up +@@ -640,6 +649,22 @@ void ProcessSingleton::LinuxWatcher::HandleMessage( + } + } + ++void ProcessSingleton::LinuxWatcher::AckCallback( ++ SocketReader* reader, ++ const base::span* response) { ++ // Send back "ACK" message to prevent the client process from starting up. ++ if (!ack_callback_called_) { ++ ack_callback_called_ = true; ++ std::string ack_message; ++ ack_message.append(kACKToken, base::size(kACKToken) - 1); ++ if (response && response->size_bytes()) { ++ ack_message.append(reinterpret_cast(response->data()), ++ response->size_bytes()); ++ } ++ reader->FinishWithACK(ack_message.c_str(), ack_message.size()); ++ } ++} ++ + void ProcessSingleton::LinuxWatcher::RemoveSocketReader(SocketReader* reader) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK(reader); +@@ -675,7 +700,8 @@ void ProcessSingleton::LinuxWatcher::SocketReader:: } } @@ -101,7 +187,7 @@ index 727333dd6abec99643e31bc77ed2cc8f3d5a0a0b..e5361397f78636816507355e7db6a9e5 const size_t kMinMessageLength = base::size(kStartToken) + 4; if (bytes_read_ < kMinMessageLength) { buf_[bytes_read_] = 0; -@@ -705,10 +710,25 @@ void ProcessSingleton::LinuxWatcher::SocketReader:: +@@ -705,10 +731,25 @@ void ProcessSingleton::LinuxWatcher::SocketReader:: tokens.erase(tokens.begin()); tokens.erase(tokens.begin()); @@ -128,18 +214,21 @@ index 727333dd6abec99643e31bc77ed2cc8f3d5a0a0b..e5361397f78636816507355e7db6a9e5 fd_watch_controller_.reset(); // LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader -@@ -737,8 +757,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK( +@@ -737,8 +778,12 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK( // ProcessSingleton::ProcessSingleton( const base::FilePath& user_data_dir, +- const NotificationCallback& notification_callback) + const base::span additional_data, - const NotificationCallback& notification_callback) ++ const NotificationCallback& notification_callback, ++ const NotificationAckCallback& notification_ack_callback) : notification_callback_(notification_callback), ++ notification_ack_callback_(notification_ack_callback), + additional_data_(additional_data), current_pid_(base::GetCurrentProcId()), watcher_(new LinuxWatcher(this)) { socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename); -@@ -855,7 +877,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( +@@ -855,7 +900,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( sizeof(socket_timeout)); // Found another process, prepare our command line @@ -149,7 +238,7 @@ index 727333dd6abec99643e31bc77ed2cc8f3d5a0a0b..e5361397f78636816507355e7db6a9e5 std::string to_send(kStartToken); to_send.push_back(kTokenDelimiter); -@@ -865,11 +888,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( +@@ -865,11 +911,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( to_send.append(current_dir.value()); const std::vector& argv = cmd_line.argv(); @@ -171,11 +260,52 @@ index 727333dd6abec99643e31bc77ed2cc8f3d5a0a0b..e5361397f78636816507355e7db6a9e5 // 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. +@@ -909,6 +965,17 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( + linux_ui->NotifyWindowManagerStartupComplete(); + #endif + ++ size_t ack_data_len = len - (base::size(kACKToken) - 1); ++ if (ack_data_len) { ++ const uint8_t* raw_ack_data = ++ reinterpret_cast(buf + base::size(kACKToken) - 1); ++ base::span ack_data = ++ base::make_span(raw_ack_data, raw_ack_data + ack_data_len); ++ notification_ack_callback_.Run(&ack_data); ++ } else { ++ notification_ack_callback_.Run(nullptr); ++ } ++ + // Assume the other process is handling the request. + return PROCESS_NOTIFIED; + } diff --git a/chrome/browser/process_singleton_win.cc b/chrome/browser/process_singleton_win.cc -index 19d5659d665321da54e05cee01be7da02e0c283b..600ff701b025ba190d05bc30994e3d3e8847df55 100644 +index 19d5659d665321da54e05cee01be7da02e0c283b..9a894fae1fbe62ee9bc37bf7c658b03733642234 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) { +@@ -23,6 +23,7 @@ + #include "base/strings/utf_string_conversions.h" + #include "base/time/time.h" + #include "base/trace_event/trace_event.h" ++#include "base/timer/timer.h" + #include "base/win/registry.h" + #include "base/win/scoped_handle.h" + #include "base/win/windows_version.h" +@@ -45,6 +46,14 @@ + namespace { + + const char kLockfile[] = "lockfile"; ++const LPCWSTR kPipeName = L"\\\\.\\pipe\\electronAckPipe"; ++const DWORD kPipeTimeout = 10000; ++const DWORD kMaxMessageLength = 32 * 1024; ++ ++std::unique_ptr> g_ack_data; ++base::OneShotTimer g_ack_timer; ++HANDLE g_write_ack_pipe; ++bool g_write_ack_callback_called = false; + + // A helper class that acquires the given |mutex| while the AutoLockMutex is in + // scope. +@@ -99,10 +108,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) { bool ParseCommandLine(const COPYDATASTRUCT* cds, base::CommandLine* parsed_command_line, @@ -190,7 +320,7 @@ index 19d5659d665321da54e05cee01be7da02e0c283b..600ff701b025ba190d05bc30994e3d3e 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, +@@ -152,11 +163,82 @@ 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); @@ -228,7 +358,52 @@ index 19d5659d665321da54e05cee01be7da02e0c283b..600ff701b025ba190d05bc30994e3d3e return true; } return false; -@@ -168,16 +201,16 @@ bool ProcessLaunchNotification( + } + ++void StoreAck(const base::span* ack_data) { ++ if (ack_data) { ++ g_ack_data = std::make_unique>(ack_data->begin(), ++ ack_data->end()); ++ } else { ++ g_ack_data = nullptr; ++ } ++} ++ ++void SendBackAck() { ++ // This is the first instance sending the ack back to the second instance. ++ if (!g_write_ack_callback_called) { ++ g_write_ack_callback_called = true; ++ const uint8_t* data_buffer = nullptr; ++ DWORD data_to_send_size = 0; ++ if (g_ack_data) { ++ data_buffer = g_ack_data->data(); ++ DWORD ack_data_size = g_ack_data->size() * sizeof(uint8_t); ++ data_to_send_size = (ack_data_size < kMaxMessageLength) ? ack_data_size : kMaxMessageLength; ++ } ++ ++ ::ConnectNamedPipe(g_write_ack_pipe, NULL); ++ ++ DWORD bytes_written = 0; ++ ::WriteFile(g_write_ack_pipe, ++ (LPCVOID)data_buffer, ++ data_to_send_size, ++ &bytes_written, ++ NULL); ++ DCHECK(bytes_written == data_to_send_size); ++ ++ ::FlushFileBuffers(g_write_ack_pipe); ++ ::DisconnectNamedPipe(g_write_ack_pipe); ++ ++ if (g_ack_data) { ++ g_ack_data.reset(); ++ } ++ } ++} ++ + bool ProcessLaunchNotification( + const ProcessSingleton::NotificationCallback& notification_callback, + UINT message, +@@ -168,16 +250,23 @@ bool ProcessLaunchNotification( // Handle the WM_COPYDATA message from another process. const COPYDATASTRUCT* cds = reinterpret_cast(lparam); @@ -237,39 +412,116 @@ index 19d5659d665321da54e05cee01be7da02e0c283b..600ff701b025ba190d05bc30994e3d3e base::FilePath current_directory; - if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) { + std::vector additional_data; -+ if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory, &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; ++ g_write_ack_callback_called = false; ++ *result = notification_callback.Run(parsed_command_line, current_directory, ++ std::move(additional_data), ++ base::BindRepeating(&StoreAck)) ++ ? TRUE ++ : FALSE; ++ g_ack_timer.Start(FROM_HERE, base::TimeDelta::FromSeconds(0), ++ base::BindOnce(&SendBackAck)); return true; } -@@ -274,9 +307,11 @@ bool ProcessSingleton::EscapeVirtualization( +@@ -274,9 +363,13 @@ bool ProcessSingleton::EscapeVirtualization( ProcessSingleton::ProcessSingleton( const std::string& program_name, const base::FilePath& user_data_dir, + const base::span additional_data, bool is_app_sandboxed, - const NotificationCallback& notification_callback) +- const NotificationCallback& notification_callback) ++ const NotificationCallback& notification_callback, ++ const NotificationAckCallback& notification_ack_callback) : notification_callback_(notification_callback), ++ notification_ack_callback_(notification_ack_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() { +@@ -291,6 +384,37 @@ ProcessSingleton::~ProcessSingleton() { + ::CloseHandle(lock_file_); + } + ++void ReadAck(const ProcessSingleton::NotificationAckCallback& ack_callback) { ++ // We are reading the ack from the first instance. ++ // First, wait for the pipe. ++ ::WaitNamedPipe(kPipeName, NMPWAIT_USE_DEFAULT_WAIT); ++ ++ HANDLE read_ack_pipe = ::CreateFile(kPipeName, ++ GENERIC_READ, ++ FILE_SHARE_READ, ++ NULL, ++ OPEN_EXISTING, ++ FILE_ATTRIBUTE_NORMAL, ++ NULL); ++ CHECK(read_ack_pipe != INVALID_HANDLE_VALUE); ++ ++ DWORD bytes_read; ++ uint8_t read_ack_buffer[kMaxMessageLength]; ++ ::ReadFile(read_ack_pipe, ++ (LPVOID)read_ack_buffer, ++ kMaxMessageLength, ++ &bytes_read, ++ NULL); ++ ++ if (!bytes_read) { ++ ack_callback.Run(nullptr); ++ } else { ++ base::span out_span(read_ack_buffer, read_ack_buffer + bytes_read); ++ ack_callback.Run(&out_span); ++ } ++ ::CloseHandle(read_ack_pipe); ++} ++ + // Code roughly based on Mozilla. + ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { + if (is_virtualized_) +@@ -301,8 +425,9 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() { return PROCESS_NONE; } - switch (chrome::AttemptToNotifyRunningChrome(remote_window_)) { + switch (chrome::AttemptToNotifyRunningChrome(remote_window_, additional_data_)) { case chrome::NOTIFY_SUCCESS: ++ ReadAck(notification_ack_callback_); return PROCESS_NOTIFIED; case chrome::NOTIFY_FAILED: + remote_window_ = NULL; +@@ -432,6 +557,18 @@ bool ProcessSingleton::Create() { + << "Lock file can not be created! Error code: " << error; + + if (lock_file_ != INVALID_HANDLE_VALUE) { ++ // We are the first instance. Create a pipe to send out ack data. ++ ack_pipe_ = ::CreateNamedPipe(kPipeName, ++ PIPE_ACCESS_OUTBOUND, ++ PIPE_TYPE_BYTE | PIPE_REJECT_REMOTE_CLIENTS, ++ PIPE_UNLIMITED_INSTANCES, ++ kMaxMessageLength, ++ 0, ++ kPipeTimeout, ++ NULL); ++ CHECK(ack_pipe_ != INVALID_HANDLE_VALUE); ++ g_write_ack_pipe = ack_pipe_; ++ + // Set the window's title to the path of our user data directory so + // other Chrome instances can decide if they should forward to us. + bool result = +@@ -458,6 +595,7 @@ bool ProcessSingleton::Create() { + } + + void ProcessSingleton::Cleanup() { ++ ::CloseHandle(ack_pipe_); + } + + void ProcessSingleton::OverrideShouldKillRemoteProcessCallbackForTesting( diff --git a/chrome/browser/win/chrome_process_finder.cc b/chrome/browser/win/chrome_process_finder.cc index b4fec8878c37b9d157ea768e3b6d99399a988c75..e1cb0f21f752aaeee2c360ce9c5fd08bfede26e3 100644 --- a/chrome/browser/win/chrome_process_finder.cc diff --git a/shell/browser/api/electron_api_app.cc b/shell/browser/api/electron_api_app.cc index 7328cedf934b0..4073b19c03361 100644 --- a/shell/browser/api/electron_api_app.cc +++ b/shell/browser/api/electron_api_app.cc @@ -517,21 +517,24 @@ bool NotificationCallbackWrapper( const base::RepeatingCallback< void(const base::CommandLine& command_line, const base::FilePath& current_directory, - const std::vector additional_data)>& callback, + const std::vector additional_data, + const ProcessSingleton::NotificationAckCallback& ack_callback)>& + callback, const base::CommandLine& cmd, const base::FilePath& cwd, - const std::vector additional_data) { + const std::vector additional_data, + const ProcessSingleton::NotificationAckCallback& ack_callback) { // Make sure the callback is called after app gets ready. if (Browser::Get()->is_ready()) { - callback.Run(cmd, cwd, std::move(additional_data)); + callback.Run(cmd, cwd, std::move(additional_data), ack_callback); } else { scoped_refptr task_runner( base::ThreadTaskRunnerHandle::Get()); // 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))); + task_runner->PostTask( + FROM_HERE, base::BindOnce(base::IgnoreResult(callback), cmd, cwd, + std::move(additional_data), ack_callback)); } // ProcessSingleton needs to know whether current process is quiting. return !Browser::Get()->is_shutting_down(); @@ -1076,15 +1079,54 @@ std::string App::GetLocaleCountryCode() { return region.size() == 2 ? region : std::string(); } -void App::OnSecondInstance(const base::CommandLine& cmd, - const base::FilePath& cwd, - const std::vector additional_data) { +void App::OnFirstInstanceAck( + const base::span* first_instance_data) { + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + base::Value data_to_send; + if (first_instance_data) { + // Don't send back the local directly, because it might be empty. + v8::Local data_local; + data_local = DeserializeV8Value(isolate, *first_instance_data); + if (!data_local.IsEmpty()) { + gin::ConvertFromV8(isolate, data_local, &data_to_send); + } + } + Emit("first-instance-ack", data_to_send); +} + +// This function handles the user calling +// the callback parameter sent out by the second-instance event. +static void AckCallbackWrapper( + const ProcessSingleton::NotificationAckCallback& ack_callback, + gin::Arguments* args) { + blink::CloneableMessage ack_message; + args->GetNext(&ack_message); + if (!ack_message.encoded_message.empty()) { + ack_callback.Run(&ack_message.encoded_message); + } else { + ack_callback.Run(nullptr); + } +} + +void App::OnSecondInstance( + const base::CommandLine& cmd, + const base::FilePath& cwd, + const std::vector additional_data, + const ProcessSingleton::NotificationAckCallback& ack_callback) { v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); v8::Locker locker(isolate); v8::HandleScope handle_scope(isolate); v8::Local data_value = DeserializeV8Value(isolate, std::move(additional_data)); - Emit("second-instance", cmd.argv(), cwd, data_value); + auto cb = base::BindOnce(&AckCallbackWrapper, ack_callback); + bool prevent_default = + Emit("second-instance", cmd.argv(), cwd, data_value, cb); + if (!prevent_default) { + // Call the callback ourselves, and send back nothing. + ack_callback.Run(nullptr); + } } bool App::HasSingleInstanceLock() const { @@ -1103,6 +1145,9 @@ bool App::RequestSingleInstanceLock(gin::Arguments* args) { base::PathService::Get(chrome::DIR_USER_DATA, &user_dir); auto cb = base::BindRepeating(&App::OnSecondInstance, base::Unretained(this)); + auto wrapped_cb = base::BindRepeating(NotificationCallbackWrapper, cb); + auto ack_cb = + base::BindRepeating(&App::OnFirstInstanceAck, base::Unretained(this)); blink::CloneableMessage additional_data_message; args->GetNext(&additional_data_message); @@ -1111,11 +1156,10 @@ bool App::RequestSingleInstanceLock(gin::Arguments* args) { IsSandboxEnabled(base::CommandLine::ForCurrentProcess()); process_singleton_ = std::make_unique( program_name, user_dir, additional_data_message.encoded_message, - app_is_sandboxed, base::BindRepeating(NotificationCallbackWrapper, cb)); + app_is_sandboxed, wrapped_cb, ack_cb); #else process_singleton_ = std::make_unique( - user_dir, additional_data_message.encoded_message, - base::BindRepeating(NotificationCallbackWrapper, cb)); + user_dir, additional_data_message.encoded_message, wrapped_cb, ack_cb); #endif switch (process_singleton_->NotifyOtherProcessOrCreate()) { diff --git a/shell/browser/api/electron_api_app.h b/shell/browser/api/electron_api_app.h index e65c2f86d1091..cc03e8f0d60db 100644 --- a/shell/browser/api/electron_api_app.h +++ b/shell/browser/api/electron_api_app.h @@ -188,9 +188,12 @@ class App : public ElectronBrowserClient::Delegate, void SetDesktopName(const std::string& desktop_name); std::string GetLocale(); std::string GetLocaleCountryCode(); - void OnSecondInstance(const base::CommandLine& cmd, - const base::FilePath& cwd, - const std::vector additional_data); + void OnFirstInstanceAck(const base::span* first_instance_data); + void OnSecondInstance( + const base::CommandLine& cmd, + const base::FilePath& cwd, + const std::vector additional_data, + const ProcessSingleton::NotificationAckCallback& ack_callback); bool HasSingleInstanceLock() const; bool RequestSingleInstanceLock(gin::Arguments* args); void ReleaseSingleInstanceLock();