From 583a5e9076669bc4bef5e1a428985d59178ff82f Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Tue, 8 Feb 2022 13:54:55 -0500 Subject: [PATCH] feat: add support for HIDDevice.forget() --- docs/api/session.md | 13 +++ filenames.gni | 1 + .../browser/api/electron_api_web_contents.cc | 106 ++++++++++++++++-- shell/browser/api/electron_api_web_contents.h | 21 +++- shell/browser/electron_permission_manager.cc | 67 +++-------- shell/browser/electron_permission_manager.h | 6 + shell/browser/hid/electron_hid_delegate.cc | 7 +- shell/browser/hid/hid_chooser_context.cc | 66 +++++++++++ shell/browser/hid/hid_chooser_context.h | 12 ++ shell/browser/hid/hid_chooser_controller.cc | 41 +++---- shell/browser/hid/hid_chooser_controller.h | 4 + .../browser/web_contents_permission_helper.cc | 20 ++++ .../browser/web_contents_permission_helper.h | 10 ++ .../hid_device_info_converter.h | 31 +++++ spec-main/chromium-spec.ts | 53 +++++++-- 15 files changed, 352 insertions(+), 106 deletions(-) create mode 100644 shell/common/gin_converters/hid_device_info_converter.h diff --git a/docs/api/session.md b/docs/api/session.md index ab2ecdb701de7..8475302e9422b 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -274,6 +274,19 @@ from `select-hid-device` is called. This event is intended for use when using a UI to ask users to pick a device so that the UI can be updated to remove the specified device. +#### Event: 'hid-device-revoked' + +Returns: + +* `event` Event +* `details` Object + * `device` [HIDDevice[]](structures/hid-device.md) + * `frame` [WebFrameMain](web-frame-main.md) + +Emitted after `HIDDevice.forget()` has been called. This event can be used +to help maintain persistent storage of permissions when +`setDevicePermissionHandler` is used. + #### Event: 'select-serial-port' Returns: diff --git a/filenames.gni b/filenames.gni index f7c6ce5dff438..e2f6aa294f8a9 100644 --- a/filenames.gni +++ b/filenames.gni @@ -566,6 +566,7 @@ filenames = { "shell/common/gin_converters/gfx_converter.h", "shell/common/gin_converters/guid_converter.h", "shell/common/gin_converters/gurl_converter.h", + "shell/common/gin_converters/hid_device_info_converter.h", "shell/common/gin_converters/image_converter.cc", "shell/common/gin_converters/image_converter.h", "shell/common/gin_converters/message_box_converter.cc", diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index f27eb39170c74..37513ce630d71 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -48,6 +48,7 @@ #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" +#include "content/public/browser/permission_type.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" @@ -3450,37 +3451,118 @@ v8::Local WebContents::TakeHeapSnapshot( void WebContents::GrantDevicePermission( const url::Origin& origin, const base::Value* device, - content::PermissionType permissionType, + content::PermissionType permission_type, content::RenderFrameHost* render_frame_host) { - granted_devices_[render_frame_host->GetFrameTreeNodeId()][permissionType] + granted_devices_[render_frame_host->GetFrameTreeNodeId()][permission_type] [origin] .push_back( std::make_unique(device->Clone())); } -std::vector WebContents::GetGrantedDevices( +void WebContents::RevokeDevicePermission( const url::Origin& origin, - content::PermissionType permissionType, + const base::Value* device, + content::PermissionType permission_type, + content::RenderFrameHost* render_frame_host) { + const auto& devices_for_frame_host_it = + granted_devices_.find(render_frame_host->GetFrameTreeNodeId()); + if (devices_for_frame_host_it == granted_devices_.end()) + return; + + const auto& current_devices_it = + devices_for_frame_host_it->second.find(permission_type); + if (current_devices_it == devices_for_frame_host_it->second.end()) + return; + + const auto& origin_devices_it = current_devices_it->second.find(origin); + if (origin_devices_it == current_devices_it->second.end()) + return; + + for (auto it = origin_devices_it->second.begin(); + it != origin_devices_it->second.end();) { + if (DoesDeviceMatch(device, it->get(), permission_type)) { + it = origin_devices_it->second.erase(it); + } else { + ++it; + } + } +} + +bool WebContents::DoesDeviceMatch(const base::Value* device, + const base::Value* device_to_compare, + content::PermissionType permission_type) { + if (permission_type == + static_cast( + WebContentsPermissionHelper::PermissionType::HID)) { + if (device->GetDict().FindInt(kHidVendorIdKey) != + device_to_compare->GetDict().FindInt(kHidVendorIdKey) || + device->GetDict().FindInt(kHidProductIdKey) != + device_to_compare->GetDict().FindInt(kHidProductIdKey)) { + return false; + } + + const auto* serial_number = + device_to_compare->GetDict().FindString(kHidSerialNumberKey); + const auto* device_serial_number = + device->GetDict().FindString(kHidSerialNumberKey); + + if (serial_number && device_serial_number && + *device_serial_number == *serial_number) + return true; + } else if (permission_type == + static_cast( + WebContentsPermissionHelper::PermissionType::SERIAL)) { +#if BUILDFLAG(IS_WIN) + if (device->GetDict().FindString(kDeviceInstanceIdKey) == + device_to_compare->GetDict().FindString(kDeviceInstanceIdKey)) + return true; +#else + if (device->GetDict().FindInt(kVendorIdKey) != + device_to_compare->GetDict().FindInt(kVendorIdKey) || + device->GetDict().FindInt(kProductIdKey) != + device_to_compare->GetDict().FindInt(kProductIdKey) || + *device->GetDict().FindString(kSerialNumberKey) != + *device_to_compare->GetDict().FindString(kSerialNumberKey)) { + return false; + } + +#if BUILDFLAG(IS_MAC) + if (*device->GetDict().FindString(kUsbDriverKey) != + *device_to_compare->GetDict().FindString(kUsbDriverKey)) { + return false; + } +#endif // BUILDFLAG(IS_MAC) + return true; +#endif // BUILDFLAG(IS_WIN) + } + return false; +} + +bool WebContents::CheckDevicePermission( + const url::Origin& origin, + const base::Value* device, + content::PermissionType permission_type, content::RenderFrameHost* render_frame_host) { const auto& devices_for_frame_host_it = granted_devices_.find(render_frame_host->GetFrameTreeNodeId()); if (devices_for_frame_host_it == granted_devices_.end()) - return {}; + return false; const auto& current_devices_it = - devices_for_frame_host_it->second.find(permissionType); + devices_for_frame_host_it->second.find(permission_type); if (current_devices_it == devices_for_frame_host_it->second.end()) - return {}; + return false; const auto& origin_devices_it = current_devices_it->second.find(origin); if (origin_devices_it == current_devices_it->second.end()) - return {}; + return false; - std::vector results; - for (const auto& object : origin_devices_it->second) - results.push_back(object->Clone()); + for (const auto& device_to_compare : origin_devices_it->second) { + if (DoesDeviceMatch(device, device_to_compare.get(), permission_type)) + return true; + } - return results; + return false; } void WebContents::UpdatePreferredSize(content::WebContents* web_contents, diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index 996c90cbbbc39..ce52eeb5864a3 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -439,16 +439,23 @@ class WebContents : public ExclusiveAccessContext, // To be used in place of ObjectPermissionContextBase::GrantObjectPermission. void GrantDevicePermission(const url::Origin& origin, const base::Value* device, - content::PermissionType permissionType, + content::PermissionType permission_type, content::RenderFrameHost* render_frame_host); + // Revokes |origin| access to |device|. + // To be used in place of ObjectPermissionContextBase::RevokeObjectPermission. + void RevokeDevicePermission(const url::Origin& origin, + const base::Value* device, + content::PermissionType permission_type, + content::RenderFrameHost* render_frame_host); + // Returns the list of devices that |origin| has been granted permission to // access. To be used in place of // ObjectPermissionContextBase::GetGrantedObjects. - std::vector GetGrantedDevices( - const url::Origin& origin, - content::PermissionType permissionType, - content::RenderFrameHost* render_frame_host); + bool CheckDevicePermission(const url::Origin& origin, + const base::Value* device, + content::PermissionType permission_type, + content::RenderFrameHost* render_frame_host); // disable copy WebContents(const WebContents&) = delete; @@ -745,6 +752,10 @@ class WebContents : public ExclusiveAccessContext, // Update the html fullscreen flag in both browser and renderer. void UpdateHtmlApiFullscreen(bool fullscreen); + bool DoesDeviceMatch(const base::Value* device, + const base::Value* device_to_compare, + content::PermissionType permission_type); + v8::Global session_; v8::Global devtools_web_contents_; v8::Global debugger_; diff --git a/shell/browser/electron_permission_manager.cc b/shell/browser/electron_permission_manager.cc index ad3ae6229fe0f..3ad405df81e4c 100644 --- a/shell/browser/electron_permission_manager.cc +++ b/shell/browser/electron_permission_manager.cc @@ -22,8 +22,6 @@ #include "shell/browser/api/electron_api_web_contents.h" #include "shell/browser/electron_browser_client.h" #include "shell/browser/electron_browser_main_parts.h" -#include "shell/browser/hid/hid_chooser_context.h" -#include "shell/browser/serial/serial_chooser_context.h" #include "shell/browser/web_contents_permission_helper.h" #include "shell/browser/web_contents_preferences.h" #include "shell/common/gin_converters/content_converter.h" @@ -308,56 +306,8 @@ bool ElectronPermissionManager::CheckDevicePermission( api::WebContents* api_web_contents = api::WebContents::From(web_contents); if (device_permission_handler_.is_null()) { if (api_web_contents) { - std::vector granted_devices = - api_web_contents->GetGrantedDevices(origin, permission, - render_frame_host); - - for (const auto& granted_device : granted_devices) { - if (permission == - static_cast( - WebContentsPermissionHelper::PermissionType::HID)) { - if (device->FindIntKey(kHidVendorIdKey) != - granted_device.FindIntKey(kHidVendorIdKey) || - device->FindIntKey(kHidProductIdKey) != - granted_device.FindIntKey(kHidProductIdKey)) { - continue; - } - - const auto* serial_number = - granted_device.FindStringKey(kHidSerialNumberKey); - const auto* device_serial_number = - device->FindStringKey(kHidSerialNumberKey); - - if (serial_number && device_serial_number && - *device_serial_number == *serial_number) - return true; - } else if (permission == - static_cast( - WebContentsPermissionHelper::PermissionType::SERIAL)) { -#if BUILDFLAG(IS_WIN) - if (device->FindStringKey(kDeviceInstanceIdKey) == - granted_device.FindStringKey(kDeviceInstanceIdKey)) - return true; -#else - if (device->FindIntKey(kVendorIdKey) != - granted_device.FindIntKey(kVendorIdKey) || - device->FindIntKey(kProductIdKey) != - granted_device.FindIntKey(kProductIdKey) || - *device->FindStringKey(kSerialNumberKey) != - *granted_device.FindStringKey(kSerialNumberKey)) { - continue; - } - -#if BUILDFLAG(IS_MAC) - if (*device->FindStringKey(kUsbDriverKey) != - *granted_device.FindStringKey(kUsbDriverKey)) { - continue; - } -#endif // BUILDFLAG(IS_MAC) - return true; -#endif // BUILDFLAG(IS_WIN) - } - } + return api_web_contents->CheckDevicePermission(origin, device, permission, + render_frame_host); } return false; } else { @@ -388,6 +338,19 @@ void ElectronPermissionManager::GrantDevicePermission( } } +void ElectronPermissionManager::RevokeDevicePermission( + content::PermissionType permission, + const url::Origin& origin, + const base::Value* device, + content::RenderFrameHost* render_frame_host) const { + auto* web_contents = + content::WebContents::FromRenderFrameHost(render_frame_host); + api::WebContents* api_web_contents = api::WebContents::From(web_contents); + if (api_web_contents) + api_web_contents->RevokeDevicePermission(origin, device, permission, + render_frame_host); +} + blink::mojom::PermissionStatus ElectronPermissionManager::GetPermissionStatusForFrame( content::PermissionType permission, diff --git a/shell/browser/electron_permission_manager.h b/shell/browser/electron_permission_manager.h index 4afce85ad80cc..d288915a93871 100644 --- a/shell/browser/electron_permission_manager.h +++ b/shell/browser/electron_permission_manager.h @@ -104,6 +104,12 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate { const base::Value* object, content::RenderFrameHost* render_frame_host) const; + void RevokeDevicePermission( + content::PermissionType permission, + const url::Origin& origin, + const base::Value* object, + content::RenderFrameHost* render_frame_host) const; + protected: void OnPermissionResponse(int request_id, int permission_id, diff --git a/shell/browser/hid/electron_hid_delegate.cc b/shell/browser/hid/electron_hid_delegate.cc index efad257969a74..75298a5870046 100644 --- a/shell/browser/hid/electron_hid_delegate.cc +++ b/shell/browser/hid/electron_hid_delegate.cc @@ -80,8 +80,11 @@ bool ElectronHidDelegate::HasDevicePermission( void ElectronHidDelegate::RevokeDevicePermission( content::RenderFrameHost* render_frame_host, const device::mojom::HidDeviceInfo& device) { - // TODO(jkleinsc) implement this for - // https://chromium-review.googlesource.com/c/chromium/src/+/3297868 + auto* chooser_context = GetChooserContext(render_frame_host); + const auto& origin = + render_frame_host->GetMainFrame()->GetLastCommittedOrigin(); + return chooser_context->RevokeDevicePermission(origin, device, + render_frame_host); } device::mojom::HidManager* ElectronHidDelegate::GetHidManager( diff --git a/shell/browser/hid/hid_chooser_context.cc b/shell/browser/hid/hid_chooser_context.cc index ca793e6ff22dc..aeebfa554e2d9 100644 --- a/shell/browser/hid/hid_chooser_context.cc +++ b/shell/browser/hid/hid_chooser_context.cc @@ -17,9 +17,15 @@ #include "components/content_settings/core/common/content_settings_types.h" #include "components/prefs/pref_service.h" #include "content/public/browser/device_service.h" +#include "gin/data_object_builder.h" #include "services/device/public/cpp/hid/hid_blocklist.h" #include "services/device/public/cpp/hid/hid_switches.h" +#include "shell/browser/api/electron_api_session.h" #include "shell/browser/web_contents_permission_helper.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" +#include "shell/common/gin_converters/value_converter.h" #include "ui/base/l10n/l10n_util.h" namespace electron { @@ -99,6 +105,66 @@ void HidChooserContext::GrantDevicePermission( } } +void HidChooserContext::RevokeDevicePermission( + const url::Origin& origin, + const device::mojom::HidDeviceInfo& device, + content::RenderFrameHost* render_frame_host) { + DCHECK(base::Contains(devices_, device.guid)); + if (CanStorePersistentEntry(device)) { + RevokePersistentDevicePermission(origin, device, render_frame_host); + } else { + RevokeEphemeralDevicePermission(origin, device); + } + auto* web_contents = + content::WebContents::FromRenderFrameHost(render_frame_host); + + api::Session* session = + api::Session::FromBrowserContext(web_contents->GetBrowserContext()); + if (session) { + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + v8::HandleScope scope(isolate); + v8::Local details = gin::DataObjectBuilder(isolate) + .Set("device", device.Clone()) + .Set("frame", render_frame_host) + .Build(); + session->Emit("hid-device-revoked", details); + } +} + +void HidChooserContext::RevokePersistentDevicePermission( + const url::Origin& origin, + const device::mojom::HidDeviceInfo& device, + content::RenderFrameHost* render_frame_host) { + auto* web_contents = + content::WebContents::FromRenderFrameHost(render_frame_host); + auto* permission_helper = + WebContentsPermissionHelper::FromWebContents(web_contents); + permission_helper->RevokeHIDDevicePermission( + origin, DeviceInfoToValue(device), render_frame_host); + RevokeEphemeralDevicePermission(origin, device); +} + +void HidChooserContext::RevokeEphemeralDevicePermission( + const url::Origin& origin, + const device::mojom::HidDeviceInfo& device) { + auto it = ephemeral_devices_.find(origin); + if (it != ephemeral_devices_.end()) { + std::set& devices = it->second; + for (auto guid = devices.begin(); guid != devices.end();) { + DCHECK(base::Contains(devices_, *guid)); + + if (devices_[*guid]->physical_device_id != device.physical_device_id) { + ++guid; + continue; + } + + guid = devices.erase(guid); + if (devices.empty()) + ephemeral_devices_.erase(it); + } + } +} + bool HidChooserContext::HasDevicePermission( const url::Origin& origin, const device::mojom::HidDeviceInfo& device, diff --git a/shell/browser/hid/hid_chooser_context.h b/shell/browser/hid/hid_chooser_context.h index a9c2f7e1def98..544984004e654 100644 --- a/shell/browser/hid/hid_chooser_context.h +++ b/shell/browser/hid/hid_chooser_context.h @@ -76,6 +76,9 @@ class HidChooserContext : public KeyedService, void GrantDevicePermission(const url::Origin& origin, const device::mojom::HidDeviceInfo& device, content::RenderFrameHost* render_frame_host); + void RevokeDevicePermission(const url::Origin& origin, + const device::mojom::HidDeviceInfo& device, + content::RenderFrameHost* render_frame_host); bool HasDevicePermission(const url::Origin& origin, const device::mojom::HidDeviceInfo& device, content::RenderFrameHost* render_frame_host); @@ -111,6 +114,15 @@ class HidChooserContext : public KeyedService, std::vector devices); void OnHidManagerConnectionError(); + // HID-specific interface for revoking device permissions. + void RevokePersistentDevicePermission( + const url::Origin& origin, + const device::mojom::HidDeviceInfo& device, + content::RenderFrameHost* render_frame_host); + void RevokeEphemeralDevicePermission( + const url::Origin& origin, + const device::mojom::HidDeviceInfo& device); + ElectronBrowserContext* browser_context_; bool is_initialized_ = false; diff --git a/shell/browser/hid/hid_chooser_controller.cc b/shell/browser/hid/hid_chooser_controller.cc index 039d7074b528d..69f660fa03016 100644 --- a/shell/browser/hid/hid_chooser_controller.cc +++ b/shell/browser/hid/hid_chooser_controller.cc @@ -20,6 +20,7 @@ #include "shell/browser/javascript_environment.h" #include "shell/common/gin_converters/callback_converter.h" #include "shell/common/gin_converters/content_converter.h" +#include "shell/common/gin_converters/hid_device_info_converter.h" #include "shell/common/gin_converters/value_converter.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/node_includes.h" @@ -28,18 +29,6 @@ namespace { -std::string PhysicalDeviceIdFromDeviceInfo( - const device::mojom::HidDeviceInfo& device) { - // A single physical device may expose multiple HID interfaces, each - // represented by a HidDeviceInfo object. When a device exposes multiple - // HID interfaces, the HidDeviceInfo objects will share a common - // |physical_device_id|. Group these devices so that a single chooser item - // is shown for each physical device. If a device's physical device ID is - // empty, use its GUID instead. - return device.physical_device_id.empty() ? device.guid - : device.physical_device_id; -} - bool FilterMatch(const blink::mojom::HidDeviceFilterPtr& filter, const device::mojom::HidDeviceInfo& device) { if (filter->device_ids) { @@ -83,21 +72,6 @@ bool FilterMatch(const blink::mojom::HidDeviceFilterPtr& filter, } // namespace -namespace gin { - -template <> -struct Converter { - static v8::Local ToV8( - v8::Isolate* isolate, - const device::mojom::HidDeviceInfoPtr& device) { - base::Value value = electron::HidChooserContext::DeviceInfoToValue(*device); - value.SetStringKey("deviceId", PhysicalDeviceIdFromDeviceInfo(*device)); - return gin::ConvertToV8(isolate, value); - } -}; - -} // namespace gin - namespace electron { HidChooserController::HidChooserController( @@ -131,6 +105,19 @@ HidChooserController::~HidChooserController() { std::move(callback_).Run(std::vector()); } +// static +std::string HidChooserController::PhysicalDeviceIdFromDeviceInfo( + const device::mojom::HidDeviceInfo& device) { + // A single physical device may expose multiple HID interfaces, each + // represented by a HidDeviceInfo object. When a device exposes multiple + // HID interfaces, the HidDeviceInfo objects will share a common + // |physical_device_id|. Group these devices so that a single chooser item + // is shown for each physical device. If a device's physical device ID is + // empty, use its GUID instead. + return device.physical_device_id.empty() ? device.guid + : device.physical_device_id; +} + api::Session* HidChooserController::GetSession() { if (!web_contents()) { return nullptr; diff --git a/shell/browser/hid/hid_chooser_controller.h b/shell/browser/hid/hid_chooser_controller.h index dde678ff981fb..63411c9f65b78 100644 --- a/shell/browser/hid/hid_chooser_controller.h +++ b/shell/browser/hid/hid_chooser_controller.h @@ -53,6 +53,10 @@ class HidChooserController HidChooserController& operator=(HidChooserController&) = delete; ~HidChooserController() override; + // static + static std::string PhysicalDeviceIdFromDeviceInfo( + const device::mojom::HidDeviceInfo& device); + // HidChooserContext::DeviceObserver: void OnDeviceAdded(const device::mojom::HidDeviceInfo& device_info) override; void OnDeviceRemoved( diff --git a/shell/browser/web_contents_permission_helper.cc b/shell/browser/web_contents_permission_helper.cc index 2fd8cb0a6c699..00681bdcbada8 100644 --- a/shell/browser/web_contents_permission_helper.cc +++ b/shell/browser/web_contents_permission_helper.cc @@ -107,6 +107,17 @@ void WebContentsPermissionHelper::GrantDevicePermission( render_frame_host); } +void WebContentsPermissionHelper::RevokeDevicePermission( + content::PermissionType permission, + const url::Origin& origin, + const base::Value* device, + content::RenderFrameHost* render_frame_host) const { + auto* permission_manager = static_cast( + web_contents_->GetBrowserContext()->GetPermissionControllerDelegate()); + permission_manager->RevokeDevicePermission(permission, origin, device, + render_frame_host); +} + void WebContentsPermissionHelper::RequestFullscreenPermission( base::OnceCallback callback) { RequestPermission( @@ -231,6 +242,15 @@ void WebContentsPermissionHelper::GrantHIDDevicePermission( &device, render_frame_host); } +void WebContentsPermissionHelper::RevokeHIDDevicePermission( + const url::Origin& origin, + base::Value device, + content::RenderFrameHost* render_frame_host) const { + return RevokeDevicePermission( + static_cast(PermissionType::HID), origin, + &device, render_frame_host); +} + WEB_CONTENTS_USER_DATA_KEY_IMPL(WebContentsPermissionHelper); } // namespace electron diff --git a/shell/browser/web_contents_permission_helper.h b/shell/browser/web_contents_permission_helper.h index 639dcd8a8829a..7ed6442a64553 100644 --- a/shell/browser/web_contents_permission_helper.h +++ b/shell/browser/web_contents_permission_helper.h @@ -68,6 +68,10 @@ class WebContentsPermissionHelper const url::Origin& origin, base::Value device, content::RenderFrameHost* render_frame_host) const; + void RevokeHIDDevicePermission( + const url::Origin& origin, + base::Value device, + content::RenderFrameHost* render_frame_host) const; private: explicit WebContentsPermissionHelper(content::WebContents* web_contents); @@ -91,6 +95,12 @@ class WebContentsPermissionHelper const base::Value* device, content::RenderFrameHost* render_frame_host) const; + void RevokeDevicePermission( + content::PermissionType permission, + const url::Origin& origin, + const base::Value* device, + content::RenderFrameHost* render_frame_host) const; + // TODO(clavin): refactor to use the WebContents provided by the // WebContentsUserData base class instead of storing a duplicate ref content::WebContents* web_contents_; diff --git a/shell/common/gin_converters/hid_device_info_converter.h b/shell/common/gin_converters/hid_device_info_converter.h new file mode 100644 index 0000000000000..6175b77ab0d3c --- /dev/null +++ b/shell/common/gin_converters/hid_device_info_converter.h @@ -0,0 +1,31 @@ +// 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_HID_DEVICE_INFO_CONVERTER_H_ +#define ELECTRON_SHELL_COMMON_GIN_CONVERTERS_HID_DEVICE_INFO_CONVERTER_H_ + +#include "gin/converter.h" +#include "services/device/public/mojom/hid.mojom.h" +#include "shell/browser/hid/hid_chooser_context.h" +#include "shell/browser/hid/hid_chooser_controller.h" + +namespace gin { + +template <> +struct Converter { + static v8::Local ToV8( + v8::Isolate* isolate, + const device::mojom::HidDeviceInfoPtr& device) { + base::Value value = electron::HidChooserContext::DeviceInfoToValue(*device); + value.SetStringKey( + "deviceId", + electron::HidChooserController::PhysicalDeviceIdFromDeviceInfo( + *device)); + return gin::ConvertToV8(isolate, value); + } +}; + +} // namespace gin + +#endif // ELECTRON_SHELL_COMMON_GIN_CONVERTERS_HID_DEVICE_INFO_CONVERTER_H_ diff --git a/spec-main/chromium-spec.ts b/spec-main/chromium-spec.ts index ca93d5e0954bd..879144b95baff 100644 --- a/spec-main/chromium-spec.ts +++ b/spec-main/chromium-spec.ts @@ -1890,7 +1890,7 @@ describe('navigator.hid', () => { serverUrl = `http://localhost:${(server.address() as any).port}`; }); - const getDevices: any = () => { + const requestDevices: any = () => { return w.webContents.executeJavaScript(` navigator.hid.requestDevice({filters: []}).then(device => device.toString()).catch(err => err.toString()); `, true); @@ -1908,7 +1908,7 @@ describe('navigator.hid', () => { it('does not return a device if select-hid-device event is not defined', async () => { w.loadFile(path.join(fixturesPath, 'pages', 'blank.html')); - const device = await getDevices(); + const device = await requestDevices(); expect(device).to.equal(''); }); @@ -1919,7 +1919,7 @@ describe('navigator.hid', () => { callback(); }); session.defaultSession.setPermissionCheckHandler(() => false); - const device = await getDevices(); + const device = await requestDevices(); expect(selectFired).to.be.false(); expect(device).to.equal(''); }); @@ -1937,7 +1937,7 @@ describe('navigator.hid', () => { callback(); } }); - const device = await getDevices(); + const device = await requestDevices(); expect(selectFired).to.be.true(); if (haveDevices) { expect(device).to.contain('[object HIDDevice]'); @@ -1986,7 +1986,7 @@ describe('navigator.hid', () => { return true; }); await w.webContents.executeJavaScript('navigator.hid.getDevices();', true); - const device = await getDevices(); + const device = await requestDevices(); expect(selectFired).to.be.true(); if (haveDevices) { expect(device).to.contain('[object HIDDevice]'); @@ -1996,7 +1996,7 @@ describe('navigator.hid', () => { } }); - it('returns excludes a device when a exclusionFilter is specified', async () => { + it('excludes a device when a exclusionFilter is specified', async () => { const exclusionFilters = []; let haveDevices = false; let checkForExcludedDevice = false; @@ -2005,7 +2005,6 @@ describe('navigator.hid', () => { if (details.deviceList.length > 0) { details.deviceList.find((device) => { if (device.name && device.name !== '' && device.serialNumber && device.serialNumber !== '') { - console.log('device is: ', device); if (checkForExcludedDevice) { const compareDevice = { vendorId: device.vendorId, @@ -2026,13 +2025,51 @@ describe('navigator.hid', () => { callback(); }); - await getDevices(); + await requestDevices(); if (haveDevices) { // We have devices to exclude, so check if exculsionFilters work checkForExcludedDevice = true; await w.webContents.executeJavaScript(` navigator.hid.requestDevice({filters: [], exclusionFilters: ${JSON.stringify(exclusionFilters)}}).then(device => device.toString()).catch(err => err.toString()); + `, true); } }); + + it('supports device.forget()', async () => { + let deletedDeviceFromEvent; + let haveDevices = false; + w.webContents.session.on('select-hid-device', (event, details, callback) => { + if (details.deviceList.length > 0) { + haveDevices = true; + callback(details.deviceList[0].deviceId); + } else { + callback(); + } + }); + w.webContents.session.on('hid-device-revoked', (event, details) => { + deletedDeviceFromEvent = details.device; + }); + await requestDevices(); + if (haveDevices) { + const grantedDevices = await w.webContents.executeJavaScript('navigator.hid.getDevices()'); + if (grantedDevices.length > 0) { + const deletedDevice = await w.webContents.executeJavaScript(` + navigator.hid.getDevices().then(devices => { + devices[0].forget(); + return { + vendorId: devices[0].vendorId, + productId: devices[0].productId, + name: devices[0].productName + } + }) + `); + const grantedDevices2 = await w.webContents.executeJavaScript('navigator.hid.getDevices()'); + expect(grantedDevices2.length).to.be.lessThan(grantedDevices.length); + if (deletedDevice.name !== '' && deletedDevice.productId && deletedDevice.vendorId) { + expect(deletedDeviceFromEvent).to.include(deletedDevice); + } + } + } + }); });