From fd2bb43e4b2a88b099ca6fc29a3e6bb96fb54704 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Tue, 16 Feb 2021 12:07:05 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1180211 - Don't emit bytecode for empty strings in emitTemplateString. r=jorendorff (32583f419) - pointer style (7c9c4fa4b) - Bug 1150779 - Split for-loop tests out of js1_8_5/reflect-parse/destructuring.js. r=efaust (a61cdcc1f) - missing bit of Bug 748550 - Remove support for |for (... = ... in/of ...)| now that (bd6995dcc) - Bug 1164741 - Readd parsing support for |for (var ...1 = ...2 in ...3)|, but completely ignore the |= ...2| assignment when ascribing semantics to it. r=jorendorff, r=efaust (89b433205) - Bug 1163851 - Actually respect the InHandling parameter in the parser. |pc->parsingForInit| is now unused except for testing; it'll be removed once this has stuck a bit, as this change *does* change semantics in some edge cases. r=efaust (221fefa9a) - Bug 1146136 - Parenthesized "destructuring patterns" shouldn't actually be destructuring patterns. r=efaust, a=KWierso (93752d091) - Bug 1146136 - Fix most in-tree tests that parenthesize destructuring pattern assignment targets, as ES6 forbids such parenthesization. (The patch making SpiderMonkey reject this syntax will follow shortly.) r=efaust, r=testingonlychange (3e14c89c8) - Bug 1165162 - Hoist GetOrigin onto BasePrincipal. r=gabor (f396e85c9) - Bug 1161183: Don't show the add-on version in the list view. r=dao (559bfa7a5) - Bug 1161183: Follow-up fix that was missed in the review comments. r=dao, a=kwierso (57dfd764c) - Bug 1146136 - Fix another test that parenthesizes destructuring pattern assignment targets, that snuck in in the last week, as ES6 forbids such parenthesization. r=bustage, a=bustage (89078a294) - Bug 1146136 - Fix another test that parenthesizes destructuring pattern assignment targets, that snuck in in the last week, as ES6 forbids such parenthesization. r=bustage, a=bustage (7a93bd669) - Bug 1146136 - Followup mop-up to require that in destructuring patterns, an assignment target with a default value must not be parenthesized. (That is, |var [(a = 3)] = [];| is invalid, while |var [a = 3] = [];| is valid.) r=evilpie, r=efaust (b5697d644) - Bug 1146136 - Followup to condition tests using |super| syntax on class syntax being enabled. r=bustage (71725bdfc) - Bug 1165835: Fix bluetooth2 build break resulting from Bug 1164292. r=btian (44cc89766) - Bug 1140952 - Implement read/write value of a descriptor for GATT client API (ipc part). f=jocelyn, r=btian (14b21ce2b) - Bug 1156229: Make ref-counted class destructors non-public, r=joliu (dc5e0101f) - Bug 1156229: Update GATT support to include Android L, r=joliu (1ceef7b41) - fix patch order (4b8a2504e) - fix patch order (0a5787b74) - Bug 1127665 - Append name of paired device to 'DevicePaired' and 'PropertyChanged' BT signal. r=btian (72dfe74d8) - Bug 1141944 - [bluetooth2] Revise device paired/unpaired handlers in BluetoothAdapter, f=jaliu, r=shuang (bb74c9b01) - Bug 1063444 - Implement LE scan related methods and LE device event. r=joliu, r=mrbkap (7b4772ace) - Bug 1163969 - Reject the stopLeScan request only if there's no ongoing LE Scan using the given handle. r=joliu (a6fd40998) --- caps/BasePrincipal.cpp | 6 + caps/BasePrincipal.h | 2 + caps/nsNullPrincipal.cpp | 4 +- caps/nsNullPrincipal.h | 2 +- caps/nsPrincipal.cpp | 8 +- caps/nsPrincipal.h | 4 +- caps/nsSystemPrincipal.cpp | 4 +- caps/nsSystemPrincipal.h | 2 +- dom/bindings/Bindings.conf | 4 + dom/bluetooth/BluetoothCommon.h | 24 + dom/bluetooth/BluetoothInterface.h | 14 +- dom/bluetooth/BluetoothUtils.cpp | 22 + dom/bluetooth/BluetoothUtils.h | 8 + .../bluedroid/BluetoothGattHALInterface.cpp | 71 +- .../bluedroid/BluetoothGattHALInterface.h | 12 +- .../bluedroid/BluetoothGattManager.cpp | 495 ++++++++- .../bluedroid/BluetoothGattManager.h | 28 +- .../bluedroid/BluetoothHALHelpers.cpp | 18 + dom/bluetooth/bluedroid/BluetoothHALHelpers.h | 5 + .../bluedroid/BluetoothServiceBluedroid.cpp | 90 +- .../bluedroid/BluetoothServiceBluedroid.h | 31 +- dom/bluetooth/bluetooth1/BluetoothAdapter.cpp | 11 +- dom/bluetooth/bluetooth1/BluetoothService.h | 4 +- .../bluetooth1/ipc/BluetoothParent.cpp | 8 +- .../ipc/BluetoothServiceChildProcess.cpp | 8 +- .../ipc/BluetoothServiceChildProcess.h | 4 +- dom/bluetooth/bluetooth2/BluetoothAdapter.cpp | 360 +++++-- dom/bluetooth/bluetooth2/BluetoothAdapter.h | 61 +- dom/bluetooth/bluetooth2/BluetoothDevice.cpp | 103 ++ dom/bluetooth/bluetooth2/BluetoothDevice.h | 10 + .../bluetooth2/BluetoothDiscoveryHandle.cpp | 99 +- .../bluetooth2/BluetoothDiscoveryHandle.h | 55 +- dom/bluetooth/bluetooth2/BluetoothGatt.cpp | 26 +- dom/bluetooth/bluetooth2/BluetoothGatt.h | 7 - .../bluetooth2/BluetoothGattDescriptor.cpp | 1 + .../bluetooth2/BluetoothGattDescriptor.h | 8 + .../bluetooth2/BluetoothLeDeviceEvent.cpp | 140 +++ .../bluetooth2/BluetoothLeDeviceEvent.h | 64 ++ .../bluetooth2/BluetoothProfileController.cpp | 1 - dom/bluetooth/bluetooth2/BluetoothService.h | 47 +- .../bluetooth2/ipc/BluetoothParent.cpp | 75 +- .../bluetooth2/ipc/BluetoothParent.h | 12 + .../ipc/BluetoothServiceChildProcess.cpp | 60 +- .../ipc/BluetoothServiceChildProcess.h | 29 +- .../bluetooth2/ipc/BluetoothTypes.ipdlh | 1 + dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl | 31 + dom/bluetooth/bluez/BluetoothDBusService.cpp | 43 +- dom/bluetooth/bluez/BluetoothDBusService.h | 29 +- dom/bluetooth/moz.build | 2 + .../bluedroid/BluetoothServiceBluedroid.cpp | 40 +- .../test/test_all_synthetic_events.html | 4 + dom/webidl/BluetoothAdapter2.webidl | 6 + dom/webidl/BluetoothLeDeviceEvent.webidl | 22 + dom/webidl/moz.build | 1 + js/src/frontend/BytecodeEmitter.cpp | 28 +- js/src/frontend/FoldConstants.cpp | 1 + js/src/frontend/FullParseHandler.h | 37 +- js/src/frontend/Parser.cpp | 330 +++--- js/src/frontend/Parser.h | 29 +- js/src/frontend/SyntaxParseHandler.h | 135 ++- .../tests/basic/destructuring-default.js | 10 +- .../tests/basic/destructuring-rest.js | 15 +- .../tests/basic/spread-call-setcall.js | 6 +- js/src/js.msg | 1 + .../destructuring-pattern-parenthesized.js | 140 +++ .../arrow-function-in-for-statement-head.js | 33 + js/src/tests/ecma_6/Statements/browser.js | 0 .../for-in-with-destructuring-assignments.js | 130 +++ .../Statements/for-of-var-with-initializer.js | 32 + js/src/tests/ecma_6/Statements/shell.js | 0 .../extensions/for-in-with-assignments.js | 74 ++ .../js1_7/extensions/destructuring-order.js | 4 +- .../tests/js1_7/extensions/regress-368224.js | 2 +- js/src/tests/js1_7/regress/regress-379442.js | 2 +- .../js1_8_5/reflect-parse/destructuring.js | 52 - .../reflect-parse/for-loop-destructuring.js | 51 + js/src/vm/Xdr.h | 4 +- .../passwordmgr/test/test_bug_627616.html | 2 +- toolkit/devtools/gcli/commands/screenshot.js | 2 +- .../tests/unit/test_forwardingprefix.js | 2 +- .../mozapps/extensions/content/extensions.css | 1 - .../mozapps/extensions/content/extensions.js | 29 + .../mozapps/extensions/content/extensions.xml | 41 +- .../mozapps/extensions/content/extensions.xul | 2 + .../test/browser/browser_bug580298.js | 91 +- .../test/browser/browser_bug596336.js | 88 +- .../test/browser/browser_experiments.js | 8 +- .../extensions/test/browser/browser_list.js | 960 ++++++++++-------- .../test/browser/browser_manualupdates.js | 92 +- .../test/browser/browser_updateid.js | 16 +- .../mozapps/extensions/test/browser/head.js | 44 + 91 files changed, 3581 insertions(+), 1139 deletions(-) create mode 100644 dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.cpp create mode 100644 dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.h create mode 100644 dom/webidl/BluetoothLeDeviceEvent.webidl create mode 100644 js/src/tests/ecma_6/Expressions/destructuring-pattern-parenthesized.js create mode 100644 js/src/tests/ecma_6/Statements/arrow-function-in-for-statement-head.js create mode 100644 js/src/tests/ecma_6/Statements/browser.js create mode 100644 js/src/tests/ecma_6/Statements/for-in-with-destructuring-assignments.js create mode 100644 js/src/tests/ecma_6/Statements/for-of-var-with-initializer.js create mode 100644 js/src/tests/ecma_6/Statements/shell.js create mode 100644 js/src/tests/ecma_6/extensions/for-in-with-assignments.js create mode 100644 js/src/tests/js1_8_5/reflect-parse/for-loop-destructuring.js diff --git a/caps/BasePrincipal.cpp b/caps/BasePrincipal.cpp index 0647d09d94..e8770cf742 100644 --- a/caps/BasePrincipal.cpp +++ b/caps/BasePrincipal.cpp @@ -54,6 +54,12 @@ OriginAttributes::Deserialize(nsIObjectInputStream* aStream) return NS_OK; } +NS_IMETHODIMP +BasePrincipal::GetOrigin(nsACString& aOrigin) +{ + return GetOriginInternal(aOrigin); +} + bool BasePrincipal::Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) { diff --git a/caps/BasePrincipal.h b/caps/BasePrincipal.h index a4329d930b..ff9827d30c 100644 --- a/caps/BasePrincipal.h +++ b/caps/BasePrincipal.h @@ -62,6 +62,7 @@ public: enum DocumentDomainConsideration { DontConsiderDocumentDomain, ConsiderDocumentDomain}; bool Subsumes(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration); + NS_IMETHOD GetOrigin(nsACString& aOrigin) final; NS_IMETHOD Equals(nsIPrincipal* other, bool* _retval) final; NS_IMETHOD EqualsConsideringDomain(nsIPrincipal* other, bool* _retval) final; NS_IMETHOD Subsumes(nsIPrincipal* other, bool* _retval) final; @@ -89,6 +90,7 @@ public: protected: virtual ~BasePrincipal() {} + virtual nsresult GetOriginInternal(nsACString& aOrigin) = 0; virtual bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsider) = 0; nsCOMPtr mCSP; diff --git a/caps/nsNullPrincipal.cpp b/caps/nsNullPrincipal.cpp index 207aa1c5fc..b3313f250d 100644 --- a/caps/nsNullPrincipal.cpp +++ b/caps/nsNullPrincipal.cpp @@ -102,8 +102,8 @@ nsNullPrincipal::SetDomain(nsIURI* aDomain) return NS_ERROR_NOT_AVAILABLE; } -NS_IMETHODIMP -nsNullPrincipal::GetOrigin(nsACString& aOrigin) +nsresult +nsNullPrincipal::GetOriginInternal(nsACString& aOrigin) { return mURI->GetSpec(aOrigin); } diff --git a/caps/nsNullPrincipal.h b/caps/nsNullPrincipal.h index 9566843219..56e27a6ed2 100644 --- a/caps/nsNullPrincipal.h +++ b/caps/nsNullPrincipal.h @@ -44,10 +44,10 @@ public: NS_IMETHOD GetURI(nsIURI** aURI) override; NS_IMETHOD GetDomain(nsIURI** aDomain) override; NS_IMETHOD SetDomain(nsIURI* aDomain) override; - NS_IMETHOD GetOrigin(nsACString& aOrigin) override; NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) override; NS_IMETHOD GetIsNullPrincipal(bool* aIsNullPrincipal) override; NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; + nsresult GetOriginInternal(nsACString& aOrigin) override; // Returns null on failure. static already_AddRefed CreateWithInheritedAttributes(nsIPrincipal *aInheritFrom); diff --git a/caps/nsPrincipal.cpp b/caps/nsPrincipal.cpp index 979cd86f68..0e15e8dc95 100644 --- a/caps/nsPrincipal.cpp +++ b/caps/nsPrincipal.cpp @@ -149,8 +149,8 @@ nsPrincipal::GetOriginForURI(nsIURI* aURI, nsACString& aOrigin) return NS_OK; } -NS_IMETHODIMP -nsPrincipal::GetOrigin(nsACString& aOrigin) +nsresult +nsPrincipal::GetOriginInternal(nsACString& aOrigin) { return GetOriginForURI(mCodebase, aOrigin); } @@ -625,8 +625,8 @@ nsExpandedPrincipal::SetDomain(nsIURI* aDomain) return NS_OK; } -NS_IMETHODIMP -nsExpandedPrincipal::GetOrigin(nsACString& aOrigin) +nsresult +nsExpandedPrincipal::GetOriginInternal(nsACString& aOrigin) { aOrigin.AssignLiteral("[Expanded Principal ["); for (size_t i = 0; i < mPrincipals.Length(); ++i) { diff --git a/caps/nsPrincipal.h b/caps/nsPrincipal.h index 31897204fa..5c70eb8ef3 100644 --- a/caps/nsPrincipal.h +++ b/caps/nsPrincipal.h @@ -26,10 +26,10 @@ public: NS_IMETHOD GetURI(nsIURI** aURI) override; NS_IMETHOD GetDomain(nsIURI** aDomain) override; NS_IMETHOD SetDomain(nsIURI* aDomain) override; - NS_IMETHOD GetOrigin(nsACString& aOrigin) override; NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) override; NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; virtual bool IsOnCSSUnprefixingWhitelist() override; + nsresult GetOriginInternal(nsACString& aOrigin) override; nsPrincipal(); @@ -94,11 +94,11 @@ public: NS_IMETHOD GetURI(nsIURI** aURI) override; NS_IMETHOD GetDomain(nsIURI** aDomain) override; NS_IMETHOD SetDomain(nsIURI* aDomain) override; - NS_IMETHOD GetOrigin(nsACString& aOrigin) override; NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) override; NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; virtual bool IsOnCSSUnprefixingWhitelist() override; virtual void GetScriptLocation(nsACString &aStr) override; + nsresult GetOriginInternal(nsACString& aOrigin) override; protected: virtual ~nsExpandedPrincipal(); diff --git a/caps/nsSystemPrincipal.cpp b/caps/nsSystemPrincipal.cpp index 0509720da2..a6857117b4 100644 --- a/caps/nsSystemPrincipal.cpp +++ b/caps/nsSystemPrincipal.cpp @@ -61,8 +61,8 @@ nsSystemPrincipal::GetURI(nsIURI** aURI) return NS_OK; } -NS_IMETHODIMP -nsSystemPrincipal::GetOrigin(nsACString& aOrigin) +nsresult +nsSystemPrincipal::GetOriginInternal(nsACString& aOrigin) { aOrigin.AssignLiteral(SYSTEM_PRINCIPAL_SPEC); return NS_OK; diff --git a/caps/nsSystemPrincipal.h b/caps/nsSystemPrincipal.h index 187d759a44..ae614af840 100644 --- a/caps/nsSystemPrincipal.h +++ b/caps/nsSystemPrincipal.h @@ -29,11 +29,11 @@ public: NS_IMETHOD GetURI(nsIURI** aURI) override; NS_IMETHOD GetDomain(nsIURI** aDomain) override; NS_IMETHOD SetDomain(nsIURI* aDomain) override; - NS_IMETHOD GetOrigin(nsACString& aOrigin) override; NS_IMETHOD CheckMayLoad(nsIURI* uri, bool report, bool allowIfInheritsPrincipal) override; NS_IMETHOD GetCsp(nsIContentSecurityPolicy** aCsp) override; NS_IMETHOD SetCsp(nsIContentSecurityPolicy* aCsp) override; NS_IMETHOD GetBaseDomain(nsACString& aBaseDomain) override; + nsresult GetOriginInternal(nsACString& aOrigin) override; nsSystemPrincipal() {} diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index da0855276c..c071db50fd 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -178,6 +178,10 @@ DOMInterfaces = { 'nativeType': 'mozilla::dom::bluetooth::BluetoothGattService', }, +'BluetoothLeDeviceEvent': { + 'nativeType': 'mozilla::dom::bluetooth::BluetoothLeDeviceEvent', +}, + 'BluetoothManager': { 'nativeType': 'mozilla::dom::bluetooth::BluetoothManager', }, diff --git a/dom/bluetooth/BluetoothCommon.h b/dom/bluetooth/BluetoothCommon.h index 1689d05607..f157bb5155 100644 --- a/dom/bluetooth/BluetoothCommon.h +++ b/dom/bluetooth/BluetoothCommon.h @@ -91,6 +91,14 @@ extern bool gBluetoothDebugFlag; array.AppendElement(BluetoothNamedValue(NS_LITERAL_STRING(name), \ BluetoothValue(value))) +/** + * Wrap literal name and value into a BluetoothNamedValue + * and insert it to the array. + */ +#define BT_INSERT_NAMED_VALUE(array, index, name, value) \ + array.InsertElementAt(index, BluetoothNamedValue(NS_LITERAL_STRING(name), \ + BluetoothValue(value))) + /** * Ensure success of system message broadcast with void return. */ @@ -741,6 +749,22 @@ struct BluetoothGattNotifyParam { bool mIsNotify; }; +/** + * EIR Data Type, Advertising Data Type (AD Type) and OOB Data Type Definitions + * Please refer to https://www.bluetooth.org/en-us/specification/\ + * assigned-numbers/generic-access-profile + */ +enum BluetoothGapDataType { + GAP_INCOMPLETE_UUID16 = 0X02, // Incomplete List of 16-bit Service Class UUIDs + GAP_COMPLETE_UUID16 = 0X03, // Complete List of 16-bit Service Class UUIDs + GAP_INCOMPLETE_UUID32 = 0X04, // Incomplete List of 32-bit Service Class UUIDs + GAP_COMPLETE_UUID32 = 0X05, // Complete List of 32-bit Service Class UUIDs» + GAP_INCOMPLETE_UUID128 = 0X06, // Incomplete List of 128-bit Service Class UUIDs + GAP_COMPLETE_UUID128 = 0X07, // Complete List of 128-bit Service Class UUIDs + GAP_SHORTENED_NAME = 0X08, // Shortened Local Name + GAP_COMPLETE_NAME = 0X09, // Complete Local Name +}; + END_BLUETOOTH_NAMESPACE #endif // mozilla_dom_bluetooth_bluetoothcommon_h__ diff --git a/dom/bluetooth/BluetoothInterface.h b/dom/bluetooth/BluetoothInterface.h index f5adff941e..48add6b8b1 100644 --- a/dom/bluetooth/BluetoothInterface.h +++ b/dom/bluetooth/BluetoothInterface.h @@ -712,6 +712,7 @@ public: virtual void Connect(int aClientIf, const nsAString& aBdAddr, bool aIsDirect, /* auto connect */ + BluetoothTransport aTransport, BluetoothGattClientResultHandler* aRes) = 0; virtual void Disconnect(int aClientIf, const nsAString& aBdAddr, @@ -768,16 +769,15 @@ public: const BluetoothGattServiceId& aServiceId, const BluetoothGattId& aCharId, const BluetoothGattId& aDescriptorId, - int aAuthReq, + BluetoothGattAuthReq aAuthReq, BluetoothGattClientResultHandler* aRes) = 0; virtual void WriteDescriptor(int aConnId, const BluetoothGattServiceId& aServiceId, const BluetoothGattId& aCharId, const BluetoothGattId& aDescriptorId, - int aWriteType, - int aLen, - int aAuthReq, - const ArrayBuffer& aValue, + BluetoothGattWriteType aWriteType, + BluetoothGattAuthReq aAuthReq, + const nsTArray& aValue, BluetoothGattClientResultHandler* aRes) = 0; /* Execute / Abort Prepared Write*/ @@ -817,6 +817,10 @@ public: int aApperance, uint8_t aManufacturerLen, const ArrayBuffer& aManufacturerData, + uint8_t aServiceDataLen, + const ArrayBuffer& aServiceData, + uint8_t aServiceUUIDLen, + const ArrayBuffer& aServiceUUID, BluetoothGattClientResultHandler* aRes) = 0; protected: diff --git a/dom/bluetooth/BluetoothUtils.cpp b/dom/bluetooth/BluetoothUtils.cpp index 1241c8983c..efa0bb1cdd 100644 --- a/dom/bluetooth/BluetoothUtils.cpp +++ b/dom/bluetooth/BluetoothUtils.cpp @@ -12,6 +12,7 @@ #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "nsContentUtils.h" #include "nsISystemMessagesInternal.h" +#include "nsIUUIDGenerator.h" #include "nsServiceManagerUtils.h" #include "nsXULAppAPI.h" @@ -76,6 +77,27 @@ StringToUuid(const char* aString, BluetoothUuid& aUuid) memcpy(&aUuid.mUuid[14], &uuid5, sizeof(uint16_t)); } +void +GenerateUuid(nsAString &aUuidString) +{ + nsresult rv; + nsCOMPtr uuidGenerator = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + nsID uuid; + rv = uuidGenerator->GenerateUUIDInPlace(&uuid); + NS_ENSURE_SUCCESS_VOID(rv); + + // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format + char uuidBuffer[NSID_LENGTH]; + uuid.ToProvidedString(uuidBuffer); + NS_ConvertASCIItoUTF16 uuidString(uuidBuffer); + + // Remove {} and the null terminator + aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3)); +} + void GeneratePathFromGattId(const BluetoothGattId& aId, nsAString& aPath, diff --git a/dom/bluetooth/BluetoothUtils.h b/dom/bluetooth/BluetoothUtils.h index f70eb2db6e..6c528c6801 100644 --- a/dom/bluetooth/BluetoothUtils.h +++ b/dom/bluetooth/BluetoothUtils.h @@ -51,6 +51,14 @@ ReversedUuidToString(const BluetoothUuid& aUuid, nsAString& aString); void StringToUuid(const char* aString, BluetoothUuid& aUuid); +/** + * Generate a random uuid. + * + * @param aUuidString [out] String to store the generated uuid. + */ +void +GenerateUuid(nsAString &aUuidString); + // // Generate bluetooth signal path from GattId // diff --git a/dom/bluetooth/bluedroid/BluetoothGattHALInterface.cpp b/dom/bluetooth/bluedroid/BluetoothGattHALInterface.cpp index de21676417..cabc965189 100644 --- a/dom/bluetooth/bluedroid/BluetoothGattHALInterface.cpp +++ b/dom/bluetooth/bluedroid/BluetoothGattHALInterface.cpp @@ -514,7 +514,9 @@ void BluetoothGattClientHALInterface::Scan( int aClientIf, bool aStart, BluetoothGattClientResultHandler* aRes) { -#if ANDROID_VERSION >= 19 +#if ANDROID_VERSION >= 21 + int status = mInterface->scan(aStart); +#elif ANDROID_VERSION >= 19 int status = mInterface->scan(aClientIf, aStart); #else int status = BT_STATUS_UNSUPPORTED; @@ -530,10 +532,21 @@ BluetoothGattClientHALInterface::Scan( void BluetoothGattClientHALInterface::Connect( int aClientIf, const nsAString& aBdAddr, - bool aIsDirect, BluetoothGattClientResultHandler* aRes) + bool aIsDirect, BluetoothTransport aTransport, + BluetoothGattClientResultHandler* aRes) { bt_status_t status; -#if ANDROID_VERSION >= 19 +#if ANDROID_VERSION >= 21 + bt_bdaddr_t bdAddr; + btgatt_transport_t transport; + + if (NS_SUCCEEDED(Convert(aBdAddr, bdAddr)) || + NS_SUCCEEDED(Convert(aTransport, transport))) { + status = mInterface->connect(aClientIf, &bdAddr, aIsDirect, transport); + } else { + status = BT_STATUS_PARM_INVALID; + } +#elif ANDROID_VERSION >= 19 bt_bdaddr_t bdAddr; if (NS_SUCCEEDED(Convert(aBdAddr, bdAddr))) { @@ -814,19 +827,21 @@ BluetoothGattClientHALInterface::ReadDescriptor( int aConnId, const BluetoothGattServiceId& aServiceId, const BluetoothGattId& aCharId, const BluetoothGattId& aDescriptorId, - int aAuthReq, BluetoothGattClientResultHandler* aRes) + BluetoothGattAuthReq aAuthReq, BluetoothGattClientResultHandler* aRes) { bt_status_t status; #if ANDROID_VERSION >= 19 btgatt_srvc_id_t serviceId; btgatt_gatt_id_t charId; btgatt_gatt_id_t descriptorId; + int authReq; if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) && NS_SUCCEEDED(Convert(aCharId, charId)) && - NS_SUCCEEDED(Convert(aDescriptorId, descriptorId))) { + NS_SUCCEEDED(Convert(aDescriptorId, descriptorId)) && + NS_SUCCEEDED(Convert(aAuthReq, authReq))) { status = mInterface->read_descriptor(aConnId, &serviceId, &charId, - &descriptorId, aAuthReq); + &descriptorId, authReq); } else { status = BT_STATUS_PARM_INVALID; } @@ -846,8 +861,9 @@ BluetoothGattClientHALInterface::WriteDescriptor( int aConnId, const BluetoothGattServiceId& aServiceId, const BluetoothGattId& aCharId, const BluetoothGattId& aDescriptorId, - int aWriteType, int aLen, int aAuthReq, - const ArrayBuffer& aValue, + BluetoothGattWriteType aWriteType, + BluetoothGattAuthReq aAuthReq, + const nsTArray& aValue, BluetoothGattClientResultHandler* aRes) { bt_status_t status; @@ -855,15 +871,18 @@ BluetoothGattClientHALInterface::WriteDescriptor( btgatt_srvc_id_t serviceId; btgatt_gatt_id_t charId; btgatt_gatt_id_t descriptorId; - char value[aLen + 1]; + int writeType; + int authReq; if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) && NS_SUCCEEDED(Convert(aCharId, charId)) && NS_SUCCEEDED(Convert(aDescriptorId, descriptorId)) && - NS_SUCCEEDED(Convert(aValue, value))) { - status = mInterface->write_descriptor(aConnId, &serviceId, &charId, - &descriptorId, aWriteType, aLen, - aAuthReq, value); + NS_SUCCEEDED(Convert(aWriteType, writeType)) && + NS_SUCCEEDED(Convert(aAuthReq, authReq))) { + status = mInterface->write_descriptor( + aConnId, &serviceId, &charId, &descriptorId, writeType, + aValue.Length() * sizeof(uint8_t), authReq, + reinterpret_cast(const_cast(aValue.Elements()))); } else { status = BT_STATUS_PARM_INVALID; } @@ -1013,10 +1032,34 @@ BluetoothGattClientHALInterface::SetAdvData( int aServerIf, bool aIsScanRsp, bool aIsNameIncluded, bool aIsTxPowerIncluded, int aMinInterval, int aMaxInterval, int aApperance, uint8_t aManufacturerLen, const ArrayBuffer& aManufacturerData, + uint8_t aServiceDataLen, const ArrayBuffer& aServiceData, + uint8_t aServiceUUIDLen, const ArrayBuffer& aServiceUUID, BluetoothGattClientResultHandler* aRes) { + /* FIXME: This method allocates a large amount of memory on the + * stack. It should be rewritten to prevent the pending stack + * overflow. Additionally |ArrayBuffer| seems like the wrong data + * type here. Why not use plain pointers instead? + */ + bt_status_t status; -#if ANDROID_VERSION >= 19 +#if ANDROID_VERSION >= 21 + char manufacturerData[aManufacturerLen + 1]; + char serviceData[aServiceDataLen + 1]; + char serviceUUID[aServiceUUIDLen + 1]; + + if (NS_SUCCEEDED(Convert(aManufacturerData, manufacturerData)) || + NS_SUCCEEDED(Convert(aServiceData, serviceData)) || + NS_SUCCEEDED(Convert(aServiceUUID, serviceUUID))) { + status = mInterface->set_adv_data( + aServerIf, aIsScanRsp, aIsNameIncluded, aIsTxPowerIncluded, + aMinInterval, aMaxInterval, aApperance, + aManufacturerLen, manufacturerData, + aServiceDataLen, serviceData, aServiceUUIDLen, serviceUUID); + } else { + status = BT_STATUS_PARM_INVALID; + } +#elif ANDROID_VERSION >= 19 char value[aManufacturerLen + 1]; if (NS_SUCCEEDED(Convert(aManufacturerData, value))) { diff --git a/dom/bluetooth/bluedroid/BluetoothGattHALInterface.h b/dom/bluetooth/bluedroid/BluetoothGattHALInterface.h index 1bc7a81630..8671e6305f 100644 --- a/dom/bluetooth/bluedroid/BluetoothGattHALInterface.h +++ b/dom/bluetooth/bluedroid/BluetoothGattHALInterface.h @@ -38,6 +38,7 @@ public: void Connect(int aClientIf, const nsAString& aBdAddr, bool aIsDirect, /* auto connect */ + BluetoothTransport aTransport, BluetoothGattClientResultHandler* aRes); void Disconnect(int aClientIf, const nsAString& aBdAddr, @@ -93,16 +94,15 @@ public: const BluetoothGattServiceId& aServiceId, const BluetoothGattId& aCharId, const BluetoothGattId& aDescriptorId, - int aAuthReq, + BluetoothGattAuthReq aAuthReq, BluetoothGattClientResultHandler* aRes); void WriteDescriptor(int aConnId, const BluetoothGattServiceId& aServiceId, const BluetoothGattId& aCharId, const BluetoothGattId& aDescriptorId, - int aWriteType, - int aLen, - int aAuthReq, - const ArrayBuffer& aValue, + BluetoothGattWriteType aWriteType, + BluetoothGattAuthReq aAuthReq, + const nsTArray& aValue, BluetoothGattClientResultHandler* aRes); /* Execute / Abort Prepared Write*/ @@ -140,6 +140,8 @@ public: int aApperance, uint8_t aManufacturerLen, const ArrayBuffer& aManufacturerData, + uint8_t aServiceDataLen, const ArrayBuffer& aServiceData, + uint8_t aServiceUUIDLen, const ArrayBuffer& aServiceUUID, BluetoothGattClientResultHandler* aRes); protected: diff --git a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp index 2f1f23573c..9ce8c324ad 100644 --- a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp +++ b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp @@ -85,6 +85,48 @@ struct BluetoothGattClientWriteCharState } }; +struct BluetoothGattClientReadDescState +{ + bool mAuthRetry; + nsRefPtr mRunnable; + + void Assign(bool aAuthRetry, + BluetoothReplyRunnable* aRunnable) + { + mAuthRetry = aAuthRetry; + mRunnable = aRunnable; + } + + void Reset() + { + mAuthRetry = false; + mRunnable = nullptr; + } +}; + +struct BluetoothGattClientWriteDescState +{ + nsTArray mWriteValue; + bool mAuthRetry; + nsRefPtr mRunnable; + + void Assign(const nsTArray& aWriteValue, + bool aAuthRetry, + BluetoothReplyRunnable* aRunnable) + { + mWriteValue = aWriteValue; + mAuthRetry = aAuthRetry; + mRunnable = aRunnable; + } + + void Reset() + { + mWriteValue.Clear(); + mAuthRetry = false; + mRunnable = nullptr; + } +}; + class mozilla::dom::bluetooth::BluetoothGattClient final : public nsISupports { public: @@ -97,19 +139,6 @@ public: , mConnId(0) { } - ~BluetoothGattClient() - { - mConnectRunnable = nullptr; - mDisconnectRunnable = nullptr; - mDiscoverRunnable = nullptr; - mUnregisterClientRunnable = nullptr; - mReadRemoteRssiRunnable = nullptr; - mRegisterNotificationsRunnable = nullptr; - mDeregisterNotificationsRunnable = nullptr; - mReadCharacteristicState.Reset(); - mWriteCharacteristicState.Reset(); - } - void NotifyDiscoverCompleted(bool aSuccess) { MOZ_ASSERT(!mAppUuid.IsEmpty()); @@ -144,6 +173,7 @@ public: nsString mDeviceAddr; int mClientIf; int mConnId; + nsRefPtr mStartLeScanRunnable; nsRefPtr mConnectRunnable; nsRefPtr mDisconnectRunnable; nsRefPtr mDiscoverRunnable; @@ -154,6 +184,8 @@ public: BluetoothGattClientReadCharState mReadCharacteristicState; BluetoothGattClientWriteCharState mWriteCharacteristicState; + BluetoothGattClientReadDescState mReadDescriptorState; + BluetoothGattClientWriteDescState mWriteDescriptorState; /** * These temporary arrays are used only during discover operations. @@ -163,6 +195,10 @@ public: nsTArray mIncludedServices; nsTArray mCharacteristics; nsTArray mDescriptors; + +private: + ~BluetoothGattClient() + { } }; NS_IMPL_ISUPPORTS0(BluetoothGattClient) @@ -453,6 +489,140 @@ BluetoothGattManager::UnregisterClient(int aClientIf, new UnregisterClientResultHandler(client)); } +class BluetoothGattManager::StartLeScanResultHandler final + : public BluetoothGattClientResultHandler +{ +public: + StartLeScanResultHandler(BluetoothGattClient* aClient) + : mClient(aClient) + { } + + void Scan() override + { + MOZ_ASSERT(mClient > 0); + + DispatchReplySuccess(mClient->mStartLeScanRunnable, + BluetoothValue(mClient->mAppUuid)); + mClient->mStartLeScanRunnable = nullptr; + } + + void OnError(BluetoothStatus aStatus) override + { + BT_WARNING("BluetoothGattClientInterface::StartLeScan failed: %d", + (int)aStatus); + MOZ_ASSERT(mClient->mStartLeScanRunnable); + + // Unregister client if startLeScan failed + if (mClient->mClientIf > 0) { + BluetoothGattManager* gattManager = BluetoothGattManager::Get(); + NS_ENSURE_TRUE_VOID(gattManager); + + nsRefPtr result = + new BluetoothVoidReplyRunnable(nullptr); + gattManager->UnregisterClient(mClient->mClientIf, result); + } + + DispatchReplyError(mClient->mStartLeScanRunnable, + BluetoothValue(mClient->mAppUuid)); + mClient->mStartLeScanRunnable = nullptr; + } + +private: + nsRefPtr mClient; +}; + +class BluetoothGattManager::StopLeScanResultHandler final + : public BluetoothGattClientResultHandler +{ +public: + StopLeScanResultHandler(BluetoothReplyRunnable* aRunnable, int aClientIf) + : mRunnable(aRunnable), mClientIf(aClientIf) + { } + + void Scan() override + { + DispatchReplySuccess(mRunnable); + + // Unregister client when stopLeScan succeeded + if (mClientIf > 0) { + BluetoothGattManager* gattManager = BluetoothGattManager::Get(); + NS_ENSURE_TRUE_VOID(gattManager); + + nsRefPtr result = + new BluetoothVoidReplyRunnable(nullptr); + gattManager->UnregisterClient(mClientIf, result); + } + } + + void OnError(BluetoothStatus aStatus) override + { + BT_WARNING("BluetoothGattClientInterface::StopLeScan failed: %d", + (int)aStatus); + DispatchReplyError(mRunnable, aStatus); + } + +private: + nsRefPtr mRunnable; + int mClientIf; +}; + +void +BluetoothGattManager::StartLeScan(const nsTArray& aServiceUuids, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRunnable); + + ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable); + + nsString appUuidStr; + GenerateUuid(appUuidStr); + + size_t index = sClients->IndexOf(appUuidStr, 0 /* Start */, UuidComparator()); + + // Reject the startLeScan request if the clientIf is being used. + if (index != sClients->NoIndex) { + DispatchReplyError(aRunnable, + NS_LITERAL_STRING("start LE scan failed")); + return; + } + + index = sClients->Length(); + sClients->AppendElement(new BluetoothGattClient(appUuidStr, EmptyString())); + nsRefPtr client = sClients->ElementAt(index); + client->mStartLeScanRunnable = aRunnable; + + BluetoothUuid appUuid; + StringToUuid(NS_ConvertUTF16toUTF8(appUuidStr).get(), appUuid); + + // 'startLeScan' will be proceeded after client registered + sBluetoothGattClientInterface->RegisterClient( + appUuid, new RegisterClientResultHandler(client)); +} + +void +BluetoothGattManager::StopLeScan(const nsAString& aScanUuid, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRunnable); + + ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable); + + size_t index = sClients->IndexOf(aScanUuid, 0 /* Start */, UuidComparator()); + if (NS_WARN_IF(index == sClients->NoIndex)) { + // Reject the stop LE scan request + DispatchReplyError(aRunnable, NS_LITERAL_STRING("StopLeScan failed")); + return; + } + + nsRefPtr client = sClients->ElementAt(index); + sBluetoothGattClientInterface->Scan( + client->mClientIf, + false /* Stop */, + new StopLeScanResultHandler(aRunnable, client->mClientIf)); +} + class BluetoothGattManager::ConnectResultHandler final : public BluetoothGattClientResultHandler { @@ -511,6 +681,7 @@ BluetoothGattManager::Connect(const nsAString& aAppUuid, sBluetoothGattClientInterface->Connect(client->mClientIf, aDeviceAddr, true, // direct connect + TRANSPORT_AUTO, new ConnectResultHandler(client)); } else { BluetoothUuid uuid; @@ -1000,6 +1171,173 @@ BluetoothGattManager::WriteCharacteristicValue( new WriteCharacteristicValueResultHandler(client)); } +class BluetoothGattManager::ReadDescriptorValueResultHandler final + : public BluetoothGattClientResultHandler +{ +public: + ReadDescriptorValueResultHandler(BluetoothGattClient* aClient) + : mClient(aClient) + { + MOZ_ASSERT(mClient); + } + + void OnError(BluetoothStatus aStatus) override + { + BT_WARNING("BluetoothGattClientInterface::ReadDescriptorValue failed: %d", + (int)aStatus); + MOZ_ASSERT(mClient->mReadDescriptorState.mRunnable); + + nsRefPtr runnable = + mClient->mReadDescriptorState.mRunnable; + mClient->mReadDescriptorState.Reset(); + + // Reject the read descriptor value request + DispatchReplyError(runnable, + NS_LITERAL_STRING("ReadDescriptorValue failed")); + } + +private: + nsRefPtr mClient; +}; + +void +BluetoothGattManager::ReadDescriptorValue( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRunnable); + + ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable); + + size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator()); + if (NS_WARN_IF(index == sClients->NoIndex)) { + // Reject the read descriptor value request + DispatchReplyError(aRunnable, + NS_LITERAL_STRING("ReadDescriptorValue failed")); + return; + } + + nsRefPtr client = sClients->ElementAt(index); + + /** + * Reject subsequent reading requests to follow ATT sequential protocol that + * handles one request at a time. Otherwise underlying layers would drop the + * subsequent requests silently. + * + * Bug 1147776 intends to solve a larger problem that other kind of requests + * may still interfere the ongoing request. + */ + if (client->mReadDescriptorState.mRunnable) { + DispatchReplyError(aRunnable, + NS_LITERAL_STRING("ReadDescriptorValue failed")); + return; + } + + client->mReadDescriptorState.Assign(false, aRunnable); + + /** + * First, read the descriptor value through an unauthenticated physical + * link. If the operation fails due to insufficient authentication/encryption + * key size, retry to read through an authenticated physical link. + */ + sBluetoothGattClientInterface->ReadDescriptor( + client->mConnId, + aServiceId, + aCharacteristicId, + aDescriptorId, + GATT_AUTH_REQ_NONE, + new ReadDescriptorValueResultHandler(client)); +} + +class BluetoothGattManager::WriteDescriptorValueResultHandler final + : public BluetoothGattClientResultHandler +{ +public: + WriteDescriptorValueResultHandler(BluetoothGattClient* aClient) + : mClient(aClient) + { + MOZ_ASSERT(mClient); + } + + void OnError(BluetoothStatus aStatus) override + { + BT_WARNING("BluetoothGattClientInterface::WriteDescriptorValue failed: %d", + (int)aStatus); + MOZ_ASSERT(mClient->mWriteDescriptorState.mRunnable); + + nsRefPtr runnable = + mClient->mWriteDescriptorState.mRunnable; + mClient->mWriteDescriptorState.Reset(); + + // Reject the write descriptor value request + DispatchReplyError(runnable, + NS_LITERAL_STRING("WriteDescriptorValue failed")); + } + +private: + nsRefPtr mClient; +}; + +void +BluetoothGattManager::WriteDescriptorValue( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + const nsTArray& aValue, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aRunnable); + + ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable); + + size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator()); + if (NS_WARN_IF(index == sClients->NoIndex)) { + // Reject the write descriptor value request + DispatchReplyError(aRunnable, + NS_LITERAL_STRING("WriteDescriptorValue failed")); + return; + } + + nsRefPtr client = sClients->ElementAt(index); + + /** + * Reject subsequent writing requests to follow ATT sequential protocol that + * handles one request at a time. Otherwise underlying layers would drop the + * subsequent requests silently. + * + * Bug 1147776 intends to solve a larger problem that other kind of requests + * may still interfere the ongoing request. + */ + if (client->mWriteDescriptorState.mRunnable) { + DispatchReplyError(aRunnable, + NS_LITERAL_STRING("WriteDescriptorValue failed")); + return; + } + + /** + * First, write the descriptor value through an unauthenticated physical + * link. If the operation fails due to insufficient authentication/encryption + * key size, retry to write through an authenticated physical link. + */ + client->mWriteDescriptorState.Assign(aValue, false, aRunnable); + + sBluetoothGattClientInterface->WriteDescriptor( + client->mConnId, + aServiceId, + aCharacteristicId, + aDescriptorId, + GATT_WRITE_TYPE_NORMAL, + GATT_AUTH_REQ_NONE, + aValue, + new WriteDescriptorValueResultHandler(client)); +} + // // Notification Handlers // @@ -1031,8 +1369,14 @@ BluetoothGattManager::RegisterClientNotification(BluetoothGattStatus aStatus, NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID), uuid, BluetoothValue(false)); // Disconnected - // Reject the connect request - if (client->mConnectRunnable) { + if (client->mStartLeScanRunnable) { + // Reject the LE scan request + DispatchReplyError(client->mStartLeScanRunnable, + NS_LITERAL_STRING( + "StartLeScan failed due to registration failed")); + client->mStartLeScanRunnable = nullptr; + } else if (client->mConnectRunnable) { + // Reject the connect request DispatchReplyError(client->mConnectRunnable, NS_LITERAL_STRING( "Connect failed due to registration failed")); @@ -1050,10 +1394,16 @@ BluetoothGattManager::RegisterClientNotification(BluetoothGattStatus aStatus, NS_LITERAL_STRING("ClientRegistered"), uuid, BluetoothValue(uint32_t(aClientIf))); - // Client just registered, proceed remaining connect request. - if (client->mConnectRunnable) { + if (client->mStartLeScanRunnable) { + // Client just registered, proceed remaining startLeScan request. + sBluetoothGattClientInterface->Scan( + aClientIf, true /* start */, + new StartLeScanResultHandler(client)); + } else if (client->mConnectRunnable) { + // Client just registered, proceed remaining connect request. sBluetoothGattClientInterface->Connect( aClientIf, client->mDeviceAddr, true /* direct connect */, + TRANSPORT_AUTO, new ConnectResultHandler(client)); } } @@ -1062,7 +1412,25 @@ void BluetoothGattManager::ScanResultNotification( const nsAString& aBdAddr, int aRssi, const BluetoothGattAdvData& aAdvData) -{ } +{ + MOZ_ASSERT(NS_IsMainThread()); + + InfallibleTArray properties; + + nsTArray advData; + advData.AppendElements(aAdvData.mAdvData, sizeof(aAdvData.mAdvData)); + + BT_APPEND_NAMED_VALUE(properties, "Address", nsString(aBdAddr)); + BT_APPEND_NAMED_VALUE(properties, "Rssi", static_cast(aRssi)); + BT_APPEND_NAMED_VALUE(properties, "GattAdv", advData); + + BluetoothService* bs = BluetoothService::Get(); + NS_ENSURE_TRUE_VOID(bs); + + bs->DistributeSignal(NS_LITERAL_STRING("LeDeviceFound"), + NS_LITERAL_STRING(KEY_ADAPTER), + BluetoothValue(properties)); +} void BluetoothGattManager::ConnectNotification(int aConnId, @@ -1537,13 +1905,97 @@ void BluetoothGattManager::ReadDescriptorNotification( int aConnId, BluetoothGattStatus aStatus, const BluetoothGattReadParam& aReadParam) -{ } +{ + MOZ_ASSERT(NS_IsMainThread()); + + BluetoothService* bs = BluetoothService::Get(); + NS_ENSURE_TRUE_VOID(bs); + + size_t index = sClients->IndexOf(aConnId, 0 /* Start */, ConnIdComparator()); + NS_ENSURE_TRUE_VOID(index != sClients->NoIndex); + + nsRefPtr client = sClients->ElementAt(index); + + MOZ_ASSERT(client->mReadDescriptorState.mRunnable); + nsRefPtr runnable = + client->mReadDescriptorState.mRunnable; + + if (aStatus == GATT_STATUS_SUCCESS) { + client->mReadDescriptorState.Reset(); + // Notify BluetoothGattDescriptor to update descriptor value + nsString path; + GeneratePathFromGattId(aReadParam.mDescriptorId, path); + + nsTArray value; + value.AppendElements(aReadParam.mValue, aReadParam.mValueLength); + + bs->DistributeSignal(NS_LITERAL_STRING("DescriptorValueUpdated"), + path, + BluetoothValue(value)); + + // Resolve the promise + DispatchReplySuccess(runnable, BluetoothValue(value)); + } else if (!client->mReadDescriptorState.mAuthRetry && + (aStatus == GATT_STATUS_INSUFFICIENT_AUTHENTICATION || + aStatus == GATT_STATUS_INSUFFICIENT_ENCRYPTION)) { + client->mReadDescriptorState.mAuthRetry = true; + // Retry with another authentication requirement + sBluetoothGattClientInterface->ReadDescriptor( + aConnId, + aReadParam.mServiceId, + aReadParam.mCharId, + aReadParam.mDescriptorId, + GATT_AUTH_REQ_MITM, + new ReadDescriptorValueResultHandler(client)); + } else { + client->mReadDescriptorState.Reset(); + // Reject the promise + DispatchReplyError(runnable, + NS_LITERAL_STRING("ReadDescriptorValue failed")); + } +} void BluetoothGattManager::WriteDescriptorNotification( int aConnId, BluetoothGattStatus aStatus, const BluetoothGattWriteParam& aWriteParam) -{ } +{ + MOZ_ASSERT(NS_IsMainThread()); + + size_t index = sClients->IndexOf(aConnId, 0 /* Start */, ConnIdComparator()); + NS_ENSURE_TRUE_VOID(index != sClients->NoIndex); + + nsRefPtr client = sClients->ElementAt(index); + + MOZ_ASSERT(client->mWriteDescriptorState.mRunnable); + nsRefPtr runnable = + client->mWriteDescriptorState.mRunnable; + + if (aStatus == GATT_STATUS_SUCCESS) { + client->mWriteDescriptorState.Reset(); + // Resolve the promise + DispatchReplySuccess(runnable); + } else if (!client->mWriteDescriptorState.mAuthRetry && + (aStatus == GATT_STATUS_INSUFFICIENT_AUTHENTICATION || + aStatus == GATT_STATUS_INSUFFICIENT_ENCRYPTION)) { + client->mWriteDescriptorState.mAuthRetry = true; + // Retry with another authentication requirement + sBluetoothGattClientInterface->WriteDescriptor( + aConnId, + aWriteParam.mServiceId, + aWriteParam.mCharId, + aWriteParam.mDescriptorId, + GATT_WRITE_TYPE_NORMAL, + GATT_AUTH_REQ_MITM, + client->mWriteDescriptorState.mWriteValue, + new WriteDescriptorValueResultHandler(client)); + } else { + client->mWriteDescriptorState.Reset(); + // Reject the promise + DispatchReplyError(runnable, + NS_LITERAL_STRING("WriteDescriptorValue failed")); + } +} void BluetoothGattManager::ExecuteWriteNotification(int aConnId, @@ -1585,7 +2037,7 @@ BluetoothGattManager::ReadRemoteRssiNotification(int aClientIf, // Resolve the read remote rssi request if (client->mReadRemoteRssiRunnable) { DispatchReplySuccess(client->mReadRemoteRssiRunnable, - BluetoothValue(static_cast(aRssi))); + BluetoothValue(static_cast(aRssi))); client->mReadRemoteRssiRunnable = nullptr; } } @@ -1629,6 +2081,7 @@ BluetoothGattManager::HandleShutdown() MOZ_ASSERT(NS_IsMainThread()); mInShutdown = true; sBluetoothGattManager = nullptr; + sClients = nullptr; } void diff --git a/dom/bluetooth/bluedroid/BluetoothGattManager.h b/dom/bluetooth/bluedroid/BluetoothGattManager.h index 1038dcacca..5fc8e9c731 100644 --- a/dom/bluetooth/bluedroid/BluetoothGattManager.h +++ b/dom/bluetooth/bluedroid/BluetoothGattManager.h @@ -26,7 +26,12 @@ public: static BluetoothGattManager* Get(); static void InitGattInterface(BluetoothProfileResultHandler* aRes); static void DeinitGattInterface(BluetoothProfileResultHandler* aRes); - virtual ~BluetoothGattManager(); + + void StartLeScan(const nsTArray& aServiceUuids, + BluetoothReplyRunnable* aRunnable); + + void StopLeScan(const nsAString& aScanUuid, + BluetoothReplyRunnable* aRunnable); void Connect(const nsAString& aAppUuid, const nsAString& aDeviceAddr, @@ -70,12 +75,31 @@ public: const nsTArray& aValue, BluetoothReplyRunnable* aRunnable); + void ReadDescriptorValue( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + BluetoothReplyRunnable* aRunnable); + + void WriteDescriptorValue( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + const nsTArray& aValue, + BluetoothReplyRunnable* aRunnable); + private: + ~BluetoothGattManager(); + class CleanupResultHandler; class CleanupResultHandlerRunnable; class InitGattResultHandler; class RegisterClientResultHandler; class UnregisterClientResultHandler; + class StartLeScanResultHandler; + class StopLeScanResultHandler; class ConnectResultHandler; class DisconnectResultHandler; class DiscoverResultHandler; @@ -84,6 +108,8 @@ private: class DeregisterNotificationsResultHandler; class ReadCharacteristicValueResultHandler; class WriteCharacteristicValueResultHandler; + class ReadDescriptorValueResultHandler; + class WriteDescriptorValueResultHandler; BluetoothGattManager(); diff --git a/dom/bluetooth/bluedroid/BluetoothHALHelpers.cpp b/dom/bluetooth/bluedroid/BluetoothHALHelpers.cpp index 25eca2d0eb..1fb8a6f648 100644 --- a/dom/bluetooth/bluedroid/BluetoothHALHelpers.cpp +++ b/dom/bluetooth/bluedroid/BluetoothHALHelpers.cpp @@ -325,6 +325,24 @@ Convert(const btgatt_notify_params_t& aIn, BluetoothGattNotifyParam& aOut) return NS_OK; } #endif // ANDROID_VERSION >= 19 + +#if ANDROID_VERSION >= 21 +nsresult +Convert(const BluetoothTransport& aIn, btgatt_transport_t& aOut) +{ + static const btgatt_transport_t sTransport[] = { + CONVERT(TRANSPORT_AUTO, GATT_TRANSPORT_AUTO), + CONVERT(TRANSPORT_BREDR, GATT_TRANSPORT_BREDR), + CONVERT(TRANSPORT_LE, GATT_TRANSPORT_LE) + }; + if (aIn >= MOZ_ARRAY_LENGTH(sTransport)) { + aOut = static_cast(0); // silence compiler warning + return NS_ERROR_ILLEGAL_VALUE; + } + aOut = sTransport[aIn]; + return NS_OK; +} +#endif #else // TODO: Support GATT #endif diff --git a/dom/bluetooth/bluedroid/BluetoothHALHelpers.h b/dom/bluetooth/bluedroid/BluetoothHALHelpers.h index faf6e520e6..1af25d0fa1 100644 --- a/dom/bluetooth/bluedroid/BluetoothHALHelpers.h +++ b/dom/bluetooth/bluedroid/BluetoothHALHelpers.h @@ -905,6 +905,11 @@ Convert(const ArrayBuffer& aIn, char* aOut) memcpy(aOut, aIn.Data(), aIn.Length()); return NS_OK; } + +#if ANDROID_VERSION >= 21 +nsresult +Convert(const BluetoothTransport& aIn, btgatt_transport_t& aOut); +#endif #else // TODO: Support GATT #endif diff --git a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp index f1b53bd1c5..749446bd3b 100644 --- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp +++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp @@ -385,6 +385,34 @@ BluetoothServiceBluedroid::StopInternal(BluetoothReplyRunnable* aRunnable) // GATT Client // +void +BluetoothServiceBluedroid::StartLeScanInternal( + const nsTArray& aServiceUuids, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable); + + BluetoothGattManager* gatt = BluetoothGattManager::Get(); + ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable); + + gatt->StartLeScan(aServiceUuids, aRunnable); +} + +void +BluetoothServiceBluedroid::StopLeScanInternal( + const nsAString& aScanUuid, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable); + + BluetoothGattManager* gatt = BluetoothGattManager::Get(); + ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable); + + gatt->StopLeScan(aScanUuid, aRunnable); +} + void BluetoothServiceBluedroid::ConnectGattClientInternal( const nsAString& aAppUuid, const nsAString& aDeviceAddress, @@ -525,6 +553,45 @@ BluetoothServiceBluedroid::GattClientWriteCharacteristicValueInternal( gatt->WriteCharacteristicValue(aAppUuid, aServiceId, aCharacteristicId, aWriteType, aValue, aRunnable); } + +void +BluetoothServiceBluedroid::GattClientReadDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + + ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable); + + BluetoothGattManager* gatt = BluetoothGattManager::Get(); + ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable); + + gatt->ReadDescriptorValue(aAppUuid, aServiceId, aCharacteristicId, + aDescriptorId, aRunnable); +} + +void +BluetoothServiceBluedroid::GattClientWriteDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + const nsTArray& aValue, + BluetoothReplyRunnable* aRunnable) +{ + MOZ_ASSERT(NS_IsMainThread()); + + ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable); + + BluetoothGattManager* gatt = BluetoothGattManager::Get(); + ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable); + + gatt->WriteDescriptorValue(aAppUuid, aServiceId, aCharacteristicId, + aDescriptorId, aValue, aRunnable); +} #else #define ENSURE_BLUETOOTH_IS_READY(runnable, result) \ @@ -536,6 +603,15 @@ BluetoothServiceBluedroid::GattClientWriteCharacteristicValueInternal( } \ } while(0) +#define ENSURE_BLUETOOTH_IS_READY_VOID(runnable) \ + do { \ + if (!sBtInterface || !IsEnabled()) { \ + NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready"); \ + DispatchBluetoothReply(runnable, BluetoothValue(), errorStr); \ + return; \ + } \ + } while(0) + // Audio: Major service class = 0x100 (Bit 21 is set) #define SET_AUDIO_BIT(cod) (cod |= 0x200000) // Rendering: Major service class = 0x20 (Bit 18 is set) @@ -1169,13 +1245,12 @@ private: BluetoothReplyRunnable* mRunnable; }; -nsresult +void BluetoothServiceBluedroid::StartDiscoveryInternal( BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); - - ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK); + ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable); #ifdef MOZ_B2G_BT_API_V2 sChangeDiscoveryRunnableArray.AppendElement(aRunnable); @@ -1184,8 +1259,6 @@ BluetoothServiceBluedroid::StartDiscoveryInternal( #endif sBtInterface->StartDiscovery(new StartDiscoveryResultHandler(aRunnable)); - - return NS_OK; } class BluetoothServiceBluedroid::CancelDiscoveryResultHandler final @@ -1268,13 +1341,12 @@ BluetoothServiceBluedroid::FetchUuidsInternal( // Missing in bluetooth1 #endif -nsresult +void BluetoothServiceBluedroid::StopDiscoveryInternal( BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); - - ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK); + ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable); #ifdef MOZ_B2G_BT_API_V2 sChangeDiscoveryRunnableArray.AppendElement(aRunnable); @@ -1283,8 +1355,6 @@ BluetoothServiceBluedroid::StopDiscoveryInternal( #endif sBtInterface->CancelDiscovery(new CancelDiscoveryResultHandler(aRunnable)); - - return NS_OK; } class BluetoothServiceBluedroid::SetAdapterPropertyResultHandler final diff --git a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h index d54e52d37e..2709b186c2 100644 --- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h +++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h @@ -56,8 +56,8 @@ public: FetchUuidsInternal(const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable) override; - virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable); - virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable); + virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable); + virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable); virtual nsresult SetProperty(BluetoothObjectType aType, @@ -191,6 +191,12 @@ public: // GATT Client // + virtual void StartLeScanInternal(const nsTArray& aServiceUuids, + BluetoothReplyRunnable* aRunnable); + + virtual void StopLeScanInternal(const nsAString& aScanUuid, + BluetoothReplyRunnable* aRunnable); + virtual void ConnectGattClientInternal(const nsAString& aAppUuid, const nsAString& aDeviceAddress, @@ -244,6 +250,23 @@ public: const nsTArray& aValue, BluetoothReplyRunnable* aRunnable) override; + virtual void + GattClientReadDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + BluetoothReplyRunnable* aRunnable) override; + + virtual void + GattClientWriteDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + const nsTArray& aValue, + BluetoothReplyRunnable* aRunnable) override; + // // Bluetooth notifications // @@ -350,8 +373,8 @@ public: const nsTArray& aDeviceAddress, BluetoothReplyRunnable* aRunnable); - virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable); - virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable); + virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable); + virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable); virtual nsresult SetProperty(BluetoothObjectType aType, diff --git a/dom/bluetooth/bluetooth1/BluetoothAdapter.cpp b/dom/bluetooth/bluetooth1/BluetoothAdapter.cpp index df27882af7..337d98c361 100644 --- a/dom/bluetooth/bluetooth1/BluetoothAdapter.cpp +++ b/dom/bluetooth/bluetooth1/BluetoothAdapter.cpp @@ -397,16 +397,11 @@ BluetoothAdapter::StartStopDiscovery(bool aStart, ErrorResult& aRv) aRv.Throw(NS_ERROR_FAILURE); return nullptr; } - nsresult rv; + if (aStart) { - rv = bs->StartDiscoveryInternal(results); + bs->StartDiscoveryInternal(results); } else { - rv = bs->StopDiscoveryInternal(results); - } - if (NS_FAILED(rv)) { - BT_WARNING("Start/Stop Discovery failed!"); - aRv.Throw(rv); - return nullptr; + bs->StopDiscoveryInternal(results); } // mDiscovering is not set here, we'll get a Property update from our external diff --git a/dom/bluetooth/bluetooth1/BluetoothService.h b/dom/bluetooth/bluetooth1/BluetoothService.h index b56890b15f..dbb0de23f3 100644 --- a/dom/bluetooth/bluetooth1/BluetoothService.h +++ b/dom/bluetooth/bluetooth1/BluetoothService.h @@ -155,7 +155,7 @@ public: * * @return NS_OK if discovery stopped correctly, false otherwise */ - virtual nsresult + virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0; /** @@ -163,7 +163,7 @@ public: * * @return NS_OK if discovery stopped correctly, false otherwise */ - virtual nsresult + virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0; /** diff --git a/dom/bluetooth/bluetooth1/ipc/BluetoothParent.cpp b/dom/bluetooth/bluetooth1/ipc/BluetoothParent.cpp index 7461ef761c..712965864d 100644 --- a/dom/bluetooth/bluetooth1/ipc/BluetoothParent.cpp +++ b/dom/bluetooth/bluetooth1/ipc/BluetoothParent.cpp @@ -343,9 +343,7 @@ BluetoothRequestParent::DoRequest(const StartDiscoveryRequest& aRequest) MOZ_ASSERT(mService); MOZ_ASSERT(mRequestType == Request::TStartDiscoveryRequest); - nsresult rv = - mService->StartDiscoveryInternal(mReplyRunnable.get()); - NS_ENSURE_SUCCESS(rv, false); + mService->StartDiscoveryInternal(mReplyRunnable.get()); return true; } @@ -356,9 +354,7 @@ BluetoothRequestParent::DoRequest(const StopDiscoveryRequest& aRequest) MOZ_ASSERT(mService); MOZ_ASSERT(mRequestType == Request::TStopDiscoveryRequest); - nsresult rv = - mService->StopDiscoveryInternal(mReplyRunnable.get()); - NS_ENSURE_SUCCESS(rv, false); + mService->StopDiscoveryInternal(mReplyRunnable.get()); return true; } diff --git a/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.cpp b/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.cpp index c1a847acf0..1fcc1a116e 100644 --- a/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.cpp +++ b/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.cpp @@ -124,20 +124,18 @@ BluetoothServiceChildProcess::GetPairedDevicePropertiesInternal( return NS_OK; } -nsresult +void BluetoothServiceChildProcess::StopDiscoveryInternal( BluetoothReplyRunnable* aRunnable) { SendRequest(aRunnable, StopDiscoveryRequest()); - return NS_OK; } -nsresult +void BluetoothServiceChildProcess::StartDiscoveryInternal( BluetoothReplyRunnable* aRunnable) { SendRequest(aRunnable, StartDiscoveryRequest()); - return NS_OK; } nsresult @@ -295,7 +293,7 @@ BluetoothServiceChildProcess::ConfirmReceivingFile( ConfirmReceivingFileRequest(nsString(aDeviceAddress))); return; } - + SendRequest(aRunnable, DenyReceivingFileRequest(nsString(aDeviceAddress))); } diff --git a/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.h b/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.h index e873bdfc52..db53344c98 100644 --- a/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.h +++ b/dom/bluetooth/bluetooth1/ipc/BluetoothServiceChildProcess.h @@ -43,10 +43,10 @@ public: GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid, BluetoothReplyRunnable* aRunnable) override; - virtual nsresult + virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override; - virtual nsresult + virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override; virtual nsresult diff --git a/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp b/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp index 0ed1982c65..879972f23b 100644 --- a/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp +++ b/dom/bluetooth/bluetooth2/BluetoothAdapter.cpp @@ -37,6 +37,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothAdapter, NS_IMPL_CYCLE_COLLECTION_UNLINK(mDevices) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDiscoveryHandleInUse) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPairingReqs) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLeScanHandleArray) /** * Unregister the bluetooth signal handler after unlinked. @@ -53,6 +54,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothAdapter, NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDevices) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDiscoveryHandleInUse) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPairingReqs) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeScanHandleArray) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END // QueryInterface implementation for BluetoothAdapter @@ -110,6 +112,101 @@ private: nsRefPtr mAdapter; }; +class StartLeScanTask final : public BluetoothReplyRunnable +{ +public: + StartLeScanTask(BluetoothAdapter* aAdapter, Promise* aPromise, + const nsTArray& aServiceUuids) + : BluetoothReplyRunnable(nullptr, aPromise, + NS_LITERAL_STRING("StartLeScan")) + , mAdapter(aAdapter) + , mServiceUuids(aServiceUuids) + { + MOZ_ASSERT(aPromise); + MOZ_ASSERT(aAdapter); + } + + bool + ParseSuccessfulReply(JS::MutableHandle aValue) + { + aValue.setUndefined(); + + AutoJSAPI jsapi; + NS_ENSURE_TRUE(jsapi.Init(mAdapter->GetParentObject()), false); + JSContext* cx = jsapi.cx(); + + const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value(); + NS_ENSURE_TRUE(v.type() == BluetoothValue::TnsString, false); + + /** + * Create a new discovery handle and wrap it to return. Each + * discovery handle is one-time-use only. + */ + nsRefPtr discoveryHandle = + BluetoothDiscoveryHandle::Create(mAdapter->GetParentObject(), + mServiceUuids, v.get_nsString(), + mAdapter); + + if (!ToJSValue(cx, discoveryHandle, aValue)) { + JS_ClearPendingException(cx); + return false; + } + + // Append a BluetoothDiscoveryHandle to LeScan handle array. + mAdapter->AppendLeScanHandle(discoveryHandle); + + return true; + } + + virtual void + ReleaseMembers() override + { + BluetoothReplyRunnable::ReleaseMembers(); + mAdapter = nullptr; + } + +private: + nsRefPtr mAdapter; + nsTArray mServiceUuids; +}; + +class StopLeScanTask final : public BluetoothReplyRunnable +{ +public: + StopLeScanTask(BluetoothAdapter* aAdapter, + Promise* aPromise, + const nsAString& aScanUuid) + : BluetoothReplyRunnable(nullptr, aPromise, + NS_LITERAL_STRING("StopLeScan")) + , mAdapter(aAdapter) + , mScanUuid(aScanUuid) + { + MOZ_ASSERT(aPromise); + MOZ_ASSERT(aAdapter); + MOZ_ASSERT(!aScanUuid.IsEmpty()); + } + +protected: + virtual bool + ParseSuccessfulReply(JS::MutableHandle aValue) override + { + mAdapter->RemoveLeScanHandle(mScanUuid); + aValue.setUndefined(); + return true; + } + + virtual void + ReleaseMembers() override + { + BluetoothReplyRunnable::ReleaseMembers(); + mAdapter = nullptr; + } + +private: + nsRefPtr mAdapter; + nsString mScanUuid; +}; + class GetDevicesTask : public BluetoothReplyRunnable { public: @@ -274,9 +371,10 @@ BluetoothAdapter::SetPropertyByValue(const BluetoothNamedValue& aValue) mState = value.get_bool() ? BluetoothAdapterState::Enabled : BluetoothAdapterState::Disabled; - // Clear saved devices when state changes to disabled + // Clear saved devices and LE scan handles when state changes to disabled if (mState == BluetoothAdapterState::Disabled) { mDevices.Clear(); + mLeScanHandleArray.Clear(); } } else if (name.EqualsLiteral("Name")) { mName = value.get_nsString(); @@ -353,6 +451,10 @@ BluetoothAdapter::Notify(const BluetoothSignal& aData) if (mDiscoveryHandleInUse) { HandleDeviceFound(v); } + } else if (aData.name().EqualsLiteral("LeDeviceFound")) { + if (!mLeScanHandleArray.IsEmpty()) { + HandleLeDeviceFound(v); + } } else if (aData.name().EqualsLiteral(DEVICE_PAIRED_ID)) { HandleDevicePaired(aData.value()); } else if (aData.name().EqualsLiteral(DEVICE_UNPAIRED_ID)) { @@ -405,6 +507,26 @@ BluetoothAdapter::SetDiscoveryHandleInUse( mDiscoveryHandleInUse = aDiscoveryHandle; } +void +BluetoothAdapter::AppendLeScanHandle( + BluetoothDiscoveryHandle* aDiscoveryHandle) +{ + mLeScanHandleArray.AppendElement(aDiscoveryHandle); +} + +void +BluetoothAdapter::RemoveLeScanHandle(const nsAString& aScanUuid) +{ + nsString uuid; + for (uint32_t i = 0; i < mLeScanHandleArray.Length(); ++i) { + mLeScanHandleArray[i]->GetLeScanUuid(uuid); + if (aScanUuid.Equals(uuid)) { + mLeScanHandleArray.RemoveElementAt(i); + break; + } + } +} + already_AddRefed BluetoothAdapter::StartDiscovery(ErrorResult& aRv) { @@ -443,9 +565,7 @@ BluetoothAdapter::StartDiscovery(ErrorResult& aRv) // Return BluetoothDiscoveryHandle in StartDiscoveryTask nsRefPtr result = new StartDiscoveryTask(this, promise); - BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(bs->StartDiscoveryInternal(result)), - promise, - NS_ERROR_DOM_OPERATION_ERR); + bs->StartDiscoveryInternal(result); return promise.forget(); } @@ -481,9 +601,68 @@ BluetoothAdapter::StopDiscovery(ErrorResult& aRv) new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */, promise, NS_LITERAL_STRING("StopDiscovery")); - BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(bs->StopDiscoveryInternal(result)), + bs->StopDiscoveryInternal(result); + + return promise.forget(); +} + +already_AddRefed +BluetoothAdapter::StartLeScan(const nsTArray& aServiceUuids, + ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(GetOwner()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsRefPtr promise = Promise::Create(global, aRv); + NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + + BT_ENSURE_TRUE_REJECT(mState == BluetoothAdapterState::Enabled, promise, - NS_ERROR_DOM_OPERATION_ERR); + NS_ERROR_DOM_INVALID_STATE_ERR); + + BluetoothService* bs = BluetoothService::Get(); + BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE); + + nsRefPtr result = + new StartLeScanTask(this, promise, aServiceUuids); + bs->StartLeScanInternal(aServiceUuids, result); + + return promise.forget(); +} + +already_AddRefed +BluetoothAdapter::StopLeScan(BluetoothDiscoveryHandle& aDiscoveryHandle, + ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(GetOwner()); + if (!global) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsRefPtr promise = Promise::Create(global, aRv); + NS_ENSURE_TRUE(!aRv.Failed(), nullptr); + + BT_ENSURE_TRUE_REJECT(mState == BluetoothAdapterState::Enabled, + promise, + NS_ERROR_DOM_INVALID_STATE_ERR); + + BluetoothService* bs = BluetoothService::Get(); + BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE); + + // Reject the request if there's no ongoing LE Scan using this handle. + BT_ENSURE_TRUE_REJECT(mLeScanHandleArray.Contains(&aDiscoveryHandle), + promise, + NS_ERROR_DOM_BLUETOOTH_DONE); + + nsString scanUuid; + aDiscoveryHandle.GetLeScanUuid(scanUuid); + nsRefPtr result = + new StopLeScanTask(this, promise, scanUuid); + bs->StopLeScanInternal(scanUuid, result); return promise.forget(); } @@ -810,7 +989,7 @@ BluetoothAdapter::IsBluetoothCertifiedApp() nsAutoCString appOrigin; doc->NodePrincipal()->GetAppStatus(&appStatus); - doc->NodePrincipal()->GetOrigin(getter_Copies(appOrigin)); + doc->NodePrincipal()->GetOrigin(appOrigin); return appStatus == nsIPrincipal::APP_STATUS_CERTIFIED && appOrigin.EqualsLiteral(BLUETOOTH_APP_ORIGIN); @@ -885,6 +1064,103 @@ BluetoothAdapter::HandleDeviceFound(const BluetoothValue& aValue) mDiscoveryHandleInUse->DispatchDeviceEvent(discoveredDevice); } +void +BluetoothAdapter::HandleLeDeviceFound(const BluetoothValue& aValue) +{ + MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue); + + const InfallibleTArray& values = + aValue.get_ArrayOfBluetoothNamedValue(); + + int rssi = 0; + nsTArray advData; + for (uint32_t i = 0; i < values.Length(); ++i) { + nsString name = values[i].name(); + BluetoothValue value = values[i].value(); + if (name.EqualsLiteral("Rssi")) { + MOZ_ASSERT(value.type() == BluetoothValue::Tint32_t); + rssi = value.get_int32_t(); + } else if (name.EqualsLiteral("GattAdv")) { + MOZ_ASSERT(value.type() == BluetoothValue::TArrayOfuint8_t); + advData = value.get_ArrayOfuint8_t(); + } else { + BT_WARNING("Receive an unexpected value name '%s'", + NS_ConvertUTF16toUTF8(name).get()); + } + } + + // Create an individual scanned BluetoothDevice for each LeDeviceEvent even + // the device exists in adapter's devices array + nsRefPtr scannedDevice = + BluetoothDevice::Create(GetOwner(), aValue); + + // Notify application of scanned devices via discovery handle + for (uint32_t i = 0; i < mLeScanHandleArray.Length(); ++i) { + mLeScanHandleArray[i]->DispatchLeDeviceEvent(scannedDevice, rssi, advData); + } +} + +void +BluetoothAdapter::HandleDevicePaired(const BluetoothValue& aValue) +{ + if (NS_WARN_IF(mState != BluetoothAdapterState::Enabled)) { + return; + } + + MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue); + + const InfallibleTArray& arr = + aValue.get_ArrayOfBluetoothNamedValue(); + + MOZ_ASSERT(arr.Length() == 3 && + arr[0].value().type() == BluetoothValue::TnsString && // Address + arr[1].value().type() == BluetoothValue::TnsString && // Name + arr[2].value().type() == BluetoothValue::Tbool); // Paired + MOZ_ASSERT(!arr[0].value().get_nsString().IsEmpty() && + arr[2].value().get_bool()); + + // Append the paired device if it doesn't exist in adapter's devices array + size_t index = mDevices.IndexOf(arr[0].value().get_nsString()); + if (index == mDevices.NoIndex) { + index = mDevices.Length(); // the new device's index + mDevices.AppendElement( + BluetoothDevice::Create(GetOwner(), aValue)); + } + + // Notify application of paired device + BluetoothDeviceEventInit init; + init.mDevice = mDevices[index]; + DispatchDeviceEvent(NS_LITERAL_STRING(DEVICE_PAIRED_ID), init); +} + +void +BluetoothAdapter::HandleDeviceUnpaired(const BluetoothValue& aValue) +{ + if (NS_WARN_IF(mState != BluetoothAdapterState::Enabled)) { + return; + } + + MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue); + + const InfallibleTArray& arr = + aValue.get_ArrayOfBluetoothNamedValue(); + + MOZ_ASSERT(arr.Length() == 2 && + arr[0].value().type() == BluetoothValue::TnsString && // Address + arr[1].value().type() == BluetoothValue::Tbool); // Paired + MOZ_ASSERT(!arr[0].value().get_nsString().IsEmpty() && + !arr[1].value().get_bool()); + + // Remove the device with the same address + nsString deviceAddress = arr[0].value().get_nsString(); + mDevices.RemoveElement(deviceAddress); + + // Notify application of unpaired device + BluetoothDeviceEventInit init; + init.mAddress = deviceAddress; + DispatchDeviceEvent(NS_LITERAL_STRING(DEVICE_UNPAIRED_ID), init); +} + void BluetoothAdapter::DispatchAttributeEvent(const Sequence& aTypes) { @@ -900,76 +1176,6 @@ BluetoothAdapter::DispatchAttributeEvent(const Sequence& aTypes) DispatchTrustedEvent(event); } -void -BluetoothAdapter::HandleDevicePaired(const BluetoothValue& aValue) -{ - MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue); - - if (mState != BluetoothAdapterState::Enabled) { - BT_WARNING("HandleDevicePaired() is called when adapter isn't enabled."); - return; - } - - const InfallibleTArray& arr = - aValue.get_ArrayOfBluetoothNamedValue(); - - MOZ_ASSERT(arr.Length() == 2 && - arr[0].value().type() == BluetoothValue::TnsString && // Address - arr[1].value().type() == BluetoothValue::Tbool); // Paired - MOZ_ASSERT(!arr[0].value().get_nsString().IsEmpty() && - arr[1].value().get_bool()); - - nsString deviceAddress = arr[0].value().get_nsString(); - - nsRefPtr pairedDevice = nullptr; - - // Check whether or not the address exists in mDevices. - size_t index = mDevices.IndexOf(deviceAddress); - if (index == mDevices.NoIndex) { - // Create a new device and append it to adapter's device array - pairedDevice = BluetoothDevice::Create(GetOwner(), aValue); - mDevices.AppendElement(pairedDevice); - } else { - // Use existing device - pairedDevice = mDevices[index]; - } - - // Notify application of paired device - BluetoothDeviceEventInit init; - init.mDevice = pairedDevice; - DispatchDeviceEvent(NS_LITERAL_STRING("devicepaired"), init); -} - -void -BluetoothAdapter::HandleDeviceUnpaired(const BluetoothValue& aValue) -{ - MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue); - - if (mState != BluetoothAdapterState::Enabled) { - BT_WARNING("HandleDeviceUnpaired() is called when adapter isn't enabled."); - return; - } - - const InfallibleTArray& arr = - aValue.get_ArrayOfBluetoothNamedValue(); - - MOZ_ASSERT(arr.Length() == 2 && - arr[0].value().type() == BluetoothValue::TnsString && // Address - arr[1].value().type() == BluetoothValue::Tbool); // Paired - MOZ_ASSERT(!arr[0].value().get_nsString().IsEmpty() && - !arr[1].value().get_bool()); - - nsString deviceAddress = arr[0].value().get_nsString(); - - // Remove the device with the same address - mDevices.RemoveElement(deviceAddress); - - // Notify application of unpaired device - BluetoothDeviceEventInit init; - init.mAddress = deviceAddress; - DispatchDeviceEvent(NS_LITERAL_STRING("deviceunpaired"), init); -} - void BluetoothAdapter::DispatchDeviceEvent(const nsAString& aType, const BluetoothDeviceEventInit& aInit) diff --git a/dom/bluetooth/bluetooth2/BluetoothAdapter.h b/dom/bluetooth/bluetooth2/BluetoothAdapter.h index 9a350bf859..71aeed967b 100644 --- a/dom/bluetooth/bluetooth2/BluetoothAdapter.h +++ b/dom/bluetooth/bluetooth2/BluetoothAdapter.h @@ -99,6 +99,12 @@ public: ErrorResult& aRv); already_AddRefed StartDiscovery(ErrorResult& aRv); already_AddRefed StopDiscovery(ErrorResult& aRv); + + already_AddRefed StartLeScan( + const nsTArray& aServiceUuids, ErrorResult& aRv); + already_AddRefed StopLeScan( + BluetoothDiscoveryHandle& aDiscoveryHandle, ErrorResult& aRv); + already_AddRefed Pair(const nsAString& aDeviceAddress, ErrorResult& aRv); already_AddRefed Unpair(const nsAString& aDeviceAddress, @@ -178,6 +184,21 @@ public: */ void SetDiscoveryHandleInUse(BluetoothDiscoveryHandle* aDiscoveryHandle); + /** + * Append a BluetoothDiscoveryHandle to LeScan handle array. + * + * @param aDiscoveryHandle [in] Discovery handle to be appended. + */ + void AppendLeScanHandle(BluetoothDiscoveryHandle* aDiscoveryHandle); + + /** + * Remove the BluetoothDiscoverHandle with the given UUID from LeScan handle + * array. + * + * @param aScanUuid [in] The UUID of the LE scan task. + */ + void RemoveLeScanHandle(const nsAString& aScanUuid); + private: BluetoothAdapter(nsPIDOMWindow* aOwner, const BluetoothValue& aValue); ~BluetoothAdapter(); @@ -222,6 +243,13 @@ private: */ void HandlePropertyChanged(const BluetoothValue& aValue); + /** + * Handle "DeviceFound" bluetooth signal. + * + * @param aValue [in] Properties array of the discovered device. + */ + void HandleDeviceFound(const BluetoothValue& aValue); + /** * Handle DEVICE_PAIRED_ID bluetooth signal. * @@ -243,11 +271,11 @@ private: void HandleDeviceUnpaired(const BluetoothValue& aValue); /** - * Handle "DeviceFound" bluetooth signal. + * Handle "LeDeviceFound" bluetooth signal. * - * @param aValue [in] Properties array of the discovered device. + * @param aValue [in] Properties array of the scanned device. */ - void HandleDeviceFound(const BluetoothValue& aValue); + void HandleLeDeviceFound(const BluetoothValue& aValue); /** * Fire BluetoothAttributeEvent to trigger onattributechanged event handler. @@ -331,19 +359,28 @@ private: nsRefPtr mDiscoveryHandleInUse; /** - * Arrays of references to BluetoothDevices created by this adapter. - * This array is empty when adapter state is Disabled. + * Handles to fire 'ondevicefound' event handler for scanned device + * + * Each non-stopped LeScan process has a LeScan handle which is + * responsible to dispatch LeDeviceEvent. + */ + nsTArray > mLeScanHandleArray; + + /** + * nsRefPtr array of BluetoothDevices created by this adapter. The array is + * empty when adapter state is Disabled. * * Devices will be appended when - * 1) Enabling BT: Paired devices reported by stack. - * 2) Discovering: Discovered devices during discovery operation. - * A device won't be appended if a device object with the same - * address already exists. + * 1) adapter is enabling: Paired devices reported by stack. + * 2) adapter is discovering: Discovered devices during discovery operation. + * 3) adapter paired with a device: The paired device reported by stack. + * Note devices with identical address won't be appended. * * Devices will be removed when - * 1) Starting discovery: All unpaired devices will be removed before this - * adapter starts a new discovery. - * 2) Disabling BT: All devices will be removed. + * 1) adapter is disabling: All devices will be removed. + * 2) adapter starts discovery: All unpaired devices will be removed before + * this new discovery starts. + * 3) adapter unpaired with a device: The unpaired device will be removed. */ nsTArray > mDevices; }; diff --git a/dom/bluetooth/bluetooth2/BluetoothDevice.cpp b/dom/bluetooth/bluetooth2/BluetoothDevice.cpp index 85a836958b..132d881dc5 100644 --- a/dom/bluetooth/bluetooth2/BluetoothDevice.cpp +++ b/dom/bluetooth/bluetooth2/BluetoothDevice.cpp @@ -162,6 +162,11 @@ BluetoothDevice::SetPropertyByValue(const BluetoothNamedValue& aValue) BluetoothDeviceBinding::ClearCachedUuidsValue(this); } else if (name.EqualsLiteral("Type")) { mType = ConvertUint32ToDeviceType(value.get_uint32_t()); + } else if (name.EqualsLiteral("GattAdv")) { + MOZ_ASSERT(value.type() == BluetoothValue::TArrayOfuint8_t); + nsTArray advData; + advData = value.get_ArrayOfuint8_t(); + UpdatePropertiesFromAdvData(advData); } else { BT_WARNING("Not handling device property: %s", NS_ConvertUTF16toUTF8(name).get()); @@ -321,6 +326,104 @@ BluetoothDevice::GetGatt() return mGatt; } +void +BluetoothDevice::UpdatePropertiesFromAdvData(const nsTArray& aAdvData) +{ + // According to BT Core Spec. Vol 3 - Ch 11, advertisement data consists of a + // significant part and a non-significant part. + // The significant part contains a sequence of AD structures. Each AD + // structure shall have a Length field of one octet, which contains the + // Length value, and a Data field of Length octets. + unsigned int offset = 0; + while (offset < aAdvData.Length()) { + int dataFieldLength = aAdvData[offset++]; + + // According to BT Core Spec, it only occurs to allow an early termination + // of the Advertising data. + if (dataFieldLength <= 0) { + break; + } + + // Length of the data field which is composed by AD type (1 byte) and + // AD data (dataFieldLength -1 bytes) + int dataLength = dataFieldLength - 1; + if (offset + dataLength >= aAdvData.Length()) { + break; + } + + // Update UUIDs and name of BluetoothDevice. + int type = aAdvData[offset++]; + switch (type) { + case GAP_INCOMPLETE_UUID16: + case GAP_COMPLETE_UUID16: + case GAP_INCOMPLETE_UUID32: + case GAP_COMPLETE_UUID32: + case GAP_INCOMPLETE_UUID128: + case GAP_COMPLETE_UUID128: { + mUuids.Clear(); + + // The length of uint16_t UUID array + uint8_t len = 0; + if (GAP_INCOMPLETE_UUID16 && GAP_COMPLETE_UUID16) { + len = 1; + } else if (GAP_INCOMPLETE_UUID32 && GAP_COMPLETE_UUID32) { + len = 2; + } else { + len = 8; + } + uint16_t uuid[len]; + + while (dataLength > 0) { + // Read (len * 2) bytes from the data buffer and compose a 16-bits + // UUID array. + for (uint8_t i = 0; i < len; ++i) { + uuid[i] = aAdvData[offset++]; + uuid[i] += (aAdvData[offset++] << 8); + dataLength -= 2; + } + + char uuidStr[36]; + if (type == GAP_INCOMPLETE_UUID16 || type == GAP_COMPLETE_UUID16) { + // Convert 16-bits UUID into string. + sprintf(uuidStr, "0000%04x-0000-1000-8000-00805f9b34fb", uuid[0]); + } else if (type == GAP_INCOMPLETE_UUID32 || type == GAP_COMPLETE_UUID32) { + // Convert 32-bits UUID into string. + sprintf(uuidStr, "%04x%04x-0000-1000-8000-00805f9b34fb", + uuid[1], uuid[0]); + } else if (type == GAP_INCOMPLETE_UUID128 || type == GAP_COMPLETE_UUID128) { + // Convert 128-bits UUID into string. + sprintf(uuidStr, "%04x%04x-%04x-%04x-%04x-%04x%04x%04x", + uuid[7], uuid[6], uuid[5], uuid[4], + uuid[3], uuid[2], uuid[1], uuid[0]); + } + nsString uuidNsString; + uuidNsString.AssignLiteral(uuidStr); + + mUuids.AppendElement(uuidNsString); + } + + BluetoothDeviceBinding::ClearCachedUuidsValue(this); + break; + } + case GAP_SHORTENED_NAME: + if (!mName.IsEmpty()) break; + case GAP_COMPLETE_NAME: { + // Read device name from data buffer. + char deviceName[dataLength]; + for (int i = 0; i < dataLength; ++i) { + deviceName[i] = aAdvData[offset++]; + } + + mName.AssignASCII(deviceName, dataLength); + break; + } + default: + offset += dataLength; + break; + } + } +} + JSObject* BluetoothDevice::WrapObject(JSContext* aContext, JS::Handle aGivenProto) diff --git a/dom/bluetooth/bluetooth2/BluetoothDevice.h b/dom/bluetooth/bluetooth2/BluetoothDevice.h index 5d5d0233ae..967081aa41 100644 --- a/dom/bluetooth/bluetooth2/BluetoothDevice.h +++ b/dom/bluetooth/bluetooth2/BluetoothDevice.h @@ -144,6 +144,16 @@ private: bool IsDeviceAttributeChanged(BluetoothDeviceAttribute aType, const BluetoothValue& aValue); + /** + * Parse advertising data to update device properties. + * + * Parse 'Advertising Data Type' from an inquiry response and set name, UUIDs + * and COD if they exist in ADV data. + * + * @param aAdvData [in] advertising data which provided by the LeScan result. + */ + void UpdatePropertiesFromAdvData(const nsTArray& aAdvData); + /**************************************************************************** * Variables ***************************************************************************/ diff --git a/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.cpp b/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.cpp index feceee10be..ac74989cdf 100644 --- a/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.cpp +++ b/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.cpp @@ -4,16 +4,21 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "BluetoothDiscoveryHandle.h" #include "BluetoothService.h" - -#include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "mozilla/dom/BluetoothDeviceEvent.h" #include "mozilla/dom/BluetoothDiscoveryHandleBinding.h" +#include "mozilla/dom/bluetooth/BluetoothCommon.h" +#include "mozilla/dom/bluetooth/BluetoothDiscoveryHandle.h" +#include "mozilla/dom/bluetooth/BluetoothLeDeviceEvent.h" +#include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "nsThreadUtils.h" USING_BLUETOOTH_NAMESPACE +NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothDiscoveryHandle, + DOMEventTargetHelper, + mAdapter) + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothDiscoveryHandle) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) @@ -22,12 +27,41 @@ NS_IMPL_RELEASE_INHERITED(BluetoothDiscoveryHandle, DOMEventTargetHelper) BluetoothDiscoveryHandle::BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow) : DOMEventTargetHelper(aWindow) + , mLeScanUuid(EmptyString()) +{ + MOZ_ASSERT(aWindow); +} + +BluetoothDiscoveryHandle::BluetoothDiscoveryHandle( + nsPIDOMWindow* aWindow, + const nsTArray& aServiceUuids, + const nsAString& aLeScanUuid, + BluetoothAdapter* aAdapter) + : DOMEventTargetHelper(aWindow) + , mLeScanUuid(aLeScanUuid) + , mServiceUuids(aServiceUuids) + , mAdapter(aAdapter) { MOZ_ASSERT(aWindow); } BluetoothDiscoveryHandle::~BluetoothDiscoveryHandle() { + // Remove itself from the adapter's mLeScanHandleArray + if (!mLeScanUuid.IsEmpty() && mAdapter != nullptr) { + mAdapter->RemoveLeScanHandle(mLeScanUuid); + } +} + +void +BluetoothDiscoveryHandle::DisconnectFromOwner() +{ + DOMEventTargetHelper::DisconnectFromOwner(); + + // Remove itself from the adapter's mLeScanHandleArray + if (!mLeScanUuid.IsEmpty() && mAdapter != nullptr) { + mAdapter->RemoveLeScanHandle(mLeScanUuid); + } } // static @@ -42,6 +76,22 @@ BluetoothDiscoveryHandle::Create(nsPIDOMWindow* aWindow) return handle.forget(); } +already_AddRefed +BluetoothDiscoveryHandle::Create( + nsPIDOMWindow* aWindow, + const nsTArray& aServiceUuids, + const nsAString& aLeScanUuid, + BluetoothAdapter* aAdapter) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aAdapter); + + nsRefPtr handle = + new BluetoothDiscoveryHandle(aWindow, aServiceUuids, aLeScanUuid, aAdapter); + return handle.forget(); +} + void BluetoothDiscoveryHandle::DispatchDeviceEvent(BluetoothDevice* aDevice) { @@ -57,6 +107,49 @@ BluetoothDiscoveryHandle::DispatchDeviceEvent(BluetoothDevice* aDevice) DispatchTrustedEvent(event); } +void +BluetoothDiscoveryHandle::DispatchLeDeviceEvent(BluetoothDevice* aLeDevice, + int32_t aRssi, nsTArray& aScanRecord) +{ + MOZ_ASSERT(aLeDevice); + + nsTArray remoteUuids; + aLeDevice->GetUuids(remoteUuids); + + bool hasUuidsFilter = !mServiceUuids.IsEmpty(); + bool noAdvertisingUuid = remoteUuids.IsEmpty(); + // If a LE device doesn't advertise its service UUIDs, it can't possibly pass + // the UUIDs filter. + if (hasUuidsFilter && noAdvertisingUuid) { + return; + } + + // The web API startLeScan() makes the device's adapter start seeking for + // remote LE devices advertising given service UUIDs. + // Since current Bluetooth stack can't filter the results of LeScan by UUIDs, + // gecko has to filter the results and dispach what API asked for. + bool matched = false; + for (size_t index = 0; index < remoteUuids.Length(); ++index) { + if (mServiceUuids.Contains(remoteUuids[index])) { + matched = true; + break; + } + } + + // Dispach 'devicefound 'event only if + // - the service UUIDs in the scan record matchs the given UUIDs. + // - the given UUIDs is empty. + if (matched || mServiceUuids.IsEmpty()) { + nsRefPtr event = + BluetoothLeDeviceEvent::Constructor(this, + NS_LITERAL_STRING("devicefound"), + aLeDevice, + aRssi, + aScanRecord); + DispatchTrustedEvent(event); + } +} + JSObject* BluetoothDiscoveryHandle::WrapObject(JSContext* aCx, JS::Handle aGivenProto) diff --git a/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h b/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h index a040dc8e3b..ffab30058f 100644 --- a/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h +++ b/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h @@ -7,10 +7,11 @@ #ifndef mozilla_dom_bluetooth_bluetoothdiscoveryhandle_h #define mozilla_dom_bluetooth_bluetoothdiscoveryhandle_h -#include "BluetoothCommon.h" #include "mozilla/Attributes.h" -#include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/dom/bluetooth/BluetoothAdapter.h" +#include "mozilla/dom/bluetooth/BluetoothCommon.h" +#include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "nsISupportsImpl.h" BEGIN_BLUETOOTH_NAMESPACE @@ -21,20 +22,70 @@ class BluetoothDiscoveryHandle final : public DOMEventTargetHelper { public: NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothDiscoveryHandle, + DOMEventTargetHelper) static already_AddRefed Create(nsPIDOMWindow* aWindow); + static already_AddRefed + Create(nsPIDOMWindow* aWindow, + const nsTArray& aServiceUuids, + const nsAString& aLeScanUuid, + BluetoothAdapter* aAdapter); + void DispatchDeviceEvent(BluetoothDevice* aDevice); + void DispatchLeDeviceEvent(BluetoothDevice* aLeDevice, + int32_t aRssi, + nsTArray& aScanRecord); + IMPL_EVENT_HANDLER(devicefound); + void GetLeScanUuid(nsString& aLeScanUuid) const + { + aLeScanUuid = mLeScanUuid; + } + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + virtual void DisconnectFromOwner() override; + private: BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow); + + BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow, + const nsTArray& aServiceUuids, + const nsAString& aLeScanUuid, + BluetoothAdapter* aAdapter); + ~BluetoothDiscoveryHandle(); + + /** + * Random generated UUID of LE scan + * + * This UUID is used only when the handle is built for LE scan. + * If BluetoothDiscoveryHandle is built for classic discovery, the value would + * remain empty string during the entire life cycle. + */ + nsString mLeScanUuid; + + /** + * A DOMString array of service UUIDs to discover / scan for. + * + * This array is only used by LE scan. If BluetoothDiscoveryHandle is built + * for classic discovery, the array should be empty. + */ + nsTArray mServiceUuids; + + /** + * The adapter which called startLeScan and created this discovery handle + * + * If BluetoothDiscoveryHandle is built for classic discovery, this value + * should be nullptr. + */ + nsRefPtr mAdapter; }; END_BLUETOOTH_NAMESPACE diff --git a/dom/bluetooth/bluetooth2/BluetoothGatt.cpp b/dom/bluetooth/bluetooth2/BluetoothGatt.cpp index aa1e09b166..474592bb2e 100644 --- a/dom/bluetooth/bluetooth2/BluetoothGatt.cpp +++ b/dom/bluetooth/bluetooth2/BluetoothGatt.cpp @@ -13,7 +13,6 @@ #include "mozilla/dom/BluetoothGattBinding.h" #include "mozilla/dom/BluetoothGattCharacteristicEvent.h" #include "mozilla/dom/Promise.h" -#include "nsIUUIDGenerator.h" #include "nsServiceManagerUtils.h" using namespace mozilla; @@ -76,27 +75,6 @@ BluetoothGatt::~BluetoothGatt() UnregisterBluetoothSignalHandler(mAppUuid, this); } -void -BluetoothGatt::GenerateUuid(nsAString &aUuidString) -{ - nsresult rv; - nsCOMPtr uuidGenerator = - do_GetService("@mozilla.org/uuid-generator;1", &rv); - NS_ENSURE_SUCCESS_VOID(rv); - - nsID uuid; - rv = uuidGenerator->GenerateUUIDInPlace(&uuid); - NS_ENSURE_SUCCESS_VOID(rv); - - // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format - char uuidBuffer[NSID_LENGTH]; - uuid.ToProvidedString(uuidBuffer); - NS_ConvertASCIItoUTF16 uuidString(uuidBuffer); - - // Remove {} and the null terminator - aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3)); -} - void BluetoothGatt::DisconnectFromOwner() { @@ -200,9 +178,9 @@ public: aValue.setUndefined(); const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value(); - NS_ENSURE_TRUE(v.type() == BluetoothValue::Tuint32_t, false); + NS_ENSURE_TRUE(v.type() == BluetoothValue::Tint32_t, false); - aValue.setInt32(static_cast(v.get_uint32_t())); + aValue.setInt32(static_cast(v.get_int32_t())); return true; } }; diff --git a/dom/bluetooth/bluetooth2/BluetoothGatt.h b/dom/bluetooth/bluetooth2/BluetoothGatt.h index 6843844f98..c897c604ad 100644 --- a/dom/bluetooth/bluetooth2/BluetoothGatt.h +++ b/dom/bluetooth/bluetooth2/BluetoothGatt.h @@ -87,13 +87,6 @@ private: */ void UpdateConnectionState(BluetoothConnectionState aState); - /** - * Generate a random uuid. - * - * @param aUuidString [out] String to store the generated uuid. - */ - void GenerateUuid(nsAString &aUuidString); - /** * Add newly discovered GATT services into mServices and update the cache * value of mServices. diff --git a/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.cpp b/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.cpp index bd23b5eabd..372fd7b48c 100644 --- a/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.cpp +++ b/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.cpp @@ -4,6 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "BluetoothReplyRunnable.h" #include "BluetoothService.h" #include "BluetoothUtils.h" #include "mozilla/dom/BluetoothGattDescriptorBinding.h" diff --git a/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.h b/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.h index 0628b9e3d3..bd3fa89d2e 100644 --- a/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.h +++ b/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.h @@ -22,6 +22,7 @@ class BluetoothValue; class BluetoothGattDescriptor final : public nsISupports , public nsWrapperCache + , public BluetoothSignalObserver { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS @@ -60,6 +61,13 @@ public: private: ~BluetoothGattDescriptor(); + /** + * Update the value of this descriptor. + * + * @param aValue [in] BluetoothValue which contains an uint8_t array. + */ + void HandleDescriptorValueUpdated(const BluetoothValue& aValue); + /**************************************************************************** * Variables ***************************************************************************/ diff --git a/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.cpp b/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.cpp new file mode 100644 index 0000000000..20e77ca7bb --- /dev/null +++ b/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/bluetooth/BluetoothLeDeviceEvent.h" + +#include "js/GCAPI.h" +#include "jsfriendapi.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/dom/BluetoothLeDeviceEventBinding.h" +#include "mozilla/dom/Nullable.h" +#include "mozilla/dom/PrimitiveConversions.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/dom/bluetooth/BluetoothDevice.h" + +USING_BLUETOOTH_NAMESPACE + +NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothLeDeviceEvent) + +NS_IMPL_ADDREF_INHERITED(BluetoothLeDeviceEvent, Event) +NS_IMPL_RELEASE_INHERITED(BluetoothLeDeviceEvent, Event) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothLeDeviceEvent, Event) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDevice) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(BluetoothLeDeviceEvent, Event) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mScanRecord) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothLeDeviceEvent, Event) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDevice) + tmp->mScanRecord = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothLeDeviceEvent) +NS_INTERFACE_MAP_END_INHERITING(Event) + +BluetoothLeDeviceEvent::BluetoothLeDeviceEvent(mozilla::dom::EventTarget* aOwner) + : Event(aOwner, nullptr, nullptr) +{ + mozilla::HoldJSObjects(this); +} + +BluetoothLeDeviceEvent::~BluetoothLeDeviceEvent() +{ + mScanRecord = nullptr; + mozilla::DropJSObjects(this); +} + +JSObject* +BluetoothLeDeviceEvent::WrapObjectInternal(JSContext* aCx, + JS::Handle aGivenProto) +{ + return BluetoothLeDeviceEventBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed +BluetoothLeDeviceEvent::Constructor( + mozilla::dom::EventTarget* aOwner, + const nsAString& aType, + BluetoothDevice* const aDevice, + const int16_t aRssi, + const nsTArray& aScanRecord) +{ + nsRefPtr e = new BluetoothLeDeviceEvent(aOwner); + bool trusted = e->Init(aOwner); + e->InitEvent(aType, false, false); + e->mDevice = aDevice; + e->mRssi = aRssi; + e->mRawScanRecord = aScanRecord; + + e->SetTrusted(trusted); + return e.forget(); +} + +already_AddRefed +BluetoothLeDeviceEvent::Constructor( + const GlobalObject& aGlobal, + const nsAString& aType, + const BluetoothLeDeviceEventInit& aEventInitDict, + ErrorResult& aRv) +{ + nsCOMPtr owner = + do_QueryInterface(aGlobal.GetAsSupports()); + + nsRefPtr e = new BluetoothLeDeviceEvent(owner); + bool trusted = e->Init(owner); + e->InitEvent(aType, aEventInitDict.mBubbles, aEventInitDict.mCancelable); + e->mDevice = aEventInitDict.mDevice; + e->mRssi = aEventInitDict.mRssi; + + aEventInitDict.mScanRecord.ComputeLengthAndData(); + const uint8_t* data = aEventInitDict.mScanRecord.Data(); + size_t length = aEventInitDict.mScanRecord.Length(); + e->mScanRecord = ArrayBuffer::Create(aGlobal.Context(), length, data); + if (!e->mScanRecord) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + e->SetTrusted(trusted); + return e.forget(); +} + +BluetoothDevice* +BluetoothLeDeviceEvent::Device() const +{ + return mDevice; +} + +int16_t +BluetoothLeDeviceEvent::Rssi() const +{ + return mRssi; +} + +void +BluetoothLeDeviceEvent::GetScanRecord( + JSContext* cx, + JS::MutableHandle aScanRecord, + ErrorResult& aRv) +{ + if (!mScanRecord) { + mScanRecord = ArrayBuffer::Create(cx, + mRawScanRecord.Length(), + mRawScanRecord.Elements()); + if (!mScanRecord) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + mRawScanRecord.Clear(); + } + JS::ExposeObjectToActiveJS(mScanRecord); + aScanRecord.set(mScanRecord); + + return; +} diff --git a/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.h b/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.h new file mode 100644 index 0000000000..480da96a60 --- /dev/null +++ b/dom/bluetooth/bluetooth2/BluetoothLeDeviceEvent.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_bluetooth_bluetoothledeviceevent_h +#define mozilla_dom_bluetooth_bluetoothledeviceevent_h + +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/BluetoothLeDeviceEventBinding.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/bluetooth/BluetoothCommon.h" + +BEGIN_BLUETOOTH_NAMESPACE + +class BluetoothLeDeviceEvent : public Event +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(BluetoothLeDeviceEvent, + Event) +protected: + virtual ~BluetoothLeDeviceEvent(); + explicit BluetoothLeDeviceEvent(mozilla::dom::EventTarget* aOwner); + + nsRefPtr mDevice; + int16_t mRssi; + JS::Heap mScanRecord; + +public: + virtual JSObject* WrapObjectInternal( + JSContext* aCx, + JS::Handle aGivenProto) override; + + static already_AddRefed + Constructor(EventTarget* aOwner, + const nsAString& aType, + BluetoothDevice* const aDevice, + const int16_t aRssi, + const nsTArray& aScanRecord); + + static already_AddRefed + Constructor(const GlobalObject& aGlobal, + const nsAString& aType, + const BluetoothLeDeviceEventInit& aEventInitDict, + ErrorResult& aRv); + + BluetoothDevice* Device() const; + + int16_t Rssi() const; + + void GetScanRecord(JSContext* cx, + JS::MutableHandle aScanRecord, + ErrorResult& aRv); + + private: + nsTArray mRawScanRecord; +}; + +END_BLUETOOTH_NAMESPACE + +#endif // mozilla_dom_bluetooth_bluetoothledeviceevent_h diff --git a/dom/bluetooth/bluetooth2/BluetoothProfileController.cpp b/dom/bluetooth/bluetooth2/BluetoothProfileController.cpp index fe372dd517..9bf997219b 100644 --- a/dom/bluetooth/bluetooth2/BluetoothProfileController.cpp +++ b/dom/bluetooth/bluetooth2/BluetoothProfileController.cpp @@ -45,7 +45,6 @@ protected: mController = nullptr; } -private: nsRefPtr mController; }; diff --git a/dom/bluetooth/bluetooth2/BluetoothService.h b/dom/bluetooth/bluetooth2/BluetoothService.h index f240d552de..d362a79cc3 100644 --- a/dom/bluetooth/bluetooth2/BluetoothService.h +++ b/dom/bluetooth/bluetooth2/BluetoothService.h @@ -174,20 +174,30 @@ public: /** * Stop device discovery (platform specific implementation) - * - * @return NS_OK if discovery stopped correctly, false otherwise */ - virtual nsresult + virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0; /** * Start device discovery (platform specific implementation) - * - * @return NS_OK if discovery stopped correctly, false otherwise */ - virtual nsresult + virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) = 0; + /** + * Stops an ongoing Bluetooth LE device scan. + */ + virtual void + StopLeScanInternal(const nsAString& aScanUuid, + BluetoothReplyRunnable* aRunnable) = 0; + + /** + * Starts a Bluetooth LE device scan. + */ + virtual void + StartLeScanInternal(const nsTArray& aServiceUuids, + BluetoothReplyRunnable* aRunnable) = 0; + /** * Set a property for the specified object * @@ -426,6 +436,31 @@ public: const nsTArray& aValue, BluetoothReplyRunnable* aRunnable) = 0; + /** + * Read the value of a descriptor of a characteristic on a GATT client. + * (platform specific implementation) + */ + virtual void + GattClientReadDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + BluetoothReplyRunnable* aRunnable) = 0; + + /** + * Write the value of a descriptor of a characteristic on a GATT client. + * (platform specific implementation) + */ + virtual void + GattClientWriteDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + const nsTArray& aValue, + BluetoothReplyRunnable* aRunnable) = 0; + bool IsEnabled() const { diff --git a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp index 3b9370a0ba..f1be8e3122 100644 --- a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp +++ b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp @@ -202,6 +202,10 @@ BluetoothParent::RecvPBluetoothRequestConstructor( return actor->DoRequest(aRequest.get_StartDiscoveryRequest()); case Request::TStopDiscoveryRequest: return actor->DoRequest(aRequest.get_StopDiscoveryRequest()); + case Request::TStartLeScanRequest: + return actor->DoRequest(aRequest.get_StartLeScanRequest()); + case Request::TStopLeScanRequest: + return actor->DoRequest(aRequest.get_StopLeScanRequest()); case Request::TPairRequest: return actor->DoRequest(aRequest.get_PairRequest()); case Request::TUnpairRequest: @@ -276,6 +280,12 @@ BluetoothParent::RecvPBluetoothRequestConstructor( case Request::TGattClientWriteCharacteristicValueRequest: return actor->DoRequest( aRequest.get_GattClientWriteCharacteristicValueRequest()); + case Request::TGattClientReadDescriptorValueRequest: + return actor->DoRequest( + aRequest.get_GattClientReadDescriptorValueRequest()); + case Request::TGattClientWriteDescriptorValueRequest: + return actor->DoRequest( + aRequest.get_GattClientWriteDescriptorValueRequest()); default: MOZ_CRASH("Unknown type!"); } @@ -397,9 +407,7 @@ BluetoothRequestParent::DoRequest(const StartDiscoveryRequest& aRequest) MOZ_ASSERT(mService); MOZ_ASSERT(mRequestType == Request::TStartDiscoveryRequest); - nsresult rv = - mService->StartDiscoveryInternal(mReplyRunnable.get()); - NS_ENSURE_SUCCESS(rv, false); + mService->StartDiscoveryInternal(mReplyRunnable.get()); return true; } @@ -410,9 +418,29 @@ BluetoothRequestParent::DoRequest(const StopDiscoveryRequest& aRequest) MOZ_ASSERT(mService); MOZ_ASSERT(mRequestType == Request::TStopDiscoveryRequest); - nsresult rv = - mService->StopDiscoveryInternal(mReplyRunnable.get()); - NS_ENSURE_SUCCESS(rv, false); + mService->StopDiscoveryInternal(mReplyRunnable.get()); + + return true; +} + +bool +BluetoothRequestParent::DoRequest(const StartLeScanRequest& aRequest) +{ + MOZ_ASSERT(mService); + MOZ_ASSERT(mRequestType == Request::TStartLeScanRequest); + + mService->StartLeScanInternal(aRequest.serviceUuids(), mReplyRunnable.get()); + + return true; +} + +bool +BluetoothRequestParent::DoRequest(const StopLeScanRequest& aRequest) +{ + MOZ_ASSERT(mService); + MOZ_ASSERT(mRequestType == Request::TStopLeScanRequest); + + mService->StopLeScanInternal(aRequest.scanUuid(), mReplyRunnable.get()); return true; } @@ -866,3 +894,38 @@ BluetoothRequestParent::DoRequest( return true; } + +bool +BluetoothRequestParent::DoRequest( + const GattClientReadDescriptorValueRequest& aRequest) +{ + MOZ_ASSERT(mService); + MOZ_ASSERT(mRequestType == + Request::TGattClientReadDescriptorValueRequest); + + mService->GattClientReadDescriptorValueInternal(aRequest.appUuid(), + aRequest.serviceId(), + aRequest.charId(), + aRequest.descId(), + mReplyRunnable.get()); + + return true; +} + +bool +BluetoothRequestParent::DoRequest( + const GattClientWriteDescriptorValueRequest& aRequest) +{ + MOZ_ASSERT(mService); + MOZ_ASSERT(mRequestType == + Request::TGattClientWriteDescriptorValueRequest); + + mService->GattClientWriteDescriptorValueInternal(aRequest.appUuid(), + aRequest.serviceId(), + aRequest.charId(), + aRequest.descId(), + aRequest.value(), + mReplyRunnable.get()); + + return true; +} diff --git a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h index 28e0fd3239..47dcc3c1d5 100644 --- a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h +++ b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h @@ -146,6 +146,12 @@ protected: bool DoRequest(const StopDiscoveryRequest& aRequest); + bool + DoRequest(const StartLeScanRequest& aRequest); + + bool + DoRequest(const StopLeScanRequest& aRequest); + bool DoRequest(const PairRequest& aRequest); @@ -249,6 +255,12 @@ protected: bool DoRequest(const GattClientWriteCharacteristicValueRequest& aRequest); + + bool + DoRequest(const GattClientReadDescriptorValueRequest& aRequest); + + bool + DoRequest(const GattClientWriteDescriptorValueRequest& aRequest); }; END_BLUETOOTH_NAMESPACE diff --git a/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp index 00244d3ff7..303769395c 100644 --- a/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp +++ b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp @@ -147,20 +147,34 @@ BluetoothServiceChildProcess::FetchUuidsInternal( return NS_OK; } -nsresult +void BluetoothServiceChildProcess::StopDiscoveryInternal( - BluetoothReplyRunnable* aRunnable) + BluetoothReplyRunnable* aRunnable) { SendRequest(aRunnable, StopDiscoveryRequest()); - return NS_OK; } -nsresult +void BluetoothServiceChildProcess::StartDiscoveryInternal( - BluetoothReplyRunnable* aRunnable) + BluetoothReplyRunnable* aRunnable) { SendRequest(aRunnable, StartDiscoveryRequest()); - return NS_OK; +} + +void +BluetoothServiceChildProcess::StopLeScanInternal( + const nsAString& aScanUuid, + BluetoothReplyRunnable* aRunnable) +{ + SendRequest(aRunnable, StopLeScanRequest(nsString(aScanUuid))); +} + +void +BluetoothServiceChildProcess::StartLeScanInternal( + const nsTArray& aServiceUuids, + BluetoothReplyRunnable* aRunnable) +{ + SendRequest(aRunnable, StartLeScanRequest(aServiceUuids)); } nsresult @@ -329,7 +343,7 @@ BluetoothServiceChildProcess::ConfirmReceivingFile( ConfirmReceivingFileRequest(nsString(aDeviceAddress))); return; } - + SendRequest(aRunnable, DenyReceivingFileRequest(nsString(aDeviceAddress))); } @@ -492,6 +506,38 @@ BluetoothServiceChildProcess::GattClientWriteCharacteristicValueInternal( aValue)); } +void +BluetoothServiceChildProcess::GattClientReadDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + BluetoothReplyRunnable* aRunnable) +{ + SendRequest(aRunnable, + GattClientReadDescriptorValueRequest(nsString(aAppUuid), + aServiceId, + aCharacteristicId, + aDescriptorId)); +} + +void +BluetoothServiceChildProcess::GattClientWriteDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + const nsTArray& aValue, + BluetoothReplyRunnable* aRunnable) +{ + SendRequest(aRunnable, + GattClientWriteDescriptorValueRequest(nsString(aAppUuid), + aServiceId, + aCharacteristicId, + aDescriptorId, + aValue)); +} + nsresult BluetoothServiceChildProcess::HandleStartup() { diff --git a/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h index 63de3022c9..aebf930de7 100644 --- a/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h +++ b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h @@ -53,12 +53,20 @@ public: FetchUuidsInternal(const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable) override; - virtual nsresult + virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override; - virtual nsresult + virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override; + virtual void + StopLeScanInternal(const nsAString& aScanUuid, + BluetoothReplyRunnable* aRunnable) override; + + virtual void + StartLeScanInternal(const nsTArray& aServiceUuids, + BluetoothReplyRunnable* aRunnable) override; + virtual nsresult SetProperty(BluetoothObjectType aType, const BluetoothNamedValue& aValue, @@ -245,6 +253,23 @@ public: const nsTArray& aValue, BluetoothReplyRunnable* aRunnable) override; + virtual void + GattClientReadDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + BluetoothReplyRunnable* aRunnable); + + virtual void + GattClientWriteDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + const nsTArray& aValue, + BluetoothReplyRunnable* aRunnable); + protected: BluetoothServiceChildProcess(); virtual ~BluetoothServiceChildProcess(); diff --git a/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh b/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh index d3787a30db..15fefbaf83 100644 --- a/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh +++ b/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh @@ -28,6 +28,7 @@ namespace bluetooth { */ union BluetoothValue { + int32_t; uint32_t; nsString; bool; diff --git a/dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl b/dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl index d7aa3d42e2..24f56cbf70 100644 --- a/dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl +++ b/dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl @@ -53,6 +53,16 @@ struct StopDiscoveryRequest { }; +struct StartLeScanRequest +{ + nsString[] serviceUuids; +}; + +struct StopLeScanRequest +{ + nsString scanUuid; +}; + struct PairRequest { nsString address; @@ -248,6 +258,23 @@ struct GattClientWriteCharacteristicValueRequest uint8_t[] value; }; +struct GattClientReadDescriptorValueRequest +{ + nsString appUuid; + BluetoothGattServiceId serviceId; + BluetoothGattId charId; + BluetoothGattId descId; +}; + +struct GattClientWriteDescriptorValueRequest +{ + nsString appUuid; + BluetoothGattServiceId serviceId; + BluetoothGattId charId; + BluetoothGattId descId; + uint8_t[] value; +}; + union Request { GetAdaptersRequest; @@ -257,6 +284,8 @@ union Request GetPropertyRequest; StartDiscoveryRequest; StopDiscoveryRequest; + StartLeScanRequest; + StopLeScanRequest; PairRequest; UnpairRequest; PinReplyRequest; @@ -291,6 +320,8 @@ union Request GattClientReadRemoteRssiRequest; GattClientReadCharacteristicValueRequest; GattClientWriteCharacteristicValueRequest; + GattClientReadDescriptorValueRequest; + GattClientWriteDescriptorValueRequest; }; protocol PBluetooth diff --git a/dom/bluetooth/bluez/BluetoothDBusService.cpp b/dom/bluetooth/bluez/BluetoothDBusService.cpp index 1ae7051424..a43887267f 100644 --- a/dom/bluetooth/bluez/BluetoothDBusService.cpp +++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp @@ -2844,16 +2844,16 @@ BluetoothDBusService::SendSinkMessage(const nsAString& aDeviceAddress, return SendAsyncDBusMessage(objectPath, DBUS_SINK_IFACE, aMessage, callback); } -nsresult +void BluetoothDBusService::StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) { - return SendDiscoveryMessage("StopDiscovery", aRunnable); + SendDiscoveryMessage("StopDiscovery", aRunnable); } -nsresult +void BluetoothDBusService::StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) { - return SendDiscoveryMessage("StartDiscovery", aRunnable); + SendDiscoveryMessage("StartDiscovery", aRunnable); } class BluetoothArrayOfDevicePropertiesReplyHandler : public DBusReplyHandler @@ -4695,6 +4695,20 @@ BluetoothDBusService::UpdateNotification(ControlEventId aEventId, } #ifdef MOZ_B2G_BT_API_V2 +void +BluetoothDBusService::StartLeScanInternal( + const nsTArray& aServiceUuids, + BluetoothReplyRunnable* aRunnable); +{ +} + +void +BluetoothDBusService::StopLeScanInternal( + const nsAString& aAppUuid, + BluetoothReplyRunnable* aRunnable); +{ +} + void BluetoothDBusService::ConnectGattClientInternal( const nsAString& aAppUuid, const nsAString& aDeviceAddress, @@ -4761,6 +4775,27 @@ BluetoothDBusService::GattClientWriteCharacteristicValueInternal( BluetoothReplyRunnable* aRunnable) { } + +void +BluetoothDBusService::GattClientReadDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + BluetoothReplyRunnable* aRunnable) +{ +} + +void +BluetoothDBusService::GattClientWriteDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + const nsTArray& aValue, + BluetoothReplyRunnable* aRunnable) +{ +} #else // Missing in bluetooth1 #endif diff --git a/dom/bluetooth/bluez/BluetoothDBusService.h b/dom/bluetooth/bluez/BluetoothDBusService.h index eefbd9cf9d..c52f394277 100644 --- a/dom/bluetooth/bluez/BluetoothDBusService.h +++ b/dom/bluetooth/bluez/BluetoothDBusService.h @@ -82,9 +82,9 @@ public: BluetoothReplyRunnable* aRunnable) override; #endif - virtual nsresult StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override; + virtual void StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override; - virtual nsresult StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override; + virtual void StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) override; virtual nsresult SetProperty(BluetoothObjectType aType, @@ -234,6 +234,14 @@ public: const nsAString& aMessage) override; #ifdef MOZ_B2G_BT_API_V2 + virtual void + StartLeScanInternal(const nsTArray& aServiceUuids, + BluetoothReplyRunnable* aRunnable) override; + + virtual void + StopLeScanInternal(const nsAString& aAppUuid, + BluetoothReplyRunnable* aRunnable) override; + virtual void ConnectGattClientInternal(const nsAString& aAppUuid, const nsAString& aDeviceAddress, @@ -286,6 +294,23 @@ public: const BluetoothGattWriteType& aWriteType, const nsTArray& aValue, BluetoothReplyRunnable* aRunnable) override; + + virtual void + GattClientReadDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + BluetoothReplyRunnable* aRunnable) override; + + virtual void + GattClientWriteDescriptorValueInternal( + const nsAString& aAppUuid, + const BluetoothGattServiceId& aServiceId, + const BluetoothGattId& aCharacteristicId, + const BluetoothGattId& aDescriptorId, + const nsTArray& aValue, + BluetoothReplyRunnable* aRunnable) override; #else // Missing in bluetooth1 #endif diff --git a/dom/bluetooth/moz.build b/dom/bluetooth/moz.build index 6ce4aef894..19452eea78 100644 --- a/dom/bluetooth/moz.build +++ b/dom/bluetooth/moz.build @@ -33,6 +33,7 @@ if CONFIG['MOZ_B2G_BT']: 'bluetooth2/BluetoothGattCharacteristic.cpp', 'bluetooth2/BluetoothGattDescriptor.cpp', 'bluetooth2/BluetoothGattService.cpp', + 'bluetooth2/BluetoothLeDeviceEvent.cpp', 'bluetooth2/BluetoothManager.cpp', 'bluetooth2/BluetoothPairingHandle.cpp', 'bluetooth2/BluetoothPairingListener.cpp', @@ -173,6 +174,7 @@ if CONFIG['MOZ_B2G_BT_API_V2']: 'bluetooth2/BluetoothGattCharacteristic.h', 'bluetooth2/BluetoothGattDescriptor.h', 'bluetooth2/BluetoothGattService.h', + 'bluetooth2/BluetoothLeDeviceEvent.h', 'bluetooth2/BluetoothManager.h', 'bluetooth2/BluetoothPairingHandle.h', 'bluetooth2/BluetoothPairingListener.h', diff --git a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp index 7cadcea36e..af4eff760c 100644 --- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp +++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp @@ -32,6 +32,7 @@ #include "mozilla/StaticMutex.h" #include "mozilla/StaticPtr.h" #include "mozilla/unused.h" +#include "nsDataHashtable.h" #define ENSURE_BLUETOOTH_IS_READY(runnable, result) \ do { \ @@ -72,6 +73,12 @@ static bool sAdapterEnabled(false); // InfallibleTArray is an alias for nsTArray. static InfallibleTArray sAdapterBondedAddressArray; +// Use a static hash table to keep the name of remote device during the pairing +// procedure. In this manner, BT service and adapter can get the name of paired +// device name when bond state changed. +// The hash Key is BD address, the Value is remote BD name. +static nsDataHashtable sPairingNameTable; + static BluetoothInterface* sBtInterface; static nsTArray > sControllerArray; static InfallibleTArray sRemoteDevicesPack; @@ -1287,10 +1294,6 @@ BluetoothServiceBluedroid::AdapterStateChangedNotification(bool aState) sAdapterDiscovering = false; BT_APPEND_NAMED_VALUE(props, "Discovering", false); } - if (!sAdapterBondedAddressArray.IsEmpty()) { - BT_APPEND_NAMED_VALUE(props, "PairedDevices", - InfallibleTArray()); - } bs->DistributeSignal(NS_LITERAL_STRING("PropertyChanged"), NS_LITERAL_STRING(KEY_ADAPTER), @@ -1316,6 +1319,7 @@ BluetoothServiceBluedroid::AdapterStateChangedNotification(bool aState) sFetchUuidsRunnableArray.Clear(); sBondingRunnableArray.Clear(); sUnbondingRunnableArray.Clear(); + sPairingNameTable.Clear(); // Bluetooth scan mode is SCAN_MODE_CONNECTABLE by default, i.e., it should // be connectable and non-discoverable. @@ -1380,10 +1384,7 @@ BluetoothServiceBluedroid::AdapterPropertiesNotification( // Whenever reloading paired devices, force refresh sAdapterBondedAddressArray.Clear(); - - for (size_t index = 0; index < p.mStringArray.Length(); index++) { - sAdapterBondedAddressArray.AppendElement(p.mStringArray[index]); - } + sAdapterBondedAddressArray.AppendElements(p.mStringArray); BT_APPEND_NAMED_VALUE(propertiesArray, "PairedDevices", sAdapterBondedAddressArray); @@ -1601,6 +1602,8 @@ BluetoothServiceBluedroid::PinRequestNotification(const nsAString& aRemoteBdAddr BT_APPEND_NAMED_VALUE(propertiesArray, "type", NS_LITERAL_STRING(PAIRING_REQ_TYPE_ENTERPINCODE)); + sPairingNameTable.Put(nsString(aRemoteBdAddr), nsString(aBdName)); + DistributeSignal(NS_LITERAL_STRING("PairingRequest"), NS_LITERAL_STRING(KEY_PAIRING_LISTENER), BluetoothValue(propertiesArray)); @@ -1647,6 +1650,8 @@ BluetoothServiceBluedroid::SspRequestNotification( BT_APPEND_NAMED_VALUE(propertiesArray, "passkey", passkey); BT_APPEND_NAMED_VALUE(propertiesArray, "type", pairingType); + sPairingNameTable.Put(nsString(aRemoteBdAddr), nsString(aBdName)); + DistributeSignal(NS_LITERAL_STRING("PairingRequest"), NS_LITERAL_STRING(KEY_PAIRING_LISTENER), BluetoothValue(propertiesArray)); @@ -1680,15 +1685,26 @@ BluetoothServiceBluedroid::BondStateChangedNotification( InfallibleTArray propertiesArray; BT_APPEND_NAMED_VALUE(propertiesArray, "Paired", bonded); + // Retrieve device name from hash table of pairing device name. + nsString deviceName = EmptyString(); + bool nameExists = sPairingNameTable.Get(aRemoteBdAddr, &deviceName); + if (nameExists) { + sPairingNameTable.Remove(aRemoteBdAddr); + } + + // Update attribute BluetoothDevice.name if the device is paired. + if (bonded && STATUS_SUCCESS) { + MOZ_ASSERT(nameExists); + BT_APPEND_NAMED_VALUE(propertiesArray, "Name", deviceName); + } + DistributeSignal(NS_LITERAL_STRING("PropertyChanged"), aRemoteBdAddr, BluetoothValue(propertiesArray)); - propertiesArray.Clear(); - // Append signal properties and notify adapter. - BT_APPEND_NAMED_VALUE(propertiesArray, "Address", nsString(aRemoteBdAddr)); - BT_APPEND_NAMED_VALUE(propertiesArray, "Paired", bonded); + // Insert address to signal properties and notify adapter. + BT_INSERT_NAMED_VALUE(propertiesArray, 0, "Address", nsString(aRemoteBdAddr)); nsString signalName = bonded ? NS_LITERAL_STRING(DEVICE_PAIRED_ID) : NS_LITERAL_STRING(DEVICE_UNPAIRED_ID); diff --git a/dom/events/test/test_all_synthetic_events.html b/dom/events/test/test_all_synthetic_events.html index e8b5c928a6..fc8bdef18b 100644 --- a/dom/events/test/test_all_synthetic_events.html +++ b/dom/events/test/test_all_synthetic_events.html @@ -68,6 +68,10 @@ const kEventConstructors = { return new BluetoothGattCharacteristicEvent(aName, aProps); }, }, + BluetoothLeDeviceEvent: { create: function (aName, aProps) { + return new BluetoothLeDeviceEvent(aName, aProps); + }, + }, BluetoothPairingEvent: { create: function (aName, aProps) { return new BluetoothPairingEvent(aName, aProps); }, diff --git a/dom/webidl/BluetoothAdapter2.webidl b/dom/webidl/BluetoothAdapter2.webidl index f5e9e3491d..a903f37b4d 100644 --- a/dom/webidl/BluetoothAdapter2.webidl +++ b/dom/webidl/BluetoothAdapter2.webidl @@ -94,6 +94,12 @@ interface BluetoothAdapter : EventTarget { sequence getPairedDevices(); + [NewObject] + Promise startLeScan(sequence serviceUuids); + + [NewObject] + Promise stopLeScan(BluetoothDiscoveryHandle discoveryHandle); + [NewObject, Throws, AvailableIn=CertifiedApps] DOMRequest getConnectedDevices(unsigned short serviceUuid); diff --git a/dom/webidl/BluetoothLeDeviceEvent.webidl b/dom/webidl/BluetoothLeDeviceEvent.webidl new file mode 100644 index 0000000000..c7fc8a489b --- /dev/null +++ b/dom/webidl/BluetoothLeDeviceEvent.webidl @@ -0,0 +1,22 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +[CheckPermissions="bluetooth", + Constructor(DOMString type, optional BluetoothLeDeviceEventInit eventInitDict)] +interface BluetoothLeDeviceEvent : Event +{ + readonly attribute BluetoothDevice device; + readonly attribute short rssi; + [Throws] + readonly attribute ArrayBuffer scanRecord; +}; + +dictionary BluetoothLeDeviceEventInit : EventInit +{ + required BluetoothDevice device; + short rssi = 0; + required ArrayBuffer scanRecord; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index adf4b139e6..6998c324c9 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -661,6 +661,7 @@ if CONFIG['MOZ_B2G_BT']: 'BluetoothGattCharacteristic.webidl', 'BluetoothGattDescriptor.webidl', 'BluetoothGattService.webidl', + 'BluetoothLeDeviceEvent.webidl', 'BluetoothManager2.webidl', 'BluetoothPairingHandle.webidl', 'BluetoothPairingListener.webidl', diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index c6301aae06..40e83de809 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4286,28 +4286,48 @@ BytecodeEmitter::emitTemplateString(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_LIST)); + bool pushedString = false; + for (ParseNode* pn2 = pn->pn_head; pn2 != NULL; pn2 = pn2->pn_next) { - if (pn2->getKind() != PNK_STRING && pn2->getKind() != PNK_TEMPLATE_STRING) { + bool isString = (pn2->getKind() == PNK_STRING || pn2->getKind() == PNK_TEMPLATE_STRING); + + // Skip empty strings. These are very common: a template string like + // `${a}${b}` has three empty strings and without this optimization + // we'd emit four JSOP_ADD operations instead of just one. + if (isString && pn2->pn_atom->empty()) + continue; + + if (!isString) { // We update source notes before emitting the expression if (!updateSourceCoordNotes(pn2->pn_pos.begin)) return false; } + if (!emitTree(pn2)) return false; - if (pn2->getKind() != PNK_STRING && pn2->getKind() != PNK_TEMPLATE_STRING) { + if (!isString) { // We need to convert the expression to a string if (!emit1(JSOP_TOSTRING)) return false; } - if (pn2 != pn->pn_head) { + if (pushedString) { // We've pushed two strings onto the stack. Add them together, leaving just one. if (!emit1(JSOP_ADD)) return false; + } else { + pushedString = true; } - } + + if (!pushedString) { + // All strings were empty, this can happen for something like `${""}`. + // Just push an empty string. + if (!emitAtomOp(cx->names().empty, JSOP_STRING)) + return false; + } + return true; } diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index c1935b56c6..98ccc45aa6 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -1155,6 +1155,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, ParseNode* expr = handler.newPropertyAccess(pn->pn_left, name, pn->pn_pos.end); if (!expr) return false; + expr->setInParens(pn->isInParens()); ReplaceNode(pnp, expr); // Supposing we're replacing |obj["prop"]| with |obj.prop|, we now diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 916fe7ad8e..ebe59012b1 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -83,8 +83,21 @@ class FullParseHandler return node->isKind(PNK_CALL); } - bool isDestructuringTarget(ParseNode* node) { - return node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY); + static bool isUnparenthesizedDestructuringPattern(ParseNode* node) { + return !node->isInParens() && (node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY)); + } + + static bool isParenthesizedDestructuringPattern(ParseNode* node) { + // Technically this isn't a destructuring pattern at all -- the grammar + // doesn't treat it as such. But we need to know when this happens to + // consider it a SyntaxError rather than an invalid-left-hand-side + // ReferenceError. + return node->isInParens() && (node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY)); + } + + static bool isDestructuringPatternAnyParentheses(ParseNode* node) { + return isUnparenthesizedDestructuringPattern(node) || + isParenthesizedDestructuringPattern(node); } FullParseHandler(ExclusiveContext* cx, LifoAlloc& alloc, @@ -780,9 +793,25 @@ class FullParseHandler bool isConstant(ParseNode* pn) { return pn->isConstant(); } - PropertyName* maybeName(ParseNode* pn) { - return pn->isKind(PNK_NAME) ? pn->pn_atom->asPropertyName() : nullptr; + + PropertyName* maybeUnparenthesizedName(ParseNode* pn) { + if (!pn->isInParens() && pn->isKind(PNK_NAME)) + return pn->pn_atom->asPropertyName(); + return nullptr; } + + PropertyName* maybeParenthesizedName(ParseNode* pn) { + if (pn->isInParens() && pn->isKind(PNK_NAME)) + return pn->pn_atom->asPropertyName(); + return nullptr; + } + + PropertyName* maybeNameAnyParentheses(ParseNode* node) { + if (PropertyName* name = maybeUnparenthesizedName(node)) + return name; + return maybeParenthesizedName(node); + } + bool isCall(ParseNode* pn) { return pn->isKind(PNK_CALL); } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index fbfcb946e9..696d6f9ddd 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -1130,7 +1130,7 @@ Parser::makeDefIntoUse(Definition* dn, ParseNode* pn, JSAtom* * helper function signature in order to share code among destructuring and * simple variable declaration parsers. In the destructuring case, the binder * function is called indirectly from the variable declaration parser by way - * of checkDestructuring and its friends. + * of checkDestructuringPattern and its friends. */ template @@ -3410,7 +3410,65 @@ Parser::bindInitialized(BindData* data, Pars template <> bool -Parser::checkDestructuring(BindData* data, ParseNode* left); +Parser::checkDestructuringName(BindData* data, ParseNode* expr) +{ + MOZ_ASSERT(!handler.isUnparenthesizedDestructuringPattern(expr)); + + // Parentheses are forbidden around destructuring *patterns* (but allowed + // around names). Use our nicer error message for parenthesized, nested + // patterns. + if (handler.isParenthesizedDestructuringPattern(expr)) { + report(ParseError, false, expr, JSMSG_BAD_DESTRUCT_PARENS); + return false; + } + + // This expression might be in a variable-binding pattern where only plain, + // unparenthesized names are permitted. + if (data) { + // Destructuring patterns in declarations must only contain + // unparenthesized names. + if (!handler.maybeUnparenthesizedName(expr)) { + report(ParseError, false, expr, JSMSG_NO_VARIABLE_NAME); + return false; + } + + return bindInitialized(data, expr); + } + + // Otherwise this is an expression in destructuring outside a declaration. + if (!reportIfNotValidSimpleAssignmentTarget(expr, KeyedDestructuringAssignment)) + return false; + + MOZ_ASSERT(!handler.isFunctionCall(expr), + "function calls shouldn't be considered valid targets in " + "destructuring patterns"); + + if (handler.maybeNameAnyParentheses(expr)) { + // The arguments/eval identifiers are simple in non-strict mode code. + // Warn to discourage their use nonetheless. + if (!reportIfArgumentsEvalTarget(expr)) + return false; + + // We may be called on a name node that has already been + // specialized, in the very weird "for (var [x] = i in o) ..." + // case. See bug 558633. + // + // XXX Is this necessary with the changes in bug 1164741? This is + // likely removable now. + handler.maybeDespecializeSet(expr); + + handler.markAsAssigned(expr); + return true; + } + + // Nothing further to do for property accesses. + MOZ_ASSERT(handler.isPropertyAccess(expr)); + return true; +} + +template <> +bool +Parser::checkDestructuringPattern(BindData* data, ParseNode* pattern); template <> bool @@ -3420,30 +3478,28 @@ Parser::checkDestructuringObject(BindData* d MOZ_ASSERT(objectPattern->isKind(PNK_OBJECT)); for (ParseNode* member = objectPattern->pn_head; member; member = member->pn_next) { - ParseNode* expr; + ParseNode* target; if (member->isKind(PNK_MUTATEPROTO)) { - expr = member->pn_kid; + target = member->pn_kid; } else { MOZ_ASSERT(member->isKind(PNK_COLON) || member->isKind(PNK_SHORTHAND)); - expr = member->pn_right; - } - if (expr->isKind(PNK_ASSIGN)) - expr = expr->pn_left; + MOZ_ASSERT_IF(member->isKind(PNK_SHORTHAND), + member->pn_left->isKind(PNK_OBJECT_PROPERTY_NAME) && + member->pn_right->isKind(PNK_NAME) && + member->pn_left->pn_atom == member->pn_right->pn_atom); - bool ok; - if (expr->isKind(PNK_ARRAY) || expr->isKind(PNK_OBJECT)) { - ok = checkDestructuring(data, expr); - } else if (data) { - if (!expr->isKind(PNK_NAME)) { - report(ParseError, false, expr, JSMSG_NO_VARIABLE_NAME); - return false; - } - ok = bindInitialized(data, expr); - } else { - ok = checkAndMarkAsAssignmentLhs(expr, KeyedDestructuringAssignment); + target = member->pn_right; + } + if (handler.isUnparenthesizedAssignment(target)) + target = target->pn_left; + + if (handler.isUnparenthesizedDestructuringPattern(target)) { + if (!checkDestructuringPattern(data, target)) + return false; + } else { + if (!checkDestructuringName(data, target)) + return false; } - if (!ok) - return false; } return true; @@ -3460,39 +3516,31 @@ Parser::checkDestructuringArray(BindData* da if (element->isKind(PNK_ELISION)) continue; - ParseNode* target = element; - if (target->isKind(PNK_SPREAD)) { - if (target->pn_next) { - report(ParseError, false, target->pn_next, JSMSG_PARAMETER_AFTER_REST); + ParseNode* target; + if (element->isKind(PNK_SPREAD)) { + if (element->pn_next) { + report(ParseError, false, element->pn_next, JSMSG_PARAMETER_AFTER_REST); return false; } - target = target->pn_kid; + target = element->pn_kid; - // The RestElement should not support nested patterns. - if (target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) { + if (handler.isUnparenthesizedDestructuringPattern(target)) { report(ParseError, false, target, JSMSG_BAD_DESTRUCT_TARGET); return false; } - } else if (target->isKind(PNK_ASSIGN)) { - target = target->pn_left; + } else if (handler.isUnparenthesizedAssignment(element)) { + target = element->pn_left; + } else { + target = element; } - bool ok; - if (target->isKind(PNK_ARRAY) || target->isKind(PNK_OBJECT)) { - ok = checkDestructuring(data, target); + if (handler.isUnparenthesizedDestructuringPattern(target)) { + if (!checkDestructuringPattern(data, target)) + return false; } else { - if (data) { - if (!target->isKind(PNK_NAME)) { - report(ParseError, false, target, JSMSG_NO_VARIABLE_NAME); - return false; - } - ok = bindInitialized(data, target); - } else { - ok = checkAndMarkAsAssignmentLhs(target, KeyedDestructuringAssignment); - } + if (!checkDestructuringName(data, target)) + return false; } - if (!ok) - return false; } return true; @@ -3512,16 +3560,16 @@ Parser::checkDestructuringArray(BindData* da * simple names; the destructuring defines them as new variables. * * In both cases, other code parses the pattern as an arbitrary - * primaryExpr, and then, here in checkDestructuring, verify that the - * tree is a valid AssignmentPattern or BindingPattern. + * primaryExpr, and then, here in checkDestructuringPattern, verify + * that the tree is a valid AssignmentPattern or BindingPattern. * * In assignment-like contexts, we parse the pattern with * pc->inDeclDestructuring clear, so the lvalue expressions in the * pattern are parsed normally. primaryExpr links variable references * into the appropriate use chains; creates placeholder definitions; - * and so on. checkDestructuring is called with |data| nullptr (since - * we won't be binding any new names), and we specialize lvalues as - * appropriate. + * and so on. checkDestructuringPattern is called with |data| nullptr + * (since we won't be binding any new names), and we specialize lvalues + * as appropriate. * * In declaration-like contexts, the normal variable reference * processing would just be an obstruction, because we're going to @@ -3529,28 +3577,28 @@ Parser::checkDestructuringArray(BindData* da * variables anyway. In this case, we parse the pattern with * pc->inDeclDestructuring set, which directs primaryExpr to leave * whatever name nodes it creates unconnected. Then, here in - * checkDestructuring, we require the pattern's property value + * checkDestructuringPattern, we require the pattern's property value * positions to be simple names, and define them as appropriate to the * context. For these calls, |data| points to the right sort of * BindData. */ template <> bool -Parser::checkDestructuring(BindData* data, ParseNode* left) +Parser::checkDestructuringPattern(BindData* data, ParseNode* pattern) { - if (left->isKind(PNK_ARRAYCOMP)) { - report(ParseError, false, left, JSMSG_ARRAY_COMP_LEFTSIDE); + if (pattern->isKind(PNK_ARRAYCOMP)) { + report(ParseError, false, pattern, JSMSG_ARRAY_COMP_LEFTSIDE); return false; } - if (left->isKind(PNK_ARRAY)) - return checkDestructuringArray(data, left); - return checkDestructuringObject(data, left); + if (pattern->isKind(PNK_ARRAY)) + return checkDestructuringArray(data, pattern); + return checkDestructuringObject(data, pattern); } template <> bool -Parser::checkDestructuring(BindData* data, Node left) +Parser::checkDestructuringPattern(BindData* data, Node pattern) { return abortIfSyntaxParser(); } @@ -3567,7 +3615,7 @@ Parser::destructuringExpr(YieldHandling yieldHandling, BindDatainDeclDestructuring = false; if (!pn) return null(); - if (!checkDestructuring(data, pn)) + if (!checkDestructuringPattern(data, pn)) return null(); return pn; } @@ -3791,7 +3839,7 @@ Parser::variables(YieldHandling yieldHandling, // handles the non-destructuring case. bool bindBeforeInitializer = (kind != PNK_LET && kind != PNK_CONST) || parsingForInOrOfInit; - if (bindBeforeInitializer && !checkDestructuring(&data, pn2)) + if (bindBeforeInitializer && !checkDestructuringPattern(&data, pn2)) return null(); if (parsingForInOrOfInit) { @@ -3806,12 +3854,26 @@ Parser::variables(YieldHandling yieldHandling, if (!init) return null(); - if (!bindBeforeInitializer && !checkDestructuring(&data, pn2)) + // Ban the nonsensical |for (var V = E1 in E2);| where V is a + // destructuring pattern. See bug 1164741 for background. + if (location == InForInit && kind == PNK_VAR) { + TokenKind afterInit; + if (!tokenStream.peekToken(&afterInit)) + return null(); + if (afterInit == TOK_IN) { + report(ParseError, false, init, JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT, + "in"); + return null(); + } + } + + if (!bindBeforeInitializer && !checkDestructuringPattern(&data, pn2)) return null(); pn2 = handler.newBinary(PNK_ASSIGN, pn2, init); if (!pn2) return null(); + handler.addList(pn, pn2); break; } @@ -3859,11 +3921,34 @@ Parser::variables(YieldHandling yieldHandling, if (!init) return null(); - if (!bindBeforeInitializer && !data.binder(&data, name, this)) - return null(); + // Ignore an initializer if we have a for-in loop declaring a + // |var| with an initializer: |for (var v = ... in ...);|. + // Warn that this syntax is invalid so that developers looking + // in the console know to fix this. ES<6 permitted the + // initializer while ES6 doesn't; ignoring it seems the best + // way to incrementally move to ES6 semantics. + bool performAssignment = true; + if (location == InForInit && kind == PNK_VAR) { + TokenKind afterInit; + if (!tokenStream.peekToken(&afterInit)) + return null(); + if (afterInit == TOK_IN) { + performAssignment = false; + if (!report(ParseWarning, pc->sc->strict(), init, + JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT, "in")) + { + return null(); + } + } + } - if (!handler.finishInitializerAssignment(pn2, init, data.op)) - return null(); + if (performAssignment) { + if (!bindBeforeInitializer && !data.binder(&data, name, this)) + return null(); + + if (!handler.finishInitializerAssignment(pn2, init, data.op)) + return null(); + } } else { if (data.isConst && location == NotInForInit) { report(ParseError, false, null(), JSMSG_BAD_CONST_DECL); @@ -4774,8 +4859,14 @@ Parser::forStatement(YieldHandling yieldHandling) if (isForDecl) { pn2 = pn1->pn_head; if ((pn2->isKind(PNK_NAME) && pn2->maybeExpr()) || pn2->isKind(PNK_ASSIGN)) { - // We have a bizarre |for (var/const/let x = ... in/of ...)| - // loop erroneously permitted by ES1-5 but removed in ES6. + MOZ_ASSERT(!(headKind == PNK_FORIN && pn1->isKind(PNK_VAR)), + "Parser::variables should have ignored the " + "initializer in the ES5-sanctioned, ES6-prohibited " + "|for (var ... = ... in ...)| syntax"); + + // Otherwise, this bizarre |for (const/let x = ... in/of ...)| + // loop isn't valid ES6 and has never been permitted in + // SpiderMonkey. report(ParseError, false, pn2, JSMSG_INVALID_FOR_INOF_DECL_WITH_INIT, headKind == PNK_FOROF ? "of" : "in"); return null(); @@ -5005,7 +5096,7 @@ Parser::forStatement(YieldHandling yieldHandling) /* Check that the left side of the 'in' or 'of' is valid. */ if (!isForDecl && - !handler.maybeName(lhsNode) && + !handler.maybeNameAnyParentheses(lhsNode) && !handler.isPropertyAccess(lhsNode)) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); @@ -6149,12 +6240,6 @@ BinaryOpParseNodeKindToJSOp(ParseNodeKind pnk) return ParseNodeKindToJSOp[pnk - PNK_BINOP_FIRST]; } -static bool -IsBinaryOpToken(TokenKind tok, bool parsingForInit) -{ - return tok == TOK_IN ? !parsingForInit : TokenKindIsBinaryOp(tok); -} - static ParseNodeKind BinaryOpTokenKindToParseNodeKind(TokenKind tok) { @@ -6230,8 +6315,6 @@ Parser::orExpr1(InHandling inHandling, YieldHandling yieldHandling if (!tokenStream.getToken(&tok)) return null(); - // FIXME: Change this to use |inHandling == InAllowed|, not - // |pc->parsingForInit|. ParseNodeKind pnk; if (tok == TOK_IN ? inHandling == InAllowed : TokenKindIsBinaryOp(tok)) { pnk = BinaryOpTokenKindToParseNodeKind(tok); @@ -6299,14 +6382,17 @@ template bool Parser::checkAndMarkAsAssignmentLhs(Node target, AssignmentFlavor flavor) { - // Handle destructuring object/array patterns specially. - if (handler.isDestructuringTarget(target)) { + MOZ_ASSERT(flavor != KeyedDestructuringAssignment, + "destructuring must use special checking/marking code, not " + "this method"); + + if (handler.isUnparenthesizedDestructuringPattern(target)) { if (flavor == CompoundAssignment) { report(ParseError, false, null(), JSMSG_BAD_DESTRUCT_ASS); return false; } - return checkDestructuring(nullptr, target); + return checkDestructuringPattern(nullptr, target); } // All other permitted targets are simple. @@ -6316,34 +6402,18 @@ Parser::checkAndMarkAsAssignmentLhs(Node target, AssignmentFlavor if (handler.isPropertyAccess(target)) return true; - if (handler.maybeName(target)) { + if (handler.maybeNameAnyParentheses(target)) { // The arguments/eval identifiers are simple in non-strict mode code, // but warn to discourage use nonetheless. if (!reportIfArgumentsEvalTarget(target)) return false; - if (flavor == KeyedDestructuringAssignment) { - // We may be called on a name node that has already been - // specialized, in the very weird "for (var [x] = i in o) ..." - // case. See bug 558633. - // - // XXX Is this necessary with the changes in bug 1164741? This is - // likely removable now. - handler.maybeDespecializeSet(target); - } else { - handler.adjustGetToSet(target); - } + handler.adjustGetToSet(target); handler.markAsAssigned(target); return true; } MOZ_ASSERT(handler.isFunctionCall(target)); - - if (flavor == KeyedDestructuringAssignment) { - report(ParseError, false, target, JSMSG_BAD_DESTRUCT_TARGET); - return false; - } - return makeSetCall(target, JSMSG_BAD_LEFTSIDE_OF_ASS); } @@ -6471,11 +6541,14 @@ Parser::assignExpr(InHandling inHandling, YieldHandling yieldHandl template bool -Parser::isValidSimpleAssignmentTarget(Node node) +Parser::isValidSimpleAssignmentTarget(Node node, + FunctionCallBehavior behavior /* = ForbidAssignmentToFunctionCalls */) { - if (PropertyName* name = handler.maybeName(node)) { - // Note that we implement *exactly* the ES6 semantics here. Warning - // for arguments/eval when extraWarnings is set isn't handled here. + // Note that this method implements *only* a boolean test. Reporting an + // error for the various syntaxes that fail this, and warning for the + // various syntaxes that "pass" this but should not, occurs elsewhere. + + if (PropertyName* name = handler.maybeNameAnyParentheses(node)) { if (!pc->sc->strict()) return true; @@ -6484,16 +6557,21 @@ Parser::isValidSimpleAssignmentTarget(Node node) if (handler.isPropertyAccess(node)) return true; - return handler.isFunctionCall(node); + + if (behavior == PermitAssignmentToFunctionCalls) { + if (handler.isFunctionCall(node)) + return true; + } + + return false; } template bool -Parser::reportIfArgumentsEvalTarget(Node target) +Parser::reportIfArgumentsEvalTarget(Node nameNode) { - PropertyName* name = handler.maybeName(target); - if (!name) - return true; + PropertyName* name = handler.maybeNameAnyParentheses(nameNode); + MOZ_ASSERT(name, "must only call this function on known names"); const char* chars = (name == context->names().arguments) ? js_arguments_str @@ -6503,7 +6581,7 @@ Parser::reportIfArgumentsEvalTarget(Node target) if (!chars) return true; - if (!report(ParseStrictError, pc->sc->strict(), target, JSMSG_BAD_STRICT_ASSIGN, chars)) + if (!report(ParseStrictError, pc->sc->strict(), nameNode, JSMSG_BAD_STRICT_ASSIGN, chars)) return false; MOZ_ASSERT(!pc->sc->strict(), "in strict mode an error should have been reported"); @@ -6512,15 +6590,21 @@ Parser::reportIfArgumentsEvalTarget(Node target) template bool -Parser::reportIfNotValidSimpleAssignmentTarget(Node target, - AssignmentFlavor flavor) +Parser::reportIfNotValidSimpleAssignmentTarget(Node target, AssignmentFlavor flavor) { - if (isValidSimpleAssignmentTarget(target)) + FunctionCallBehavior behavior = flavor == KeyedDestructuringAssignment + ? ForbidAssignmentToFunctionCalls + : PermitAssignmentToFunctionCalls; + if (isValidSimpleAssignmentTarget(target, behavior)) return true; - // Use a special error if the target is arguments/eval, as a nicety. - if (!reportIfArgumentsEvalTarget(target)) - return false; + if (handler.maybeNameAnyParentheses(target)) { + // Use a special error if the target is arguments/eval. This ensures + // targeting these names is consistently a SyntaxError (which error numbers + // below don't guarantee) while giving us a nicer error message. + if (!reportIfArgumentsEvalTarget(target)) + return false; + } unsigned errnum; const char* extra = nullptr; @@ -6554,17 +6638,19 @@ template bool Parser::checkAndMarkAsIncOperand(Node target, AssignmentFlavor flavor) { + MOZ_ASSERT(flavor == IncrementAssignment || flavor == DecrementAssignment); + // Check. if (!reportIfNotValidSimpleAssignmentTarget(target, flavor)) return false; - // Assignment to arguments/eval is allowed outside strict mode code, - // but it's dodgy. Report a strict warning (error, if werror was set). - if (!reportIfArgumentsEvalTarget(target)) - return false; - // Mark. - if (handler.maybeName(target)) { + if (handler.maybeNameAnyParentheses(target)) { + // Assignment to arguments/eval is allowed outside strict mode code, + // but it's dodgy. Report a strict warning (error, if werror was set). + if (!reportIfArgumentsEvalTarget(target)) + return false; + handler.markAsAssigned(target); } else if (handler.isFunctionCall(target)) { if (!makeSetCall(target, JSMSG_BAD_INCOP_OPERAND)) @@ -6650,7 +6736,7 @@ Parser::unaryExpr(YieldHandling yieldHandling, InvokedPrediction i // Per spec, deleting any unary expression is valid -- it simply // returns true -- except for one case that is illegal in strict mode. - if (handler.maybeName(expr)) { + if (handler.maybeNameAnyParentheses(expr)) { if (!report(ParseStrictError, pc->sc->strict(), expr, JSMSG_DEPRECATED_DELETE_OPERAND)) return null(); pc->sc->setBindingsAccessedDynamically(); @@ -7136,7 +7222,7 @@ Parser::legacyComprehensionTail(ParseNode* bodyExpr, unsigned switch (tt) { case TOK_LB: case TOK_LC: - if (!checkDestructuring(&data, pn3)) + if (!checkDestructuringPattern(&data, pn3)) return null(); break; @@ -7891,7 +7977,7 @@ Parser::memberExpr(YieldHandling yieldHandling, TokenKind tt, bool return null(); JSOp op = JSOP_CALL; - if (PropertyName* name = handler.maybeName(lhs)) { + if (PropertyName* name = handler.maybeNameAnyParentheses(lhs)) { if (tt == TOK_LP && name == context->names().eval) { /* Select JSOP_EVAL and flag pc as heavyweight. */ op = pc->sc->strict() ? JSOP_STRICTEVAL : JSOP_EVAL; @@ -8196,7 +8282,7 @@ Parser::computedPropertyName(YieldHandling yieldHandling, Node lit // Turn off the inDeclDestructuring flag when parsing computed property // names. In short, when parsing 'let {[x + y]: z} = obj;', noteNameUse() // should be called on x and y, but not on z. See the comment on - // Parser<>::checkDestructuring() for details. + // Parser<>::checkDestructuringPattern() for details. bool saved = pc->inDeclDestructuring; pc->inDeclDestructuring = false; Node assignNode = assignExpr(InAllowed, yieldHandling); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 6d2a0a9931..d5cbc5fa65 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -700,11 +700,15 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter ParseNodeKind headKind); bool checkForHeadConstInitializers(Node pn1); - bool isValidSimpleAssignmentTarget(Node node); + enum FunctionCallBehavior { + PermitAssignmentToFunctionCalls, + ForbidAssignmentToFunctionCalls + }; - // Invalid assignment targets are handled differently in different places. - // Select the desired semantics using |flavor|. - bool reportIfArgumentsEvalTarget(Node target); + bool isValidSimpleAssignmentTarget(Node node, + FunctionCallBehavior behavior = ForbidAssignmentToFunctionCalls); + + bool reportIfArgumentsEvalTarget(Node nameNode); bool reportIfNotValidSimpleAssignmentTarget(Node target, AssignmentFlavor flavor); bool checkAndMarkAsIncOperand(Node kid, AssignmentFlavor flavor); @@ -725,13 +729,22 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node propertyList(YieldHandling yieldHandling, PropListType type); Node newPropertyListNode(PropListType type); - bool checkAndPrepareLexical(bool isConst, const TokenPos &errorPos); - Node makeInitializedLexicalBinding(HandlePropertyName name, bool isConst, const TokenPos &pos); + bool checkAndPrepareLexical(bool isConst, const TokenPos& errorPos); + Node makeInitializedLexicalBinding(HandlePropertyName name, bool isConst, const TokenPos& pos); Node newBindingNode(PropertyName* name, bool functionScope, VarContext varContext = HoistVars); - bool checkDestructuring(BindData* data, Node left); - bool checkDestructuringObject(BindData* data, Node objectPattern); + + // Top-level entrypoint into destructuring pattern checking/name-analyzing. + bool checkDestructuringPattern(BindData* data, Node pattern); + + // Recursive methods for checking/name-analyzing subcomponents of a + // destructuring pattern. The array/object methods *must* be passed arrays + // or objects. The name method may be passed anything but will report an + // error if not passed a name. bool checkDestructuringArray(BindData* data, Node arrayPattern); + bool checkDestructuringObject(BindData* data, Node objectPattern); + bool checkDestructuringName(BindData* data, Node expr); + bool bindInitialized(BindData* data, Node pn); bool makeSetCall(Node node, unsigned errnum); Node cloneDestructuringDefault(Node opn); diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 2c8fdbcd3e..2ce8876668 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -46,6 +46,9 @@ class SyntaxParseHandler NodeBreak, NodeThrow, + NodeSuperProperty, + NodeSuperElement, + // This is needed for proper assignment-target handling. ES6 formally // requires function calls *not* pass IsValidSimpleAssignmentTarget, // but at last check there were still sites with |f() = 5| and similar @@ -53,25 +56,48 @@ class SyntaxParseHandler // noticed). NodeFunctionCall, - // Nodes representing names. These *must* be sequential per |isName|. - NodeArgumentsName, - NodeEvalName, - NodeName, + // Nodes representing *parenthesized* IsValidSimpleAssignmentTarget + // nodes. We can't simply treat all such parenthesized nodes + // identically, because in assignment and increment/decrement contexts + // ES6 says that parentheses constitute a syntax error. + // + // var obj = {}; + // var val; + // (val) = 3; (obj.prop) = 4; // okay per ES5's little mind + // [(a)] = [3]; [(obj.prop)] = [4]; // invalid ES6 syntax + // // ...and so on for the other IsValidSimpleAssignmentTarget nodes + // + // We don't know in advance in the current parser when we're parsing + // in a place where name parenthesization changes meaning, so we must + // have multiple node values for these cases. + NodeParenthesizedArgumentsName, + NodeParenthesizedEvalName, + NodeParenthesizedName, NodeDottedProperty, NodeElement, - NodeSuperProperty, - NodeSuperElement, - // Valuable for recognizing potential destructuring patterns. - NodeArray, - NodeObject, + // Destructuring target patterns can't be parenthesized: |([a]) = [3];| + // must be a syntax error. (We can't use NodeGeneric instead of these + // because that would trigger invalid-left-hand-side ReferenceError + // semantics when SyntaxError semantics are desired.) + NodeParenthesizedArray, + NodeParenthesizedObject, // In rare cases a parenthesized |node| doesn't have the same semantics // as |node|. Each such node has a special Node value, and we use a // different Node value to represent the parenthesized form. See also - // isUnparenthesized*(Node), newExprStatement(Node, uint32_t), - // parenthesize(Node), and meaningMightChangeIfParenthesized(Node). + // is{Unp,P}arenthesized*(Node), parenthesize(Node), and the various + // functions that deal in NodeUnparenthesized* below. + + // Nodes representing unparenthesized names. + NodeUnparenthesizedArgumentsName, + NodeUnparenthesizedEvalName, + NodeUnparenthesizedName, + + // Valuable for recognizing potential destructuring patterns. + NodeUnparenthesizedArray, + NodeUnparenthesizedObject, // The directive prologue at the start of a FunctionBody or ScriptBody // is the longest sequence (possibly empty) of string literal @@ -119,18 +145,22 @@ class SyntaxParseHandler return node == NodeFunctionCall; } - bool isDestructuringTarget(Node node) { - return node == NodeArray || node == NodeObject; + static bool isUnparenthesizedDestructuringPattern(Node node) { + return node == NodeUnparenthesizedArray || node == NodeUnparenthesizedObject; } - private: - static bool meaningMightChangeIfParenthesized(Node node) { - return node == NodeUnparenthesizedString || - node == NodeUnparenthesizedCommaExpr || - node == NodeUnparenthesizedYieldExpr || - node == NodeUnparenthesizedAssignment; + static bool isParenthesizedDestructuringPattern(Node node) { + // Technically this isn't a destructuring target at all -- the grammar + // doesn't treat it as such. But we need to know when this happens to + // consider it a SyntaxError rather than an invalid-left-hand-side + // ReferenceError. + return node == NodeParenthesizedArray || node == NodeParenthesizedObject; } + static bool isDestructuringPatternAnyParentheses(Node node) { + return isUnparenthesizedDestructuringPattern(node) || + isParenthesizedDestructuringPattern(node); + } public: SyntaxParseHandler(ExclusiveContext* cx, LifoAlloc& alloc, @@ -147,14 +177,14 @@ class SyntaxParseHandler Node newName(PropertyName* name, uint32_t blockid, const TokenPos& pos, ExclusiveContext* cx) { lastAtom = name; if (name == cx->names().arguments) - return NodeArgumentsName; + return NodeUnparenthesizedArgumentsName; if (name == cx->names().eval) - return NodeEvalName; - return NodeName; + return NodeUnparenthesizedEvalName; + return NodeUnparenthesizedName; } Node newComputedName(Node expr, uint32_t start, uint32_t end) { - return NodeName; + return NodeGeneric; } DefinitionNode newPlaceholder(JSAtom* atom, uint32_t blockid, const TokenPos& pos) { @@ -162,7 +192,7 @@ class SyntaxParseHandler } Node newObjectLiteralPropertyName(JSAtom* atom, const TokenPos& pos) { - return NodeName; + return NodeUnparenthesizedName; } Node newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) { return NodeGeneric; } @@ -231,7 +261,7 @@ class SyntaxParseHandler Node newArrayComprehension(Node body, unsigned blockid, const TokenPos& pos) { return NodeGeneric; } - Node newArrayLiteral(uint32_t begin, unsigned blockid) { return NodeArray; } + Node newArrayLiteral(uint32_t begin, unsigned blockid) { return NodeUnparenthesizedArray; } bool addElision(Node literal, const TokenPos& pos) { return true; } bool addSpreadElement(Node literal, uint32_t begin, Node inner) { return true; } void addArrayElement(Node literal, Node element) { } @@ -239,7 +269,7 @@ class SyntaxParseHandler Node newCall() { return NodeFunctionCall; } Node newTaggedTemplate() { return NodeGeneric; } - Node newObjectLiteral(uint32_t begin) { return NodeObject; } + Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; } Node newClassMethodList(uint32_t begin) { return NodeGeneric; } Node newSuperProperty(PropertyName* prop, const TokenPos& pos) { @@ -368,8 +398,8 @@ class SyntaxParseHandler void addList(Node list, Node kid) { MOZ_ASSERT(list == NodeGeneric || - list == NodeArray || - list == NodeObject || + list == NodeUnparenthesizedArray || + list == NodeUnparenthesizedObject || list == NodeUnparenthesizedCommaExpr || list == NodeHoistableDeclaration || list == NodeFunctionCall); @@ -408,8 +438,30 @@ class SyntaxParseHandler void setFlag(Node pn, unsigned flag) {} void setListFlag(Node pn, unsigned flag) {} MOZ_WARN_UNUSED_RESULT Node parenthesize(Node node) { - if (meaningMightChangeIfParenthesized(node)) + // A number of nodes have different behavior upon parenthesization, but + // only in some circumstances. Convert these nodes to special + // parenthesized forms. + if (node == NodeUnparenthesizedArgumentsName) + return NodeParenthesizedArgumentsName; + if (node == NodeUnparenthesizedEvalName) + return NodeParenthesizedEvalName; + if (node == NodeUnparenthesizedName) + return NodeParenthesizedName; + + if (node == NodeUnparenthesizedArray) + return NodeParenthesizedArray; + if (node == NodeUnparenthesizedObject) + return NodeParenthesizedObject; + + // Other nodes need not be recognizable after parenthesization; convert + // them to a generic node. + if (node == NodeUnparenthesizedString || + node == NodeUnparenthesizedCommaExpr || + node == NodeUnparenthesizedYieldExpr || + node == NodeUnparenthesizedAssignment) + { return NodeGeneric; + } // In all other cases, the parenthesized form of |node| is equivalent // to the unparenthesized form: return |node| unchanged. @@ -421,12 +473,33 @@ class SyntaxParseHandler void setPrologue(Node pn) {} bool isConstant(Node pn) { return false; } - PropertyName* maybeName(Node pn) { - if (pn == NodeName || pn == NodeArgumentsName || pn == NodeEvalName) + + PropertyName* maybeUnparenthesizedName(Node node) { + if (node == NodeUnparenthesizedName || + node == NodeUnparenthesizedArgumentsName || + node == NodeUnparenthesizedEvalName) + { return lastAtom->asPropertyName(); + } return nullptr; } + PropertyName* maybeParenthesizedName(Node node) { + if (node == NodeParenthesizedName || + node == NodeParenthesizedArgumentsName || + node == NodeParenthesizedEvalName) + { + return lastAtom->asPropertyName(); + } + return nullptr; + } + + PropertyName* maybeNameAnyParentheses(Node node) { + if (PropertyName* name = maybeUnparenthesizedName(node)) + return name; + return maybeParenthesizedName(node); + } + PropertyName* maybeDottedProperty(Node node) { // Note: |super.apply(...)| is a special form that calls an "apply" // method retrieved from one value, but using a *different* value as diff --git a/js/src/jit-test/tests/basic/destructuring-default.js b/js/src/jit-test/tests/basic/destructuring-default.js index d7cd552750..545a1f186d 100644 --- a/js/src/jit-test/tests/basic/destructuring-default.js +++ b/js/src/jit-test/tests/basic/destructuring-default.js @@ -49,7 +49,7 @@ testAll(testConst); function testGlobal(pattern, input) { return new Function('input', - '(' + pattern + ') = input;' + + '(' + pattern + ' = input);' + 'return [a, b, c, d, e, f];' )(input); } @@ -58,7 +58,7 @@ testAll(testGlobal); function testClosure(pattern, input) { return new Function('input', 'var rest; (function () {' + - '(' + pattern + ') = input;' + + '(' + pattern + ' = input);' + '})();' + 'return [a, b, c, d, e, f];' )(input); @@ -102,7 +102,7 @@ assertEq(cc, 3); // test that the assignment happens in source order var a = undefined, b = undefined, c = undefined; -({a: a = 1, c: c = 2, b: b = 3}) = { +({a: a = 1, c: c = 2, b: b = 3} = { get a() { assertEq(a, undefined); assertEq(c, undefined); @@ -121,7 +121,7 @@ var a = undefined, b = undefined, c = undefined; assertEq(b, undefined); return undefined; } -}; +}); assertEq(b, 4); assertThrowsInstanceOf(() => { var {a: {a} = null} = {}; }, TypeError); @@ -139,7 +139,7 @@ assertEq(a.y, 2); // defaults are evaluated even if there is no binding var evaled = false; -({a: {} = (evaled = true, {})}) = {}; +({a: {} = (evaled = true, {})} = {}); assertEq(evaled, true); evaled = false; assertThrowsInstanceOf(() => { [[] = (evaled = true, 2)] = [] }, TypeError); diff --git a/js/src/jit-test/tests/basic/destructuring-rest.js b/js/src/jit-test/tests/basic/destructuring-rest.js index 386c9b7fbd..352df41177 100644 --- a/js/src/jit-test/tests/basic/destructuring-rest.js +++ b/js/src/jit-test/tests/basic/destructuring-rest.js @@ -13,16 +13,15 @@ assertThrowsInstanceOf(() => new Function('[...a++] = []'), SyntaxError, 'postfi assertThrowsInstanceOf(() => new Function('[...!a] = []'), SyntaxError, 'unary expression'); assertThrowsInstanceOf(() => new Function('[...a+b] = []'), SyntaxError, 'binary expression'); assertThrowsInstanceOf(() => new Function('var [...a.x] = []'), SyntaxError, 'lvalue expression in declaration'); +assertThrowsInstanceOf(() => new Function('var [...(b)] = []'), SyntaxError); -// XXX: The way the current parser works, certain things, like a trailing comma -// and parenthesis, are lost before we check for destructuring. -// See bug 1041341. Once fixed, please update these assertions +// XXX: The way the current parser works, a trailing comma is lost before we +// check for destructuring. See bug 1041341. Once fixed, please update +// this assertion. assertThrowsInstanceOf(() => assertThrowsInstanceOf(() => new Function('[...b,] = []'), SyntaxError) , Error); -assertThrowsInstanceOf(() => - assertThrowsInstanceOf(() => new Function('var [...(b)] = []'), SyntaxError) - , Error); + var inputArray = [1, 2, 3]; var inputDeep = [1, inputArray]; @@ -81,7 +80,7 @@ testDeclaration(testVar); function testGlobal(pattern, input, binding) { binding = binding || 'rest'; return new Function('input', - '(' + pattern + ') = input;' + + '(' + pattern + ' = input);' + 'return ' + binding )(input); } @@ -91,7 +90,7 @@ function testClosure(pattern, input, binding) { binding = binding || 'rest'; return new Function('input', 'var ' + binding + '; (function () {' + - '(' + pattern + ') = input;' + + '(' + pattern + ' = input);' + '})();' + 'return ' + binding )(input); diff --git a/js/src/jit-test/tests/basic/spread-call-setcall.js b/js/src/jit-test/tests/basic/spread-call-setcall.js index 8afb686c74..5a1100b8e9 100644 --- a/js/src/jit-test/tests/basic/spread-call-setcall.js +++ b/js/src/jit-test/tests/basic/spread-call-setcall.js @@ -24,6 +24,6 @@ check("eval(...['1']) ++"); checkDestructuring("[g(...[])] = []"); checkDestructuring("[a.g(...[])] = []"); checkDestructuring("[eval(...['1'])] = []"); -checkDestructuring("({y: g(...[])}) = 1"); -checkDestructuring("({y: a.g(...[])}) = 1"); -checkDestructuring("({y: eval(...['1'])}) = 1"); +checkDestructuring("({y: g(...[])} = 1)"); +checkDestructuring("({y: a.g(...[])} = 1)"); +checkDestructuring("({y: eval(...['1'])} = 1)"); diff --git a/js/src/js.msg b/js/src/js.msg index 58f27c763a..0886afc492 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -190,6 +190,7 @@ MSG_DEF(JSMSG_BAD_CONST_ASSIGN, 1, JSEXN_SYNTAXERR, "invalid assignment t MSG_DEF(JSMSG_BAD_CONTINUE, 0, JSEXN_SYNTAXERR, "continue must be inside loop") MSG_DEF(JSMSG_BAD_DESTRUCT_ASS, 0, JSEXN_REFERENCEERR, "invalid destructuring assignment operator") MSG_DEF(JSMSG_BAD_DESTRUCT_TARGET, 0, JSEXN_SYNTAXERR, "invalid destructuring target") +MSG_DEF(JSMSG_BAD_DESTRUCT_PARENS, 0, JSEXN_SYNTAXERR, "destructuring patterns in assignments can't be parenthesized") MSG_DEF(JSMSG_BAD_DESTRUCT_DECL, 0, JSEXN_SYNTAXERR, "missing = in destructuring declaration") MSG_DEF(JSMSG_BAD_DUP_ARGS, 0, JSEXN_SYNTAXERR, "duplicate argument names not allowed in this context") MSG_DEF(JSMSG_BAD_FOR_EACH_LOOP, 0, JSEXN_SYNTAXERR, "invalid for each loop") diff --git a/js/src/tests/ecma_6/Expressions/destructuring-pattern-parenthesized.js b/js/src/tests/ecma_6/Expressions/destructuring-pattern-parenthesized.js new file mode 100644 index 0000000000..ca495c8868 --- /dev/null +++ b/js/src/tests/ecma_6/Expressions/destructuring-pattern-parenthesized.js @@ -0,0 +1,140 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +//----------------------------------------------------------------------------- +var BUGNUMBER = 1146136; +var summary = + 'Parenthesized "destructuring patterns" are not usable as destructuring ' + + 'patterns'; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +// Don't pollute the top-level script with eval references. +var savedEval = this[String.fromCharCode(101, 118, 97, 108)]; + +function checkError(code, nonstrictErr, strictErr) +{ + function helper(exec, prefix, err) + { + var fullCode = prefix + code; + try + { + var f = exec(fullCode); + + var error = + "no early error, parsed code <" + fullCode + "> using " + exec.name; + if (typeof f === "function") + { + try + { + f(); + error += ", and the function can be called without error"; + } + catch (e) + { + error +=", and calling the function throws " + e; + } + } + + throw new Error(error); + } + catch (e) + { + assertEq(e instanceof err, true, + "expected " + err.name + ", got " + e + " " + + "for code <" + fullCode + "> when parsed using " + exec.name); + } + } + + helper(Function, "", nonstrictErr); + helper(Function, "'use strict'; ", strictErr); + helper(savedEval, "", nonstrictErr); + helper(savedEval, "'use strict'; ", strictErr); +} + +// Parenthesized destructuring patterns don't trigger grammar refinement, so we +// get the currently-usual ReferenceError for an invalid assignment target, per +// 12.14.1 second bullet. +checkError("var a, b; ([a, b]) = [1, 2];", ReferenceError, ReferenceError); +checkError("var a, b; ({a, b}) = { a: 1, b: 2 };", ReferenceError, ReferenceError); + +// *Nested* parenthesized destructuring patterns, on the other hand, do trigger +// grammar refinement. But subtargets in a destructuring pattern must be +// either object/array literals that match the destructuring pattern refinement +// *or* valid simple assignment targets (or such things with a default, with the +// entire subtarget unparenthesized: |a = 3| is fine, |(a) = 3| is fine for +// destructuring in an expression, |(a = 3)| is forbidden). Parenthesized +// object/array patterns are neither. And so 12.14.5.1 third bullet requires an +// early SyntaxError. +checkError("var a, b; ({ a: ({ b: b }) } = { a: { b: 42 } });", SyntaxError, SyntaxError); +checkError("var a, b; ({ a: { b: (b = 7) } } = { a: {} });", SyntaxError, SyntaxError); +checkError("var a, b; ({ a: ([b]) } = { a: [42] });", SyntaxError, SyntaxError); +checkError("var a, b; [(a = 5)] = [1];", SyntaxError, SyntaxError); +checkError("var a, b; ({ a: (b = 7)} = { b: 1 });", SyntaxError, SyntaxError); + +Function("var a, b; [(a), b] = [1, 2];")(); +Function("var a, b; [(a) = 5, b] = [1, 2];")(); +Function("var a, b; [(arguments), b] = [1, 2];")(); +Function("var a, b; [(arguments) = 5, b] = [1, 2];")(); +Function("var a, b; [(eval), b] = [1, 2];")(); +Function("var a, b; [(eval) = 5, b] = [1, 2];")(); + +var repair = {}, demolition = {}; + +Function("var a, b; [(repair.man), b] = [1, 2];")(); +Function("var a, b; [(demolition['man']) = 'motel', b] = [1, 2];")(); +Function("var a, b; [(demolition['man' + {}]) = 'motel', b] = [1, 2];")(); // evade constant-folding + +function classesEnabled() +{ + try + { + new Function("class B { constructor() { } }; class D extends B { constructor() { super(); } }"); + return true; + } + catch (e if e instanceof SyntaxError) + { + return false; + } +} + +if (classesEnabled()) +{ + Function("var a, b; var obj = { x() { [(super.man), b] = [1, 2]; } };")(); + Function("var a, b; var obj = { x() { [(super[8]) = 'motel', b] = [1, 2]; } };")(); + Function("var a, b; var obj = { x() { [(super[8 + {}]) = 'motel', b] = [1, 2]; } };")(); // evade constant-folding +} + +// As noted above, when the assignment element has an initializer, the +// assignment element must not be parenthesized. +checkError("var a, b; [(repair.man = 17)] = [1];", SyntaxError, SyntaxError); +checkError("var a, b; [(demolition['man'] = 'motel')] = [1, 2];", SyntaxError, SyntaxError); +checkError("var a, b; [(demolition['man' + {}] = 'motel')] = [1];", SyntaxError, SyntaxError); // evade constant-folding +if (classesEnabled()) +{ + checkError("var a, b; var obj = { x() { [(super.man = 5)] = [1]; } };", SyntaxError, SyntaxError); + checkError("var a, b; var obj = { x() { [(super[8] = 'motel')] = [1]; } };", SyntaxError, SyntaxError); + checkError("var a, b; var obj = { x() { [(super[8 + {}] = 'motel')] = [1]; } };", SyntaxError, SyntaxError); // evade constant-folding +} + +// In strict mode, assignment to funcall *immediately* triggers ReferenceError +// before we can recognize this doesn't even match the destructuring grammar to +// begin with. Bleh. :-( Probably they should all be made SyntaxError in the +// specs; see . +checkError("var a, b; [f() = 'ohai', b] = [1, 2];", SyntaxError, ReferenceError); +checkError("var a, b; [(f()) = 'kthxbai', b] = [1, 2];", SyntaxError, ReferenceError); + +Function("var a, b; ({ a: (a), b} = { a: 1, b: 2 });")(); +Function("var a, b; ({ a: (a) = 5, b} = { a: 1, b: 2 });")(); + + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/tests/ecma_6/Statements/arrow-function-in-for-statement-head.js b/js/src/tests/ecma_6/Statements/arrow-function-in-for-statement-head.js new file mode 100644 index 0000000000..4b636d3acf --- /dev/null +++ b/js/src/tests/ecma_6/Statements/arrow-function-in-for-statement-head.js @@ -0,0 +1,33 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/licenses/publicdomain/ + +//----------------------------------------------------------------------------- +var gTestfile = "arrow-function-in-for-statement-head.js"; +var BUGNUMBER = 1163851; +var summary = + "|for (x => 0 in 1;;) break;| must be a syntax error per ES6, not an " + + "elaborate nop"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +try +{ + Function("for (x => 0 in 1;;) break;"); + throw new Error("didn't throw"); +} +catch (e) +{ + assertEq(e instanceof SyntaxError, true, + "expected syntax error, got " + e); +} + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/tests/ecma_6/Statements/browser.js b/js/src/tests/ecma_6/Statements/browser.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/src/tests/ecma_6/Statements/for-in-with-destructuring-assignments.js b/js/src/tests/ecma_6/Statements/for-in-with-destructuring-assignments.js new file mode 100644 index 0000000000..6741c885e5 --- /dev/null +++ b/js/src/tests/ecma_6/Statements/for-in-with-destructuring-assignments.js @@ -0,0 +1,130 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var gTestfile = "for-in-with-destructuring-assignments.js"; +var BUGNUMBER = 1164741; +var summary = "|for (var = ... in ...)| is invalid syntax"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +// This is a total grab-bag of junk originally in tests changed when this +// syntax was removed. Avert your eyes! + +assertThrowsInstanceOf(() => eval(` + for (var [x] = x>>x in [[]<[]]) + { + []; + }`), + SyntaxError); + +/******************************************************************************/ + +assertThrowsInstanceOf(function() { + // Abandon all hope, ye who try to read this. + eval(` + (function () { + for + (var [x] = function(){} + in + (function m(a) { + if (a < 1) { + x; + return; + } + return m(a - 1) + m(a - 2); + })(7)(eval("")) + ) + { + []; + } + }) + `)(); +}, SyntaxError); + +/******************************************************************************/ + +assertThrowsInstanceOf(() => eval(` + for (var [e] = [] in (eval("for (b = 0; b < 6; ++b) gc()"))) {} +`), SyntaxError); + +/******************************************************************************/ + +assertThrowsInstanceOf(() => eval("for (var [ v , c ] = 0 in undefined) { }"), + SyntaxError); + +/******************************************************************************/ + +assertThrowsInstanceOf(() => eval("var b = e; for (var [e] = b in w) c"), + SyntaxError); + +/******************************************************************************/ + +assertThrowsInstanceOf(() => eval("for (var {a: []} = 2 in []) { }"), + SyntaxError); + +/******************************************************************************/ + +assertThrowsInstanceOf(() => eval(`try + { + for (var [,{y}] = 1 in []) {} + } + catch(ex) + { + }`), +SyntaxError); + +/******************************************************************************/ + +assertThrowsInstanceOf(() => eval("for (var [x] = [] in null);"), + SyntaxError); + +/******************************************************************************/ + +assertThrowsInstanceOf(() => eval("for (var [x] = x in y) var x;"), + SyntaxError); + +/******************************************************************************/ + +assertThrowsInstanceOf(() => eval(` + for (var [arguments] = ({ get y(){} }) in y ) (x); +`), +SyntaxError); + +/******************************************************************************/ + +if (typeof evalcx == 'function') { + var src = 'try {\n' + + ' for (var [e] = /x/ in d) {\n' + + ' (function () {});\n' + + ' }\n' + + '} catch (e) {}\n' + + 'try {\n' + + ' let(x = Object.freeze(this, /x/))\n' + + ' e = {}.toString\n' + + ' function y() {}\n' + + '} catch (e) {}'; + + try + { + evalcx(src); + throw new Error("didn't throw"); + } + catch (e) + { + assertEq(e.name === "SyntaxError", true, + "expected invalid syntax, got " + e); + } +} + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/tests/ecma_6/Statements/for-of-var-with-initializer.js b/js/src/tests/ecma_6/Statements/for-of-var-with-initializer.js new file mode 100644 index 0000000000..ae42e153b3 --- /dev/null +++ b/js/src/tests/ecma_6/Statements/for-of-var-with-initializer.js @@ -0,0 +1,32 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var gTestfile = "for-of-var-with-initializer.js"; +var BUGNUMBER = 1164741; +var summary = "Don't assert parsing |for (var x = 3 of 42);|"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +try +{ + Function("for (var x = 3 of 42);"); + throw new Error("didn't throw"); +} +catch (e) +{ + assertEq(e instanceof SyntaxError, true, + "expected syntax error, got: " + e); +} + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/tests/ecma_6/Statements/shell.js b/js/src/tests/ecma_6/Statements/shell.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/src/tests/ecma_6/extensions/for-in-with-assignments.js b/js/src/tests/ecma_6/extensions/for-in-with-assignments.js new file mode 100644 index 0000000000..9a07ce811e --- /dev/null +++ b/js/src/tests/ecma_6/extensions/for-in-with-assignments.js @@ -0,0 +1,74 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +var gTestfile = "for-in-with-assignments.js"; +var BUGNUMBER = 1164741; +var summary = + "Parse |for (var ... = ... in ...)| but execute it as if the assignment " + + "weren't there"; + +print(BUGNUMBER + ": " + summary); + +/************** + * BEGIN TEST * + **************/ + +// This is a total grab-bag of junk originally in tests changed when this +// syntax was removed. Leaving it all in one file will make it easier to +// eventually remove. Avert your eyes! + +if (typeof Reflect !== "undefined") + Reflect.parse("for (var x = 3 in []) { }"); + +/******************************************************************************/ + +function testQ() { + try { + for (var i = 0 in this) throw p; + } catch (e) { + if (i !== 94) + return "what"; + } +} +testQ(); + +/******************************************************************************/ + +function f3(i,o){for(var x=i in o)parseInt(o[x]); return x} +function f4(i,o){with(this)for(var x=i in o)parseInt(o[x]); return x} + +assertEq(f3(42, []), undefined); +assertEq(f3(42, ['first']), "0"); +assertEq(f4(42, []), undefined); +assertEq(f4(42, ['first']), "0"); + +/******************************************************************************/ + +function SetLangHead(l){ + with(p){ + for(var i=0 in x) + if(getElementById("TxtH"+i)!=undefined) + parseInt('huh'); + } +} +x=[0,1,2,3]; +p={getElementById: function (id){parseInt(uneval(this), id); return undefined;}}; +SetLangHead(1); + +/******************************************************************************/ + +(function(){for(var x = arguments in []){} function x(){}})(); + +/******************************************************************************/ + +with (0) + for (var b = 0 in 0); // don't assert in parser + +/******************************************************************************/ + +if (typeof reportCompare === "function") + reportCompare(true, true); + +print("Tests complete"); diff --git a/js/src/tests/js1_7/extensions/destructuring-order.js b/js/src/tests/js1_7/extensions/destructuring-order.js index 1a52c05a79..72bba5b099 100644 --- a/js/src/tests/js1_7/extensions/destructuring-order.js +++ b/js/src/tests/js1_7/extensions/destructuring-order.js @@ -30,7 +30,7 @@ function partialEvalObj() { try { - ({a:a, b:b}) = exceptObj(); + ({a:a, b:b} = exceptObj()); throw "FAILED"; } catch (ex) @@ -114,7 +114,7 @@ function objWithGetters() function partialEvalObj2() { - ({g: g, h: h, i: i, j: j, k: k}) = objWithGetters(); + ({g: g, h: h, i: i, j: j, k: k} = objWithGetters()); } try diff --git a/js/src/tests/js1_7/extensions/regress-368224.js b/js/src/tests/js1_7/extensions/regress-368224.js index a8776050cf..234f26856d 100644 --- a/js/src/tests/js1_7/extensions/regress-368224.js +++ b/js/src/tests/js1_7/extensions/regress-368224.js @@ -20,7 +20,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - ({ x: a }) = {} + ({ x: a } = {}); reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_7/regress/regress-379442.js b/js/src/tests/js1_7/regress/regress-379442.js index 768eab716a..6395232705 100644 --- a/js/src/tests/js1_7/regress/regress-379442.js +++ b/js/src/tests/js1_7/regress/regress-379442.js @@ -22,7 +22,7 @@ function test() reportCompare(expect, actual, summary); - (function () { ({ y: [] }) = {} }); + (function () { ({ y: [] } = {}); }); exitFunc ('test'); } diff --git a/js/src/tests/js1_8_5/reflect-parse/destructuring.js b/js/src/tests/js1_8_5/reflect-parse/destructuring.js index dcf368b4f9..5a0b722a4f 100644 --- a/js/src/tests/js1_8_5/reflect-parse/destructuring.js +++ b/js/src/tests/js1_8_5/reflect-parse/destructuring.js @@ -177,58 +177,6 @@ testAssignmentCombinations(function (n) ("{a" + n + ":x" + n + "," + "b" + n + " assignProp("c" + n, ident("z" + n))]), lit(0)))); - -// destructuring in for-in and for-each-in loop heads - -var axbycz = objPatt([assignProp("a", ident("x")), - assignProp("b", ident("y")), - assignProp("c", ident("z"))]); -var xyz = arrPatt([assignElem("x"), assignElem("y"), assignElem("z")]); - -assertStmt("for (var {a:x,b:y,c:z} in foo);", forInStmt(varDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for (let {a:x,b:y,c:z} in foo);", forInStmt(letDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for ({a:x,b:y,c:z} in foo);", forInStmt(axbycz, ident("foo"), emptyStmt)); -assertStmt("for (var [x,y,z] in foo);", forInStmt(varDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for (let [x,y,z] in foo);", forInStmt(letDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for ([x,y,z] in foo);", forInStmt(xyz, ident("foo"), emptyStmt)); -assertStmt("for (var {a:x,b:y,c:z} of foo);", forOfStmt(varDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for (let {a:x,b:y,c:z} of foo);", forOfStmt(letDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for ({a:x,b:y,c:z} of foo);", forOfStmt(axbycz, ident("foo"), emptyStmt)); -assertStmt("for (var [x,y,z] of foo);", forOfStmt(varDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for (let [x,y,z] of foo);", forOfStmt(letDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for ([x,y,z] of foo);", forOfStmt(xyz, ident("foo"), emptyStmt)); -assertStmt("for each (var {a:x,b:y,c:z} in foo);", forEachInStmt(varDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for each (let {a:x,b:y,c:z} in foo);", forEachInStmt(letDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for each ({a:x,b:y,c:z} in foo);", forEachInStmt(axbycz, ident("foo"), emptyStmt)); -assertStmt("for each (var [x,y,z] in foo);", forEachInStmt(varDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for each (let [x,y,z] in foo);", forEachInStmt(letDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); -assertStmt("for each ([x,y,z] in foo);", forEachInStmt(xyz, ident("foo"), emptyStmt)); -assertError("for (const x in foo);", SyntaxError); -assertError("for (const {a:x,b:y,c:z} in foo);", SyntaxError); -assertError("for (const [x,y,z] in foo);", SyntaxError); -assertError("for (const x of foo);", SyntaxError); -assertError("for (const {a:x,b:y,c:z} of foo);", SyntaxError); -assertError("for (const [x,y,z] of foo);", SyntaxError); -assertError("for each (const x in foo);", SyntaxError); -assertError("for each (const {a:x,b:y,c:z} in foo);", SyntaxError); -assertError("for each (const [x,y,z] in foo);", SyntaxError); - -// destructuring in for-in and for-each-in loop heads with initializers - -assertStmt("for (var {a:x,b:y,c:z} = 22 in foo);", forInStmt(varDecl([{ id: axbycz, init: lit(22) }]), ident("foo"), emptyStmt)); -assertStmt("for (var [x,y,z] = 22 in foo);", forInStmt(varDecl([{ id: xyz, init: lit(22) }]), ident("foo"), emptyStmt)); -assertStmt("for each (var {a:x,b:y,c:z} = 22 in foo);", forEachInStmt(varDecl([{ id: axbycz, init: lit(22) }]), ident("foo"), emptyStmt)); -assertStmt("for each (var [x,y,z] = 22 in foo);", forEachInStmt(varDecl([{ id: xyz, init: lit(22) }]), ident("foo"), emptyStmt)); -assertError("for (x = 22 in foo);", SyntaxError); -assertError("for ({a:x,b:y,c:z} = 22 in foo);", SyntaxError); -assertError("for ([x,y,z] = 22 in foo);", SyntaxError); -assertError("for (const x = 22 in foo);", SyntaxError); -assertError("for (const {a:x,b:y,c:z} = 22 in foo);", SyntaxError); -assertError("for (const [x,y,z] = 22 in foo);", SyntaxError); -assertError("for each (const x = 22 in foo);", SyntaxError); -assertError("for each (const {a:x,b:y,c:z} = 22 in foo);", SyntaxError); -assertError("for each (const [x,y,z] = 22 in foo);", SyntaxError); - } runtest(test); diff --git a/js/src/tests/js1_8_5/reflect-parse/for-loop-destructuring.js b/js/src/tests/js1_8_5/reflect-parse/for-loop-destructuring.js new file mode 100644 index 0000000000..a13571882a --- /dev/null +++ b/js/src/tests/js1_8_5/reflect-parse/for-loop-destructuring.js @@ -0,0 +1,51 @@ +// |reftest| skip-if(!xulRuntime.shell) +function test() { + +// destructuring in for-in and for-each-in loop heads + +var axbycz = objPatt([assignProp("a", ident("x")), + assignProp("b", ident("y")), + assignProp("c", ident("z"))]); +var xyz = arrPatt([assignElem("x"), assignElem("y"), assignElem("z")]); + +assertStmt("for (var {a:x,b:y,c:z} in foo);", forInStmt(varDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for (let {a:x,b:y,c:z} in foo);", forInStmt(letDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for ({a:x,b:y,c:z} in foo);", forInStmt(axbycz, ident("foo"), emptyStmt)); +assertStmt("for (var [x,y,z] in foo);", forInStmt(varDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for (let [x,y,z] in foo);", forInStmt(letDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for ([x,y,z] in foo);", forInStmt(xyz, ident("foo"), emptyStmt)); +assertStmt("for (var {a:x,b:y,c:z} of foo);", forOfStmt(varDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for (let {a:x,b:y,c:z} of foo);", forOfStmt(letDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for ({a:x,b:y,c:z} of foo);", forOfStmt(axbycz, ident("foo"), emptyStmt)); +assertStmt("for (var [x,y,z] of foo);", forOfStmt(varDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for (let [x,y,z] of foo);", forOfStmt(letDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for ([x,y,z] of foo);", forOfStmt(xyz, ident("foo"), emptyStmt)); +assertStmt("for each (var {a:x,b:y,c:z} in foo);", forEachInStmt(varDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for each (let {a:x,b:y,c:z} in foo);", forEachInStmt(letDecl([{ id: axbycz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for each ({a:x,b:y,c:z} in foo);", forEachInStmt(axbycz, ident("foo"), emptyStmt)); +assertStmt("for each (var [x,y,z] in foo);", forEachInStmt(varDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for each (let [x,y,z] in foo);", forEachInStmt(letDecl([{ id: xyz, init: null }]), ident("foo"), emptyStmt)); +assertStmt("for each ([x,y,z] in foo);", forEachInStmt(xyz, ident("foo"), emptyStmt)); +assertError("for (const x in foo);", SyntaxError); +assertError("for (const {a:x,b:y,c:z} in foo);", SyntaxError); +assertError("for (const [x,y,z] in foo);", SyntaxError); +assertError("for (const x of foo);", SyntaxError); +assertError("for (const {a:x,b:y,c:z} of foo);", SyntaxError); +assertError("for (const [x,y,z] of foo);", SyntaxError); +assertError("for each (const x in foo);", SyntaxError); +assertError("for each (const {a:x,b:y,c:z} in foo);", SyntaxError); +assertError("for each (const [x,y,z] in foo);", SyntaxError); + +assertError("for (x = 22 in foo);", SyntaxError);- +assertError("for ({a:x,b:y,c:z} = 22 in foo);", SyntaxError); +assertError("for ([x,y,z] = 22 in foo);", SyntaxError); +assertError("for (const x = 22 in foo);", SyntaxError); +assertError("for (const {a:x,b:y,c:z} = 22 in foo);", SyntaxError); +assertError("for (const [x,y,z] = 22 in foo);", SyntaxError); +assertError("for each (const x = 22 in foo);", SyntaxError); +assertError("for each (const {a:x,b:y,c:z} = 22 in foo);", SyntaxError); +assertError("for each (const [x,y,z] = 22 in foo);", SyntaxError); + +} + +runtest(test); diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 7d361f255d..87242c3d5b 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,11 +29,11 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 288; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 289; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 398, +static_assert(JSErr_Limit == 399, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's " diff --git a/toolkit/components/passwordmgr/test/test_bug_627616.html b/toolkit/components/passwordmgr/test/test_bug_627616.html index 738a6341dc..704c1232b8 100644 --- a/toolkit/components/passwordmgr/test/test_bug_627616.html +++ b/toolkit/components/passwordmgr/test/test_bug_627616.html @@ -105,7 +105,7 @@ Cc["@mozilla.org/network/http-auth-manager;1"].getService(SpecialPowers.Ci.nsIHttpAuthManager).clearAll(); if (pendingTests.length > 0) { ({expectedDialogs: gExpectedDialogs, - test: gCurrentTest}) = pendingTests.shift(); + test: gCurrentTest} = pendingTests.shift()); gCurrentTest.call(this); } else { cleanup(); diff --git a/toolkit/devtools/gcli/commands/screenshot.js b/toolkit/devtools/gcli/commands/screenshot.js index 6be50647fe..a1af9501fc 100644 --- a/toolkit/devtools/gcli/commands/screenshot.js +++ b/toolkit/devtools/gcli/commands/screenshot.js @@ -284,7 +284,7 @@ function createScreenshotData(document, args) { } else if (args.selector) { const lh = new LayoutHelpers(window); - ({ top, left, width, height }) = lh.getRect(args.selector, window); + ({ top, left, width, height } = lh.getRect(args.selector, window)); } else { left = window.scrollX; diff --git a/toolkit/devtools/server/tests/unit/test_forwardingprefix.js b/toolkit/devtools/server/tests/unit/test_forwardingprefix.js index 387f28de4d..2a34cf6dc4 100644 --- a/toolkit/devtools/server/tests/unit/test_forwardingprefix.js +++ b/toolkit/devtools/server/tests/unit/test_forwardingprefix.js @@ -50,7 +50,7 @@ function newConnection(aPrefix) /* Create the main connection for these tests. */ function createMainConnection() { - ({ conn: gMainConnection, transport: gMainTransport }) = newConnection(); + ({ conn: gMainConnection, transport: gMainTransport } = newConnection()); gClient = new DebuggerClient(gMainTransport); gClient.connect((aType, aTraits) => run_next_test()); } diff --git a/toolkit/mozapps/extensions/content/extensions.css b/toolkit/mozapps/extensions/content/extensions.css index 41c1405657..70fe86c78c 100644 --- a/toolkit/mozapps/extensions/content/extensions.css +++ b/toolkit/mozapps/extensions/content/extensions.css @@ -249,7 +249,6 @@ richlistitem:not([selected]) * { .view-pane[type="experiment"] .addon:not([pending="uninstall"]) .pending, .view-pane[type="experiment"] .disabled-postfix, .view-pane[type="experiment"] .update-postfix, -.view-pane[type="experiment"] .version, #detail-view[type="experiment"] .alert-container, #detail-view[type="experiment"] #detail-version, #detail-view[type="experiment"] #detail-creator { diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index a799eeebbe..8e310f22da 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -460,6 +460,31 @@ var gEventManager = { menuSep.hidden = (countMenuItemsBeforeSep == 0); }, false); + + let addonTooltip = document.getElementById("addonitem-tooltip"); + addonTooltip.addEventListener("popupshowing", function() { + let addonItem = addonTooltip.triggerNode; + // The way the test triggers the tooltip the richlistitem is the + // tooltipNode but in normal use it is the anonymous node. This allows + // any case + if (addonItem.localName != "richlistitem") + addonItem = document.getBindingParent(addonItem); + + let tiptext = addonItem.getAttribute("name"); + + if (addonItem.mAddon) { + if (shouldShowVersionNumber(addonItem.mAddon)) { + tiptext += " " + (addonItem.hasAttribute("upgrade") ? addonItem.mManualUpdate.version + : addonItem.mAddon.version); + } + } + else { + if (shouldShowVersionNumber(addonItem.mInstall)) + tiptext += " " + addonItem.mInstall.version; + } + + addonTooltip.label = tiptext; + }, false); }, shutdown: function gEM_shutdown() { @@ -1435,6 +1460,10 @@ function shouldShowVersionNumber(aAddon) { if (!aAddon.version) return false; + // The version number is hidden for experiments. + if (aAddon.type == "experiment") + return false; + // The version number is hidden for lightweight themes. if (aAddon.type == "theme") return !/@personas\.mozilla\.org$/.test(aAddon.id); diff --git a/toolkit/mozapps/extensions/content/extensions.xml b/toolkit/mozapps/extensions/content/extensions.xml index 9c15902b5d..f144ec78f7 100644 --- a/toolkit/mozapps/extensions/content/extensions.xml +++ b/toolkit/mozapps/extensions/content/extensions.xml @@ -853,8 +853,7 @@ - + tooltip="addonitem-tooltip" xbl:inherits="value=name"/> @@ -1039,9 +1038,6 @@ document.getAnonymousElementByAttribute(this, "anonid", "info"); - - document.getAnonymousElementByAttribute(this, "anonid", "version"); - document.getAnonymousElementByAttribute(this, "anonid", "experiment-state"); @@ -1178,11 +1174,6 @@ else this._icon.src = ""; - if (shouldShowVersionNumber(this.mAddon)) - this._version.value = this.mAddon.version; - else - this._version.hidden = true; - if (this.mAddon.description) this._description.value = this.mAddon.description; else @@ -1471,14 +1462,6 @@ ]]> - - - - @@ -1971,8 +1953,7 @@ - - @@ -1994,9 +1975,6 @@ document.getAnonymousElementByAttribute(this, "anonid", "name"); - - document.getAnonymousElementByAttribute(this, "anonid", "version"); - document.getAnonymousElementByAttribute(this, "anonid", "warning"); @@ -2024,14 +2002,6 @@ this._icon.src = this.mAddon.iconURL || (this.mInstall ? this.mInstall.iconURL : ""); this._name.value = this.mAddon.name; - - if (this.mAddon.version) { - this._version.value = this.mAddon.version; - this._version.hidden = false; - } else { - this._version.hidden = true; - } - } else { this._icon.src = this.mInstall.iconURL; // AddonInstall.name isn't always available - fallback to filename @@ -2045,13 +2015,6 @@ url.QueryInterface(Components.interfaces.nsIURL); this._name.value = url.fileName; } - - if (this.mInstall.version) { - this._version.value = this.mInstall.version; - this._version.hidden = false; - } else { - this._version.hidden = true; - } } if (this.mInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) { diff --git a/toolkit/mozapps/extensions/content/extensions.xul b/toolkit/mozapps/extensions/content/extensions.xul index c1a8edc86b..ea2dee317b 100644 --- a/toolkit/mozapps/extensions/content/extensions.xul +++ b/toolkit/mozapps/extensions/content/extensions.xul @@ -71,6 +71,8 @@ label="&cmd.about.label;" accesskey="&cmd.about.accesskey;"/> + +