diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js
index f25c6c0d67..e2807b5080 100644
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -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);
}
diff --git a/browser/components/preferences/in-content/content.xul b/browser/components/preferences/in-content/content.xul
index cc84efd50f..a70630ac24 100644
--- a/browser/components/preferences/in-content/content.xul
+++ b/browser/components/preferences/in-content/content.xul
@@ -27,6 +27,9 @@
+
#ifdef MOZ_WEBRTC
+
+
+
+
+
#ifdef MOZ_WEBRTC
diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties
index 8f34a10cd9..76ceaa4e7d 100644
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -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
diff --git a/browser/locales/en-US/chrome/browser/preferences/content.dtd b/browser/locales/en-US/chrome/browser/preferences/content.dtd
index 85fd1fcf25..39519a6a4e 100644
--- a/browser/locales/en-US/chrome/browser/preferences/content.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/content.dtd
@@ -48,6 +48,8 @@
+
+
#ifdef MOZ_WEBRTC
diff --git a/browser/modules/PermissionUI.jsm b/browser/modules/PermissionUI.jsm
index 984f95cd58..9d99d19227 100644
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -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.
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
index 26789c2881..149a62b7b6 100644
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -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 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
diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h
index d674f5cedc..9201d9ae3d 100644
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -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& aLanguages);
@@ -298,6 +300,7 @@ private:
nsTArray > mVRGetDisplaysPromises;
nsTArray mRequestedVibrationPattern;
RefPtr mStorageManager;
+ RefPtr mUSB;
};
} // namespace dom
diff --git a/dom/moz.build b/dom/moz.build
index 585dcce59c..b0538d2e72 100644
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -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']
-
diff --git a/dom/usb/USB.cpp b/dom/usb/USB.cpp
new file mode 100644
index 0000000000..d26ecadc20
--- /dev/null
+++ b/dom/usb/USB.cpp
@@ -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>&& aCandidates)
+ : mUSB(aUSB)
+ , mPromise(aPromise)
+ , mWindow(aWindow)
+ , mCandidates(Move(aCandidates))
+ , mRequester(new nsContentPermissionRequester(aWindow))
+ {
+ }
+
+private:
+ ~USBPermissionRequest() = default;
+
+ RefPtr mUSB;
+ RefPtr mPromise;
+ nsCOMPtr mWindow;
+ nsTArray> mCandidates;
+ RefPtr 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 principal = mWindow->GetDoc()->NodePrincipal();
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+USBPermissionRequest::GetTypes(nsIArray** aTypes)
+{
+ nsTArray options;
+ for (const auto& device : mCandidates) {
+ Nullable 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 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 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 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 aGivenProto)
+{
+ return USBBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed
+USB::GetDevices(ErrorResult& aRv)
+{
+ RefPtr promise = Promise::Create(GetOwnerGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ promise->MaybeResolve(mAuthorizedDevices);
+ return promise.forget();
+}
+
+already_AddRefed
+USB::RequestDevice(const USBDeviceRequestOptions& aOptions, ErrorResult& aRv)
+{
+ RefPtr promise = Promise::Create(GetOwnerGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ nsTArray infos;
+ nsresult rv = EnumerateUSBDevices(infos);
+ if (NS_FAILED(rv)) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+
+ nsTArray> candidates;
+ for (const auto& info : infos) {
+ if (!MatchesRequest(info, aOptions)) {
+ continue;
+ }
+ RefPtr 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 window = do_QueryInterface(GetOwnerGlobal());
+ if (!window) {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return promise.forget();
+ }
+
+ RefPtr 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
diff --git a/dom/usb/USB.h b/dom/usb/USB.h
new file mode 100644
index 0000000000..1b94e89570
--- /dev/null
+++ b/dom/usb/USB.h
@@ -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 GetDevices(ErrorResult& aRv);
+ already_AddRefed 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 aGivenProto) override;
+
+private:
+ ~USB();
+
+ nsTArray> mAuthorizedDevices;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_USB_h
diff --git a/dom/usb/USBBackend.cpp b/dom/usb/USBBackend.cpp
new file mode 100644
index 0000000000..26fdfa74fc
--- /dev/null
+++ b/dom/usb/USBBackend.cpp
@@ -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& aDevices)
+{
+ aDevices.Clear();
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+already_AddRefed
+CreateUSBDeviceHandle(const nsAString& aDevicePath)
+{
+ (void)aDevicePath;
+ return nullptr;
+}
+#endif
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/usb/USBBackend.h b/dom/usb/USBBackend.h
new file mode 100644
index 0000000000..9f0e7e28c9
--- /dev/null
+++ b/dom/usb/USBBackend.h
@@ -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
+
+#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 mEndpoints;
+};
+
+struct USBInterfaceInfo {
+ uint8_t mNumber = 0;
+ nsTArray mAlternates;
+};
+
+struct USBConfigurationInfo {
+ uint8_t mValue = 0;
+ nsTArray 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& 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& aData) = 0;
+ virtual nsresult ControlTransferOut(const USBControlTransferInfo& aSetup,
+ const nsTArray& aData,
+ uint32_t& aWritten) = 0;
+ virtual nsresult TransferIn(uint8_t aEndpoint, uint32_t aLength,
+ nsTArray& aData) = 0;
+ virtual nsresult TransferOut(uint8_t aEndpoint,
+ const nsTArray& 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& aDevices);
+
+already_AddRefed
+CreateUSBDeviceHandle(const nsAString& aDevicePath);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_USBBackend_h
diff --git a/dom/usb/USBBackendWin.cpp b/dom/usb/USBBackendWin.cpp
new file mode 100644
index 0000000000..1b75e67627
--- /dev/null
+++ b/dom/usb/USBBackendWin.cpp
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+#include "USBBackend.h"
+
+#ifdef XP_WIN
+
+#include
+#include
+#include
+#include
+
+#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(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& 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(&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(header[2] |
+ (header[3] << 8));
+ if (totalLength < sizeof(header)) {
+ continue;
+ }
+ nsTArray 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 = ¤tInterface->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(
+ 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& 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(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& aData,
+ uint32_t& aWritten) override
+ {
+ aWritten = 0;
+ if (!mInterface) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ WINUSB_SETUP_PACKET setup;
+ setup.RequestType = static_cast(
+ ((aSetup.mRequestType & 0x03) << 5) | (aSetup.mRecipient & 0x1f));
+ setup.Request = aSetup.mRequest;
+ setup.Value = aSetup.mValue;
+ setup.Index = aSetup.mIndex;
+ setup.Length = static_cast(aData.Length());
+ ULONG transferred = 0;
+ PUCHAR data = aData.IsEmpty()
+ ? nullptr : const_cast(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& 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& 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(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(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 buffer;
+ if (!buffer.SetLength((size / sizeof(wchar_t)) + 1, mozilla::fallible)) {
+ return;
+ }
+
+ if (SetupDiGetDeviceRegistryPropertyW(aDevices, &aDevice, aProperty,
+ &type,
+ reinterpret_cast(buffer.Elements()),
+ size, nullptr)) {
+ aValue.Assign(reinterpret_cast(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(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(id + i + 4), 4, value)) {
+ aInfo.mProductId = value;
+ }
+ }
+ }
+}
+
+} // anonymous namespace
+
+nsresult
+EnumerateUSBDevices(nsTArray& 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 detailBuffer;
+ if (!detailBuffer.SetLength(detailSize, mozilla::fallible)) {
+ continue;
+ }
+ SP_DEVICE_INTERFACE_DETAIL_DATA_W* detail =
+ reinterpret_cast(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(detail->DevicePath));
+ aDevices.AppendElement(Move(info));
+ }
+
+ SetupDiDestroyDeviceInfoList(devices);
+ return NS_OK;
+}
+
+already_AddRefed
+CreateUSBDeviceHandle(const nsAString& aDevicePath)
+{
+ RefPtr handle = new WinUSBDeviceHandle(aDevicePath);
+ return handle.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // XP_WIN
diff --git a/dom/usb/USBDescriptors.cpp b/dom/usb/USBDescriptors.cpp
new file mode 100644
index 0000000000..6ce7afae72
--- /dev/null
+++ b/dom/usb/USBDescriptors.cpp
@@ -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 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>&& aEndpoints)
+ : mOwner(aOwner)
+ , mSetting(aSetting)
+ , mClass(aClass)
+ , mSubclass(aSubclass)
+ , mProtocol(aProtocol)
+ , mEndpoints(Move(aEndpoints))
+{
+}
+
+JSObject*
+USBAlternateInterface::WrapObject(JSContext* aCx,
+ JS::Handle 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>&& 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 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>&& aInterfaces)
+ : mOwner(aOwner)
+ , mValue(aValue)
+ , mInterfaces(Move(aInterfaces))
+{
+}
+
+JSObject*
+USBConfiguration::WrapObject(JSContext* aCx, JS::Handle 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& 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 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 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 aGivenProto)
+{
+ return USBOutTransferResultBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/usb/USBDescriptors.h b/dom/usb/USBDescriptors.h
new file mode 100644
index 0000000000..277802e245
--- /dev/null
+++ b/dom/usb/USBDescriptors.h
@@ -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 aGivenProto) override;
+
+private:
+ ~USBEndpoint() = default;
+
+ nsCOMPtr 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>&& 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& aValue) const
+ { aValue = mName; }
+ void GetEndpoints(nsTArray>& aValue) const
+ { aValue = mEndpoints; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) override;
+
+private:
+ ~USBAlternateInterface() = default;
+
+ nsCOMPtr mOwner;
+ uint8_t mSetting;
+ uint8_t mClass;
+ uint8_t mSubclass;
+ uint8_t mProtocol;
+ Nullable mName;
+ nsTArray> mEndpoints;
+};
+
+class USBInterface final : public nsISupports, public nsWrapperCache
+{
+public:
+ USBInterface(nsIGlobalObject* aOwner, uint8_t aNumber,
+ nsTArray>&& 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>& aValue) const
+ { aValue = mAlternates; }
+ bool Claimed() const { return mClaimed; }
+ void SetClaimed(bool aClaimed) { mClaimed = aClaimed; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) override;
+
+private:
+ ~USBInterface() = default;
+
+ nsCOMPtr mOwner;
+ uint8_t mNumber;
+ RefPtr mAlternate;
+ nsTArray> mAlternates;
+ bool mClaimed;
+};
+
+class USBConfiguration final : public nsISupports, public nsWrapperCache
+{
+public:
+ USBConfiguration(nsIGlobalObject* aOwner, uint8_t aValue,
+ nsTArray>&& 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& aValue) const
+ { aValue = mName; }
+ void GetInterfaces(nsTArray>& aValue) const
+ { aValue = mInterfaces; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) override;
+
+private:
+ ~USBConfiguration() = default;
+
+ nsCOMPtr mOwner;
+ uint8_t mValue;
+ Nullable mName;
+ nsTArray> mInterfaces;
+};
+
+class USBInTransferResult final : public nsISupports, public nsWrapperCache
+{
+public:
+ USBInTransferResult(nsIGlobalObject* aOwner, USBTransferStatus aStatus,
+ const nsTArray& 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 aData,
+ ErrorResult& aRv);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle aGivenProto) override;
+
+private:
+ ~USBInTransferResult();
+
+ nsCOMPtr mOwner;
+ USBTransferStatus mStatus;
+ nsTArray mRawData;
+ JS::Heap 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 aGivenProto) override;
+
+private:
+ ~USBOutTransferResult() = default;
+
+ nsCOMPtr mOwner;
+ USBTransferStatus mStatus;
+ uint32_t mBytesWritten;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_USBDescriptors_h
diff --git a/dom/usb/USBDevice.cpp b/dom/usb/USBDevice.cpp
new file mode 100644
index 0000000000..65a1dd54b0
--- /dev/null
+++ b/dom/usb/USBDevice.cpp
@@ -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
+BackendPromise(nsIGlobalObject* aOwner, nsresult aResult, ErrorResult& aRv)
+{
+ RefPtr 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> 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(aSetup.mRequestType);
+ setup.mRecipient = static_cast(aSetup.mRecipient);
+ setup.mRequest = aSetup.mRequest;
+ setup.mValue = aSetup.mValue;
+ setup.mIndex = aSetup.mIndex;
+ return setup;
+}
+
+void
+CopyTransferData(const ArrayBufferViewOrArrayBuffer& aData,
+ nsTArray& 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& aConfigurations)
+{
+ mConfigurations.Clear();
+ mConfiguration = nullptr;
+
+ for (const auto& configurationInfo : aConfigurations) {
+ nsTArray> interfaces;
+ for (const auto& interfaceInfo : configurationInfo.mInterfaces) {
+ nsTArray> alternates;
+ for (const auto& alternateInfo : interfaceInfo.mAlternates) {
+ nsTArray> 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 configuration = new USBConfiguration(
+ GetOwnerGlobal(), configurationInfo.mValue, Move(interfaces));
+ if (!mConfiguration) {
+ mConfiguration = configuration;
+ }
+ mConfigurations.AppendElement(configuration);
+ }
+}
+
+JSObject*
+USBDevice::WrapObject(JSContext* aCx, JS::Handle aGivenProto)
+{
+ return USBDeviceBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed
+USBDevice::Open(ErrorResult& aRv)
+{
+ RefPtr 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 configurations;
+ if (NS_SUCCEEDED(mHandle->GetConfigurations(configurations))) {
+ SetConfigurations(configurations);
+ }
+ promise->MaybeResolve();
+ return promise.forget();
+}
+
+already_AddRefed
+USBDevice::Close(ErrorResult& aRv)
+{
+ RefPtr 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
+USBDevice::Forget(ErrorResult& aRv)
+{
+ RefPtr promise = Promise::Create(GetOwnerGlobal(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (mHandle) {
+ mHandle->Close();
+ mHandle = nullptr;
+ }
+ mOpened = false;
+ if (mUSB) {
+ mUSB->RemoveAuthorizedDevice(this);
+ }
+
+ nsCOMPtr window = do_QueryInterface(GetOwnerGlobal());
+ if (window && window->GetDoc()) {
+ nsCOMPtr permissionManager =
+ services::GetPermissionManager();
+ if (permissionManager) {
+ permissionManager->RemoveFromPrincipal(window->GetDoc()->NodePrincipal(),
+ "usb");
+ }
+ }
+ promise->MaybeResolve();
+ return promise.forget();
+}
+
+already_AddRefed
+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
+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
+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
+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
+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
+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> interfaces;
+ if (mConfiguration) {
+ mConfiguration->GetInterfaces(interfaces);
+ for (const auto& interfaceObject : interfaces) {
+ interfaceObject->SetClaimed(false);
+ }
+ }
+ }
+ return BackendPromise(GetOwnerGlobal(), rv, aRv);
+}
+
+already_AddRefed
+USBDevice::ControlTransferIn(const USBControlTransferParameters& aSetup,
+ uint16_t aLength, ErrorResult& aRv)
+{
+ RefPtr 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 data;
+ nsresult rv = mHandle->ControlTransferIn(
+ MakeControlTransferInfo(aSetup), aLength, data);
+ if (NS_FAILED(rv)) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+
+ RefPtr result = new USBInTransferResult(
+ GetOwnerGlobal(), USBTransferStatus::Ok, data);
+ promise->MaybeResolve(result);
+ return promise.forget();
+}
+
+already_AddRefed
+USBDevice::ControlTransferOut(
+ const USBControlTransferParameters& aSetup,
+ const Optional& aData, ErrorResult& aRv)
+{
+ RefPtr 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 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 result = new USBOutTransferResult(
+ GetOwnerGlobal(), USBTransferStatus::Ok, written);
+ promise->MaybeResolve(result);
+ return promise.forget();
+}
+
+already_AddRefed
+USBDevice::TransferIn(uint8_t aEndpoint, uint32_t aLength, ErrorResult& aRv)
+{
+ RefPtr 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 data;
+ nsresult rv = mHandle->TransferIn(aEndpoint, aLength, data);
+ if (NS_FAILED(rv)) {
+ promise->MaybeReject(rv);
+ return promise.forget();
+ }
+ RefPtr result = new USBInTransferResult(
+ GetOwnerGlobal(), USBTransferStatus::Ok, data);
+ promise->MaybeResolve(result);
+ return promise.forget();
+}
+
+already_AddRefed
+USBDevice::TransferOut(uint8_t aEndpoint,
+ const ArrayBufferViewOrArrayBuffer& aData,
+ ErrorResult& aRv)
+{
+ RefPtr 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 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 result = new USBOutTransferResult(
+ GetOwnerGlobal(), USBTransferStatus::Ok, written);
+ promise->MaybeResolve(result);
+ return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/usb/USBDevice.h b/dom/usb/USBDevice.h
new file mode 100644
index 0000000000..e8d5509f43
--- /dev/null
+++ b/dom/usb/USBDevice.h
@@ -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
+
+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 mManufacturerName;
+ Nullable mProductName;
+ Nullable mSerialNumber;
+ nsString mDevicePath;
+ RefPtr mHandle;
+ RefPtr mConfiguration;
+ nsTArray> mConfigurations;
+ USB* mUSB = nullptr;
+ bool mOpened;
+
+public:
+ explicit USBDevice(nsIGlobalObject* aOwner);
+
+ void SetDeviceInfo(const USBDeviceInfo& aInfo);
+ void SetConfigurations(const nsTArray& 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& aValue) const
+ { aValue = mManufacturerName; }
+ void GetProductName(Nullable& aValue) const
+ { aValue = mProductName; }
+ void GetSerialNumber(Nullable& aValue) const
+ { aValue = mSerialNumber; }
+ bool Opened() const { return mOpened; }
+ USBConfiguration* GetConfiguration() const { return mConfiguration; }
+ void GetConfigurations(nsTArray>& aValue) const
+ { aValue = mConfigurations; }
+
+ already_AddRefed Open(ErrorResult& aRv);
+ already_AddRefed Close(ErrorResult& aRv);
+ already_AddRefed Forget(ErrorResult& aRv);
+ already_AddRefed SelectConfiguration(uint8_t aValue,
+ ErrorResult& aRv);
+ already_AddRefed ClaimInterface(uint8_t aNumber, ErrorResult& aRv);
+ already_AddRefed ReleaseInterface(uint8_t aNumber,
+ ErrorResult& aRv);
+ already_AddRefed SelectAlternateInterface(uint8_t aNumber,
+ uint8_t aSetting,
+ ErrorResult& aRv);
+ already_AddRefed ClearHalt(USBDirection aDirection,
+ uint8_t aEndpoint, ErrorResult& aRv);
+ already_AddRefed Reset(ErrorResult& aRv);
+ already_AddRefed ControlTransferIn(
+ const USBControlTransferParameters& aSetup, uint16_t aLength,
+ ErrorResult& aRv);
+ already_AddRefed ControlTransferOut(
+ const USBControlTransferParameters& aSetup,
+ const Optional& aData, ErrorResult& aRv);
+ already_AddRefed TransferIn(uint8_t aEndpoint, uint32_t aLength,
+ ErrorResult& aRv);
+ already_AddRefed 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 aGivenProto) override;
+
+private:
+ ~USBDevice();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_USBDevice_h
diff --git a/dom/usb/moz.build b/dom/usb/moz.build
new file mode 100644
index 0000000000..ec8da04a74
--- /dev/null
+++ b/dom/usb/moz.build
@@ -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']
diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl
index 4541210a08..74271d547d 100644
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -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 {
diff --git a/dom/webidl/USB.webidl b/dom/webidl/USB.webidl
new file mode 100644
index 0000000000..77a57195c6
--- /dev/null
+++ b/dom/webidl/USB.webidl
@@ -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> getDevices();
+
+ [NewObject]
+ Promise requestDevice(optional USBDeviceRequestOptions options = {});
+
+ attribute EventHandler onconnect;
+ attribute EventHandler ondisconnect;
+};
+
+dictionary USBDeviceRequestOptions {
+ required sequence filters;
+ sequence 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 configurations;
+ readonly attribute boolean opened;
+
+ [NewObject] Promise open();
+ [NewObject] Promise close();
+ [NewObject] Promise forget();
+ [NewObject] Promise selectConfiguration(octet configurationValue);
+ [NewObject] Promise claimInterface(octet interfaceNumber);
+ [NewObject] Promise releaseInterface(octet interfaceNumber);
+ [NewObject] Promise selectAlternateInterface(octet interfaceNumber,
+ octet alternateSetting);
+ [NewObject] Promise clearHalt(USBDirection direction,
+ octet endpointNumber);
+ [NewObject] Promise reset();
+ [NewObject] Promise controlTransferIn(
+ USBControlTransferParameters setup, unsigned short length);
+ [NewObject] Promise controlTransferOut(
+ USBControlTransferParameters setup, optional BufferSource data);
+ [NewObject] Promise transferIn(octet endpointNumber,
+ unsigned long length);
+ [NewObject] Promise transferOut(
+ octet endpointNumber, BufferSource data);
+};
+
+[SecureContext, Exposed=Window]
+interface USBConfiguration {
+ readonly attribute octet configurationValue;
+ readonly attribute DOMString? configurationName;
+ readonly attribute sequence interfaces;
+};
+
+[SecureContext, Exposed=Window]
+interface USBInterface {
+ readonly attribute octet interfaceNumber;
+ readonly attribute USBAlternateInterface alternate;
+ readonly attribute sequence 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 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;
+};
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
index 11f39128ae..fa0c6e2a53 100644
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -16,6 +16,7 @@ PREPROCESSED_WEBIDL_FILES = [
]
WEBIDL_FILES = [
+ 'USB.webidl',
'AbortController.webidl',
'AbortSignal.webidl',
'AbstractWorker.webidl',
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 71f9ed86da..ad2d255b67 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -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