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 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 31f5b160e4cd755cfb56a62b04261ee1bee80277..8dbc5ac458481d2f805f90101069f02adcfe4090 100644 --- a/chrome/browser/process_singleton.h +++ b/chrome/browser/process_singleton.h @@ -18,6 +18,7 @@ #include "base/functional/callback.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 BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_ANDROID) @@ -100,21 +101,24 @@ 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)>; #if BUILDFLAG(IS_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); #else ProcessSingleton(const base::FilePath& user_data_dir, + const base::span 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 @@ -178,7 +182,10 @@ class ProcessSingleton { #endif private: + // A callback to run when the first instance receives data from the second. NotificationCallback notification_callback_; // Handler for notifications. + // Custom data to pass to the other instance during notify. + base::span additional_data_; #if BUILDFLAG(IS_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 f8cf2fb4ab66dae92b80c17cdda8b984fe4807c7..1a5d5323c0b37171b61f1fb2445dc18e1738f4ba 100644 --- a/chrome/browser/process_singleton_posix.cc +++ b/chrome/browser/process_singleton_posix.cc @@ -615,6 +615,7 @@ class ProcessSingleton::LinuxWatcher // |reader| is for sending back ACK message. void HandleMessage(const std::string& current_dir, const std::vector& argv, + const std::vector additional_data, SocketReader* reader); // Called when the ProcessSingleton that owns this class is about to be @@ -674,13 +675,17 @@ void ProcessSingleton::LinuxWatcher::StartListening(int socket) { } void ProcessSingleton::LinuxWatcher::HandleMessage( - const std::string& current_dir, const std::vector& argv, + const std::string& current_dir, + const std::vector& argv, + const std::vector additional_data, SocketReader* reader) { DCHECK(ui_task_runner_->BelongsToCurrentThread()); DCHECK(reader); if (parent_ && parent_->notification_callback_.Run( - base::CommandLine(argv), base::FilePath(current_dir))) { + 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, std::size(kACKToken) - 1); } else { @@ -728,7 +733,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 = std::size(kStartToken) + 4; if (bytes_read_ < kMinMessageLength) { buf_[bytes_read_] = 0; @@ -758,10 +764,28 @@ void ProcessSingleton::LinuxWatcher::SocketReader:: tokens.erase(tokens.begin()); tokens.erase(tokens.begin()); + size_t num_args; + base::StringToSizeT(tokens[0], &num_args); + std::vector command_line(tokens.begin() + 1, tokens.begin() + 1 + num_args); + + std::vector additional_data; + if (tokens.size() >= 3 + num_args) { + size_t additional_data_size; + base::StringToSizeT(tokens[1 + num_args], &additional_data_size); + std::string remaining_args = base::JoinString( + base::make_span(tokens.begin() + 2 + num_args, tokens.end()), + std::string(1, kTokenDelimiter)); + const uint8_t* additional_data_bits = + reinterpret_cast(remaining_args.c_str()); + additional_data = std::vector( + 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 @@ -790,8 +814,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK( // ProcessSingleton::ProcessSingleton( const base::FilePath& user_data_dir, + const base::span additional_data, const NotificationCallback& notification_callback) : notification_callback_(notification_callback), + additional_data_(additional_data), current_pid_(base::GetCurrentProcId()) { socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename); lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename); @@ -912,7 +938,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( sizeof(socket_timeout)); // Found another process, prepare our command line - // format is "START\0\0\0...\0". + // format is "START\0\0\0\0...\0 + // \0\0". std::string to_send(kStartToken); to_send.push_back(kTokenDelimiter); @@ -922,11 +949,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( to_send.append(current_dir.value()); const std::vector& 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(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 a0ef81d42bd9a58786b00a1601625e6822bc26f6..27da17eed8fcd69b32dd1f409f6a2efd46a40d88 100644 --- a/chrome/browser/process_singleton_win.cc +++ b/chrome/browser/process_singleton_win.cc @@ -81,10 +81,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* 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) { @@ -134,6 +136,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(additional_data.c_str()); + *parsed_additional_data = std::vector(additional_data_bytes, + additional_data_bytes + additional_data_length); + return true; } return false; @@ -155,13 +188,14 @@ bool ProcessLaunchNotification( base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM); 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)) { *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; } @@ -265,9 +299,11 @@ 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) : notification_callback_(notification_callback), + additional_data_(additional_data), program_name_(program_name), is_app_sandboxed_(is_app_sandboxed), is_virtualized_(false), @@ -294,7 +330,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 f572b9db19cc1341cf0493d7486bb484121e7384..5b053e160d4653cd6de7eabc37e3b4a264a6c2c7 100644 --- a/chrome/browser/win/chrome_process_finder.cc +++ b/chrome/browser/win/chrome_process_finder.cc @@ -41,7 +41,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 additional_data) { TRACE_EVENT0("startup", "AttemptToNotifyRunningChrome"); DCHECK(remote_window); @@ -70,12 +72,29 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) { new_command_line.AppendSwitchNative(switches::kSourceShortcut, si.lpTitle); // Send the command line to the remote chrome window. - // Format is "START\0<<>>\0<<>>". + // Format is + // "START\0\0\0\0". std::wstring to_send = base::StrCat( {std::wstring_view{L"START\0", 6}, cur_dir.value(), std::wstring_view{L"\0", 1}, new_command_line.GetCommandLineString(), std::wstring_view{L"\0", 1}}); + 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(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 ddea93de709db5967a353bb73d433737c6aac40c..43c6896923032ffa16a0df4efd48a42f869c15d7 100644 --- a/chrome/browser/win/chrome_process_finder.h +++ b/chrome/browser/win/chrome_process_finder.h @@ -7,6 +7,7 @@ #include +#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 additional_data); // Changes the notification timeout to |new_timeout|, returns the old timeout. base::TimeDelta SetNotificationTimeoutForTesting(base::TimeDelta new_timeout);