electron/shell/browser/serial/serial_chooser_controller.cc
electron-roller[bot] 6779769d22
chore: bump chromium to 139.0.7219.0 (main) (#47348)
* chore: bump chromium in DEPS to 139.0.7205.0

* 6543986: Mac: decouple deserializing and applying sandbox policy

Refs 6543986

* 6580079: Reland 'Remove the third-party blocking feature'
Refs 6580079

* 6505716: guest-contents: Add components/guest_contents
Refs 6505716

* 6572556: Move LogMessageManager out of gpu_service_impl.cc.
Refs 6572556

* 6566111: Change UtilityProcessHost to manage its instance internally
Refs 6566111

* 6550237: Rename ReconnectEventObserver to ConnectionChangeObserverClient
Refs 6550237

* 6565918: Validate path is valid UTF8 in SelectFileDialogLinuxPortal
Refs 6565918

* 6579713: Remove base::NotFatalUntil::M130 usage
6566111: Change UtilityProcessHost to manage its instance internally
Refs
6579713
6566111

* chore: update chromium patches

* chore: update remaining patches

* fixup! 6566111: Change UtilityProcessHost to manage its instance internally Refs 6566111

* 6577970: Remove superfluous includes for base/strings/stringprintf.h in headers
Refs 6577970

* 6568811: Add FunctionCall structured metrics event for DevTools
Refs 6568811

* [PDF Ink Signatures] Support PdfAnnotationsEnabled policy
6558970

* build: disable libcxx modules for rbe

* chore: bump chromium in DEPS to 139.0.7217.0

* chore: bump chromium in DEPS to 139.0.7218.0

* chore: update patches

fix_use_delegated_generic_capturer_when_available.patch was updated to handle a small change:
6582142: Use content::Create*Capturer in DesktopCaptureDevice. | 6582142

* chore: bump chromium in DEPS to 139.0.7219.0

* chore: update patches

* 6594615: Change Chromium's deployment target to macOS 12
6594615

Updated the assertion message to match the docs structure now too. I removed the callout to the supported versions doc because it has moved and doesn't contain minimum platform version information.

* 6606232: [views] Remove DesktopWindowTreeHostWin::window_enlargement_
6606232

|NativeWindow::GetContentMinimumSize| and |NativeWindow::GetContentMaximumSize| may be good opportunities for a refactor now.

* add squirrel.mac patch for removed function

This was triggered by the macOS 12.0 deployment upgrade change.

See: https://developer.apple.com/documentation/coreservices/1444079-uttypeconformsto?language=objc

* 6582142: Use content::Create*Capturer in DesktopCaptureDevice.
6582142

* 6579732: Two minor API "quality of life" cleanups in OSCrypt Async
6579732

* chore: add include for base::SingleThreadTaskRunner

Not sure what change caused this, I expect it would be a removed include somewhere else, but it's likely not important to track down.

* chore: update libcxx filenames

* chore: update CI build-tools commit target for macOS SDK 15.4

The following change uses an API that was added in the macOS 15.4 SDK. Support for that SDK version was added later than the current build-tools commit target.

6575804: Use a quick-and-dirty solution to avoid glitching with paste-and-go | 6575804

See: https://developer.apple.com/documentation/appkit/nspasteboard/accessbehavior-swift.enum?language=objc

* fixup! 6606232: [views] Remove DesktopWindowTreeHostWin::window_enlargement_ 6606232

* chore: bump chromium in DEPS to 139.0.7220.0

* chore: update patches

Minor changes due to:

6613978: pwa: let events fall through in the transparent area of TopContainerView | 6613978
6614778: Refactor auto pip tab observer for Android support | 6614778

* 6543986: Mac: decouple deserializing and applying sandbox policy
6543986

The DecodeVarInt and DecodeString functions look benign from a MAS perspective. I suspect they were patched out to avoid "unused function" errors. Their complements for encoding are unpatched, supporting this idea.

The code that uses these functions was refactored out of the section that we patch out. Instead of patching out that new function, I decided to treat it the same as the serialization function that is unpatched.

* chore: bump chromium in DEPS to 139.0.7222.0

* chore: bump chromium in DEPS to 139.0.7224.0

* chore: bump chromium in DEPS to 139.0.7226.0

* chore: bump chromium in DEPS to 139.0.7228.0

* chore: update patches

* Don't use static variable for UseExternalPopupMenus

6534657

* Reland "Roll libc++ from a01c02c9d4ac to a9cc573e7c59

6607589

* chore: bump chromium in DEPS to 139.0.7219.0

* chore: update patches

* revert Don't use static variable for UseExternalPopupMenus

* tls: remove deprecated tls.createSecurePair and SecurePair

https://github.com/nodejs/node/pull/57361

* Revert "Reland "Roll libc++ from a01c02c9d4ac to a9cc573e7c59"

This reverts commit 33e1436a0c598ffa8cd308d08de481ed9cd22168.

* test: cleanup api-desktop-capturer-spec.ts

* test: more cleanup of api-desktop-capturer-spec.ts

* chore: debug dcheck error in webrtc on linux

* fixup patch

* add debugging to desktop capturer spec

* test: fixup api-desktop-capturer-spec.ts for linux

* chore: remove debugging patch

* Revert "fixup! 6606232: [views] Remove DesktopWindowTreeHostWin::window_enlargement_ 6606232"

This reverts commit 32e75651c14512572d322e819c98ab1469663bb6.

* Revert "6606232: [views] Remove DesktopWindowTreeHostWin::window_enlargement_"

This reverts commit 89c51aa1c7771fd7f1d634486bc77f493f1d2ea9.

* [views] Remove DesktopWindowTreeHostWin::window_enlargement_

6606232

Reverting as we need this functionality for now.

* fixup: remove patch that was accidentally added back

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Samuel Maddock <smaddock@slack-corp.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
Co-authored-by: clavin <clavin@electronjs.org>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2025-06-16 12:46:06 -05:00

338 lines
11 KiB
C++

// Copyright (c) 2020 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/serial/serial_chooser_controller.h"
#include <algorithm>
#include <utility>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/serial/serial_blocklist.h"
#include "content/public/browser/console_message.h"
#include "content/public/browser/web_contents.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/serial/electron_serial_delegate.h"
#include "shell/browser/serial/serial_chooser_context.h"
#include "shell/browser/serial/serial_chooser_context_factory.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/content_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/promise.h"
#include "ui/base/l10n/l10n_util.h"
namespace gin {
template <>
struct Converter<device::mojom::SerialPortInfoPtr> {
static v8::Local<v8::Value> ToV8(
v8::Isolate* isolate,
const device::mojom::SerialPortInfoPtr& port) {
auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
dict.Set("portId", port->token.ToString());
dict.Set("portName", port->path.BaseName().LossyDisplayName());
if (port->display_name && !port->display_name->empty()) {
dict.Set("displayName", *port->display_name);
}
if (port->has_vendor_id) {
dict.Set("vendorId", base::NumberToString(port->vendor_id));
}
if (port->has_product_id) {
dict.Set("productId", base::NumberToString(port->product_id));
}
if (port->serial_number && !port->serial_number->empty()) {
dict.Set("serialNumber", *port->serial_number);
}
#if BUILDFLAG(IS_MAC)
if (port->usb_driver_name && !port->usb_driver_name->empty()) {
dict.Set("usbDriverName", *port->usb_driver_name);
}
#elif BUILDFLAG(IS_WIN)
if (!port->device_instance_id.empty()) {
dict.Set("deviceInstanceId", port->device_instance_id);
}
#endif
return gin::ConvertToV8(isolate, dict);
}
};
} // namespace gin
namespace electron {
namespace {
using ::device::BluetoothAdapter;
using ::device::BluetoothAdapterFactory;
using ::device::mojom::SerialPortType;
bool FilterMatchesPort(const blink::mojom::SerialPortFilter& filter,
const device::mojom::SerialPortInfo& port) {
if (filter.bluetooth_service_class_id) {
if (!port.bluetooth_service_class_id) {
return false;
}
return device::BluetoothUUID(*port.bluetooth_service_class_id) ==
device::BluetoothUUID(*filter.bluetooth_service_class_id);
}
if (!filter.has_vendor_id) {
return true;
}
if (!port.has_vendor_id || port.vendor_id != filter.vendor_id) {
return false;
}
if (!filter.has_product_id) {
return true;
}
return port.has_product_id && port.product_id == filter.product_id;
}
bool BluetoothPortIsAllowed(
const std::vector<::device::BluetoothUUID>& allowed_ids,
const device::mojom::SerialPortInfo& port) {
if (!port.bluetooth_service_class_id) {
return true;
}
// Serial Port Profile is allowed by default.
if (*port.bluetooth_service_class_id == device::GetSerialPortProfileUUID()) {
return true;
}
return base::Contains(allowed_ids, port.bluetooth_service_class_id.value());
}
} // namespace
SerialChooserController::SerialChooserController(
content::RenderFrameHost* render_frame_host,
std::vector<blink::mojom::SerialPortFilterPtr> filters,
std::vector<::device::BluetoothUUID> allowed_bluetooth_service_class_ids,
content::SerialChooser::Callback callback,
content::WebContents* web_contents,
base::WeakPtr<ElectronSerialDelegate> serial_delegate)
: web_contents_{web_contents ? web_contents->GetWeakPtr()
: base::WeakPtr<content::WebContents>()},
filters_(std::move(filters)),
allowed_bluetooth_service_class_ids_(
std::move(allowed_bluetooth_service_class_ids)),
callback_(std::move(callback)),
initiator_document_(render_frame_host->GetWeakDocumentPtr()) {
origin_ = web_contents_->GetPrimaryMainFrame()->GetLastCommittedOrigin();
chooser_context_ = SerialChooserContextFactory::GetForBrowserContext(
web_contents_->GetBrowserContext())
->AsWeakPtr();
DCHECK(chooser_context_);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&SerialChooserController::GetDevices,
weak_factory_.GetWeakPtr()));
observation_.Observe(chooser_context_.get());
}
SerialChooserController::~SerialChooserController() {
RunCallback(/*port=*/nullptr);
}
api::Session* SerialChooserController::GetSession() {
if (!web_contents_) {
return nullptr;
}
return api::Session::FromBrowserContext(web_contents_->GetBrowserContext());
}
void SerialChooserController::GetDevices() {
if (IsWirelessSerialPortOnly()) {
if (!adapter_) {
BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
&SerialChooserController::OnGetAdapter, weak_factory_.GetWeakPtr(),
base::BindOnce(&SerialChooserController::GetDevices,
weak_factory_.GetWeakPtr())));
return;
}
}
chooser_context_->GetPortManager()->GetDevices(base::BindOnce(
&SerialChooserController::OnGetDevices, weak_factory_.GetWeakPtr()));
}
void SerialChooserController::AdapterPoweredChanged(BluetoothAdapter* adapter,
bool powered) {
// TODO(codebytere): maybe emit an event here?
if (powered) {
GetDevices();
}
}
void SerialChooserController::OnPortAdded(
const device::mojom::SerialPortInfo& port) {
if (!DisplayDevice(port))
return;
ports_.push_back(port.Clone());
api::Session* session = GetSession();
if (session) {
session->Emit("serial-port-added", port.Clone(), web_contents_.get());
}
}
void SerialChooserController::OnPortRemoved(
const device::mojom::SerialPortInfo& port) {
const auto it = std::ranges::find(ports_, port.token,
&device::mojom::SerialPortInfo::token);
if (it != ports_.end()) {
if (api::Session* session = GetSession())
session->Emit("serial-port-removed", port.Clone(), web_contents_.get());
ports_.erase(it);
}
}
void SerialChooserController::OnPortManagerConnectionError() {
observation_.Reset();
}
void SerialChooserController::OnSerialChooserContextShutdown() {
observation_.Reset();
}
void SerialChooserController::OnDeviceChosen(const std::string& port_id) {
if (port_id.empty()) {
RunCallback(/*port=*/nullptr);
} else {
const auto it = std::ranges::find_if(ports_, [&port_id](const auto& ptr) {
return ptr->token.ToString() == port_id;
});
if (it != ports_.end()) {
auto* rfh = initiator_document_.AsRenderFrameHostIfValid();
chooser_context_->GrantPortPermission(origin_, *it->get(), rfh);
RunCallback(it->Clone());
} else {
RunCallback(/*port=*/nullptr);
}
}
}
void SerialChooserController::OnGetDevices(
std::vector<device::mojom::SerialPortInfoPtr> ports) {
// Sort ports by file paths.
std::ranges::sort(ports, [](const auto& port1, const auto& port2) {
return port1->path.BaseName() < port2->path.BaseName();
});
ports_.clear();
for (auto& port : ports) {
if (DisplayDevice(*port))
ports_.push_back(std::move(port));
}
bool prevent_default = false;
if (api::Session* session = GetSession()) {
prevent_default = session->Emit(
"select-serial-port", ports_, web_contents_.get(),
base::BindRepeating(&SerialChooserController::OnDeviceChosen,
weak_factory_.GetWeakPtr()));
}
if (!prevent_default) {
RunCallback(/*port=*/nullptr);
}
}
bool SerialChooserController::DisplayDevice(
const device::mojom::SerialPortInfo& port) const {
bool blocklist_disabled = base::CommandLine::ForCurrentProcess()->HasSwitch(
kDisableSerialBlocklist);
if (!blocklist_disabled && SerialBlocklist::Get().IsExcluded(port)) {
if (port.has_vendor_id && port.has_product_id) {
AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kInfo,
base::StringPrintf(
"Skipping a port blocked by "
"the Serial blocklist: vendorId=%d, "
"productId=%d, name='%s', serial='%s'",
port.vendor_id, port.product_id,
port.display_name ? port.display_name.value().c_str() : "",
port.serial_number ? port.serial_number.value().c_str() : ""));
} else if (port.bluetooth_service_class_id) {
AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kInfo,
base::StringPrintf(
"Skipping a port blocked by "
"the Serial blocklist: bluetoothServiceClassId=%s, "
"name='%s'",
port.bluetooth_service_class_id->value().c_str(),
port.display_name ? port.display_name.value().c_str() : ""));
} else {
NOTREACHED();
}
return false;
}
if (filters_.empty()) {
return BluetoothPortIsAllowed(allowed_bluetooth_service_class_ids_, port);
}
for (const auto& filter : filters_) {
if (FilterMatchesPort(*filter, port) &&
BluetoothPortIsAllowed(allowed_bluetooth_service_class_ids_, port)) {
return true;
}
}
return false;
}
void SerialChooserController::AddMessageToConsole(
blink::mojom::ConsoleMessageLevel level,
const std::string& message) const {
if (content::RenderFrameHost* rfh =
initiator_document_.AsRenderFrameHostIfValid()) {
rfh->AddMessageToConsole(level, message);
}
}
void SerialChooserController::RunCallback(
device::mojom::SerialPortInfoPtr port) {
if (callback_) {
std::move(callback_).Run(std::move(port));
}
}
void SerialChooserController::OnGetAdapter(
base::OnceClosure callback,
scoped_refptr<BluetoothAdapter> adapter) {
CHECK(adapter);
adapter_ = std::move(adapter);
adapter_observation_.Observe(adapter_.get());
std::move(callback).Run();
}
bool SerialChooserController::IsWirelessSerialPortOnly() const {
if (allowed_bluetooth_service_class_ids_.empty()) {
return false;
}
// The system's wired and wireless serial ports can be shown if there is no
// filter.
if (filters_.empty()) {
return false;
}
// Check if all the filters are meant for serial port from Bluetooth device.
for (const auto& filter : filters_) {
if (!filter->bluetooth_service_class_id) {
return false;
}
}
return true;
}
} // namespace electron