WebUSB initial commit

This commit is contained in:
wuggy 2026-09-19 11:35:20 -07:00
commit b898673be8
22 changed files with 2231 additions and 1 deletions

View file

@ -2241,6 +2241,9 @@ const ContentPermissionIntegration = {
case "geolocation": {
return new PermissionUI.GeolocationPermissionPrompt(request);
}
case "usb": {
return new PermissionUI.USBPermissionPrompt(request);
}
case "desktop-notification": {
return new PermissionUI.DesktopNotificationPermissionPrompt(request);
}

View file

@ -27,6 +27,9 @@
<preference id="javascript.options.wasm"
name="javascript.options.wasm"
type="bool"/>
<preference id="dom.usb.enabled"
name="dom.usb.enabled"
type="bool"/>
#ifdef MOZ_WEBRTC
<preference id="media.peerconnection.enabled"
name="media.peerconnection.enabled"
@ -246,6 +249,14 @@
accesskey="&javascriptWasm.accesskey;" />
</vbox>
</row>
<row id="javascriptWebUSBRow">
<vbox align="start">
<checkbox id="javascriptWebUSBPolicy"
preference="dom.usb.enabled"
label="&javascriptWebusb.label;"
accesskey="&javascriptWebusb.accesskey;" />
</vbox>
</row>
#ifdef MOZ_WEBRTC
<row id="javascriptRow2">
<vbox align="start">

View file

@ -367,6 +367,12 @@ geolocation.neverShareLocation.accesskey=N
geolocation.shareWithSite2=Would you like to share your location with this site?
geolocation.shareWithFile2=Would you like to share your location with this file?
webUSB.connectToSite=Would you like to let this site access USB devices?
webUSB.allow=Allow USB Devices
webUSB.allow.accesskey=A
webUSB.block=Block USB Devices
webUSB.block.accesskey=B
webNotifications.receiveForSession=Receive for this session
webNotifications.receiveForSession.accesskey=s
webNotifications.alwaysReceive=Always Receive Notifications

View file

@ -48,6 +48,8 @@
<!ENTITY javascriptWasm.label "Enable WebAssembly (WASM)">
<!ENTITY javascriptWasm.accesskey "y">
<!ENTITY javascriptWebusb.label "Enable WebUSB">
<!ENTITY javascriptWebusb.accesskey "U">
#ifdef MOZ_WEBRTC
<!ENTITY javascriptWebrtc.label "Enable WebRTC">
<!ENTITY javascriptWebrtc.accesskey "R">

View file

@ -472,6 +472,59 @@ GeolocationPermissionPrompt.prototype = {
PermissionUI.GeolocationPermissionPrompt = GeolocationPermissionPrompt;
/**
* Creates a PermissionPrompt for the WebUSB API.
* USB access is granted for the current browser session.
*/
function getUSBString(name, fallback) {
try {
return gBrowserBundle.GetStringFromName(name);
} catch (ex) {
return fallback;
}
}
function USBPermissionPrompt(request) {
this.request = request;
}
USBPermissionPrompt.prototype = {
__proto__: PermissionPromptForRequestPrototype,
get permissionKey() {
return "usb";
},
get notificationID() {
return "web-usb";
},
get anchorID() {
return "default-notification-icon";
},
get message() {
return getUSBString("webUSB.connectToSite",
"Would you like to let this site access USB devices?");
},
get promptActions() {
return [{
label: getUSBString("webUSB.allow", "Allow USB Devices"),
accessKey: getUSBString("webUSB.allow.accesskey", "A"),
action: Ci.nsIPermissionManager.ALLOW_ACTION,
expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
}, {
label: getUSBString("webUSB.block", "Block USB Devices"),
accessKey: getUSBString("webUSB.block.accesskey", "B"),
action: Ci.nsIPermissionManager.DENY_ACTION,
expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
}];
},
};
PermissionUI.USBPermissionPrompt = USBPermissionPrompt;
/**
* Creates a PermissionPrompt for a nsIContentPermissionRequest for
* the Desktop Notification API.

View file

@ -40,6 +40,7 @@
#include "mozilla/dom/Permissions.h"
#include "mozilla/dom/ServiceWorkerContainer.h"
#include "mozilla/dom/StorageManager.h"
#include "mozilla/dom/USB.h"
#include "mozilla/dom/TCPSocket.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/workers/RuntimeService.h"
@ -189,6 +190,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPowerManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUSB)
#ifdef MOZ_AUDIO_CHANNEL_MANAGER
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelManager)
#endif
@ -224,6 +226,7 @@ Navigator::Invalidate()
mPermissions = nullptr;
mStorageManager = nullptr;
mUSB = nullptr;
// If there is a page transition, make sure delete the geolocation object.
if (mGeolocation) {
@ -547,6 +550,21 @@ Navigator::Storage()
return mStorageManager;
}
USB*
Navigator::GetUsb()
{
MOZ_ASSERT(mWindow);
if (!mUSB) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
MOZ_ASSERT(global);
mUSB = new USB(global);
}
return mUSB;
}
// Values for the network.cookie.cookieBehavior pref are documented in
// nsCookieService.cpp.
#define COOKIE_BEHAVIOR_REJECT 2

View file

@ -72,6 +72,7 @@ class Connection;
class PowerManager;
class LegacyMozTCPSocket;
class StorageManager;
class USB;
namespace time {
class TimeManager;
@ -226,6 +227,7 @@ public:
bool MozE10sEnabled();
StorageManager* Storage();
USB* GetUsb();
static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
@ -298,6 +300,7 @@ private:
nsTArray<RefPtr<Promise> > mVRGetDisplaysPromises;
nsTArray<uint32_t> mRequestedVibrationPattern;
RefPtr<StorageManager> mStorageManager;
RefPtr<USB> mUSB;
};
} // namespace dom

View file

@ -93,6 +93,7 @@ DIRS += [
'xul',
'manifest',
'u2f',
'usb',
'console',
'performance',
'xhr',
@ -113,4 +114,3 @@ TEST_DIRS += [
if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'windows', 'android', 'cocoa'):
TEST_DIRS += ['plugins/test']

297
dom/usb/USB.cpp Normal file
View file

@ -0,0 +1,297 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
#include "USB.h"
#include "mozilla/dom/USBBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/USBDevice.h"
#include "nsContentPermissionHelper.h"
#include "nsIDocument.h"
#include "nsError.h"
#include "nsPIDOMWindow.h"
#include "nsTArray.h"
namespace mozilla {
namespace dom {
namespace {
bool
MatchesFilter(const USBDeviceInfo& aDevice, const USBDeviceFilter& aFilter)
{
if (aFilter.mVendorId.WasPassed() &&
aFilter.mVendorId.Value() != aDevice.mVendorId) {
return false;
}
if (aFilter.mProductId.WasPassed() &&
aFilter.mProductId.Value() != aDevice.mProductId) {
return false;
}
if (aFilter.mClassCode.WasPassed() &&
aFilter.mClassCode.Value() != aDevice.mDeviceClass) {
return false;
}
if (aFilter.mSubclassCode.WasPassed() &&
aFilter.mSubclassCode.Value() != aDevice.mDeviceSubclass) {
return false;
}
if (aFilter.mProtocolCode.WasPassed() &&
aFilter.mProtocolCode.Value() != aDevice.mDeviceProtocol) {
return false;
}
if (aFilter.mSerialNumber.WasPassed() &&
!aFilter.mSerialNumber.Value().Equals(aDevice.mSerialNumber)) {
return false;
}
return true;
}
bool
MatchesRequest(const USBDeviceInfo& aDevice,
const USBDeviceRequestOptions& aOptions)
{
bool included = false;
for (const auto& filter : aOptions.mFilters) {
if (MatchesFilter(aDevice, filter)) {
included = true;
break;
}
}
if (!included) {
return false;
}
for (const auto& filter : aOptions.mExclusionFilters) {
if (MatchesFilter(aDevice, filter)) {
return false;
}
}
return true;
}
class USBPermissionRequest final : public nsIContentPermissionRequest
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSICONTENTPERMISSIONREQUEST
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(USBPermissionRequest,
nsIContentPermissionRequest)
USBPermissionRequest(USB* aUSB,
Promise* aPromise,
nsPIDOMWindowInner* aWindow,
nsTArray<RefPtr<USBDevice>>&& aCandidates)
: mUSB(aUSB)
, mPromise(aPromise)
, mWindow(aWindow)
, mCandidates(Move(aCandidates))
, mRequester(new nsContentPermissionRequester(aWindow))
{
}
private:
~USBPermissionRequest() = default;
RefPtr<USB> mUSB;
RefPtr<Promise> mPromise;
nsCOMPtr<nsPIDOMWindowInner> mWindow;
nsTArray<RefPtr<USBDevice>> mCandidates;
RefPtr<nsContentPermissionRequester> mRequester;
};
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USBPermissionRequest)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(USBPermissionRequest)
NS_IMPL_CYCLE_COLLECTING_RELEASE(USBPermissionRequest)
NS_IMPL_CYCLE_COLLECTION(USBPermissionRequest, mUSB, mPromise, mWindow,
mCandidates, mRequester)
NS_IMETHODIMP
USBPermissionRequest::GetPrincipal(nsIPrincipal** aPrincipal)
{
NS_ENSURE_ARG_POINTER(aPrincipal);
*aPrincipal = nullptr;
if (!mWindow || !mWindow->GetDoc()) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPrincipal> principal = mWindow->GetDoc()->NodePrincipal();
principal.forget(aPrincipal);
return NS_OK;
}
NS_IMETHODIMP
USBPermissionRequest::GetTypes(nsIArray** aTypes)
{
nsTArray<nsString> options;
for (const auto& device : mCandidates) {
Nullable<nsString> label;
device->GetProductName(label);
if (label.IsNull() || label.Value().IsEmpty()) {
options.AppendElement(NS_LITERAL_STRING("USB device"));
} else {
options.AppendElement(label.Value());
}
}
return nsContentPermissionUtils::CreatePermissionArray(
NS_LITERAL_CSTRING("usb"), NS_LITERAL_CSTRING("device"), options, aTypes);
}
NS_IMETHODIMP
USBPermissionRequest::GetWindow(mozIDOMWindow** aWindow)
{
NS_ENSURE_ARG_POINTER(aWindow);
nsCOMPtr<mozIDOMWindow> window = do_QueryInterface(mWindow);
window.forget(aWindow);
return NS_OK;
}
NS_IMETHODIMP
USBPermissionRequest::GetElement(nsIDOMElement** aElement)
{
NS_ENSURE_ARG_POINTER(aElement);
*aElement = nullptr;
return NS_OK;
}
NS_IMETHODIMP
USBPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
{
NS_ENSURE_ARG_POINTER(aRequester);
nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
requester.forget(aRequester);
return NS_OK;
}
NS_IMETHODIMP
USBPermissionRequest::Cancel()
{
mPromise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
return NS_OK;
}
NS_IMETHODIMP
USBPermissionRequest::Allow(JS::HandleValue aChoices)
{
(void)aChoices;
if (mCandidates.IsEmpty()) {
mPromise->MaybeReject(NS_ERROR_DOM_NOT_FOUND_ERR);
return NS_OK;
}
RefPtr<USBDevice> device = mCandidates[0];
mUSB->AddAuthorizedDevice(device);
mPromise->MaybeResolve(device);
return NS_OK;
}
} // anonymous namespace
NS_IMPL_CYCLE_COLLECTION_INHERITED(USB, DOMEventTargetHelper,
mAuthorizedDevices)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USB)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(USB, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(USB, DOMEventTargetHelper)
USB::USB(nsIGlobalObject* aOwner)
: DOMEventTargetHelper(aOwner)
{
}
USB::~USB()
{
for (const auto& device : mAuthorizedDevices) {
device->ClearUSB();
}
}
void
USB::AddAuthorizedDevice(USBDevice* aDevice)
{
if (!aDevice || mAuthorizedDevices.Contains(aDevice)) {
return;
}
aDevice->SetUSB(this);
mAuthorizedDevices.AppendElement(aDevice);
}
void
USB::RemoveAuthorizedDevice(USBDevice* aDevice)
{
if (!aDevice) {
return;
}
aDevice->ClearUSB();
mAuthorizedDevices.RemoveElement(aDevice);
}
JSObject*
USB::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return USBBinding::Wrap(aCx, this, aGivenProto);
}
already_AddRefed<Promise>
USB::GetDevices(ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
promise->MaybeResolve(mAuthorizedDevices);
return promise.forget();
}
already_AddRefed<Promise>
USB::RequestDevice(const USBDeviceRequestOptions& aOptions, ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
nsTArray<USBDeviceInfo> infos;
nsresult rv = EnumerateUSBDevices(infos);
if (NS_FAILED(rv)) {
promise->MaybeReject(rv);
return promise.forget();
}
nsTArray<RefPtr<USBDevice>> candidates;
for (const auto& info : infos) {
if (!MatchesRequest(info, aOptions)) {
continue;
}
RefPtr<USBDevice> device = new USBDevice(GetOwnerGlobal());
device->SetDeviceInfo(info);
candidates.AppendElement(device);
}
if (candidates.IsEmpty()) {
promise->MaybeReject(NS_ERROR_DOM_NOT_FOUND_ERR);
return promise.forget();
}
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetOwnerGlobal());
if (!window) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
RefPtr<USBPermissionRequest> request =
new USBPermissionRequest(this, promise, window, Move(candidates));
rv = nsContentPermissionUtils::AskPermission(request, window);
if (NS_FAILED(rv)) {
promise->MaybeReject(rv);
}
return promise.forget();
}
} // namespace dom
} // namespace mozilla

48
dom/usb/USB.h Normal file
View file

@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
#ifndef mozilla_dom_USB_h
#define mozilla_dom_USB_h
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/USBBackend.h"
#include "nsTArray.h"
namespace mozilla {
namespace dom {
class Promise;
class USBDevice;
struct USBDeviceRequestOptions;
class USB final : public DOMEventTargetHelper
{
public:
explicit USB(nsIGlobalObject* aOwner);
IMPL_EVENT_HANDLER(connect)
IMPL_EVENT_HANDLER(disconnect)
already_AddRefed<Promise> GetDevices(ErrorResult& aRv);
already_AddRefed<Promise> RequestDevice(
const USBDeviceRequestOptions& aOptions, ErrorResult& aRv);
void AddAuthorizedDevice(USBDevice* aDevice);
void RemoveAuthorizedDevice(USBDevice* aDevice);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(USB, DOMEventTargetHelper)
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
private:
~USB();
nsTArray<RefPtr<USBDevice>> mAuthorizedDevices;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_USB_h

27
dom/usb/USBBackend.cpp Normal file
View file

@ -0,0 +1,27 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
#include "USBBackend.h"
#include "nsError.h"
namespace mozilla {
namespace dom {
#ifndef XP_WIN
nsresult
EnumerateUSBDevices(nsTArray<USBDeviceInfo>& aDevices)
{
aDevices.Clear();
return NS_ERROR_NOT_IMPLEMENTED;
}
already_AddRefed<USBDeviceHandle>
CreateUSBDeviceHandle(const nsAString& aDevicePath)
{
(void)aDevicePath;
return nullptr;
}
#endif
} // namespace dom
} // namespace mozilla

109
dom/usb/USBBackend.h Normal file
View file

@ -0,0 +1,109 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
#ifndef mozilla_dom_USBBackend_h
#define mozilla_dom_USBBackend_h
#include <stdint.h>
#include "nsError.h"
#include "nsISupports.h"
#include "nsString.h"
#include "nsTArray.h"
namespace mozilla {
namespace dom {
struct USBDeviceInfo {
uint16_t mVendorId = 0;
uint16_t mProductId = 0;
uint8_t mUSBVersionMajor = 0;
uint8_t mUSBVersionMinor = 0;
uint8_t mUSBVersionSubminor = 0;
uint8_t mDeviceClass = 0;
uint8_t mDeviceSubclass = 0;
uint8_t mDeviceProtocol = 0;
uint8_t mDeviceVersionMajor = 0;
uint8_t mDeviceVersionMinor = 0;
uint8_t mDeviceVersionSubminor = 0;
nsString mManufacturerName;
nsString mProductName;
nsString mSerialNumber;
nsString mDevicePath;
};
struct USBEndpointInfo {
uint8_t mNumber = 0;
uint8_t mDirection = 0; // 0 = in, 1 = out
uint8_t mType = 0; // 1 = isochronous, 2 = bulk, 3 = interrupt
uint16_t mPacketSize = 0;
};
struct USBAlternateInterfaceInfo {
uint8_t mSetting = 0;
uint8_t mClass = 0;
uint8_t mSubclass = 0;
uint8_t mProtocol = 0;
nsTArray<USBEndpointInfo> mEndpoints;
};
struct USBInterfaceInfo {
uint8_t mNumber = 0;
nsTArray<USBAlternateInterfaceInfo> mAlternates;
};
struct USBConfigurationInfo {
uint8_t mValue = 0;
nsTArray<USBInterfaceInfo> mInterfaces;
};
struct USBControlTransferInfo {
uint8_t mRequestType = 0;
uint8_t mRecipient = 0;
uint8_t mRequest = 0;
uint16_t mValue = 0;
uint16_t mIndex = 0;
};
class USBDeviceHandle : public nsISupports
{
public:
NS_DECL_ISUPPORTS
virtual nsresult Open() = 0;
virtual void Close() = 0;
virtual nsresult GetConfigurations(
nsTArray<USBConfigurationInfo>& aConfigurations) = 0;
virtual nsresult SelectConfiguration(uint8_t aConfigurationValue) = 0;
virtual nsresult ClaimInterface(uint8_t aInterfaceNumber) = 0;
virtual nsresult ReleaseInterface(uint8_t aInterfaceNumber) = 0;
virtual nsresult SelectAlternateInterface(uint8_t aInterfaceNumber,
uint8_t aAlternateSetting) = 0;
virtual nsresult ClearHalt(bool aIn, uint8_t aEndpointNumber) = 0;
virtual nsresult Reset() = 0;
virtual nsresult ControlTransferIn(const USBControlTransferInfo& aSetup,
uint16_t aLength,
nsTArray<uint8_t>& aData) = 0;
virtual nsresult ControlTransferOut(const USBControlTransferInfo& aSetup,
const nsTArray<uint8_t>& aData,
uint32_t& aWritten) = 0;
virtual nsresult TransferIn(uint8_t aEndpoint, uint32_t aLength,
nsTArray<uint8_t>& aData) = 0;
virtual nsresult TransferOut(uint8_t aEndpoint,
const nsTArray<uint8_t>& aData,
uint32_t& aWritten) = 0;
protected:
virtual ~USBDeviceHandle() = default;
};
// Enumeration returns stable device metadata and a native path. Native
// handles are created only after the caller has obtained permission.
nsresult EnumerateUSBDevices(nsTArray<USBDeviceInfo>& aDevices);
already_AddRefed<USBDeviceHandle>
CreateUSBDeviceHandle(const nsAString& aDevicePath);
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_USBBackend_h

471
dom/usb/USBBackendWin.cpp Normal file
View file

@ -0,0 +1,471 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
#include "USBBackend.h"
#ifdef XP_WIN
#include <setupapi.h>
#include <string.h>
#include <windows.h>
#include <winusb.h>
#include "mozilla/fallible.h"
#include "nsError.h"
namespace mozilla {
namespace dom {
namespace {
const GUID kUSBDeviceInterface = {
0xA5DCBF10, 0x6530, 0x11D2,
{ 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED }
};
class WinUSBDeviceHandle final : public USBDeviceHandle
{
public:
explicit WinUSBDeviceHandle(const nsAString& aPath)
: mPath(aPath)
, mDevice(INVALID_HANDLE_VALUE)
, mInterface(nullptr)
{
}
NS_DECL_ISUPPORTS
nsresult Open() override
{
if (mPath.IsEmpty()) {
return NS_ERROR_INVALID_ARG;
}
mDevice = CreateFileW(reinterpret_cast<const wchar_t*>(mPath.get()),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
nullptr);
if (mDevice == INVALID_HANDLE_VALUE) {
return NS_ERROR_FAILURE;
}
if (!WinUsb_Initialize(mDevice, &mInterface)) {
CloseHandle(mDevice);
mDevice = INVALID_HANDLE_VALUE;
return NS_ERROR_FAILURE;
}
return NS_OK;
}
void Close() override
{
if (mInterface) {
WinUsb_Free(mInterface);
mInterface = nullptr;
}
if (mDevice != INVALID_HANDLE_VALUE) {
CloseHandle(mDevice);
mDevice = INVALID_HANDLE_VALUE;
}
}
nsresult GetConfigurations(
nsTArray<USBConfigurationInfo>& aConfigurations) override
{
aConfigurations.Clear();
if (!mInterface) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
USB_DEVICE_DESCRIPTOR deviceDescriptor;
ULONG transferred = 0;
if (!WinUsb_GetDescriptor(mInterface, USB_DEVICE_DESCRIPTOR_TYPE, 0, 0,
reinterpret_cast<PUCHAR>(&deviceDescriptor),
sizeof(deviceDescriptor), &transferred) ||
transferred < sizeof(deviceDescriptor)) {
return NS_ERROR_FAILURE;
}
for (uint8_t index = 0; index < deviceDescriptor.bNumConfigurations;
++index) {
uint8_t header[9];
if (!WinUsb_GetDescriptor(mInterface, USB_CONFIGURATION_DESCRIPTOR_TYPE,
index, 0, header, sizeof(header), &transferred) ||
transferred < sizeof(header)) {
continue;
}
uint16_t totalLength = static_cast<uint16_t>(header[2] |
(header[3] << 8));
if (totalLength < sizeof(header)) {
continue;
}
nsTArray<uint8_t> descriptor;
if (!descriptor.SetLength(totalLength, mozilla::fallible) ||
!WinUsb_GetDescriptor(mInterface, USB_CONFIGURATION_DESCRIPTOR_TYPE,
index, 0, descriptor.Elements(), totalLength,
&transferred) ||
transferred < sizeof(header)) {
continue;
}
USBConfigurationInfo configuration;
configuration.mValue = descriptor[5];
USBInterfaceInfo* currentInterface = nullptr;
USBAlternateInterfaceInfo* currentAlternate = nullptr;
uint32_t offset = descriptor[0];
while (offset + 2 <= transferred) {
uint8_t length = descriptor[offset];
uint8_t type = descriptor[offset + 1];
if (!length || offset + length > transferred) {
break;
}
if (type == USB_INTERFACE_DESCRIPTOR_TYPE && length >= 9) {
USBInterfaceInfo interfaceInfo;
interfaceInfo.mNumber = descriptor[offset + 2];
USBAlternateInterfaceInfo alternate;
alternate.mSetting = descriptor[offset + 3];
alternate.mClass = descriptor[offset + 5];
alternate.mSubclass = descriptor[offset + 6];
alternate.mProtocol = descriptor[offset + 7];
configuration.mInterfaces.AppendElement(Move(interfaceInfo));
currentInterface = &configuration.mInterfaces.LastElement();
currentInterface->mAlternates.AppendElement(Move(alternate));
currentAlternate = &currentInterface->mAlternates.LastElement();
} else if (type == USB_ENDPOINT_DESCRIPTOR_TYPE && length >= 7 &&
currentAlternate) {
USBEndpointInfo endpoint;
uint8_t address = descriptor[offset + 2];
endpoint.mNumber = address & 0x0f;
endpoint.mDirection = (address & 0x80) ? 0 : 1;
endpoint.mType = descriptor[offset + 3] & 0x03;
endpoint.mPacketSize = static_cast<uint16_t>(
descriptor[offset + 4] | (descriptor[offset + 5] << 8));
if (endpoint.mType) {
currentAlternate->mEndpoints.AppendElement(Move(endpoint));
}
}
offset += length;
}
aConfigurations.AppendElement(Move(configuration));
}
return NS_OK;
}
nsresult SelectConfiguration(uint8_t aConfigurationValue) override
{
if (!mInterface) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
WINUSB_SETUP_PACKET setup;
setup.RequestType = 0x00; // Host-to-device, standard, device.
setup.Request = 9; // SET_CONFIGURATION.
setup.Value = aConfigurationValue;
setup.Index = 0;
setup.Length = 0;
ULONG transferred = 0;
return WinUsb_ControlTransfer(mInterface, setup, nullptr, 0,
&transferred, nullptr)
? NS_OK : NS_ERROR_FAILURE;
}
nsresult ClaimInterface(uint8_t aInterfaceNumber) override
{
return mInterface && WinUsb_ClaimInterface(mInterface, aInterfaceNumber)
? NS_OK : NS_ERROR_FAILURE;
}
nsresult ReleaseInterface(uint8_t aInterfaceNumber) override
{
return mInterface && WinUsb_ReleaseInterface(mInterface, aInterfaceNumber)
? NS_OK : NS_ERROR_FAILURE;
}
nsresult SelectAlternateInterface(uint8_t aInterfaceNumber,
uint8_t aAlternateSetting) override
{
return mInterface &&
WinUsb_SetCurrentAlternateSetting(mInterface, aInterfaceNumber,
aAlternateSetting)
? NS_OK : NS_ERROR_FAILURE;
}
nsresult ClearHalt(bool aIn, uint8_t aEndpointNumber) override
{
uint8_t pipe = aEndpointNumber & 0x0f;
if (aIn) {
pipe |= 0x80;
}
return mInterface && WinUsb_ResetPipe(mInterface, pipe)
? NS_OK : NS_ERROR_FAILURE;
}
nsresult Reset() override
{
return mInterface && WinUsb_ResetDevice(mInterface)
? NS_OK : NS_ERROR_FAILURE;
}
nsresult ControlTransferIn(const USBControlTransferInfo& aSetup,
uint16_t aLength,
nsTArray<uint8_t>& aData) override
{
if (!mInterface) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
aData.Clear();
if (!aData.SetLength(aLength, mozilla::fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
WINUSB_SETUP_PACKET setup;
setup.RequestType = static_cast<UCHAR>(0x80 |
((aSetup.mRequestType & 0x03) << 5) | (aSetup.mRecipient & 0x1f));
setup.Request = aSetup.mRequest;
setup.Value = aSetup.mValue;
setup.Index = aSetup.mIndex;
setup.Length = aLength;
ULONG transferred = 0;
if (!WinUsb_ControlTransfer(mInterface, setup, aData.Elements(), aLength,
&transferred, nullptr)) {
aData.Clear();
return NS_ERROR_FAILURE;
}
aData.SetLength(transferred);
return NS_OK;
}
nsresult ControlTransferOut(const USBControlTransferInfo& aSetup,
const nsTArray<uint8_t>& aData,
uint32_t& aWritten) override
{
aWritten = 0;
if (!mInterface) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
WINUSB_SETUP_PACKET setup;
setup.RequestType = static_cast<UCHAR>(
((aSetup.mRequestType & 0x03) << 5) | (aSetup.mRecipient & 0x1f));
setup.Request = aSetup.mRequest;
setup.Value = aSetup.mValue;
setup.Index = aSetup.mIndex;
setup.Length = static_cast<USHORT>(aData.Length());
ULONG transferred = 0;
PUCHAR data = aData.IsEmpty()
? nullptr : const_cast<PUCHAR>(aData.Elements());
if (!WinUsb_ControlTransfer(mInterface, setup, data, aData.Length(),
&transferred, nullptr)) {
return NS_ERROR_FAILURE;
}
aWritten = transferred;
return NS_OK;
}
nsresult TransferIn(uint8_t aEndpoint, uint32_t aLength,
nsTArray<uint8_t>& aData) override
{
if (!mInterface) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
if (!aData.SetLength(aLength, mozilla::fallible)) {
return NS_ERROR_OUT_OF_MEMORY;
}
ULONG transferred = 0;
if (!WinUsb_ReadPipe(mInterface, aEndpoint | 0x80, aData.Elements(),
aLength, &transferred, nullptr)) {
aData.Clear();
return NS_ERROR_FAILURE;
}
aData.SetLength(transferred);
return NS_OK;
}
nsresult TransferOut(uint8_t aEndpoint, const nsTArray<uint8_t>& aData,
uint32_t& aWritten) override
{
aWritten = 0;
if (!mInterface) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
ULONG transferred = 0;
PUCHAR data = aData.IsEmpty()
? nullptr : const_cast<PUCHAR>(aData.Elements());
if (!WinUsb_WritePipe(mInterface, aEndpoint, data, aData.Length(),
&transferred, nullptr)) {
return NS_ERROR_FAILURE;
}
aWritten = transferred;
return NS_OK;
}
private:
~WinUSBDeviceHandle() override
{
Close();
}
nsString mPath;
HANDLE mDevice;
WINUSB_INTERFACE_HANDLE mInterface;
};
NS_IMPL_ISUPPORTS(WinUSBDeviceHandle, USBDeviceHandle)
bool
ParseHex(const wchar_t* aValue, uint32_t aLength, uint16_t& aResult)
{
uint32_t value = 0;
for (uint32_t i = 0; i < aLength; ++i) {
wchar_t c = aValue[i];
uint32_t digit;
if (c >= L'0' && c <= L'9') {
digit = c - L'0';
} else if (c >= L'A' && c <= L'F') {
digit = c - L'A' + 10;
} else if (c >= L'a' && c <= L'f') {
digit = c - L'a' + 10;
} else {
return false;
}
value = (value << 4) | digit;
}
if (value > 0xffff) {
return false;
}
aResult = static_cast<uint16_t>(value);
return true;
}
void
CopyRegistryString(HDEVINFO aDevices,
SP_DEVINFO_DATA& aDevice,
DWORD aProperty,
nsString& aValue)
{
DWORD type = 0;
DWORD size = 0;
if (!SetupDiGetDeviceRegistryPropertyW(aDevices, &aDevice, aProperty,
&type, nullptr, 0, &size) ||
GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
(type != REG_SZ && type != REG_MULTI_SZ)) {
return;
}
nsTArray<wchar_t> buffer;
if (!buffer.SetLength((size / sizeof(wchar_t)) + 1, mozilla::fallible)) {
return;
}
if (SetupDiGetDeviceRegistryPropertyW(aDevices, &aDevice, aProperty,
&type,
reinterpret_cast<BYTE*>(buffer.Elements()),
size, nullptr)) {
aValue.Assign(reinterpret_cast<const char16_t*>(buffer.Elements()));
}
}
void
ParseHardwareId(const nsString& aHardwareId, USBDeviceInfo& aInfo)
{
const char16_t* id = aHardwareId.BeginReading();
uint32_t length = aHardwareId.Length();
for (uint32_t i = 0; i + 8 <= length; ++i) {
if ((id[i] == 'V' || id[i] == 'v') &&
(id[i + 1] == 'I' || id[i + 1] == 'i') &&
(id[i + 2] == 'D' || id[i + 2] == 'd') && id[i + 3] == '_') {
uint16_t value;
if (ParseHex(reinterpret_cast<const wchar_t*>(id + i + 4), 4, value)) {
aInfo.mVendorId = value;
}
}
if ((id[i] == 'P' || id[i] == 'p') &&
(id[i + 1] == 'I' || id[i + 1] == 'i') &&
(id[i + 2] == 'D' || id[i + 2] == 'd') && id[i + 3] == '_') {
uint16_t value;
if (ParseHex(reinterpret_cast<const wchar_t*>(id + i + 4), 4, value)) {
aInfo.mProductId = value;
}
}
}
}
} // anonymous namespace
nsresult
EnumerateUSBDevices(nsTArray<USBDeviceInfo>& aDevices)
{
aDevices.Clear();
HDEVINFO devices = SetupDiGetClassDevsW(&kUSBDeviceInterface, nullptr,
nullptr,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (devices == INVALID_HANDLE_VALUE) {
return NS_ERROR_FAILURE;
}
for (DWORD index = 0; ; ++index) {
SP_DEVICE_INTERFACE_DATA interfaceData;
memset(&interfaceData, 0, sizeof(interfaceData));
interfaceData.cbSize = sizeof(interfaceData);
if (!SetupDiEnumDeviceInterfaces(devices, nullptr, &kUSBDeviceInterface,
index, &interfaceData)) {
break;
}
DWORD detailSize = 0;
SetupDiGetDeviceInterfaceDetailW(devices, &interfaceData, nullptr, 0,
&detailSize, nullptr);
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || !detailSize) {
continue;
}
nsTArray<BYTE> detailBuffer;
if (!detailBuffer.SetLength(detailSize, mozilla::fallible)) {
continue;
}
SP_DEVICE_INTERFACE_DETAIL_DATA_W* detail =
reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(detailBuffer.Elements());
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
SP_DEVINFO_DATA device;
memset(&device, 0, sizeof(device));
device.cbSize = sizeof(device);
if (!SetupDiGetDeviceInterfaceDetailW(devices, &interfaceData, detail,
detailSize, nullptr, &device)) {
continue;
}
nsString hardwareId;
CopyRegistryString(devices, device, SPDRP_HARDWAREID, hardwareId);
USBDeviceInfo info;
ParseHardwareId(hardwareId, info);
if (!info.mVendorId && !info.mProductId) {
continue;
}
CopyRegistryString(devices, device, SPDRP_MFG, info.mManufacturerName);
CopyRegistryString(devices, device, SPDRP_DEVICEDESC, info.mProductName);
info.mDevicePath.Assign(reinterpret_cast<const char16_t*>(detail->DevicePath));
aDevices.AppendElement(Move(info));
}
SetupDiDestroyDeviceInfoList(devices);
return NS_OK;
}
already_AddRefed<USBDeviceHandle>
CreateUSBDeviceHandle(const nsAString& aDevicePath)
{
RefPtr<USBDeviceHandle> handle = new WinUSBDeviceHandle(aDevicePath);
return handle.forget();
}
} // namespace dom
} // namespace mozilla
#endif // XP_WIN

209
dom/usb/USBDescriptors.cpp Normal file
View file

@ -0,0 +1,209 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
#include "USBDescriptors.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/dom/ArrayBuffer.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(USBEndpoint)
NS_IMPL_CYCLE_COLLECTING_ADDREF(USBEndpoint)
NS_IMPL_CYCLE_COLLECTING_RELEASE(USBEndpoint)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USBEndpoint)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
USBEndpoint::USBEndpoint(nsIGlobalObject* aOwner, uint8_t aNumber,
USBDirection aDirection, USBEndpointType aType,
uint16_t aPacketSize)
: mOwner(aOwner)
, mNumber(aNumber)
, mDirection(aDirection)
, mType(aType)
, mPacketSize(aPacketSize)
{
}
JSObject*
USBEndpoint::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return USBEndpointBinding::Wrap(aCx, this, aGivenProto);
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(USBAlternateInterface, mEndpoints)
NS_IMPL_CYCLE_COLLECTING_ADDREF(USBAlternateInterface)
NS_IMPL_CYCLE_COLLECTING_RELEASE(USBAlternateInterface)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USBAlternateInterface)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
USBAlternateInterface::USBAlternateInterface(
nsIGlobalObject* aOwner, uint8_t aSetting, uint8_t aClass,
uint8_t aSubclass, uint8_t aProtocol,
nsTArray<RefPtr<USBEndpoint>>&& aEndpoints)
: mOwner(aOwner)
, mSetting(aSetting)
, mClass(aClass)
, mSubclass(aSubclass)
, mProtocol(aProtocol)
, mEndpoints(Move(aEndpoints))
{
}
JSObject*
USBAlternateInterface::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
{
return USBAlternateInterfaceBinding::Wrap(aCx, this, aGivenProto);
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(USBInterface, mAlternate, mAlternates)
NS_IMPL_CYCLE_COLLECTING_ADDREF(USBInterface)
NS_IMPL_CYCLE_COLLECTING_RELEASE(USBInterface)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USBInterface)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
USBInterface::USBInterface(
nsIGlobalObject* aOwner, uint8_t aNumber,
nsTArray<RefPtr<USBAlternateInterface>>&& aAlternates)
: mOwner(aOwner)
, mNumber(aNumber)
, mAlternates(Move(aAlternates))
, mClaimed(false)
{
if (!mAlternates.IsEmpty()) {
mAlternate = mAlternates[0];
}
}
void
USBInterface::SetAlternate(uint8_t aSetting)
{
for (const auto& alternate : mAlternates) {
if (alternate->AlternateSetting() == aSetting) {
mAlternate = alternate;
return;
}
}
}
JSObject*
USBInterface::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return USBInterfaceBinding::Wrap(aCx, this, aGivenProto);
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(USBConfiguration, mInterfaces)
NS_IMPL_CYCLE_COLLECTING_ADDREF(USBConfiguration)
NS_IMPL_CYCLE_COLLECTING_RELEASE(USBConfiguration)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USBConfiguration)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
USBConfiguration::USBConfiguration(
nsIGlobalObject* aOwner, uint8_t aValue,
nsTArray<RefPtr<USBInterface>>&& aInterfaces)
: mOwner(aOwner)
, mValue(aValue)
, mInterfaces(Move(aInterfaces))
{
}
JSObject*
USBConfiguration::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return USBConfigurationBinding::Wrap(aCx, this, aGivenProto);
}
NS_IMPL_CYCLE_COLLECTION_CLASS(USBInTransferResult)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(USBInTransferResult)
tmp->mData = nullptr;
mozilla::DropJSObjects(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(USBInTransferResult)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(USBInTransferResult)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mData)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(USBInTransferResult)
NS_IMPL_CYCLE_COLLECTING_RELEASE(USBInTransferResult)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USBInTransferResult)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
USBInTransferResult::USBInTransferResult(
nsIGlobalObject* aOwner, USBTransferStatus aStatus,
const nsTArray<uint8_t>& aData)
: mOwner(aOwner)
, mStatus(aStatus)
, mRawData(aData)
, mData(nullptr)
{
mozilla::HoldJSObjects(this);
}
USBInTransferResult::~USBInTransferResult()
{
mData = nullptr;
mozilla::DropJSObjects(this);
}
void
USBInTransferResult::GetData(JSContext* aCx,
JS::MutableHandle<JSObject*> aData,
ErrorResult& aRv)
{
if (!mData) {
mData = ArrayBuffer::Create(aCx, this, mRawData.Length(),
mRawData.Elements());
if (!mData) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
mRawData.Clear();
}
aData.set(mData);
}
JSObject*
USBInTransferResult::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
{
return USBInTransferResultBinding::Wrap(aCx, this, aGivenProto);
}
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(USBOutTransferResult, mOwner)
NS_IMPL_CYCLE_COLLECTING_ADDREF(USBOutTransferResult)
NS_IMPL_CYCLE_COLLECTING_RELEASE(USBOutTransferResult)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USBOutTransferResult)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
USBOutTransferResult::USBOutTransferResult(nsIGlobalObject* aOwner,
USBTransferStatus aStatus,
uint32_t aBytesWritten)
: mOwner(aOwner)
, mStatus(aStatus)
, mBytesWritten(aBytesWritten)
{
}
JSObject*
USBOutTransferResult::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto)
{
return USBOutTransferResultBinding::Wrap(aCx, this, aGivenProto);
}
} // namespace dom
} // namespace mozilla

200
dom/usb/USBDescriptors.h Normal file
View file

@ -0,0 +1,200 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
#ifndef mozilla_dom_USBDescriptors_h
#define mozilla_dom_USBDescriptors_h
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/USBBinding.h"
#include "mozilla/dom/USBAlternateInterfaceBinding.h"
#include "mozilla/dom/USBConfigurationBinding.h"
#include "mozilla/dom/USBEndpointBinding.h"
#include "mozilla/dom/USBInterfaceBinding.h"
#include "mozilla/dom/USBInTransferResultBinding.h"
#include "mozilla/dom/USBOutTransferResultBinding.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsWrapperCache.h"
namespace mozilla {
namespace dom {
class USBEndpoint final : public nsISupports, public nsWrapperCache
{
public:
USBEndpoint(nsIGlobalObject* aOwner, uint8_t aNumber,
USBDirection aDirection, USBEndpointType aType,
uint16_t aPacketSize);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(USBEndpoint)
nsISupports* GetParentObject() const { return mOwner; }
uint8_t EndpointNumber() const { return mNumber; }
USBDirection Direction() const { return mDirection; }
USBEndpointType Type() const { return mType; }
uint16_t PacketSize() const { return mPacketSize; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
private:
~USBEndpoint() = default;
nsCOMPtr<nsIGlobalObject> mOwner;
uint8_t mNumber;
USBDirection mDirection;
USBEndpointType mType;
uint16_t mPacketSize;
};
class USBAlternateInterface final : public nsISupports, public nsWrapperCache
{
public:
USBAlternateInterface(nsIGlobalObject* aOwner, uint8_t aSetting,
uint8_t aClass, uint8_t aSubclass, uint8_t aProtocol,
nsTArray<RefPtr<USBEndpoint>>&& aEndpoints);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(USBAlternateInterface)
nsISupports* GetParentObject() const { return mOwner; }
uint8_t AlternateSetting() const { return mSetting; }
uint8_t InterfaceClass() const { return mClass; }
uint8_t InterfaceSubclass() const { return mSubclass; }
uint8_t InterfaceProtocol() const { return mProtocol; }
void GetInterfaceName(Nullable<nsString>& aValue) const
{ aValue = mName; }
void GetEndpoints(nsTArray<RefPtr<USBEndpoint>>& aValue) const
{ aValue = mEndpoints; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
private:
~USBAlternateInterface() = default;
nsCOMPtr<nsIGlobalObject> mOwner;
uint8_t mSetting;
uint8_t mClass;
uint8_t mSubclass;
uint8_t mProtocol;
Nullable<nsString> mName;
nsTArray<RefPtr<USBEndpoint>> mEndpoints;
};
class USBInterface final : public nsISupports, public nsWrapperCache
{
public:
USBInterface(nsIGlobalObject* aOwner, uint8_t aNumber,
nsTArray<RefPtr<USBAlternateInterface>>&& aAlternates);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(USBInterface)
nsISupports* GetParentObject() const { return mOwner; }
uint8_t InterfaceNumber() const { return mNumber; }
USBAlternateInterface* GetAlternate() const { return mAlternate; }
void SetAlternate(uint8_t aSetting);
void GetAlternates(nsTArray<RefPtr<USBAlternateInterface>>& aValue) const
{ aValue = mAlternates; }
bool Claimed() const { return mClaimed; }
void SetClaimed(bool aClaimed) { mClaimed = aClaimed; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
private:
~USBInterface() = default;
nsCOMPtr<nsIGlobalObject> mOwner;
uint8_t mNumber;
RefPtr<USBAlternateInterface> mAlternate;
nsTArray<RefPtr<USBAlternateInterface>> mAlternates;
bool mClaimed;
};
class USBConfiguration final : public nsISupports, public nsWrapperCache
{
public:
USBConfiguration(nsIGlobalObject* aOwner, uint8_t aValue,
nsTArray<RefPtr<USBInterface>>&& aInterfaces);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(USBConfiguration)
nsISupports* GetParentObject() const { return mOwner; }
uint8_t ConfigurationValue() const { return mValue; }
void GetConfigurationName(Nullable<nsString>& aValue) const
{ aValue = mName; }
void GetInterfaces(nsTArray<RefPtr<USBInterface>>& aValue) const
{ aValue = mInterfaces; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
private:
~USBConfiguration() = default;
nsCOMPtr<nsIGlobalObject> mOwner;
uint8_t mValue;
Nullable<nsString> mName;
nsTArray<RefPtr<USBInterface>> mInterfaces;
};
class USBInTransferResult final : public nsISupports, public nsWrapperCache
{
public:
USBInTransferResult(nsIGlobalObject* aOwner, USBTransferStatus aStatus,
const nsTArray<uint8_t>& aData);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(USBInTransferResult)
nsISupports* GetParentObject() const { return mOwner; }
USBTransferStatus Status() const { return mStatus; }
void GetData(JSContext* aCx, JS::MutableHandle<JSObject*> aData,
ErrorResult& aRv);
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
private:
~USBInTransferResult();
nsCOMPtr<nsIGlobalObject> mOwner;
USBTransferStatus mStatus;
nsTArray<uint8_t> mRawData;
JS::Heap<JSObject*> mData;
};
class USBOutTransferResult final : public nsISupports, public nsWrapperCache
{
public:
USBOutTransferResult(nsIGlobalObject* aOwner, USBTransferStatus aStatus,
uint32_t aBytesWritten);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(USBOutTransferResult)
nsISupports* GetParentObject() const { return mOwner; }
USBTransferStatus Status() const { return mStatus; }
uint32_t BytesWritten() const { return mBytesWritten; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
private:
~USBOutTransferResult() = default;
nsCOMPtr<nsIGlobalObject> mOwner;
USBTransferStatus mStatus;
uint32_t mBytesWritten;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_USBDescriptors_h

477
dom/usb/USBDevice.cpp Normal file
View file

@ -0,0 +1,477 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
#include "USBDevice.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/USB.h"
#include "mozilla/dom/USBDeviceBinding.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/Services.h"
#include "nsError.h"
#include "nsIDocument.h"
#include "nsIPermissionManager.h"
#include "nsPIDOMWindow.h"
namespace mozilla {
namespace dom {
namespace {
already_AddRefed<Promise>
BackendPromise(nsIGlobalObject* aOwner, nsresult aResult, ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(aOwner, aRv);
if (aRv.Failed()) {
return nullptr;
}
if (NS_SUCCEEDED(aResult)) {
promise->MaybeResolve();
} else {
promise->MaybeReject(aResult);
}
return promise.forget();
}
USBInterface*
FindInterface(USBConfiguration* aConfiguration, uint8_t aNumber)
{
if (!aConfiguration) {
return nullptr;
}
nsTArray<RefPtr<USBInterface>> interfaces;
aConfiguration->GetInterfaces(interfaces);
for (const auto& interfaceObject : interfaces) {
if (interfaceObject->InterfaceNumber() == aNumber) {
return interfaceObject;
}
}
return nullptr;
}
USBControlTransferInfo
MakeControlTransferInfo(const USBControlTransferParameters& aSetup)
{
USBControlTransferInfo setup;
setup.mRequestType = static_cast<uint8_t>(aSetup.mRequestType);
setup.mRecipient = static_cast<uint8_t>(aSetup.mRecipient);
setup.mRequest = aSetup.mRequest;
setup.mValue = aSetup.mValue;
setup.mIndex = aSetup.mIndex;
return setup;
}
void
CopyTransferData(const ArrayBufferViewOrArrayBuffer& aData,
nsTArray<uint8_t>& aOutput)
{
aOutput.Clear();
if (aData.IsArrayBuffer()) {
const ArrayBuffer& buffer = aData.GetAsArrayBuffer();
buffer.ComputeLengthAndData();
aOutput.AppendElements(buffer.Data(), buffer.Length());
} else {
const ArrayBufferView& view = aData.GetAsArrayBufferView();
view.ComputeLengthAndData();
aOutput.AppendElements(view.Data(), view.Length());
}
}
} // anonymous namespace
NS_IMPL_CYCLE_COLLECTION_INHERITED(USBDevice, DOMEventTargetHelper,
mHandle, mConfiguration, mConfigurations)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(USBDevice)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(USBDevice, DOMEventTargetHelper)
NS_IMPL_RELEASE_INHERITED(USBDevice, DOMEventTargetHelper)
USBDevice::USBDevice(nsIGlobalObject* aOwner)
: DOMEventTargetHelper(aOwner)
, mVendorId(0)
, mProductId(0)
, mUSBVersionMajor(0)
, mUSBVersionMinor(0)
, mUSBVersionSubminor(0)
, mDeviceClass(0)
, mDeviceSubclass(0)
, mDeviceProtocol(0)
, mDeviceVersionMajor(0)
, mDeviceVersionMinor(0)
, mDeviceVersionSubminor(0)
, mOpened(false)
{
}
USBDevice::~USBDevice()
{
}
void
USBDevice::SetDeviceInfo(const USBDeviceInfo& aInfo)
{
mVendorId = aInfo.mVendorId;
mProductId = aInfo.mProductId;
mUSBVersionMajor = aInfo.mUSBVersionMajor;
mUSBVersionMinor = aInfo.mUSBVersionMinor;
mUSBVersionSubminor = aInfo.mUSBVersionSubminor;
mDeviceClass = aInfo.mDeviceClass;
mDeviceSubclass = aInfo.mDeviceSubclass;
mDeviceProtocol = aInfo.mDeviceProtocol;
mDeviceVersionMajor = aInfo.mDeviceVersionMajor;
mDeviceVersionMinor = aInfo.mDeviceVersionMinor;
mDeviceVersionSubminor = aInfo.mDeviceVersionSubminor;
mDevicePath = aInfo.mDevicePath;
if (aInfo.mManufacturerName.IsEmpty()) {
mManufacturerName.SetNull();
} else {
mManufacturerName.SetValue(aInfo.mManufacturerName);
}
if (aInfo.mProductName.IsEmpty()) {
mProductName.SetNull();
} else {
mProductName.SetValue(aInfo.mProductName);
}
if (aInfo.mSerialNumber.IsEmpty()) {
mSerialNumber.SetNull();
} else {
mSerialNumber.SetValue(aInfo.mSerialNumber);
}
}
void
USBDevice::SetConfigurations(
const nsTArray<USBConfigurationInfo>& aConfigurations)
{
mConfigurations.Clear();
mConfiguration = nullptr;
for (const auto& configurationInfo : aConfigurations) {
nsTArray<RefPtr<USBInterface>> interfaces;
for (const auto& interfaceInfo : configurationInfo.mInterfaces) {
nsTArray<RefPtr<USBAlternateInterface>> alternates;
for (const auto& alternateInfo : interfaceInfo.mAlternates) {
nsTArray<RefPtr<USBEndpoint>> endpoints;
for (const auto& endpointInfo : alternateInfo.mEndpoints) {
USBDirection direction = endpointInfo.mDirection == 0
? USBDirection::In : USBDirection::Out;
USBEndpointType type;
switch (endpointInfo.mType) {
case 1:
type = USBEndpointType::Isochronous;
break;
case 3:
type = USBEndpointType::Interrupt;
break;
default:
type = USBEndpointType::Bulk;
break;
}
endpoints.AppendElement(new USBEndpoint(
GetOwnerGlobal(), endpointInfo.mNumber, direction, type,
endpointInfo.mPacketSize));
}
alternates.AppendElement(new USBAlternateInterface(
GetOwnerGlobal(), alternateInfo.mSetting, alternateInfo.mClass,
alternateInfo.mSubclass, alternateInfo.mProtocol, Move(endpoints)));
}
interfaces.AppendElement(new USBInterface(
GetOwnerGlobal(), interfaceInfo.mNumber, Move(alternates)));
}
RefPtr<USBConfiguration> configuration = new USBConfiguration(
GetOwnerGlobal(), configurationInfo.mValue, Move(interfaces));
if (!mConfiguration) {
mConfiguration = configuration;
}
mConfigurations.AppendElement(configuration);
}
}
JSObject*
USBDevice::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return USBDeviceBinding::Wrap(aCx, this, aGivenProto);
}
already_AddRefed<Promise>
USBDevice::Open(ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
if (mOpened) {
promise->MaybeResolve();
return promise.forget();
}
mHandle = CreateUSBDeviceHandle(mDevicePath);
if (!mHandle || NS_FAILED(mHandle->Open())) {
mHandle = nullptr;
promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return promise.forget();
}
mOpened = true;
nsTArray<USBConfigurationInfo> configurations;
if (NS_SUCCEEDED(mHandle->GetConfigurations(configurations))) {
SetConfigurations(configurations);
}
promise->MaybeResolve();
return promise.forget();
}
already_AddRefed<Promise>
USBDevice::Close(ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
if (mHandle) {
mHandle->Close();
mHandle = nullptr;
}
mOpened = false;
promise->MaybeResolve();
return promise.forget();
}
already_AddRefed<Promise>
USBDevice::Forget(ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
if (mHandle) {
mHandle->Close();
mHandle = nullptr;
}
mOpened = false;
if (mUSB) {
mUSB->RemoveAuthorizedDevice(this);
}
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(GetOwnerGlobal());
if (window && window->GetDoc()) {
nsCOMPtr<nsIPermissionManager> permissionManager =
services::GetPermissionManager();
if (permissionManager) {
permissionManager->RemoveFromPrincipal(window->GetDoc()->NodePrincipal(),
"usb");
}
}
promise->MaybeResolve();
return promise.forget();
}
already_AddRefed<Promise>
USBDevice::SelectConfiguration(uint8_t aValue, ErrorResult& aRv)
{
if (!mOpened || !mHandle) {
return BackendPromise(GetOwnerGlobal(), NS_ERROR_DOM_INVALID_STATE_ERR, aRv);
}
nsresult rv = mHandle->SelectConfiguration(aValue);
if (NS_SUCCEEDED(rv)) {
for (const auto& configuration : mConfigurations) {
if (configuration->ConfigurationValue() == aValue) {
mConfiguration = configuration;
break;
}
}
}
return BackendPromise(GetOwnerGlobal(), rv, aRv);
}
already_AddRefed<Promise>
USBDevice::ClaimInterface(uint8_t aNumber, ErrorResult& aRv)
{
if (!mOpened || !mHandle || !FindInterface(mConfiguration, aNumber)) {
return BackendPromise(GetOwnerGlobal(), NS_ERROR_DOM_INVALID_STATE_ERR, aRv);
}
nsresult rv = mHandle->ClaimInterface(aNumber);
if (NS_SUCCEEDED(rv)) {
FindInterface(mConfiguration, aNumber)->SetClaimed(true);
}
return BackendPromise(GetOwnerGlobal(), rv, aRv);
}
already_AddRefed<Promise>
USBDevice::ReleaseInterface(uint8_t aNumber, ErrorResult& aRv)
{
USBInterface* interfaceObject = FindInterface(mConfiguration, aNumber);
if (!mOpened || !mHandle || !interfaceObject) {
return BackendPromise(GetOwnerGlobal(), NS_ERROR_DOM_INVALID_STATE_ERR, aRv);
}
nsresult rv = mHandle->ReleaseInterface(aNumber);
if (NS_SUCCEEDED(rv)) {
interfaceObject->SetClaimed(false);
}
return BackendPromise(GetOwnerGlobal(), rv, aRv);
}
already_AddRefed<Promise>
USBDevice::SelectAlternateInterface(uint8_t aNumber, uint8_t aSetting,
ErrorResult& aRv)
{
USBInterface* interfaceObject = FindInterface(mConfiguration, aNumber);
if (!mOpened || !mHandle || !interfaceObject) {
return BackendPromise(GetOwnerGlobal(), NS_ERROR_DOM_INVALID_STATE_ERR, aRv);
}
nsresult rv = mHandle->SelectAlternateInterface(aNumber, aSetting);
if (NS_SUCCEEDED(rv)) {
interfaceObject->SetAlternate(aSetting);
}
return BackendPromise(GetOwnerGlobal(), rv, aRv);
}
already_AddRefed<Promise>
USBDevice::ClearHalt(USBDirection aDirection, uint8_t aEndpoint,
ErrorResult& aRv)
{
if (!mOpened || !mHandle) {
return BackendPromise(GetOwnerGlobal(), NS_ERROR_DOM_INVALID_STATE_ERR, aRv);
}
return BackendPromise(GetOwnerGlobal(),
mHandle->ClearHalt(aDirection == USBDirection::In,
aEndpoint), aRv);
}
already_AddRefed<Promise>
USBDevice::Reset(ErrorResult& aRv)
{
if (!mOpened || !mHandle) {
return BackendPromise(GetOwnerGlobal(), NS_ERROR_DOM_INVALID_STATE_ERR, aRv);
}
nsresult rv = mHandle->Reset();
if (NS_SUCCEEDED(rv)) {
nsTArray<RefPtr<USBInterface>> interfaces;
if (mConfiguration) {
mConfiguration->GetInterfaces(interfaces);
for (const auto& interfaceObject : interfaces) {
interfaceObject->SetClaimed(false);
}
}
}
return BackendPromise(GetOwnerGlobal(), rv, aRv);
}
already_AddRefed<Promise>
USBDevice::ControlTransferIn(const USBControlTransferParameters& aSetup,
uint16_t aLength, ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
if (!mOpened || !mHandle) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
nsTArray<uint8_t> data;
nsresult rv = mHandle->ControlTransferIn(
MakeControlTransferInfo(aSetup), aLength, data);
if (NS_FAILED(rv)) {
promise->MaybeReject(rv);
return promise.forget();
}
RefPtr<USBInTransferResult> result = new USBInTransferResult(
GetOwnerGlobal(), USBTransferStatus::Ok, data);
promise->MaybeResolve(result);
return promise.forget();
}
already_AddRefed<Promise>
USBDevice::ControlTransferOut(
const USBControlTransferParameters& aSetup,
const Optional<ArrayBufferViewOrArrayBuffer>& aData, ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
if (!mOpened || !mHandle) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
nsTArray<uint8_t> data;
if (aData.WasPassed()) {
CopyTransferData(aData.Value(), data);
}
uint32_t written = 0;
nsresult rv = mHandle->ControlTransferOut(
MakeControlTransferInfo(aSetup), data, written);
if (NS_FAILED(rv)) {
promise->MaybeReject(rv);
return promise.forget();
}
RefPtr<USBOutTransferResult> result = new USBOutTransferResult(
GetOwnerGlobal(), USBTransferStatus::Ok, written);
promise->MaybeResolve(result);
return promise.forget();
}
already_AddRefed<Promise>
USBDevice::TransferIn(uint8_t aEndpoint, uint32_t aLength, ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
if (!mOpened || !mHandle) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
nsTArray<uint8_t> data;
nsresult rv = mHandle->TransferIn(aEndpoint, aLength, data);
if (NS_FAILED(rv)) {
promise->MaybeReject(rv);
return promise.forget();
}
RefPtr<USBInTransferResult> result = new USBInTransferResult(
GetOwnerGlobal(), USBTransferStatus::Ok, data);
promise->MaybeResolve(result);
return promise.forget();
}
already_AddRefed<Promise>
USBDevice::TransferOut(uint8_t aEndpoint,
const ArrayBufferViewOrArrayBuffer& aData,
ErrorResult& aRv)
{
RefPtr<Promise> promise = Promise::Create(GetOwnerGlobal(), aRv);
if (aRv.Failed()) {
return nullptr;
}
if (!mOpened || !mHandle) {
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
return promise.forget();
}
nsTArray<uint8_t> data;
CopyTransferData(aData, data);
uint32_t written = 0;
nsresult rv = mHandle->TransferOut(aEndpoint, data, written);
if (NS_FAILED(rv)) {
promise->MaybeReject(rv);
return promise.forget();
}
RefPtr<USBOutTransferResult> result = new USBOutTransferResult(
GetOwnerGlobal(), USBTransferStatus::Ok, written);
promise->MaybeResolve(result);
return promise.forget();
}
} // namespace dom
} // namespace mozilla

115
dom/usb/USBDevice.h Normal file
View file

@ -0,0 +1,115 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
#ifndef mozilla_dom_USBDevice_h
#define mozilla_dom_USBDevice_h
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/USBBackend.h"
#include "mozilla/dom/USBDescriptors.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/Nullable.h"
#include "nsString.h"
#include <stdint.h>
namespace mozilla {
namespace dom {
class Promise;
class USB;
class ArrayBufferViewOrArrayBuffer;
struct USBControlTransferParameters;
class USBDevice final : public DOMEventTargetHelper
{
uint16_t mVendorId;
uint16_t mProductId;
uint8_t mUSBVersionMajor;
uint8_t mUSBVersionMinor;
uint8_t mUSBVersionSubminor;
uint8_t mDeviceClass;
uint8_t mDeviceSubclass;
uint8_t mDeviceProtocol;
uint8_t mDeviceVersionMajor;
uint8_t mDeviceVersionMinor;
uint8_t mDeviceVersionSubminor;
Nullable<nsString> mManufacturerName;
Nullable<nsString> mProductName;
Nullable<nsString> mSerialNumber;
nsString mDevicePath;
RefPtr<USBDeviceHandle> mHandle;
RefPtr<USBConfiguration> mConfiguration;
nsTArray<RefPtr<USBConfiguration>> mConfigurations;
USB* mUSB = nullptr;
bool mOpened;
public:
explicit USBDevice(nsIGlobalObject* aOwner);
void SetDeviceInfo(const USBDeviceInfo& aInfo);
void SetConfigurations(const nsTArray<USBConfigurationInfo>& aConfigurations);
void SetUSB(USB* aUSB) { mUSB = aUSB; }
void ClearUSB() { mUSB = nullptr; }
uint16_t VendorId() const { return mVendorId; }
uint16_t ProductId() const { return mProductId; }
uint8_t USBVersionMajor() const { return mUSBVersionMajor; }
uint8_t USBVersionMinor() const { return mUSBVersionMinor; }
uint8_t USBVersionSubminor() const { return mUSBVersionSubminor; }
uint8_t DeviceClass() const { return mDeviceClass; }
uint8_t DeviceSubclass() const { return mDeviceSubclass; }
uint8_t DeviceProtocol() const { return mDeviceProtocol; }
uint8_t DeviceVersionMajor() const { return mDeviceVersionMajor; }
uint8_t DeviceVersionMinor() const { return mDeviceVersionMinor; }
uint8_t DeviceVersionSubminor() const { return mDeviceVersionSubminor; }
void GetManufacturerName(Nullable<nsString>& aValue) const
{ aValue = mManufacturerName; }
void GetProductName(Nullable<nsString>& aValue) const
{ aValue = mProductName; }
void GetSerialNumber(Nullable<nsString>& aValue) const
{ aValue = mSerialNumber; }
bool Opened() const { return mOpened; }
USBConfiguration* GetConfiguration() const { return mConfiguration; }
void GetConfigurations(nsTArray<RefPtr<USBConfiguration>>& aValue) const
{ aValue = mConfigurations; }
already_AddRefed<Promise> Open(ErrorResult& aRv);
already_AddRefed<Promise> Close(ErrorResult& aRv);
already_AddRefed<Promise> Forget(ErrorResult& aRv);
already_AddRefed<Promise> SelectConfiguration(uint8_t aValue,
ErrorResult& aRv);
already_AddRefed<Promise> ClaimInterface(uint8_t aNumber, ErrorResult& aRv);
already_AddRefed<Promise> ReleaseInterface(uint8_t aNumber,
ErrorResult& aRv);
already_AddRefed<Promise> SelectAlternateInterface(uint8_t aNumber,
uint8_t aSetting,
ErrorResult& aRv);
already_AddRefed<Promise> ClearHalt(USBDirection aDirection,
uint8_t aEndpoint, ErrorResult& aRv);
already_AddRefed<Promise> Reset(ErrorResult& aRv);
already_AddRefed<Promise> ControlTransferIn(
const USBControlTransferParameters& aSetup, uint16_t aLength,
ErrorResult& aRv);
already_AddRefed<Promise> ControlTransferOut(
const USBControlTransferParameters& aSetup,
const Optional<ArrayBufferViewOrArrayBuffer>& aData, ErrorResult& aRv);
already_AddRefed<Promise> TransferIn(uint8_t aEndpoint, uint32_t aLength,
ErrorResult& aRv);
already_AddRefed<Promise> TransferOut(
uint8_t aEndpoint, const ArrayBufferViewOrArrayBuffer& aData,
ErrorResult& aRv);
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(USBDevice, DOMEventTargetHelper)
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
private:
~USBDevice();
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_USBDevice_h

19
dom/usb/moz.build Normal file
View file

@ -0,0 +1,19 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
EXPORTS.mozilla.dom += [
'USB.h',
'USBBackend.h',
'USBDescriptors.h',
'USBDevice.h',
]
UNIFIED_SOURCES += [
'USB.cpp',
'USBBackend.cpp',
'USBDescriptors.cpp',
'USBDevice.cpp',
]
if CONFIG['OS_TARGET'] == 'WINNT':
SOURCES += ['USBBackendWin.cpp']
OS_LIBS += ['setupapi', 'winusb']

View file

@ -105,6 +105,11 @@ partial interface Navigator {
readonly attribute Permissions permissions;
};
partial interface Navigator {
[SameObject, SecureContext, Pref="dom.usb.enabled"]
readonly attribute USB usb;
};
// Things that definitely need to be in the spec and and are not for some
// reason. See https://www.w3.org/Bugs/Public/show_bug.cgi?id=22406
partial interface Navigator {

153
dom/webidl/USB.webidl Normal file
View file

@ -0,0 +1,153 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
[SecureContext, Pref="dom.usb.enabled", Exposed=Window]
interface USB : EventTarget {
[NewObject]
Promise<sequence<USBDevice>> getDevices();
[NewObject]
Promise<USBDevice> requestDevice(optional USBDeviceRequestOptions options = {});
attribute EventHandler onconnect;
attribute EventHandler ondisconnect;
};
dictionary USBDeviceRequestOptions {
required sequence<USBDeviceFilter> filters;
sequence<USBDeviceFilter> exclusionFilters = [];
};
dictionary USBDeviceFilter {
unsigned short vendorId;
unsigned short productId;
octet classCode;
octet subclassCode;
octet protocolCode;
DOMString serialNumber;
};
enum USBDirection {
"in",
"out"
};
enum USBEndpointType {
"bulk",
"interrupt",
"isochronous"
};
enum USBTransferStatus {
"ok",
"stall",
"babble"
};
enum USBRequestType {
"standard",
"class",
"vendor"
};
enum USBRecipient {
"device",
"interface",
"endpoint",
"other"
};
dictionary USBControlTransferParameters {
required USBRequestType requestType;
required USBRecipient recipient;
required octet request;
required unsigned short value;
required unsigned short index;
};
[SecureContext, Pref="dom.usb.enabled", Exposed=Window]
interface USBDevice : EventTarget {
readonly attribute octet usbVersionMajor;
readonly attribute octet usbVersionMinor;
readonly attribute octet usbVersionSubminor;
readonly attribute octet deviceClass;
readonly attribute octet deviceSubclass;
readonly attribute octet deviceProtocol;
readonly attribute unsigned short vendorId;
readonly attribute unsigned short productId;
readonly attribute octet deviceVersionMajor;
readonly attribute octet deviceVersionMinor;
readonly attribute octet deviceVersionSubminor;
readonly attribute DOMString? manufacturerName;
readonly attribute DOMString? productName;
readonly attribute DOMString? serialNumber;
readonly attribute USBConfiguration? configuration;
readonly attribute sequence<USBConfiguration> configurations;
readonly attribute boolean opened;
[NewObject] Promise<void> open();
[NewObject] Promise<void> close();
[NewObject] Promise<void> forget();
[NewObject] Promise<void> selectConfiguration(octet configurationValue);
[NewObject] Promise<void> claimInterface(octet interfaceNumber);
[NewObject] Promise<void> releaseInterface(octet interfaceNumber);
[NewObject] Promise<void> selectAlternateInterface(octet interfaceNumber,
octet alternateSetting);
[NewObject] Promise<void> clearHalt(USBDirection direction,
octet endpointNumber);
[NewObject] Promise<void> reset();
[NewObject] Promise<USBInTransferResult> controlTransferIn(
USBControlTransferParameters setup, unsigned short length);
[NewObject] Promise<USBOutTransferResult> controlTransferOut(
USBControlTransferParameters setup, optional BufferSource data);
[NewObject] Promise<USBInTransferResult> transferIn(octet endpointNumber,
unsigned long length);
[NewObject] Promise<USBOutTransferResult> transferOut(
octet endpointNumber, BufferSource data);
};
[SecureContext, Exposed=Window]
interface USBConfiguration {
readonly attribute octet configurationValue;
readonly attribute DOMString? configurationName;
readonly attribute sequence<USBInterface> interfaces;
};
[SecureContext, Exposed=Window]
interface USBInterface {
readonly attribute octet interfaceNumber;
readonly attribute USBAlternateInterface alternate;
readonly attribute sequence<USBAlternateInterface> alternates;
readonly attribute boolean claimed;
};
[SecureContext, Exposed=Window]
interface USBAlternateInterface {
readonly attribute octet alternateSetting;
readonly attribute octet interfaceClass;
readonly attribute octet interfaceSubclass;
readonly attribute octet interfaceProtocol;
readonly attribute DOMString? interfaceName;
readonly attribute sequence<USBEndpoint> endpoints;
};
[SecureContext, Exposed=Window]
interface USBEndpoint {
readonly attribute octet endpointNumber;
readonly attribute USBDirection direction;
readonly attribute USBEndpointType type;
readonly attribute unsigned short packetSize;
};
[SecureContext, Exposed=Window]
interface USBInTransferResult {
readonly attribute USBTransferStatus status;
// UXP's binding generator does not expose DataView as a WebIDL type;
// transfer data is returned as an ArrayBuffer until that binding exists.
readonly attribute ArrayBuffer? data;
};
[SecureContext, Exposed=Window]
interface USBOutTransferResult {
readonly attribute USBTransferStatus status;
readonly attribute unsigned long bytesWritten;
};

View file

@ -16,6 +16,7 @@ PREPROCESSED_WEBIDL_FILES = [
]
WEBIDL_FILES = [
'USB.webidl',
'AbortController.webidl',
'AbortSignal.webidl',
'AbstractWorker.webidl',

View file

@ -5322,6 +5322,9 @@ pref("security.block_ftp_subresources", true);
// Disable Storage api by default. This needs specific front-end parts to be usable.
pref("dom.storageManager.enabled", false);
// Disable WebUSB by default.
pref("dom.usb.enabled", false);
// DoS protection for HTTP Auth prompt spawning.
// -1 = completely disable HTTP Auth prompting. (careful!)
// 0 = disable this DoS protection