Skip to content

Commit

Permalink
usb: Introduce ServiceWorkerUsbDelegateObserver for WebUsbService
Browse files Browse the repository at this point in the history
This change introduces ServiceWorkerUsbDelegateObserver as a broker
between UsbDelegate and WebUsbService. It is not covered in this CL yet,
but this class is set to be responsible to keep track what
ServiceWorkerRegistration should respond to USB events and wake up the
service worker if it is not in running state. The full functionality
will be completed by the following CL.

Bug: 1446487
Change-Id: I1ab8809ec816ed3ad5c4cf55d0bd3521452a41c9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4763985
Reviewed-by: Yoshisato Yanagisawa <yyanagisawa@chromium.org>
Reviewed-by: Jack Hsieh <chengweih@chromium.org>
Commit-Queue: Juan Garza Sanchez <juangarza@chromium.org>
Auto-Submit: Juan Garza Sanchez <juangarza@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1187996}
  • Loading branch information
Juan Garza Sanchez authored and Chromium LUCI CQ committed Aug 24, 2023
1 parent 3d35ece commit 62cce61
Show file tree
Hide file tree
Showing 17 changed files with 836 additions and 24 deletions.
31 changes: 19 additions & 12 deletions chrome/browser/usb/chrome_usb_delegate_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ device::mojom::UsbOpenDeviceResultPtr NewUsbOpenDeviceSuccess() {
}

