feat: add support for WebUSB (#36289)

* feat: add support for WebUSB

* fixup for gn check

* fixup gn check on Windows

* Apply review feedback

Co-authored-by: Charles Kerr <charles@charleskerr.com>

* chore: address review feedback

* chore: removed unneeded code

* Migrate non-default ScopedObservation<> instantiations to ScopedObservationTraits<> in chrome/browser/

4016595

Co-authored-by: Charles Kerr <charles@charleskerr.com>
This commit is contained in:
John Kleinschmidt 2022-11-22 16:50:32 -05:00 committed by GitHub
parent 2751c2b07f
commit 629c54ba36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 1772 additions and 23 deletions

View file

@ -1715,6 +1715,12 @@ content::BluetoothDelegate* ElectronBrowserClient::GetBluetoothDelegate() {
return bluetooth_delegate_.get();
}
content::UsbDelegate* ElectronBrowserClient::GetUsbDelegate() {
if (!usb_delegate_)
usb_delegate_ = std::make_unique<ElectronUsbDelegate>();
return usb_delegate_.get();
}
void BindBadgeServiceForServiceWorker(
const content::ServiceWorkerVersionBaseInfo& info,
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {

View file

@ -22,6 +22,7 @@
#include "shell/browser/bluetooth/electron_bluetooth_delegate.h"
#include "shell/browser/hid/electron_hid_delegate.h"
#include "shell/browser/serial/electron_serial_delegate.h"
#include "shell/browser/usb/electron_usb_delegate.h"
#include "third_party/blink/public/mojom/badging/badging.mojom-forward.h"
namespace content {
@ -103,6 +104,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
content::BluetoothDelegate* GetBluetoothDelegate() override;
content::HidDelegate* GetHidDelegate() override;
content::UsbDelegate* GetUsbDelegate() override;
content::WebAuthenticationDelegate* GetWebAuthenticationDelegate() override;
@ -326,6 +328,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
std::unique_ptr<ElectronSerialDelegate> serial_delegate_;
std::unique_ptr<ElectronBluetoothDelegate> bluetooth_delegate_;
std::unique_ptr<ElectronUsbDelegate> usb_delegate_;
std::unique_ptr<ElectronHidDelegate> hid_delegate_;
std::unique_ptr<ElectronWebAuthenticationDelegate>
web_authentication_delegate_;

View file

@ -51,6 +51,7 @@
#include "shell/browser/web_view_manager.h"
#include "shell/browser/zoom_level_delegate.h"
#include "shell/common/application_info.h"
#include "shell/common/electron_constants.h"
#include "shell/common/electron_paths.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_helper/error_thrower.h"
@ -583,19 +584,22 @@ bool ElectronBrowserContext::DoesDeviceMatch(
const base::Value* device_to_compare,
blink::PermissionType permission_type) {
if (permission_type ==
static_cast<blink::PermissionType>(
WebContentsPermissionHelper::PermissionType::HID)) {
if (device.GetDict().FindInt(kHidVendorIdKey) !=
device_to_compare->GetDict().FindInt(kHidVendorIdKey) ||
device.GetDict().FindInt(kHidProductIdKey) !=
device_to_compare->GetDict().FindInt(kHidProductIdKey)) {
static_cast<blink::PermissionType>(
WebContentsPermissionHelper::PermissionType::HID) ||
permission_type ==
static_cast<blink::PermissionType>(
WebContentsPermissionHelper::PermissionType::USB)) {
if (device.GetDict().FindInt(kDeviceVendorIdKey) !=
device_to_compare->GetDict().FindInt(kDeviceVendorIdKey) ||
device.GetDict().FindInt(kDeviceProductIdKey) !=
device_to_compare->GetDict().FindInt(kDeviceProductIdKey)) {
return false;
}
const auto* serial_number =
device_to_compare->GetDict().FindString(kHidSerialNumberKey);
device_to_compare->GetDict().FindString(kDeviceSerialNumberKey);
const auto* device_serial_number =
device.GetDict().FindString(kHidSerialNumberKey);
device.GetDict().FindString(kDeviceSerialNumberKey);
if (serial_number && device_serial_number &&
*device_serial_number == *serial_number)

View file

@ -17,6 +17,10 @@
#include "net/base/features.h"
#include "services/network/public/cpp/features.h"
#if BUILDFLAG(IS_MAC)
#include "device/base/features.h" // nogncheck
#endif
namespace electron {
void InitializeFeatureList() {
@ -32,6 +36,11 @@ void InitializeFeatureList() {
disable_features +=
std::string(",") + features::kSpareRendererForSitePerProcess.name;
#if BUILDFLAG(IS_MAC)
// Needed for WebUSB implementation
enable_features += std::string(",") + device::kNewUsbBackend.name;
#endif
#if !BUILDFLAG(ENABLE_PICTURE_IN_PICTURE)
disable_features += std::string(",") + media::kPictureInPicture.name;
#endif

View file

@ -22,6 +22,7 @@
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/electron_permission_manager.h"
#include "shell/browser/web_contents_permission_helper.h"
#include "shell/common/electron_constants.h"
#include "shell/common/gin_converters/content_converter.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_converters/hid_device_info_converter.h"
@ -35,9 +36,6 @@ namespace electron {
const char kHidDeviceNameKey[] = "name";
const char kHidGuidKey[] = "guid";
const char kHidVendorIdKey[] = "vendorId";
const char kHidProductIdKey[] = "productId";
const char kHidSerialNumberKey[] = "serialNumber";
HidChooserContext::HidChooserContext(ElectronBrowserContext* context)
: browser_context_(context) {}
@ -76,12 +74,12 @@ base::Value HidChooserContext::DeviceInfoToValue(
value.SetStringKey(
kHidDeviceNameKey,
base::UTF16ToUTF8(HidChooserContext::DisplayNameFromDeviceInfo(device)));
value.SetIntKey(kHidVendorIdKey, device.vendor_id);
value.SetIntKey(kHidProductIdKey, device.product_id);
value.SetIntKey(kDeviceVendorIdKey, device.vendor_id);
value.SetIntKey(kDeviceProductIdKey, device.product_id);
if (HidChooserContext::CanStorePersistentEntry(device)) {
// Use the USB serial number as a persistent identifier. If it is
// unavailable, only ephemeral permissions may be granted.
value.SetStringKey(kHidSerialNumberKey, device.serial_number);
value.SetStringKey(kDeviceSerialNumberKey, device.serial_number);
} else {
// The GUID is a temporary ID created on connection that remains valid until
// the device is disconnected. Ephemeral permissions are keyed by this ID

View file

@ -33,9 +33,7 @@ namespace electron {
extern const char kHidDeviceNameKey[];
extern const char kHidGuidKey[];
extern const char kHidVendorIdKey[];
extern const char kHidProductIdKey[];
extern const char kHidSerialNumberKey[];
// Manages the internal state and connection to the device service for the
// Human Interface Device (HID) chooser UI.

View file

@ -29,9 +29,6 @@ class Value;
namespace electron {
extern const char kHidVendorIdKey[];
extern const char kHidProductIdKey[];
#if BUILDFLAG(IS_WIN)
extern const char kDeviceInstanceIdKey[];
#else

View file

@ -0,0 +1,317 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/usb/electron_usb_delegate.h"
#include <utility>
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/scoped_observation.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/buildflags/buildflags.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"
#include "shell/browser/electron_permission_manager.h"
#include "shell/browser/usb/usb_chooser_context.h"
#include "shell/browser/usb/usb_chooser_context_factory.h"
#include "shell/browser/usb/usb_chooser_controller.h"
#include "shell/browser/web_contents_permission_helper.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "base/containers/fixed_flat_set.h"
#include "chrome/common/chrome_features.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#endif
namespace {
using ::content::UsbChooser;
electron::UsbChooserContext* GetChooserContext(
content::BrowserContext* browser_context) {
return electron::UsbChooserContextFactory::GetForBrowserContext(
browser_context);
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// These extensions can claim the smart card USB class and automatically gain
// permissions for devices that have an interface with this class.
constexpr auto kSmartCardPrivilegedExtensionIds =
base::MakeFixedFlatSet<base::StringPiece>({
// Smart Card Connector Extension and its Beta version, see
// crbug.com/1233881.
"khpfeaanjngmcnplbdlpegiifgpfgdco",
"mockcojkppdndnhgonljagclgpkjbkek",
});
bool DeviceHasInterfaceWithClass(
const device::mojom::UsbDeviceInfo& device_info,
uint8_t interface_class) {
for (const auto& configuration : device_info.configurations) {
for (const auto& interface : configuration->interfaces) {
for (const auto& alternate : interface->alternates) {
if (alternate->class_code == interface_class)
return true;
}
}
}
return false;
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
bool IsDevicePermissionAutoGranted(
const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device_info) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Note: The `DeviceHasInterfaceWithClass()` call is made after checking the
// origin, since that method call is expensive.
if (origin.scheme() == extensions::kExtensionScheme &&
base::Contains(kSmartCardPrivilegedExtensionIds, origin.host()) &&
DeviceHasInterfaceWithClass(device_info,
device::mojom::kUsbSmartCardClass)) {
return true;
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
return false;
}
} // namespace
namespace electron {
// Manages the UsbDelegate observers for a single browser context.
class ElectronUsbDelegate::ContextObservation
: public UsbChooserContext::DeviceObserver {
public:
ContextObservation(ElectronUsbDelegate* parent,
content::BrowserContext* browser_context)
: parent_(parent), browser_context_(browser_context) {
auto* chooser_context = GetChooserContext(browser_context_);
device_observation_.Observe(chooser_context);
}
ContextObservation(ContextObservation&) = delete;
ContextObservation& operator=(ContextObservation&) = delete;
~ContextObservation() override = default;
// UsbChooserContext::DeviceObserver:
void OnDeviceAdded(const device::mojom::UsbDeviceInfo& device_info) override {
for (auto& observer : observer_list_)
observer.OnDeviceAdded(device_info);
}
void OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) override {
for (auto& observer : observer_list_)
observer.OnDeviceRemoved(device_info);
}
void OnDeviceManagerConnectionError() override {
for (auto& observer : observer_list_)
observer.OnDeviceManagerConnectionError();
}
void OnBrowserContextShutdown() override {
parent_->observations_.erase(browser_context_);
// Return since `this` is now deleted.
}
void AddObserver(content::UsbDelegate::Observer* observer) {
observer_list_.AddObserver(observer);
}
void RemoveObserver(content::UsbDelegate::Observer* observer) {
observer_list_.RemoveObserver(observer);
}
private:
// Safe because `parent_` owns `this`.
const raw_ptr<ElectronUsbDelegate> parent_;
// Safe because `this` is destroyed when the context is lost.
const raw_ptr<content::BrowserContext> browser_context_;
base::ScopedObservation<UsbChooserContext, UsbChooserContext::DeviceObserver>
device_observation_{this};
base::ObserverList<content::UsbDelegate::Observer> observer_list_;
};
ElectronUsbDelegate::ElectronUsbDelegate() = default;
ElectronUsbDelegate::~ElectronUsbDelegate() = default;
void ElectronUsbDelegate::AdjustProtectedInterfaceClasses(
content::BrowserContext* browser_context,
const url::Origin& origin,
content::RenderFrameHost* frame,
std::vector<uint8_t>& classes) {
// Isolated Apps have unrestricted access to any USB interface class.
if (frame && frame->GetWebExposedIsolationLevel() >=
content::RenderFrameHost::WebExposedIsolationLevel::
kMaybeIsolatedApplication) {
// TODO(https://crbug.com/1236706): Should the list of interface classes the
// app expects to claim be encoded in the Web App Manifest?
classes.clear();
return;
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Don't enforce protected interface classes for Chrome Apps since the
// chrome.usb API has no such restriction.
if (origin.scheme() == extensions::kExtensionScheme) {
auto* extension_registry =
extensions::ExtensionRegistry::Get(browser_context);
if (extension_registry) {
const extensions::Extension* extension =
extension_registry->enabled_extensions().GetByID(origin.host());
if (extension && extension->is_platform_app()) {
classes.clear();
return;
}
}
}
if (origin.scheme() == extensions::kExtensionScheme &&
base::Contains(kSmartCardPrivilegedExtensionIds, origin.host())) {
base::Erase(classes, device::mojom::kUsbSmartCardClass);
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
}
std::unique_ptr<UsbChooser> ElectronUsbDelegate::RunChooser(
content::RenderFrameHost& frame,
std::vector<device::mojom::UsbDeviceFilterPtr> filters,
blink::mojom::WebUsbService::GetPermissionCallback callback) {
UsbChooserController* controller = ControllerForFrame(&frame);
if (controller) {
DeleteControllerForFrame(&frame);
}
AddControllerForFrame(&frame, std::move(filters), std::move(callback));
// Return a nullptr because the return value isn't used for anything. The
// return value is simply used in Chromium to cleanup the chooser UI once the
// usb service is destroyed.
return nullptr;
}
bool ElectronUsbDelegate::CanRequestDevicePermission(
content::BrowserContext* browser_context,
const url::Origin& origin) {
base::Value::Dict details;
details.Set("securityOrigin", origin.GetURL().spec());
auto* permission_manager = static_cast<ElectronPermissionManager*>(
browser_context->GetPermissionControllerDelegate());
return permission_manager->CheckPermissionWithDetails(
static_cast<blink::PermissionType>(
WebContentsPermissionHelper::PermissionType::USB),
nullptr, origin.GetURL(), std::move(details));
}
void ElectronUsbDelegate::RevokeDevicePermissionWebInitiated(
content::BrowserContext* browser_context,
const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device) {
GetChooserContext(browser_context)
->RevokeDevicePermissionWebInitiated(origin, device);
}
const device::mojom::UsbDeviceInfo* ElectronUsbDelegate::GetDeviceInfo(
content::BrowserContext* browser_context,
const std::string& guid) {
return GetChooserContext(browser_context)->GetDeviceInfo(guid);
}
bool ElectronUsbDelegate::HasDevicePermission(
content::BrowserContext* browser_context,
const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device) {
if (IsDevicePermissionAutoGranted(origin, device))
return true;
return GetChooserContext(browser_context)
->HasDevicePermission(origin, device);
}
void ElectronUsbDelegate::GetDevices(
content::BrowserContext* browser_context,
blink::mojom::WebUsbService::GetDevicesCallback callback) {
GetChooserContext(browser_context)->GetDevices(std::move(callback));
}
void ElectronUsbDelegate::GetDevice(
content::BrowserContext* browser_context,
const std::string& guid,
base::span<const uint8_t> blocked_interface_classes,
mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver,
mojo::PendingRemote<device::mojom::UsbDeviceClient> device_client) {
GetChooserContext(browser_context)
->GetDevice(guid, blocked_interface_classes, std::move(device_receiver),
std::move(device_client));
}
void ElectronUsbDelegate::AddObserver(content::BrowserContext* browser_context,
Observer* observer) {
GetContextObserver(browser_context)->AddObserver(observer);
}
void ElectronUsbDelegate::RemoveObserver(
content::BrowserContext* browser_context,
Observer* observer) {
GetContextObserver(browser_context)->RemoveObserver(observer);
}
ElectronUsbDelegate::ContextObservation*
ElectronUsbDelegate::GetContextObserver(
content::BrowserContext* browser_context) {
if (!base::Contains(observations_, browser_context)) {
observations_.emplace(browser_context, std::make_unique<ContextObservation>(
this, browser_context));
}
return observations_[browser_context].get();
}
bool ElectronUsbDelegate::IsServiceWorkerAllowedForOrigin(
const url::Origin& origin) {
#if BUILDFLAG(ENABLE_EXTENSIONS)
// WebUSB is only available on extension service workers for now.
if (base::FeatureList::IsEnabled(
features::kEnableWebUsbOnExtensionServiceWorker) &&
origin.scheme() == extensions::kExtensionScheme) {
return true;
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
return false;
}
UsbChooserController* ElectronUsbDelegate::ControllerForFrame(
content::RenderFrameHost* render_frame_host) {
auto mapping = controller_map_.find(render_frame_host);
return mapping == controller_map_.end() ? nullptr : mapping->second.get();
}
UsbChooserController* ElectronUsbDelegate::AddControllerForFrame(
content::RenderFrameHost* render_frame_host,
std::vector<device::mojom::UsbDeviceFilterPtr> filters,
blink::mojom::WebUsbService::GetPermissionCallback callback) {
auto* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
auto controller = std::make_unique<UsbChooserController>(
render_frame_host, std::move(filters), std::move(callback), web_contents,
weak_factory_.GetWeakPtr());
controller_map_.insert(
std::make_pair(render_frame_host, std::move(controller)));
return ControllerForFrame(render_frame_host);
}
void ElectronUsbDelegate::DeleteControllerForFrame(
content::RenderFrameHost* render_frame_host) {
controller_map_.erase(render_frame_host);
}
} // namespace electron

View file

@ -0,0 +1,106 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_USB_ELECTRON_USB_DELEGATE_H_
#define ELECTRON_SHELL_BROWSER_USB_ELECTRON_USB_DELEGATE_H_
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "base/containers/span.h"
#include "base/memory/weak_ptr.h"
#include "content/public/browser/usb_chooser.h"
#include "content/public/browser/usb_delegate.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/device/public/mojom/usb_device.mojom-forward.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom-forward.h"
#include "services/device/public/mojom/usb_manager.mojom-forward.h"
#include "third_party/blink/public/mojom/usb/web_usb_service.mojom.h"
#include "url/origin.h"
namespace content {
class BrowserContext;
class RenderFrameHost;
} // namespace content
namespace electron {
class UsbChooserController;
class ElectronUsbDelegate : public content::UsbDelegate {
public:
ElectronUsbDelegate();
ElectronUsbDelegate(ElectronUsbDelegate&&) = delete;
ElectronUsbDelegate& operator=(ElectronUsbDelegate&) = delete;
~ElectronUsbDelegate() override;
// content::UsbDelegate:
void AdjustProtectedInterfaceClasses(content::BrowserContext* browser_context,
const url::Origin& origin,
content::RenderFrameHost* frame,
std::vector<uint8_t>& classes) override;
std::unique_ptr<content::UsbChooser> RunChooser(
content::RenderFrameHost& frame,
std::vector<device::mojom::UsbDeviceFilterPtr> filters,
blink::mojom::WebUsbService::GetPermissionCallback callback) override;
bool CanRequestDevicePermission(content::BrowserContext* browser_context,
const url::Origin& origin) override;
void RevokeDevicePermissionWebInitiated(
content::BrowserContext* browser_context,
const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device) override;
const device::mojom::UsbDeviceInfo* GetDeviceInfo(
content::BrowserContext* browser_context,
const std::string& guid) override;
bool HasDevicePermission(content::BrowserContext* browser_context,
const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device) override;
void GetDevices(
content::BrowserContext* browser_context,
blink::mojom::WebUsbService::GetDevicesCallback callback) override;
void GetDevice(
content::BrowserContext* browser_context,
const std::string& guid,
base::span<const uint8_t> blocked_interface_classes,
mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver,
mojo::PendingRemote<device::mojom::UsbDeviceClient> device_client)
override;
void AddObserver(content::BrowserContext* browser_context,
Observer* observer) override;
void RemoveObserver(content::BrowserContext* browser_context,
Observer* observer) override;
bool IsServiceWorkerAllowedForOrigin(const url::Origin& origin) override;
void DeleteControllerForFrame(content::RenderFrameHost* render_frame_host);
private:
UsbChooserController* ControllerForFrame(
content::RenderFrameHost* render_frame_host);
UsbChooserController* AddControllerForFrame(
content::RenderFrameHost* render_frame_host,
std::vector<device::mojom::UsbDeviceFilterPtr> filters,
blink::mojom::WebUsbService::GetPermissionCallback callback);
class ContextObservation;
ContextObservation* GetContextObserver(
content::BrowserContext* browser_context);
base::flat_map<content::BrowserContext*, std::unique_ptr<ContextObservation>>
observations_;
std::unordered_map<content::RenderFrameHost*,
std::unique_ptr<UsbChooserController>>
controller_map_;
base::WeakPtrFactory<ElectronUsbDelegate> weak_factory_{this};
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_USB_ELECTRON_USB_DELEGATE_H_

View file

@ -0,0 +1,354 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/usb/usb_chooser_context.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/observer_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/content_settings/core/common/content_settings.h"
#include "content/public/browser/device_service.h"
#include "services/device/public/cpp/usb/usb_ids.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/electron_permission_manager.h"
#include "shell/browser/web_contents_permission_helper.h"
#include "shell/common/electron_constants.h"
#include "shell/common/gin_converters/usb_device_info_converter.h"
#include "shell/common/node_includes.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
constexpr char kDeviceNameKey[] = "productName";
constexpr char kDeviceIdKey[] = "deviceId";
constexpr int kUsbClassMassStorage = 0x08;
bool CanStorePersistentEntry(const device::mojom::UsbDeviceInfo& device_info) {
return device_info.serial_number && !device_info.serial_number->empty();
}
bool IsMassStorageInterface(const device::mojom::UsbInterfaceInfo& interface) {
for (auto& alternate : interface.alternates) {
if (alternate->class_code == kUsbClassMassStorage)
return true;
}
return false;
}
bool ShouldExposeDevice(const device::mojom::UsbDeviceInfo& device_info) {
// blink::USBDevice::claimInterface() disallows claiming mass storage
// interfaces, but explicitly prevent access in the browser process as
// ChromeOS would allow these interfaces to be claimed.
for (auto& configuration : device_info.configurations) {
if (configuration->interfaces.size() == 0) {
return true;
}
for (auto& interface : configuration->interfaces) {
if (!IsMassStorageInterface(*interface))
return true;
}
}
return false;
}
} // namespace
namespace electron {
void UsbChooserContext::DeviceObserver::OnDeviceAdded(
const device::mojom::UsbDeviceInfo& device_info) {}
void UsbChooserContext::DeviceObserver::OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) {}
void UsbChooserContext::DeviceObserver::OnDeviceManagerConnectionError() {}
UsbChooserContext::UsbChooserContext(ElectronBrowserContext* context)
: browser_context_(context) {}
// static
base::Value UsbChooserContext::DeviceInfoToValue(
const device::mojom::UsbDeviceInfo& device_info) {
base::Value device_value(base::Value::Type::DICTIONARY);
device_value.SetStringKey(kDeviceNameKey, device_info.product_name
? *device_info.product_name
: base::StringPiece16());
device_value.SetIntKey(kDeviceVendorIdKey, device_info.vendor_id);
device_value.SetIntKey(kDeviceProductIdKey, device_info.product_id);
if (device_info.manufacturer_name) {
device_value.SetStringKey("manufacturerName",
*device_info.manufacturer_name);
}
// CanStorePersistentEntry checks if |device_info.serial_number| is not empty.
if (CanStorePersistentEntry(device_info)) {
device_value.SetStringKey(kDeviceSerialNumberKey,
*device_info.serial_number);
}
device_value.SetStringKey(kDeviceIdKey, device_info.guid);
device_value.SetIntKey("usbVersionMajor", device_info.usb_version_major);
device_value.SetIntKey("usbVersionMinor", device_info.usb_version_minor);
device_value.SetIntKey("usbVersionSubminor",
device_info.usb_version_subminor);
device_value.SetIntKey("deviceClass", device_info.class_code);
device_value.SetIntKey("deviceSubclass", device_info.subclass_code);
device_value.SetIntKey("deviceProtocol", device_info.protocol_code);
device_value.SetIntKey("deviceVersionMajor",
device_info.device_version_major);
device_value.SetIntKey("deviceVersionMinor",
device_info.device_version_minor);
device_value.SetIntKey("deviceVersionSubminor",
device_info.device_version_subminor);
return device_value;
}
void UsbChooserContext::InitDeviceList(
std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
for (auto& device_info : devices) {
DCHECK(device_info);
if (ShouldExposeDevice(*device_info)) {
devices_.insert(
std::make_pair(device_info->guid, std::move(device_info)));
}
}
is_initialized_ = true;
while (!pending_get_devices_requests_.empty()) {
std::vector<device::mojom::UsbDeviceInfoPtr> device_list;
for (const auto& entry : devices_) {
device_list.push_back(entry.second->Clone());
}
std::move(pending_get_devices_requests_.front())
.Run(std::move(device_list));
pending_get_devices_requests_.pop();
}
}
void UsbChooserContext::EnsureConnectionWithDeviceManager() {
if (device_manager_)
return;
// Receive mojo::Remote<UsbDeviceManager> from DeviceService.
content::GetDeviceService().BindUsbDeviceManager(
device_manager_.BindNewPipeAndPassReceiver());
SetUpDeviceManagerConnection();
}
void UsbChooserContext::SetUpDeviceManagerConnection() {
DCHECK(device_manager_);
device_manager_.set_disconnect_handler(
base::BindOnce(&UsbChooserContext::OnDeviceManagerConnectionError,
base::Unretained(this)));
// Listen for added/removed device events.
DCHECK(!client_receiver_.is_bound());
device_manager_->EnumerateDevicesAndSetClient(
client_receiver_.BindNewEndpointAndPassRemote(),
base::BindOnce(&UsbChooserContext::InitDeviceList,
weak_factory_.GetWeakPtr()));
}
UsbChooserContext::~UsbChooserContext() {
OnDeviceManagerConnectionError();
for (auto& observer : device_observer_list_) {
observer.OnBrowserContextShutdown();
DCHECK(!device_observer_list_.HasObserver(&observer));
}
}
void UsbChooserContext::RevokeDevicePermissionWebInitiated(
const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device) {
DCHECK(base::Contains(devices_, device.guid));
RevokeObjectPermissionInternal(origin, DeviceInfoToValue(device),
/*revoked_by_website=*/true);
}
void UsbChooserContext::RevokeObjectPermissionInternal(
const url::Origin& origin,
const base::Value& object,
bool revoked_by_website = false) {
if (object.FindStringKey(kDeviceSerialNumberKey)) {
auto* permission_manager = static_cast<ElectronPermissionManager*>(
browser_context_->GetPermissionControllerDelegate());
permission_manager->RevokeDevicePermission(
static_cast<blink::PermissionType>(
WebContentsPermissionHelper::PermissionType::USB),
origin, object, browser_context_);
} else {
const std::string* guid = object.FindStringKey(kDeviceIdKey);
auto it = ephemeral_devices_.find(origin);
if (it != ephemeral_devices_.end()) {
it->second.erase(*guid);
if (it->second.empty())
ephemeral_devices_.erase(it);
}
}
api::Session* session = api::Session::FromBrowserContext(browser_context_);
if (session) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
gin_helper::Dictionary details =
gin_helper::Dictionary::CreateEmpty(isolate);
details.Set("device", object.Clone());
details.Set("origin", origin.Serialize());
session->Emit("usb-device-revoked", details);
}
}
void UsbChooserContext::GrantDevicePermission(
const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device_info) {
if (CanStorePersistentEntry(device_info)) {
auto* permission_manager = static_cast<ElectronPermissionManager*>(
browser_context_->GetPermissionControllerDelegate());
permission_manager->GrantDevicePermission(
static_cast<blink::PermissionType>(
WebContentsPermissionHelper::PermissionType::USB),
origin, DeviceInfoToValue(device_info), browser_context_);
} else {
ephemeral_devices_[origin].insert(device_info.guid);
}
}
bool UsbChooserContext::HasDevicePermission(
const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device_info) {
auto it = ephemeral_devices_.find(origin);
if (it != ephemeral_devices_.end() &&
base::Contains(it->second, device_info.guid)) {
return true;
}
auto* permission_manager = static_cast<ElectronPermissionManager*>(
browser_context_->GetPermissionControllerDelegate());
return permission_manager->CheckDevicePermission(
static_cast<blink::PermissionType>(
WebContentsPermissionHelper::PermissionType::USB),
origin, DeviceInfoToValue(device_info), browser_context_);
}
void UsbChooserContext::GetDevices(
device::mojom::UsbDeviceManager::GetDevicesCallback callback) {
if (!is_initialized_) {
EnsureConnectionWithDeviceManager();
pending_get_devices_requests_.push(std::move(callback));
return;
}
std::vector<device::mojom::UsbDeviceInfoPtr> device_list;
for (const auto& pair : devices_) {
device_list.push_back(pair.second->Clone());
}
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(device_list)));
}
void UsbChooserContext::GetDevice(
const std::string& guid,
base::span<const uint8_t> blocked_interface_classes,
mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver,
mojo::PendingRemote<device::mojom::UsbDeviceClient> device_client) {
EnsureConnectionWithDeviceManager();
device_manager_->GetDevice(
guid,
std::vector<uint8_t>(blocked_interface_classes.begin(),
blocked_interface_classes.end()),
std::move(device_receiver), std::move(device_client));
}
const device::mojom::UsbDeviceInfo* UsbChooserContext::GetDeviceInfo(
const std::string& guid) {
DCHECK(is_initialized_);
auto it = devices_.find(guid);
return it == devices_.end() ? nullptr : it->second.get();
}
void UsbChooserContext::AddObserver(DeviceObserver* observer) {
EnsureConnectionWithDeviceManager();
device_observer_list_.AddObserver(observer);
}
void UsbChooserContext::RemoveObserver(DeviceObserver* observer) {
device_observer_list_.RemoveObserver(observer);
}
base::WeakPtr<UsbChooserContext> UsbChooserContext::AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void UsbChooserContext::OnDeviceAdded(
device::mojom::UsbDeviceInfoPtr device_info) {
DCHECK(device_info);
// Update the device list.
DCHECK(!base::Contains(devices_, device_info->guid));
if (!ShouldExposeDevice(*device_info))
return;
devices_.insert(std::make_pair(device_info->guid, device_info->Clone()));
// Notify all observers.
for (auto& observer : device_observer_list_)
observer.OnDeviceAdded(*device_info);
}
void UsbChooserContext::OnDeviceRemoved(
device::mojom::UsbDeviceInfoPtr device_info) {
DCHECK(device_info);
if (!ShouldExposeDevice(*device_info)) {
DCHECK(!base::Contains(devices_, device_info->guid));
return;
}
// Update the device list.
DCHECK(base::Contains(devices_, device_info->guid));
devices_.erase(device_info->guid);
// Notify all device observers.
for (auto& observer : device_observer_list_)
observer.OnDeviceRemoved(*device_info);
// If the device was persistent, return. Otherwise, notify all permission
// observers that its permissions were revoked.
if (device_info->serial_number &&
!device_info->serial_number.value().empty()) {
return;
}
for (auto& map_entry : ephemeral_devices_) {
map_entry.second.erase(device_info->guid);
}
}
void UsbChooserContext::OnDeviceManagerConnectionError() {
device_manager_.reset();
client_receiver_.reset();
devices_.clear();
is_initialized_ = false;
ephemeral_devices_.clear();
// Notify all device observers.
for (auto& observer : device_observer_list_)
observer.OnDeviceManagerConnectionError();
}
} // namespace electron

View file

@ -0,0 +1,122 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTEXT_H_
#define ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTEXT_H_
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/queue.h"
#include "base/observer_list.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/keyed_service/core/keyed_service.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/mojom/usb_manager.mojom.h"
#include "services/device/public/mojom/usb_manager_client.mojom.h"
#include "shell/browser/electron_browser_context.h"
#include "url/origin.h"
namespace electron {
class UsbChooserContext : public KeyedService,
public device::mojom::UsbDeviceManagerClient {
public:
explicit UsbChooserContext(ElectronBrowserContext* context);
UsbChooserContext(const UsbChooserContext&) = delete;
UsbChooserContext& operator=(const UsbChooserContext&) = delete;
~UsbChooserContext() override;
// This observer can be used to be notified of changes to USB devices that are
// connected.
class DeviceObserver : public base::CheckedObserver {
public:
virtual void OnDeviceAdded(const device::mojom::UsbDeviceInfo&);
virtual void OnDeviceRemoved(const device::mojom::UsbDeviceInfo&);
virtual void OnDeviceManagerConnectionError();
// Called when the BrowserContext is shutting down. Observers must remove
// themselves before returning.
virtual void OnBrowserContextShutdown() = 0;
};
static base::Value DeviceInfoToValue(
const device::mojom::UsbDeviceInfo& device_info);
// Grants |origin| access to the USB device.
void GrantDevicePermission(const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device_info);
// Checks if |origin| has access to a device with |device_info|.
bool HasDevicePermission(const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device_info);
// Revokes |origin| access to the USB device ordered by website.
void RevokeDevicePermissionWebInitiated(
const url::Origin& origin,
const device::mojom::UsbDeviceInfo& device);
void AddObserver(DeviceObserver* observer);
void RemoveObserver(DeviceObserver* observer);
// Forward UsbDeviceManager methods.
void GetDevices(device::mojom::UsbDeviceManager::GetDevicesCallback callback);
void GetDevice(
const std::string& guid,
base::span<const uint8_t> blocked_interface_classes,
mojo::PendingReceiver<device::mojom::UsbDevice> device_receiver,
mojo::PendingRemote<device::mojom::UsbDeviceClient> device_client);
// This method should only be called when you are sure that |devices_| has
// been initialized. It will return nullptr if the guid cannot be found.
const device::mojom::UsbDeviceInfo* GetDeviceInfo(const std::string& guid);
base::WeakPtr<UsbChooserContext> AsWeakPtr();
void InitDeviceList(std::vector<::device::mojom::UsbDeviceInfoPtr> devices);
private:
// device::mojom::UsbDeviceManagerClient implementation.
void OnDeviceAdded(device::mojom::UsbDeviceInfoPtr device_info) override;
void OnDeviceRemoved(device::mojom::UsbDeviceInfoPtr device_info) override;
void RevokeObjectPermissionInternal(const url::Origin& origin,
const base::Value& object,
bool revoked_by_website);
void OnDeviceManagerConnectionError();
void EnsureConnectionWithDeviceManager();
void SetUpDeviceManagerConnection();
bool is_initialized_ = false;
base::queue<device::mojom::UsbDeviceManager::GetDevicesCallback>
pending_get_devices_requests_;
std::map<url::Origin, std::set<std::string>> ephemeral_devices_;
std::map<std::string, device::mojom::UsbDeviceInfoPtr> devices_;
// Connection to |device_manager_instance_|.
mojo::Remote<device::mojom::UsbDeviceManager> device_manager_;
mojo::AssociatedReceiver<device::mojom::UsbDeviceManagerClient>
client_receiver_{this};
base::ObserverList<DeviceObserver> device_observer_list_;
ElectronBrowserContext* browser_context_;
base::WeakPtrFactory<UsbChooserContext> weak_factory_{this};
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTEXT_H_

View file

@ -0,0 +1,45 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/usb/usb_chooser_context_factory.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "shell/browser/electron_browser_context.h"
#include "shell/browser/usb/usb_chooser_context.h"
namespace electron {
UsbChooserContextFactory::UsbChooserContextFactory()
: BrowserContextKeyedServiceFactory(
"UsbChooserContext",
BrowserContextDependencyManager::GetInstance()) {}
UsbChooserContextFactory::~UsbChooserContextFactory() {}
KeyedService* UsbChooserContextFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
auto* browser_context =
static_cast<electron::ElectronBrowserContext*>(context);
return new UsbChooserContext(browser_context);
}
// static
UsbChooserContextFactory* UsbChooserContextFactory::GetInstance() {
return base::Singleton<UsbChooserContextFactory>::get();
}
// static
UsbChooserContext* UsbChooserContextFactory::GetForBrowserContext(
content::BrowserContext* context) {
return static_cast<UsbChooserContext*>(
GetInstance()->GetServiceForBrowserContext(context, /*create=*/true));
}
UsbChooserContext* UsbChooserContextFactory::GetForBrowserContextIfExists(
content::BrowserContext* context) {
return static_cast<UsbChooserContext*>(
GetInstance()->GetServiceForBrowserContext(context, /*create=*/false));
}
} // namespace electron

View file

@ -0,0 +1,39 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTEXT_FACTORY_H_
#define ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTEXT_FACTORY_H_
#include "base/memory/singleton.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
namespace electron {
class UsbChooserContext;
class UsbChooserContextFactory : public BrowserContextKeyedServiceFactory {
public:
static UsbChooserContext* GetForBrowserContext(
content::BrowserContext* context);
static UsbChooserContext* GetForBrowserContextIfExists(
content::BrowserContext* context);
static UsbChooserContextFactory* GetInstance();
UsbChooserContextFactory(const UsbChooserContextFactory&) = delete;
UsbChooserContextFactory& operator=(const UsbChooserContextFactory&) = delete;
private:
friend struct base::DefaultSingletonTraits<UsbChooserContextFactory>;
UsbChooserContextFactory();
~UsbChooserContextFactory() override;
// BrowserContextKeyedServiceFactory methods:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* profile) const override;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTEXT_FACTORY_H_

View file

@ -0,0 +1,165 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/usb/usb_chooser_controller.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "gin/data_object_builder.h"
#include "services/device/public/cpp/usb/usb_utils.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/usb/usb_chooser_context_factory.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/content_converter.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_converters/usb_device_info_converter.h"
#include "shell/common/node_includes.h"
#include "shell/common/process_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
using content::RenderFrameHost;
using content::WebContents;
namespace electron {
UsbChooserController::UsbChooserController(
RenderFrameHost* render_frame_host,
std::vector<device::mojom::UsbDeviceFilterPtr> device_filters,
blink::mojom::WebUsbService::GetPermissionCallback callback,
content::WebContents* web_contents,
base::WeakPtr<ElectronUsbDelegate> usb_delegate)
: WebContentsObserver(web_contents),
filters_(std::move(device_filters)),
callback_(std::move(callback)),
origin_(render_frame_host->GetMainFrame()->GetLastCommittedOrigin()),
usb_delegate_(usb_delegate),
render_frame_host_id_(render_frame_host->GetGlobalId()) {
chooser_context_ = UsbChooserContextFactory::GetForBrowserContext(
web_contents->GetBrowserContext())
->AsWeakPtr();
DCHECK(chooser_context_);
chooser_context_->GetDevices(base::BindOnce(
&UsbChooserController::GotUsbDeviceList, weak_factory_.GetWeakPtr()));
}
UsbChooserController::~UsbChooserController() {
RunCallback(/*device=*/nullptr);
}
api::Session* UsbChooserController::GetSession() {
if (!web_contents()) {
return nullptr;
}
return api::Session::FromBrowserContext(web_contents()->GetBrowserContext());
}
void UsbChooserController::OnDeviceAdded(
const device::mojom::UsbDeviceInfo& device_info) {
if (DisplayDevice(device_info)) {
api::Session* session = GetSession();
if (session) {
session->Emit("usb-device-added", device_info.Clone(), web_contents());
}
}
}
void UsbChooserController::OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) {
api::Session* session = GetSession();
if (session) {
session->Emit("usb-device-removed", device_info.Clone(), web_contents());
}
}
void UsbChooserController::OnDeviceChosen(gin::Arguments* args) {
std::string device_id;
if (!args->GetNext(&device_id) || device_id.empty()) {
RunCallback(/*device=*/nullptr);
} else {
auto* device_info = chooser_context_->GetDeviceInfo(device_id);
if (device_info) {
RunCallback(device_info->Clone());
} else {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
node::Environment* env = node::Environment::GetCurrent(isolate);
EmitWarning(env, "The device id " + device_id + " was not found.",
"UnknownUsbDeviceId");
RunCallback(/*device=*/nullptr);
}
}
}
void UsbChooserController::OnBrowserContextShutdown() {
observation_.Reset();
}
// Get a list of devices that can be shown in the chooser bubble UI for
// user to grant permsssion.
void UsbChooserController::GotUsbDeviceList(
std::vector<::device::mojom::UsbDeviceInfoPtr> devices) {
// Listen to UsbChooserContext for OnDeviceAdded/Removed events after the
// enumeration.
if (chooser_context_)
observation_.Observe(chooser_context_.get());
bool prevent_default = false;
api::Session* session = GetSession();
if (session) {
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
.Set("deviceList", devices)
.Set("frame", rfh)
.Build();
prevent_default =
session->Emit("select-usb-device", details,
base::AdaptCallbackForRepeating(
base::BindOnce(&UsbChooserController::OnDeviceChosen,
weak_factory_.GetWeakPtr())));
}
if (!prevent_default) {
RunCallback(/*port=*/nullptr);
}
}
bool UsbChooserController::DisplayDevice(
const device::mojom::UsbDeviceInfo& device_info) const {
if (!device::UsbDeviceFilterMatchesAny(filters_, device_info))
return false;
return true;
}
void UsbChooserController::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (usb_delegate_) {
usb_delegate_->DeleteControllerForFrame(render_frame_host);
}
}
void UsbChooserController::RunCallback(
device::mojom::UsbDeviceInfoPtr device_info) {
if (callback_) {
if (!chooser_context_ || !device_info) {
std::move(callback_).Run(nullptr);
} else {
chooser_context_->GrantDevicePermission(origin_, *device_info);
std::move(callback_).Run(std::move(device_info));
}
}
}
} // namespace electron

View file

@ -0,0 +1,81 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTROLLER_H_
#define ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTROLLER_H_
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/usb/electron_usb_delegate.h"
#include "shell/browser/usb/usb_chooser_context.h"
#include "third_party/blink/public/mojom/usb/web_usb_service.mojom.h"
#include "url/origin.h"
namespace content {
class RenderFrameHost;
}
namespace electron {
// UsbChooserController creates a chooser for WebUSB.
class UsbChooserController final : public UsbChooserContext::DeviceObserver,
public content::WebContentsObserver {
public:
UsbChooserController(
content::RenderFrameHost* render_frame_host,
std::vector<device::mojom::UsbDeviceFilterPtr> device_filters,
blink::mojom::WebUsbService::GetPermissionCallback callback,
content::WebContents* web_contents,
base::WeakPtr<ElectronUsbDelegate> usb_delegate);
UsbChooserController(const UsbChooserController&) = delete;
UsbChooserController& operator=(const UsbChooserController&) = delete;
~UsbChooserController() override;
// UsbChooserContext::DeviceObserver implementation:
void OnDeviceAdded(const device::mojom::UsbDeviceInfo& device_info) override;
void OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) override;
void OnBrowserContextShutdown() override;
// content::WebContentsObserver:
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
private:
api::Session* GetSession();
void GotUsbDeviceList(std::vector<device::mojom::UsbDeviceInfoPtr> devices);
bool DisplayDevice(const device::mojom::UsbDeviceInfo& device) const;
void RunCallback(device::mojom::UsbDeviceInfoPtr device_info);
void OnDeviceChosen(gin::Arguments* args);
std::vector<device::mojom::UsbDeviceFilterPtr> filters_;
blink::mojom::WebUsbService::GetPermissionCallback callback_;
url::Origin origin_;
base::WeakPtr<UsbChooserContext> chooser_context_;
base::ScopedObservation<UsbChooserContext, UsbChooserContext::DeviceObserver>
observation_{this};
base::WeakPtr<ElectronUsbDelegate> usb_delegate_;
content::GlobalRenderFrameHostId render_frame_host_id_;
base::WeakPtrFactory<UsbChooserController> weak_factory_{this};
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTROLLER_H_

View file

@ -29,7 +29,8 @@ class WebContentsPermissionHelper
FULLSCREEN,
OPEN_EXTERNAL,
SERIAL,
HID
HID,
USB
};
// Asynchronous Requests

View file

@ -25,6 +25,10 @@ const char kSecureProtocolDescription[] =
"The connection to this site is using a strong protocol version "
"and cipher suite.";
const char kDeviceVendorIdKey[] = "vendorId";
const char kDeviceProductIdKey[] = "productId";
const char kDeviceSerialNumberKey[] = "serialNumber";
#if BUILDFLAG(ENABLE_RUN_AS_NODE)
const char kRunAsNode[] = "ELECTRON_RUN_AS_NODE";
#endif

View file

@ -25,6 +25,11 @@ extern const char kValidCertificateDescription[];
extern const char kSecureProtocol[];
extern const char kSecureProtocolDescription[];
// Keys for Device APIs
extern const char kDeviceVendorIdKey[];
extern const char kDeviceProductIdKey[];
extern const char kDeviceSerialNumberKey[];
#if BUILDFLAG(ENABLE_RUN_AS_NODE)
extern const char kRunAsNode[];
#endif

View file

@ -209,6 +209,8 @@ v8::Local<v8::Value> Converter<blink::PermissionType>::ToV8(
return StringToV8(isolate, "serial");
case PermissionType::HID:
return StringToV8(isolate, "hid");
case PermissionType::USB:
return StringToV8(isolate, "usb");
default:
return StringToV8(isolate, "unknown");
}

View file

@ -0,0 +1,27 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_COMMON_GIN_CONVERTERS_USB_DEVICE_INFO_CONVERTER_H_
#define ELECTRON_SHELL_COMMON_GIN_CONVERTERS_USB_DEVICE_INFO_CONVERTER_H_
#include "gin/converter.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "shell/browser/usb/usb_chooser_context.h"
#include "shell/common/gin_converters/value_converter.h"
namespace gin {
template <>
struct Converter<device::mojom::UsbDeviceInfoPtr> {
static v8::Local<v8::Value> ToV8(
v8::Isolate* isolate,
const device::mojom::UsbDeviceInfoPtr& device) {
base::Value value = electron::UsbChooserContext::DeviceInfoToValue(*device);
return gin::ConvertToV8(isolate, value);
}
};
} // namespace gin
#endif // ELECTRON_SHELL_COMMON_GIN_CONVERTERS_USB_DEVICE_INFO_CONVERTER_H_