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