#if BUILDFLAG(ENABLE_EXTENSIONS)
// Creates a FakeUsbDeviceInfo with HID class code.
scoped_refptr<device::FakeUsbDeviceInfo> CreateFakeHidDeviceInfo() {
// Creates a FakeUsbDeviceInfo with USB class code.
scoped_refptr<device::FakeUsbDeviceInfo> CreateFakeUsbDeviceInfo() {
auto alternate_setting = device::mojom::UsbAlternateInterfaceInfo::New();
alternate_setting->alternate_setting = 0;
alternate_setting->class_code = device::mojom::kUsbHidClass;
Expand Down Expand Up @@ -442,6 +442,8 @@ class ChromeUsbTestHelper {
device_manager()->SetMockForDevice(device_info->guid, &mock_device);

// Call GetDevices and expect the device to be returned.
MockDeviceManagerClient mock_client;
service->SetClient(mock_client.CreateInterfacePtrAndBind());
GetDevicesBlocking(service.get(), {device_info->guid});

// Call GetDevice to get the device. The WebContents should not indicate we
Expand Down Expand Up @@ -494,6 +496,8 @@ class ChromeUsbTestHelper {
device_manager()->SetMockForDevice(device_info->guid, &mock_device);

// Call GetDevices and expect the device to be returned.
MockDeviceManagerClient mock_client;
service->SetClient(mock_client.CreateInterfacePtrAndBind());
GetDevicesBlocking(service.get(), {device_info->guid});

// Call GetDevice to get the device. The WebContents should not indicate we
Expand Down Expand Up @@ -550,12 +554,13 @@ class ChromeUsbTestHelper {
void TestAllowlistedImprivataExtension(content::WebContents* web_contents) {
auto imprivata_origin = url::Origin::Create(origin_url_);
auto* context = GetChooserContext();
auto device_info = device_manager()->AddDevice(CreateFakeHidDeviceInfo());
auto device_info = device_manager()->AddDevice(CreateFakeUsbDeviceInfo());
context->GrantDevicePermission(imprivata_origin, *device_info);

mojo::Remote<blink::mojom::WebUsbService> service;
ConnectToService(service.BindNewPipeAndPassReceiver());

MockDeviceManagerClient mock_client;
service->SetClient(mock_client.CreateInterfacePtrAndBind());
GetDevicesBlocking(service.get(), {device_info->guid});

mojo::Remote<device::mojom::UsbDevice> device;
Expand Down Expand Up @@ -595,14 +600,16 @@ class ChromeUsbTestHelper {
// that access is not automatically granted to it.
auto ccid_device_info =
device_manager()->AddDevice(CreateFakeSmartCardDeviceInfo());
auto hid_device_info =
device_manager()->AddDevice(CreateFakeHidDeviceInfo());
auto usb_device_info =
device_manager()->AddDevice(CreateFakeUsbDeviceInfo());

// No need to grant permission. It is granted automatically for smart
// card device.

mojo::Remote<blink::mojom::WebUsbService> service;
ConnectToService(service.BindNewPipeAndPassReceiver());
MockDeviceManagerClient mock_client;
service->SetClient(mock_client.CreateInterfacePtrAndBind());

// Check that the extensions is automatically granted access to the CCID
// device and can claim its interfaces.
Expand Down Expand Up @@ -633,16 +640,16 @@ class ChromeUsbTestHelper {
device::mojom::UsbClaimInterfaceResult::kSuccess);
}

// Check that the extension, if granted permission to a HID device can't
// Check that the extension, if granted permission to a USB device can't
// claim its interfaces.
{
GetChooserContext()->GrantDevicePermission(extension_origin,
*hid_device_info);
*usb_device_info);
GetDevicesBlocking(service.get(),
{ccid_device_info->guid, hid_device_info->guid});
{ccid_device_info->guid, usb_device_info->guid});

mojo::Remote<device::mojom::UsbDevice> device;
service->GetDevice(hid_device_info->guid,
service->GetDevice(usb_device_info->guid,
device.BindNewPipeAndPassReceiver());

TestFuture<device::mojom::UsbOpenDeviceResultPtr> open_future;
Expand Down Expand Up @@ -689,7 +696,7 @@ class ChromeUsbDelegateRenderFrameTestBase
NavigateAndCommit(origin_url_);
}

// ChromeHidTestHelper:
// ChromeUsbTestHelper:
void ConnectToService(
mojo::PendingReceiver<blink::mojom::WebUsbService> receiver) override {
SetUpFakeDeviceManager();
Expand Down Expand Up @@ -759,7 +766,7 @@ class ChromeUsbDelegateServiceWorkerTestBase
content::EmbeddedWorkerInstanceTestHarness::TearDown();
}

// ChromeHidTestHelper:
// ChromeUsbTestHelper:
void ConnectToService(
mojo::PendingReceiver<blink::mojom::WebUsbService> receiver) override {
SetUpFakeDeviceManager();
Expand Down
7 changes: 6 additions & 1 deletion chrome/browser/usb/usb_chooser_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,12 @@ void UsbChooserContext::OnDeviceManagerConnectionError() {

void UsbChooserContext::SetDeviceManagerForTesting(
mojo::PendingRemote<device::mojom::UsbDeviceManager> fake_device_manager) {
DCHECK(!device_manager_);
// `device_manager_` can be bound in some test scenarios, in that case, just
// reset the connection.
if (device_manager_) {
device_manager_.reset();
client_receiver_.reset();
}
DCHECK(fake_device_manager);
device_manager_.Bind(std::move(fake_device_manager));
SetUpDeviceManagerConnection();
Expand Down
4 changes: 4 additions & 0 deletions content/browser/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -3251,6 +3251,10 @@ source_set("browser") {
"service_worker/service_worker_hid_delegate_observer.cc",
"service_worker/service_worker_hid_delegate_observer.h",

# WebUSB on extension service workers is non-Android
"service_worker/service_worker_usb_delegate_observer.cc",
"service_worker/service_worker_usb_delegate_observer.h",

# Most speech code is non-Android.
"speech/endpointer/endpointer.cc",
"speech/endpointer/endpointer.h",
Expand Down
2 changes: 2 additions & 0 deletions content/browser/service_worker/OWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ per-file *_type_converter*.*=set noparent
per-file *_type_converter*.*=file://ipc/SECURITY_OWNERS
per-file service_worker_hid_*=file://content/browser/hid/OWNERS
per-file service_worker_device_*=file://content/browser/hid/OWNERS
per-file service_worker_usb_*=file://content/browser/usb/OWNERS
per-file service_worker_device_*=file://content/browser/usb/OWNERS
16 changes: 15 additions & 1 deletion content/browser/service_worker/service_worker_context_core.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "content/browser/service_worker/service_worker_register_job.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_security_utils.h"
#include "content/browser/service_worker/service_worker_usb_delegate_observer.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/storage_partition_impl.h"
#include "content/common/content_navigation_policy.h"
Expand Down Expand Up @@ -1314,6 +1315,19 @@ void ServiceWorkerContextCore::SetServiceWorkerHidDelegateObserverForTesting(
std::unique_ptr<ServiceWorkerHidDelegateObserver> hid_delegate_observer) {
hid_delegate_observer_ = std::move(hid_delegate_observer);
}
#endif // !BUILDFLAG(IS_ANDROID)

ServiceWorkerUsbDelegateObserver*
ServiceWorkerContextCore::usb_delegate_observer() {
if (!usb_delegate_observer_) {
usb_delegate_observer_ =
std::make_unique<ServiceWorkerUsbDelegateObserver>(this);
}
return usb_delegate_observer_.get();
}

void ServiceWorkerContextCore::SetServiceWorkerUsbDelegateObserverForTesting(
std::unique_ptr<ServiceWorkerUsbDelegateObserver> usb_delegate_observer) {
usb_delegate_observer_ = std::move(usb_delegate_observer);
}
#endif // !BUILDFLAG(IS_ANDROID)
} // namespace content
10 changes: 10 additions & 0 deletions content/browser/service_worker/service_worker_context_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class ServiceWorkerQuotaClient;
class ServiceWorkerRegistration;
#if !BUILDFLAG(IS_ANDROID)
class ServiceWorkerHidDelegateObserver;
class ServiceWorkerUsbDelegateObserver;
#endif // !BUILDFLAG(IS_ANDROID)

// This class manages data associated with service workers.
Expand Down Expand Up @@ -413,6 +414,14 @@ class CONTENT_EXPORT ServiceWorkerContextCore

void SetServiceWorkerHidDelegateObserverForTesting(
std::unique_ptr<ServiceWorkerHidDelegateObserver> hid_delegate_observer);

// In the service worker case, WebUSB is only available in extension service
// workers. Since extension isn't available in ANDROID, guard
// ServiceWorkerUsbDelegateObserver within non-android platforms.
ServiceWorkerUsbDelegateObserver* usb_delegate_observer();

void SetServiceWorkerUsbDelegateObserverForTesting(
std::unique_ptr<ServiceWorkerUsbDelegateObserver> usb_delegate_observer);
#endif // !BUILDFLAG(IS_ANDROID)

private:
Expand Down Expand Up @@ -549,6 +558,7 @@ class CONTENT_EXPORT ServiceWorkerContextCore

#if !BUILDFLAG(IS_ANDROID)
std::unique_ptr<ServiceWorkerHidDelegateObserver> hid_delegate_observer_;
std::unique_ptr<ServiceWorkerUsbDelegateObserver> usb_delegate_observer_;
#endif // !BUILDFLAG(IS_ANDROID)

base::WeakPtrFactory<ServiceWorkerContextCore> weak_factory_{this};
Expand Down
127 changes: 127 additions & 0 deletions content/browser/service_worker/service_worker_usb_delegate_observer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/service_worker/service_worker_usb_delegate_observer.h"

#include <cstdint>

#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/usb/web_usb_service_impl.h"
#include "content/public/common/content_client.h"

namespace content {

ServiceWorkerUsbDelegateObserver::ServiceWorkerUsbDelegateObserver(
ServiceWorkerContextCore* context)
: ServiceWorkerDeviceDelegateObserver(context) {}

ServiceWorkerUsbDelegateObserver::~ServiceWorkerUsbDelegateObserver() = default;

void ServiceWorkerUsbDelegateObserver::OnDeviceAdded(
const device::mojom::UsbDeviceInfo& device_info) {
DispatchUsbDeviceEventToWorkers(
device_info,
base::BindRepeating([](const device::mojom::UsbDeviceInfo& device_info,
WebUsbServiceImpl* service) {
service->OnDeviceAdded(device_info);
}));
}

void ServiceWorkerUsbDelegateObserver::OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) {
DispatchUsbDeviceEventToWorkers(
device_info,
base::BindRepeating([](const device::mojom::UsbDeviceInfo& device_info,
WebUsbServiceImpl* service) {
service->OnDeviceRemoved(device_info);
}));
}

void ServiceWorkerUsbDelegateObserver::OnDeviceManagerConnectionError() {
for (auto const& [id, info] : registration_id_map()) {
auto* usb_service = GetUsbService(id);
if (usb_service) {
usb_service->OnDeviceManagerConnectionError();
}
}
}

void ServiceWorkerUsbDelegateObserver::OnPermissionRevoked(
const url::Origin& origin) {
for (auto const& [id, info] : registration_id_map()) {
auto* usb_service = GetUsbService(id);
if (usb_service) {
usb_service->OnPermissionRevoked(origin);
}
}
}

void ServiceWorkerUsbDelegateObserver::RegisterUsbService(
int64_t registration_id,
base::WeakPtr<WebUsbServiceImpl> usb_service) {
Register(registration_id);
// `usb_services_` may already have an entry for `registration_id` in a case
// where the service worker went to sleep and now is worken up. In that
// case, the WebUsbServiceImpl from `usb_services_[registration_id]` is the
// weak ptr of previous WebUsbServiceImpl before the service worker went to
// sleep. We don't care about the previous WebUsbServiceImpl, so here just
// overwrite it with `usb_service`, which is the latest one.
usb_services_[registration_id] = usb_service;
}

void ServiceWorkerUsbDelegateObserver::RegistrationAdded(
int64_t registration_id) {
if (registration_id_map().size() == 1) {
UsbDelegate* delegate = GetContentClient()->browser()->GetUsbDelegate();
if (delegate) {
usb_delegate_observation.Observe(delegate);
}
}
}

void ServiceWorkerUsbDelegateObserver::RegistrationRemoved(
int64_t registration_id) {
if (registration_id_map().empty()) {
usb_delegate_observation.Reset();
}
}

void ServiceWorkerUsbDelegateObserver::DispatchUsbDeviceEventToWorkers(
const device::mojom::UsbDeviceInfo& device_info,
UsbServiceDeviceEventCallback callback) {
for (auto const& [id, info] : registration_id_map()) {
// No need to proceed if the registration doesn't have any event listeners.
if (!info.has_event_handlers) {
continue;
}
// Forward it to UsbService if the service worker is running, UsbService is
// available, and it has clients registered.
auto* usb_service = GetUsbService(id);
if (usb_service) {
auto version = usb_service->service_worker_version();
if (version &&
version->running_status() == EmbeddedWorkerStatus::RUNNING) {
callback.Run(device_info, usb_service);
continue;
}
}
}
}

WebUsbServiceImpl* ServiceWorkerUsbDelegateObserver::GetUsbService(
int64_t registration_id) {
auto it = usb_services_.find(registration_id);
if (it == usb_services_.end()) {
return nullptr;
}
return it->second.get();
}

} // namespace content

0 comments on commit 62cce61

Please sign in to comment.