blob: 4faf249be6f2760251ba9cc91c502aa3c4c5e7d6 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/webusb/USB.h"
#include "bindings/core/v8/ScriptPromise.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/dom/DOMException.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
#include "device/usb/public/interfaces/device.mojom-blink.h"
#include "modules/EventTargetModules.h"
#include "modules/webusb/USBConnectionEvent.h"
#include "modules/webusb/USBDevice.h"
#include "modules/webusb/USBDeviceFilter.h"
#include "modules/webusb/USBDeviceRequestOptions.h"
#include "platform/UserGestureIndicator.h"
#include "platform/mojo/MojoHelper.h"
#include "platform/wtf/Functional.h"
#include "public/platform/InterfaceProvider.h"
#include "public/platform/Platform.h"
using device::mojom::blink::UsbDeviceFilterPtr;
using device::mojom::blink::UsbDeviceInfoPtr;
using device::mojom::blink::UsbDevicePtr;
namespace blink {
namespace {
const char kNoDeviceSelected[] = "No device selected.";
UsbDeviceFilterPtr ConvertDeviceFilter(const USBDeviceFilter& filter) {
auto mojo_filter = device::mojom::blink::UsbDeviceFilter::New();
mojo_filter->has_vendor_id = filter.hasVendorId();
if (mojo_filter->has_vendor_id)
mojo_filter->vendor_id = filter.vendorId();
mojo_filter->has_product_id = filter.hasProductId();
if (mojo_filter->has_product_id)
mojo_filter->product_id = filter.productId();
mojo_filter->has_class_code = filter.hasClassCode();
if (mojo_filter->has_class_code)
mojo_filter->class_code = filter.classCode();
mojo_filter->has_subclass_code = filter.hasSubclassCode();
if (mojo_filter->has_subclass_code)
mojo_filter->subclass_code = filter.subclassCode();
mojo_filter->has_protocol_code = filter.hasProtocolCode();
if (mojo_filter->has_protocol_code)
mojo_filter->protocol_code = filter.protocolCode();
if (filter.hasSerialNumber())
mojo_filter->serial_number = filter.serialNumber();
return mojo_filter;
}
} // namespace
USB::USB(LocalFrame& frame)
: ContextLifecycleObserver(frame.GetDocument()), client_binding_(this) {}
USB::~USB() {
// |m_deviceManager| and |m_chooserService| may still be valid but there
// should be no more outstanding requests to them because each holds a
// persistent handle to this object.
DCHECK(device_manager_requests_.IsEmpty());
DCHECK(chooser_service_requests_.IsEmpty());
}
void USB::Dispose() {
// The pipe to this object must be closed when is marked unreachable to
// prevent messages from being dispatched before lazy sweeping.
client_binding_.Close();
}
ScriptPromise USB::getDevices(ScriptState* script_state) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
EnsureDeviceManagerConnection();
if (!device_manager_) {
resolver->Reject(DOMException::Create(kNotSupportedError));
} else {
device_manager_requests_.insert(resolver);
device_manager_->GetDevices(
nullptr, ConvertToBaseCallback(WTF::Bind(&USB::OnGetDevices,
WrapPersistent(this),
WrapPersistent(resolver))));
}
return promise;
}
ScriptPromise USB::requestDevice(ScriptState* script_state,
const USBDeviceRequestOptions& options) {
ScriptPromiseResolver* resolver = ScriptPromiseResolver::Create(script_state);
ScriptPromise promise = resolver->Promise();
if (!chooser_service_) {
if (!GetFrame()) {
resolver->Reject(DOMException::Create(kNotSupportedError));
return promise;
}
GetFrame()->GetInterfaceProvider()->GetInterface(
mojo::MakeRequest(&chooser_service_));
chooser_service_.set_connection_error_handler(
ConvertToBaseCallback(WTF::Bind(&USB::OnChooserServiceConnectionError,
WrapWeakPersistent(this))));
}
if (!UserGestureIndicator::ConsumeUserGesture()) {
resolver->Reject(DOMException::Create(
kSecurityError,
"Must be handling a user gesture to show a permission request."));
} else {
Vector<UsbDeviceFilterPtr> filters;
if (options.hasFilters()) {
filters.ReserveCapacity(options.filters().size());
for (const auto& filter : options.filters())
filters.push_back(ConvertDeviceFilter(filter));
}
chooser_service_requests_.insert(resolver);
chooser_service_->GetPermission(
std::move(filters), ConvertToBaseCallback(WTF::Bind(
&USB::OnGetPermission, WrapPersistent(this),
WrapPersistent(resolver))));
}
return promise;
}
ExecutionContext* USB::GetExecutionContext() const {
return ContextLifecycleObserver::GetExecutionContext();
}
const AtomicString& USB::InterfaceName() const {
return EventTargetNames::USB;
}
void USB::ContextDestroyed(ExecutionContext*) {
device_manager_.reset();
device_manager_requests_.Clear();
chooser_service_.reset();
chooser_service_requests_.Clear();
}
USBDevice* USB::GetOrCreateDevice(UsbDeviceInfoPtr device_info) {
USBDevice* device = device_cache_.at(device_info->guid);
if (!device) {
String guid = device_info->guid;
UsbDevicePtr pipe;
device_manager_->GetDevice(guid, mojo::MakeRequest(&pipe));
device = USBDevice::Create(std::move(device_info), std::move(pipe),
GetExecutionContext());
device_cache_.insert(guid, device);
}
return device;
}
void USB::OnGetDevices(ScriptPromiseResolver* resolver,
Vector<UsbDeviceInfoPtr> device_infos) {
auto request_entry = device_manager_requests_.Find(resolver);
if (request_entry == device_manager_requests_.end())
return;
device_manager_requests_.erase(request_entry);
HeapVector<Member<USBDevice>> devices;
for (auto& device_info : device_infos)
devices.push_back(GetOrCreateDevice(std::move(device_info)));
resolver->Resolve(devices);
device_manager_requests_.erase(resolver);
}
void USB::OnGetPermission(ScriptPromiseResolver* resolver,
UsbDeviceInfoPtr device_info) {
auto request_entry = chooser_service_requests_.Find(resolver);
if (request_entry == chooser_service_requests_.end())
return;
chooser_service_requests_.erase(request_entry);
EnsureDeviceManagerConnection();
if (device_manager_ && device_info) {
resolver->Resolve(GetOrCreateDevice(std::move(device_info)));
} else {
resolver->Reject(DOMException::Create(kNotFoundError, kNoDeviceSelected));
}
}
void USB::OnDeviceAdded(UsbDeviceInfoPtr device_info) {
if (!device_manager_)
return;
DispatchEvent(USBConnectionEvent::Create(
EventTypeNames::connect, GetOrCreateDevice(std::move(device_info))));
}
void USB::OnDeviceRemoved(UsbDeviceInfoPtr device_info) {
String guid = device_info->guid;
USBDevice* device = device_cache_.at(guid);
if (!device) {
device = USBDevice::Create(std::move(device_info), nullptr,
GetExecutionContext());
}
DispatchEvent(USBConnectionEvent::Create(EventTypeNames::disconnect, device));
device_cache_.erase(guid);
}
void USB::OnDeviceManagerConnectionError() {
device_manager_.reset();
client_binding_.Close();
for (ScriptPromiseResolver* resolver : device_manager_requests_)
resolver->Resolve(HeapVector<Member<USBDevice>>(0));
device_manager_requests_.Clear();
}
void USB::OnChooserServiceConnectionError() {
chooser_service_.reset();
for (ScriptPromiseResolver* resolver : chooser_service_requests_)
resolver->Reject(DOMException::Create(kNotFoundError, kNoDeviceSelected));
chooser_service_requests_.Clear();
}
void USB::AddedEventListener(const AtomicString& event_type,
RegisteredEventListener& listener) {
EventTargetWithInlineData::AddedEventListener(event_type, listener);
if (event_type == EventTypeNames::connect ||
event_type == EventTypeNames::disconnect) {
EnsureDeviceManagerConnection();
}
}
void USB::EnsureDeviceManagerConnection() {
if (device_manager_ || !GetFrame())
return;
GetFrame()->GetInterfaceProvider()->GetInterface(
mojo::MakeRequest(&device_manager_));
device_manager_.set_connection_error_handler(ConvertToBaseCallback(WTF::Bind(
&USB::OnDeviceManagerConnectionError, WrapWeakPersistent(this))));
DCHECK(!client_binding_.is_bound());
device_manager_->SetClient(client_binding_.CreateInterfacePtrAndBind());
}
DEFINE_TRACE(USB) {
visitor->Trace(device_manager_requests_);
visitor->Trace(chooser_service_requests_);
visitor->Trace(device_cache_);
EventTargetWithInlineData::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
}
} // namespace blink