mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
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)
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<nsIContentSecurityPolicy> mCSP;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<nsNullPrincipal> CreateWithInheritedAttributes(nsIPrincipal *aInheritFrom);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
|
||||
@@ -178,6 +178,10 @@ DOMInterfaces = {
|
||||
'nativeType': 'mozilla::dom::bluetooth::BluetoothGattService',
|
||||
},
|
||||
|
||||
'BluetoothLeDeviceEvent': {
|
||||
'nativeType': 'mozilla::dom::bluetooth::BluetoothLeDeviceEvent',
|
||||
},
|
||||
|
||||
'BluetoothManager': {
|
||||
'nativeType': 'mozilla::dom::bluetooth::BluetoothManager',
|
||||
},
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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<uint8_t>& 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:
|
||||
|
||||
@@ -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<nsIUUIDGenerator> 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,
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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<uint8_t>& 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<char*>(const_cast<uint8_t*>(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))) {
|
||||
|
||||
@@ -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<uint8_t>& 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:
|
||||
|
||||
@@ -85,6 +85,48 @@ struct BluetoothGattClientWriteCharState
|
||||
}
|
||||
};
|
||||
|
||||
struct BluetoothGattClientReadDescState
|
||||
{
|
||||
bool mAuthRetry;
|
||||
nsRefPtr<BluetoothReplyRunnable> mRunnable;
|
||||
|
||||
void Assign(bool aAuthRetry,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
mAuthRetry = aAuthRetry;
|
||||
mRunnable = aRunnable;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
mAuthRetry = false;
|
||||
mRunnable = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct BluetoothGattClientWriteDescState
|
||||
{
|
||||
nsTArray<uint8_t> mWriteValue;
|
||||
bool mAuthRetry;
|
||||
nsRefPtr<BluetoothReplyRunnable> mRunnable;
|
||||
|
||||
void Assign(const nsTArray<uint8_t>& 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<BluetoothReplyRunnable> mStartLeScanRunnable;
|
||||
nsRefPtr<BluetoothReplyRunnable> mConnectRunnable;
|
||||
nsRefPtr<BluetoothReplyRunnable> mDisconnectRunnable;
|
||||
nsRefPtr<BluetoothReplyRunnable> 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<BluetoothGattServiceId> mIncludedServices;
|
||||
nsTArray<BluetoothGattCharAttribute> mCharacteristics;
|
||||
nsTArray<BluetoothGattId> 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<BluetoothVoidReplyRunnable> result =
|
||||
new BluetoothVoidReplyRunnable(nullptr);
|
||||
gattManager->UnregisterClient(mClient->mClientIf, result);
|
||||
}
|
||||
|
||||
DispatchReplyError(mClient->mStartLeScanRunnable,
|
||||
BluetoothValue(mClient->mAppUuid));
|
||||
mClient->mStartLeScanRunnable = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothGattClient> 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<BluetoothVoidReplyRunnable> 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<BluetoothReplyRunnable> mRunnable;
|
||||
int mClientIf;
|
||||
};
|
||||
|
||||
void
|
||||
BluetoothGattManager::StartLeScan(const nsTArray<nsString>& 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<BluetoothGattClient> 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<BluetoothGattClient> 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<BluetoothReplyRunnable> runnable =
|
||||
mClient->mReadDescriptorState.mRunnable;
|
||||
mClient->mReadDescriptorState.Reset();
|
||||
|
||||
// Reject the read descriptor value request
|
||||
DispatchReplyError(runnable,
|
||||
NS_LITERAL_STRING("ReadDescriptorValue failed"));
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothGattClient> 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<BluetoothGattClient> 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<BluetoothReplyRunnable> runnable =
|
||||
mClient->mWriteDescriptorState.mRunnable;
|
||||
mClient->mWriteDescriptorState.Reset();
|
||||
|
||||
// Reject the write descriptor value request
|
||||
DispatchReplyError(runnable,
|
||||
NS_LITERAL_STRING("WriteDescriptorValue failed"));
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothGattClient> mClient;
|
||||
};
|
||||
|
||||
void
|
||||
BluetoothGattManager::WriteDescriptorValue(
|
||||
const nsAString& aAppUuid,
|
||||
const BluetoothGattServiceId& aServiceId,
|
||||
const BluetoothGattId& aCharacteristicId,
|
||||
const BluetoothGattId& aDescriptorId,
|
||||
const nsTArray<uint8_t>& 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<BluetoothGattClient> 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<BluetoothNamedValue> properties;
|
||||
|
||||
nsTArray<uint8_t> advData;
|
||||
advData.AppendElements(aAdvData.mAdvData, sizeof(aAdvData.mAdvData));
|
||||
|
||||
BT_APPEND_NAMED_VALUE(properties, "Address", nsString(aBdAddr));
|
||||
BT_APPEND_NAMED_VALUE(properties, "Rssi", static_cast<int32_t>(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<BluetoothGattClient> client = sClients->ElementAt(index);
|
||||
|
||||
MOZ_ASSERT(client->mReadDescriptorState.mRunnable);
|
||||
nsRefPtr<BluetoothReplyRunnable> 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<uint8_t> 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<BluetoothGattClient> client = sClients->ElementAt(index);
|
||||
|
||||
MOZ_ASSERT(client->mWriteDescriptorState.mRunnable);
|
||||
nsRefPtr<BluetoothReplyRunnable> 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<uint32_t>(aRssi)));
|
||||
BluetoothValue(static_cast<int32_t>(aRssi)));
|
||||
client->mReadRemoteRssiRunnable = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -1629,6 +2081,7 @@ BluetoothGattManager::HandleShutdown()
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mInShutdown = true;
|
||||
sBluetoothGattManager = nullptr;
|
||||
sClients = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -26,7 +26,12 @@ public:
|
||||
static BluetoothGattManager* Get();
|
||||
static void InitGattInterface(BluetoothProfileResultHandler* aRes);
|
||||
static void DeinitGattInterface(BluetoothProfileResultHandler* aRes);
|
||||
virtual ~BluetoothGattManager();
|
||||
|
||||
void StartLeScan(const nsTArray<nsString>& 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<uint8_t>& 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<uint8_t>& 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();
|
||||
|
||||
|
||||
@@ -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<btgatt_transport_t>(0); // silence compiler warning
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
aOut = sTransport[aIn];
|
||||
return NS_OK;
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
// TODO: Support GATT
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -385,6 +385,34 @@ BluetoothServiceBluedroid::StopInternal(BluetoothReplyRunnable* aRunnable)
|
||||
// GATT Client
|
||||
//
|
||||
|
||||
void
|
||||
BluetoothServiceBluedroid::StartLeScanInternal(
|
||||
const nsTArray<nsString>& 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<uint8_t>& 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
|
||||
|
||||
@@ -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<nsString>& 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<uint8_t>& 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<uint8_t>& aValue,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
//
|
||||
// Bluetooth notifications
|
||||
//
|
||||
@@ -350,8 +373,8 @@ public:
|
||||
const nsTArray<nsString>& 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<BluetoothAdapter> mAdapter;
|
||||
};
|
||||
|
||||
class StartLeScanTask final : public BluetoothReplyRunnable
|
||||
{
|
||||
public:
|
||||
StartLeScanTask(BluetoothAdapter* aAdapter, Promise* aPromise,
|
||||
const nsTArray<nsString>& aServiceUuids)
|
||||
: BluetoothReplyRunnable(nullptr, aPromise,
|
||||
NS_LITERAL_STRING("StartLeScan"))
|
||||
, mAdapter(aAdapter)
|
||||
, mServiceUuids(aServiceUuids)
|
||||
{
|
||||
MOZ_ASSERT(aPromise);
|
||||
MOZ_ASSERT(aAdapter);
|
||||
}
|
||||
|
||||
bool
|
||||
ParseSuccessfulReply(JS::MutableHandle<JS::Value> 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<BluetoothDiscoveryHandle> 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<BluetoothAdapter> mAdapter;
|
||||
nsTArray<nsString> 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<JS::Value> aValue) override
|
||||
{
|
||||
mAdapter->RemoveLeScanHandle(mScanUuid);
|
||||
aValue.setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void
|
||||
ReleaseMembers() override
|
||||
{
|
||||
BluetoothReplyRunnable::ReleaseMembers();
|
||||
mAdapter = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothAdapter> 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<Promise>
|
||||
BluetoothAdapter::StartDiscovery(ErrorResult& aRv)
|
||||
{
|
||||
@@ -443,9 +565,7 @@ BluetoothAdapter::StartDiscovery(ErrorResult& aRv)
|
||||
// Return BluetoothDiscoveryHandle in StartDiscoveryTask
|
||||
nsRefPtr<BluetoothReplyRunnable> 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<Promise>
|
||||
BluetoothAdapter::StartLeScan(const nsTArray<nsString>& aServiceUuids,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
|
||||
if (!global) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<Promise> 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<BluetoothReplyRunnable> result =
|
||||
new StartLeScanTask(this, promise, aServiceUuids);
|
||||
bs->StartLeScanInternal(aServiceUuids, result);
|
||||
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
BluetoothAdapter::StopLeScan(BluetoothDiscoveryHandle& aDiscoveryHandle,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
|
||||
if (!global) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<Promise> 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<BluetoothReplyRunnable> 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<BluetoothNamedValue>& values =
|
||||
aValue.get_ArrayOfBluetoothNamedValue();
|
||||
|
||||
int rssi = 0;
|
||||
nsTArray<uint8_t> 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<BluetoothDevice> 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<BluetoothNamedValue>& 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<BluetoothNamedValue>& 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<nsString>& aTypes)
|
||||
{
|
||||
@@ -900,76 +1176,6 @@ BluetoothAdapter::DispatchAttributeEvent(const Sequence<nsString>& 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<BluetoothNamedValue>& 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<BluetoothDevice> 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<BluetoothNamedValue>& 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)
|
||||
|
||||
@@ -99,6 +99,12 @@ public:
|
||||
ErrorResult& aRv);
|
||||
already_AddRefed<Promise> StartDiscovery(ErrorResult& aRv);
|
||||
already_AddRefed<Promise> StopDiscovery(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise> StartLeScan(
|
||||
const nsTArray<nsString>& aServiceUuids, ErrorResult& aRv);
|
||||
already_AddRefed<Promise> StopLeScan(
|
||||
BluetoothDiscoveryHandle& aDiscoveryHandle, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise> Pair(const nsAString& aDeviceAddress,
|
||||
ErrorResult& aRv);
|
||||
already_AddRefed<Promise> 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<BluetoothDiscoveryHandle> 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<nsRefPtr<BluetoothDiscoveryHandle> > 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<nsRefPtr<BluetoothDevice> > mDevices;
|
||||
};
|
||||
|
||||
@@ -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<uint8_t> 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<uint8_t>& 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<JSObject*> aGivenProto)
|
||||
|
||||
@@ -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<uint8_t>& aAdvData);
|
||||
|
||||
/****************************************************************************
|
||||
* Variables
|
||||
***************************************************************************/
|
||||
|
||||
@@ -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<nsString>& 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>
|
||||
BluetoothDiscoveryHandle::Create(
|
||||
nsPIDOMWindow* aWindow,
|
||||
const nsTArray<nsString>& aServiceUuids,
|
||||
const nsAString& aLeScanUuid,
|
||||
BluetoothAdapter* aAdapter)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aWindow);
|
||||
MOZ_ASSERT(aAdapter);
|
||||
|
||||
nsRefPtr<BluetoothDiscoveryHandle> 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<uint8_t>& aScanRecord)
|
||||
{
|
||||
MOZ_ASSERT(aLeDevice);
|
||||
|
||||
nsTArray<nsString> 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<BluetoothLeDeviceEvent> event =
|
||||
BluetoothLeDeviceEvent::Constructor(this,
|
||||
NS_LITERAL_STRING("devicefound"),
|
||||
aLeDevice,
|
||||
aRssi,
|
||||
aScanRecord);
|
||||
DispatchTrustedEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
JSObject*
|
||||
BluetoothDiscoveryHandle::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto)
|
||||
|
||||
@@ -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<BluetoothDiscoveryHandle>
|
||||
Create(nsPIDOMWindow* aWindow);
|
||||
|
||||
static already_AddRefed<BluetoothDiscoveryHandle>
|
||||
Create(nsPIDOMWindow* aWindow,
|
||||
const nsTArray<nsString>& aServiceUuids,
|
||||
const nsAString& aLeScanUuid,
|
||||
BluetoothAdapter* aAdapter);
|
||||
|
||||
void DispatchDeviceEvent(BluetoothDevice* aDevice);
|
||||
|
||||
void DispatchLeDeviceEvent(BluetoothDevice* aLeDevice,
|
||||
int32_t aRssi,
|
||||
nsTArray<uint8_t>& aScanRecord);
|
||||
|
||||
IMPL_EVENT_HANDLER(devicefound);
|
||||
|
||||
void GetLeScanUuid(nsString& aLeScanUuid) const
|
||||
{
|
||||
aLeScanUuid = mLeScanUuid;
|
||||
}
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
virtual void DisconnectFromOwner() override;
|
||||
|
||||
private:
|
||||
BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow);
|
||||
|
||||
BluetoothDiscoveryHandle(nsPIDOMWindow* aWindow,
|
||||
const nsTArray<nsString>& 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<nsString> mServiceUuids;
|
||||
|
||||
/**
|
||||
* The adapter which called startLeScan and created this discovery handle
|
||||
*
|
||||
* If BluetoothDiscoveryHandle is built for classic discovery, this value
|
||||
* should be nullptr.
|
||||
*/
|
||||
nsRefPtr<BluetoothAdapter> mAdapter;
|
||||
};
|
||||
|
||||
END_BLUETOOTH_NAMESPACE
|
||||
|
||||
@@ -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<nsIUUIDGenerator> 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<int32_t>(v.get_uint32_t()));
|
||||
aValue.setInt32(static_cast<int32_t>(v.get_int32_t()));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
***************************************************************************/
|
||||
|
||||
@@ -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<JSObject*> aGivenProto)
|
||||
{
|
||||
return BluetoothLeDeviceEventBinding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
already_AddRefed<BluetoothLeDeviceEvent>
|
||||
BluetoothLeDeviceEvent::Constructor(
|
||||
mozilla::dom::EventTarget* aOwner,
|
||||
const nsAString& aType,
|
||||
BluetoothDevice* const aDevice,
|
||||
const int16_t aRssi,
|
||||
const nsTArray<uint8_t>& aScanRecord)
|
||||
{
|
||||
nsRefPtr<BluetoothLeDeviceEvent> 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>
|
||||
BluetoothLeDeviceEvent::Constructor(
|
||||
const GlobalObject& aGlobal,
|
||||
const nsAString& aType,
|
||||
const BluetoothLeDeviceEventInit& aEventInitDict,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<mozilla::dom::EventTarget> owner =
|
||||
do_QueryInterface(aGlobal.GetAsSupports());
|
||||
|
||||
nsRefPtr<BluetoothLeDeviceEvent> 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<JSObject*> 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;
|
||||
}
|
||||
@@ -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<BluetoothDevice> mDevice;
|
||||
int16_t mRssi;
|
||||
JS::Heap<JSObject*> mScanRecord;
|
||||
|
||||
public:
|
||||
virtual JSObject* WrapObjectInternal(
|
||||
JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
static already_AddRefed<BluetoothLeDeviceEvent>
|
||||
Constructor(EventTarget* aOwner,
|
||||
const nsAString& aType,
|
||||
BluetoothDevice* const aDevice,
|
||||
const int16_t aRssi,
|
||||
const nsTArray<uint8_t>& aScanRecord);
|
||||
|
||||
static already_AddRefed<BluetoothLeDeviceEvent>
|
||||
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<JSObject*> aScanRecord,
|
||||
ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
nsTArray<uint8_t> mRawScanRecord;
|
||||
};
|
||||
|
||||
END_BLUETOOTH_NAMESPACE
|
||||
|
||||
#endif // mozilla_dom_bluetooth_bluetoothledeviceevent_h
|
||||
@@ -45,7 +45,6 @@ protected:
|
||||
mController = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<BluetoothProfileController> mController;
|
||||
};
|
||||
|
||||
|
||||
@@ -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<nsString>& aServiceUuids,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
||||
/**
|
||||
* Set a property for the specified object
|
||||
*
|
||||
@@ -426,6 +436,31 @@ public:
|
||||
const nsTArray<uint8_t>& 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<uint8_t>& aValue,
|
||||
BluetoothReplyRunnable* aRunnable) = 0;
|
||||
|
||||
bool
|
||||
IsEnabled() const
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<nsString>& 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<uint8_t>& aValue,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
SendRequest(aRunnable,
|
||||
GattClientWriteDescriptorValueRequest(nsString(aAppUuid),
|
||||
aServiceId,
|
||||
aCharacteristicId,
|
||||
aDescriptorId,
|
||||
aValue));
|
||||
}
|
||||
|
||||
nsresult
|
||||
BluetoothServiceChildProcess::HandleStartup()
|
||||
{
|
||||
|
||||
@@ -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<nsString>& aServiceUuids,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
|
||||
virtual nsresult
|
||||
SetProperty(BluetoothObjectType aType,
|
||||
const BluetoothNamedValue& aValue,
|
||||
@@ -245,6 +253,23 @@ public:
|
||||
const nsTArray<uint8_t>& 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<uint8_t>& aValue,
|
||||
BluetoothReplyRunnable* aRunnable);
|
||||
|
||||
protected:
|
||||
BluetoothServiceChildProcess();
|
||||
virtual ~BluetoothServiceChildProcess();
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace bluetooth {
|
||||
*/
|
||||
union BluetoothValue
|
||||
{
|
||||
int32_t;
|
||||
uint32_t;
|
||||
nsString;
|
||||
bool;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<nsString>& 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<uint8_t>& aValue,
|
||||
BluetoothReplyRunnable* aRunnable)
|
||||
{
|
||||
}
|
||||
#else
|
||||
// Missing in bluetooth1
|
||||
#endif
|
||||
|
||||
@@ -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<nsString>& 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<uint8_t>& 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<uint8_t>& aValue,
|
||||
BluetoothReplyRunnable* aRunnable) override;
|
||||
#else
|
||||
// Missing in bluetooth1
|
||||
#endif
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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<nsString> 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<nsStringHashKey, nsString> sPairingNameTable;
|
||||
|
||||
static BluetoothInterface* sBtInterface;
|
||||
static nsTArray<nsRefPtr<BluetoothProfileController> > sControllerArray;
|
||||
static InfallibleTArray<BluetoothNamedValue> 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<nsString>());
|
||||
}
|
||||
|
||||
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<BluetoothNamedValue> 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);
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -94,6 +94,12 @@ interface BluetoothAdapter : EventTarget {
|
||||
|
||||
sequence<BluetoothDevice> getPairedDevices();
|
||||
|
||||
[NewObject]
|
||||
Promise<BluetoothDiscoveryHandle> startLeScan(sequence<DOMString> serviceUuids);
|
||||
|
||||
[NewObject]
|
||||
Promise<void> stopLeScan(BluetoothDiscoveryHandle discoveryHandle);
|
||||
|
||||
[NewObject, Throws, AvailableIn=CertifiedApps]
|
||||
DOMRequest getConnectedDevices(unsigned short serviceUuid);
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -661,6 +661,7 @@ if CONFIG['MOZ_B2G_BT']:
|
||||
'BluetoothGattCharacteristic.webidl',
|
||||
'BluetoothGattDescriptor.webidl',
|
||||
'BluetoothGattService.webidl',
|
||||
'BluetoothLeDeviceEvent.webidl',
|
||||
'BluetoothManager2.webidl',
|
||||
'BluetoothPairingHandle.webidl',
|
||||
'BluetoothPairingListener.webidl',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+208
-122
@@ -1130,7 +1130,7 @@ Parser<FullParseHandler>::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 <typename ParseHandler>
|
||||
@@ -3410,7 +3410,65 @@ Parser<FullParseHandler>::bindInitialized(BindData<FullParseHandler>* data, Pars
|
||||
|
||||
template <>
|
||||
bool
|
||||
Parser<FullParseHandler>::checkDestructuring(BindData<FullParseHandler>* data, ParseNode* left);
|
||||
Parser<FullParseHandler>::checkDestructuringName(BindData<FullParseHandler>* 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<FullParseHandler>::checkDestructuringPattern(BindData<FullParseHandler>* data, ParseNode* pattern);
|
||||
|
||||
template <>
|
||||
bool
|
||||
@@ -3420,30 +3478,28 @@ Parser<FullParseHandler>::checkDestructuringObject(BindData<FullParseHandler>* 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<FullParseHandler>::checkDestructuringArray(BindData<FullParseHandler>* 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<FullParseHandler>::checkDestructuringArray(BindData<FullParseHandler>* 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<FullParseHandler>::checkDestructuringArray(BindData<FullParseHandler>* 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<FullParseHandler>::checkDestructuring(BindData<FullParseHandler>* data, ParseNode* left)
|
||||
Parser<FullParseHandler>::checkDestructuringPattern(BindData<FullParseHandler>* 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<SyntaxParseHandler>::checkDestructuring(BindData<SyntaxParseHandler>* data, Node left)
|
||||
Parser<SyntaxParseHandler>::checkDestructuringPattern(BindData<SyntaxParseHandler>* data, Node pattern)
|
||||
{
|
||||
return abortIfSyntaxParser();
|
||||
}
|
||||
@@ -3567,7 +3615,7 @@ Parser<ParseHandler>::destructuringExpr(YieldHandling yieldHandling, BindData<Pa
|
||||
pc->inDeclDestructuring = false;
|
||||
if (!pn)
|
||||
return null();
|
||||
if (!checkDestructuring(data, pn))
|
||||
if (!checkDestructuringPattern(data, pn))
|
||||
return null();
|
||||
return pn;
|
||||
}
|
||||
@@ -3791,7 +3839,7 @@ Parser<ParseHandler>::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<ParseHandler>::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<ParseHandler>::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<FullParseHandler>::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<SyntaxParseHandler>::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<ParseHandler>::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 <typename ParseHandler>
|
||||
bool
|
||||
Parser<ParseHandler>::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<ParseHandler>::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<ParseHandler>::assignExpr(InHandling inHandling, YieldHandling yieldHandl
|
||||
|
||||
template <typename ParseHandler>
|
||||
bool
|
||||
Parser<ParseHandler>::isValidSimpleAssignmentTarget(Node node)
|
||||
Parser<ParseHandler>::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<ParseHandler>::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 <typename ParseHandler>
|
||||
bool
|
||||
Parser<ParseHandler>::reportIfArgumentsEvalTarget(Node target)
|
||||
Parser<ParseHandler>::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<ParseHandler>::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<ParseHandler>::reportIfArgumentsEvalTarget(Node target)
|
||||
|
||||
template <typename ParseHandler>
|
||||
bool
|
||||
Parser<ParseHandler>::reportIfNotValidSimpleAssignmentTarget(Node target,
|
||||
AssignmentFlavor flavor)
|
||||
Parser<ParseHandler>::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 <typename ParseHandler>
|
||||
bool
|
||||
Parser<ParseHandler>::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<ParseHandler>::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<FullParseHandler>::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<ParseHandler>::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<ParseHandler>::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);
|
||||
|
||||
@@ -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<ParseHandler>* data, Node left);
|
||||
bool checkDestructuringObject(BindData<ParseHandler>* data, Node objectPattern);
|
||||
|
||||
// Top-level entrypoint into destructuring pattern checking/name-analyzing.
|
||||
bool checkDestructuringPattern(BindData<ParseHandler>* 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<ParseHandler>* data, Node arrayPattern);
|
||||
bool checkDestructuringObject(BindData<ParseHandler>* data, Node objectPattern);
|
||||
bool checkDestructuringName(BindData<ParseHandler>* data, Node expr);
|
||||
|
||||
bool bindInitialized(BindData<ParseHandler>* data, Node pn);
|
||||
bool makeSetCall(Node node, unsigned errnum);
|
||||
Node cloneDestructuringDefault(Node opn);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)");
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 <https://bugs.ecmascript.org/show_bug.cgi?id=4375>.
|
||||
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");
|
||||
@@ -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");
|
||||
@@ -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 <pat> = ... 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");
|
||||
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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
|
||||
|
||||
@@ -20,7 +20,7 @@ function test()
|
||||
printBugNumber(BUGNUMBER);
|
||||
printStatus (summary);
|
||||
|
||||
({ x: a }) = {}
|
||||
({ x: a } = {});
|
||||
|
||||
reportCompare(expect, actual, summary);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function test()
|
||||
|
||||
reportCompare(expect, actual, summary);
|
||||
|
||||
(function () { ({ y: [] }) = {} });
|
||||
(function () { ({ y: [] } = {}); });
|
||||
|
||||
exitFunc ('test');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
+2
-2
@@ -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 "
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -853,8 +853,7 @@
|
||||
<xul:hbox class="basicinfo-container">
|
||||
<xul:hbox class="name-container">
|
||||
<xul:label anonid="name" class="name" crop="end" flex="1"
|
||||
xbl:inherits="value=name,tooltiptext=name"/>
|
||||
<xul:label anonid="version" class="version"/>
|
||||
tooltip="addonitem-tooltip" xbl:inherits="value=name"/>
|
||||
<xul:label class="nativeIndicator nativeAddon" value="●" tooltiptext="&addon.nativeAddon;"/>
|
||||
<xul:label class="nativeIndicator compatAddon" value="●" tooltiptext="&addon.compatAddon;"/>
|
||||
<xul:label class="disabled-postfix" value="&addon.disabled.postfix;"/>
|
||||
@@ -1039,9 +1038,6 @@
|
||||
document.getAnonymousElementByAttribute(this, "anonid",
|
||||
"info");
|
||||
</field>
|
||||
<field name="_version">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "version");
|
||||
</field>
|
||||
<field name="_experimentState">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "experiment-state");
|
||||
</field>
|
||||
@@ -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 @@
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_updateUpgradeInfo">
|
||||
<body><![CDATA[
|
||||
// Only update the version string if we're displaying the upgrade info
|
||||
if (this.hasAttribute("upgrade") && shouldShowVersionNumber(this.mAddon))
|
||||
this._version.value = this.mManualUpdate.version;
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_fetchReleaseNotes">
|
||||
<parameter name="aURI"/>
|
||||
<body><![CDATA[
|
||||
@@ -1769,7 +1752,6 @@
|
||||
|
||||
this.mManualUpdate = aInstall;
|
||||
this._showStatus("update-available");
|
||||
this._updateUpgradeInfo();
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
@@ -1971,8 +1953,7 @@
|
||||
</xul:vbox>
|
||||
<xul:vbox class="fade name-outer-container" flex="1">
|
||||
<xul:hbox class="name-container">
|
||||
<xul:label anonid="name" class="name" crop="end"/>
|
||||
<xul:label anonid="version" class="version" hidden="true"/>
|
||||
<xul:label anonid="name" class="name" crop="end" tooltip="addonitem-tooltip"/>
|
||||
</xul:hbox>
|
||||
</xul:vbox>
|
||||
<xul:vbox class="install-status-container">
|
||||
@@ -1994,9 +1975,6 @@
|
||||
<field name="_name">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "name");
|
||||
</field>
|
||||
<field name="_version">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "version");
|
||||
</field>
|
||||
<field name="_warning">
|
||||
document.getAnonymousElementByAttribute(this, "anonid", "warning");
|
||||
</field>
|
||||
@@ -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) {
|
||||
|
||||
@@ -71,6 +71,8 @@
|
||||
label="&cmd.about.label;"
|
||||
accesskey="&cmd.about.accesskey;"/>
|
||||
</menupopup>
|
||||
|
||||
<tooltip id="addonitem-tooltip"/>
|
||||
</popupset>
|
||||
|
||||
<!-- global commands - these act on all addons, or affect the addons manager
|
||||
|
||||
@@ -9,9 +9,7 @@ var gManagerWindow;
|
||||
var gCategoryUtilities;
|
||||
var gProvider;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(function test() {
|
||||
gProvider = new MockProvider();
|
||||
|
||||
gProvider.createAddons([{
|
||||
@@ -31,16 +29,9 @@ function test() {
|
||||
version: "789"
|
||||
}]);
|
||||
|
||||
open_manager(null, function(aWindow) {
|
||||
gManagerWindow = aWindow;
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
run_next_test();
|
||||
});
|
||||
}
|
||||
|
||||
function end_test() {
|
||||
close_manager(gManagerWindow, finish);
|
||||
}
|
||||
gManagerWindow = yield open_manager();
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
});
|
||||
|
||||
function get(aId) {
|
||||
return gManagerWindow.document.getElementById(aId);
|
||||
@@ -54,58 +45,54 @@ function open_details(aList, aItem, aCallback) {
|
||||
aList.ensureElementIsVisible(aItem);
|
||||
EventUtils.synthesizeMouseAtCenter(aItem, { clickCount: 1 }, gManagerWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(aItem, { clickCount: 2 }, gManagerWindow);
|
||||
wait_for_view_load(gManagerWindow, aCallback);
|
||||
return new Promise(resolve => wait_for_view_load(gManagerWindow, resolve));
|
||||
}
|
||||
|
||||
function check_addon_has_version(aList, aName, aVersion) {
|
||||
let check_addon_has_version = Task.async(function*(aList, aName, aVersion) {
|
||||
for (let i = 0; i < aList.itemCount; i++) {
|
||||
let item = aList.getItemAtIndex(i);
|
||||
if (get_node(item, "name").value === aName) {
|
||||
ok(true, "Item with correct name found");
|
||||
is(get_node(item, "version").value, aVersion, "Item has correct version");
|
||||
let { version } = yield get_tooltip_info(item);
|
||||
is(version, aVersion, "Item has correct version");
|
||||
return item;
|
||||
}
|
||||
}
|
||||
ok(false, "Item with correct name was not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
add_test(function() {
|
||||
gCategoryUtilities.openType("extension", function() {
|
||||
info("Extension");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
let item = check_addon_has_version(list, "Extension 1", "123");
|
||||
open_details(list, item, function() {
|
||||
is_element_visible(get("detail-version"), "Details view has version visible");
|
||||
is(get("detail-version").value, "123", "Details view has correct version");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
gCategoryUtilities.openType("theme", function() {
|
||||
info("Normal theme");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
let item = check_addon_has_version(list, "Theme 2", "456");
|
||||
open_details(list, item, function() {
|
||||
is_element_visible(get("detail-version"), "Details view has version visible");
|
||||
is(get("detail-version").value, "456", "Details view has correct version");
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
add_task(function*() {
|
||||
yield gCategoryUtilities.openType("extension");
|
||||
info("Extension");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
let item = yield check_addon_has_version(list, "Extension 1", "123");
|
||||
yield open_details(list, item);
|
||||
is_element_visible(get("detail-version"), "Details view has version visible");
|
||||
is(get("detail-version").value, "123", "Details view has correct version");
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
gCategoryUtilities.openType("theme", function() {
|
||||
info("Lightweight theme");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
// See that the version isn't displayed
|
||||
let item = check_addon_has_version(list, "Persona 3", "");
|
||||
open_details(list, item, function() {
|
||||
is_element_hidden(get("detail-version"), "Details view has version hidden");
|
||||
// If the version element is hidden then we don't care about its value
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
add_task(function*() {
|
||||
yield gCategoryUtilities.openType("theme");
|
||||
info("Normal theme");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
let item = yield check_addon_has_version(list, "Theme 2", "456");
|
||||
yield open_details(list, item);
|
||||
is_element_visible(get("detail-version"), "Details view has version visible");
|
||||
is(get("detail-version").value, "456", "Details view has correct version");
|
||||
});
|
||||
|
||||
add_task(function*() {
|
||||
yield gCategoryUtilities.openType("theme");
|
||||
info("Lightweight theme");
|
||||
let list = gManagerWindow.document.getElementById("addon-list");
|
||||
// See that the version isn't displayed
|
||||
let item = yield check_addon_has_version(list, "Persona 3", undefined);
|
||||
yield open_details(list, item);
|
||||
is_element_hidden(get("detail-version"), "Details view has version hidden");
|
||||
// If the version element is hidden then we don't care about its value
|
||||
});
|
||||
|
||||
add_task(function end_test() {
|
||||
close_manager(gManagerWindow, finish);
|
||||
});
|
||||
|
||||
@@ -8,19 +8,12 @@
|
||||
var gManagerWindow;
|
||||
var gCategoryUtilities;
|
||||
|
||||
function test() {
|
||||
add_task(function* test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
open_manager("addons://list/extension", function(aWindow) {
|
||||
gManagerWindow = aWindow;
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
run_next_test();
|
||||
});
|
||||
}
|
||||
|
||||
function end_test() {
|
||||
close_manager(gManagerWindow, finish);
|
||||
}
|
||||
gManagerWindow = yield open_manager("addons://list/extension");
|
||||
gCategoryUtilities = new CategoryUtilities(gManagerWindow);
|
||||
});
|
||||
|
||||
function get_list_item_count() {
|
||||
return get_test_items_in_list(gManagerWindow).length;
|
||||
@@ -34,21 +27,23 @@ function get_class_node(parent, cls) {
|
||||
return parent.ownerDocument.getAnonymousElementByAttribute(parent, "class", cls);
|
||||
}
|
||||
|
||||
function install_addon(aXpi, aCallback) {
|
||||
AddonManager.getInstallForURL(TESTROOT + "addons/" + aXpi + ".xpi",
|
||||
function(aInstall) {
|
||||
aInstall.addListener({
|
||||
onInstallEnded: function(aInstall) {
|
||||
executeSoon(aCallback);
|
||||
}
|
||||
});
|
||||
aInstall.install();
|
||||
}, "application/x-xpinstall");
|
||||
function install_addon(aXpi) {
|
||||
return new Promise(resolve => {
|
||||
AddonManager.getInstallForURL(TESTROOT + "addons/" + aXpi + ".xpi",
|
||||
function(aInstall) {
|
||||
aInstall.addListener({
|
||||
onInstallEnded: function(aInstall) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
aInstall.install();
|
||||
}, "application/x-xpinstall");
|
||||
});
|
||||
}
|
||||
|
||||
function check_addon(aAddon, version) {
|
||||
let check_addon = Task.async(function*(aAddon, aVersion) {
|
||||
is(get_list_item_count(), 1, "Should be one item in the list");
|
||||
is(aAddon.version, version, "Add-on should have the right version");
|
||||
is(aAddon.version, aVersion, "Add-on should have the right version");
|
||||
|
||||
let item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
ok(!!item, "Should see the add-on in the list");
|
||||
@@ -147,34 +142,33 @@ add_test(function() {
|
||||
|
||||
// Install version 1, disable it, click the remove button and then upgrade to
|
||||
// version 2 with the manager open
|
||||
add_test(function() {
|
||||
install_addon("browser_bug596336_1", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
aAddon.userDisabled = true;
|
||||
check_addon(aAddon, "1.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
add_task(function() {
|
||||
yield install_addon("browser_bug596336_1");
|
||||
let [aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
aAddon.userDisabled = true;
|
||||
yield check_addon(aAddon, "1.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
|
||||
let item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
EventUtils.synthesizeMouseAtCenter(get_node(item, "remove-btn"), { }, gManagerWindow);
|
||||
let item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
EventUtils.synthesizeMouseAtCenter(get_node(item, "remove-btn"), { }, gManagerWindow);
|
||||
|
||||
// Force XBL to apply
|
||||
item.clientTop;
|
||||
// Force XBL to apply
|
||||
item.clientTop;
|
||||
|
||||
ok(!!(aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL), "Add-on should be pending uninstall");
|
||||
is_element_visible(get_class_node(item, "pending"), "Pending message should be visible");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
ok(!aAddon.pendingUninstall, "Add-on should not be pending uninstall");
|
||||
is_element_visible(get_class_node(item, "pending"), "Pending message should be visible");
|
||||
|
||||
install_addon("browser_bug596336_2", function() {
|
||||
AddonManager.getAddonByID("addon1@tests.mozilla.org", function(aAddon) {
|
||||
check_addon(aAddon, "2.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
yield install_addon("browser_bug596336_2");
|
||||
[aAddon] = yield promiseAddonsByIDs(["addon1@tests.mozilla.org"]);
|
||||
yield check_addon(aAddon, "2.0");
|
||||
ok(aAddon.userDisabled, "Add-on should be disabled");
|
||||
|
||||
aAddon.uninstall();
|
||||
aAddon.uninstall();
|
||||
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
is(get_list_item_count(), 0, "Should be no items in the list");
|
||||
});
|
||||
|
||||
add_task(function end_test() {
|
||||
close_manager(gManagerWindow, finish);
|
||||
});
|
||||
|
||||
@@ -426,8 +426,8 @@ add_task(function testActivateRealExperiments() {
|
||||
is_element_hidden(el, "warning-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
|
||||
is_element_hidden(el, "pending-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version");
|
||||
is_element_hidden(el, "version should be hidden.");
|
||||
let { version } = yield get_tooltip_info(item);
|
||||
Assert.equal(version, undefined, "version should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
|
||||
is_element_hidden(el, "disabled-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
@@ -459,8 +459,8 @@ add_task(function testActivateRealExperiments() {
|
||||
is_element_hidden(el, "warning-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "pending-container");
|
||||
is_element_hidden(el, "pending-container should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "anonid", "version");
|
||||
is_element_hidden(el, "version should be hidden.");
|
||||
({ version } = yield get_tooltip_info(item));
|
||||
Assert.equal(version, undefined, "version should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "disabled-postfix");
|
||||
is_element_hidden(el, "disabled-postfix should be hidden.");
|
||||
el = item.ownerDocument.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,11 +55,17 @@ add_test(function() {
|
||||
|
||||
|
||||
add_test(function() {
|
||||
let finished = 0;
|
||||
function maybeRunNext() {
|
||||
if (++finished == 2)
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
gAvailableCategory.addEventListener("CategoryBadgeUpdated", function() {
|
||||
gAvailableCategory.removeEventListener("CategoryBadgeUpdated", arguments.callee, false);
|
||||
is(gCategoryUtilities.isVisible(gAvailableCategory), true, "Available Updates category should now be visible");
|
||||
is(gAvailableCategory.badgeCount, 1, "Badge for Available Updates should now be 1");
|
||||
run_next_test();
|
||||
maybeRunNext();
|
||||
}, false);
|
||||
|
||||
gCategoryUtilities.openType("extension", function() {
|
||||
@@ -71,7 +77,10 @@ add_test(function() {
|
||||
}]);
|
||||
|
||||
var item = get_addon_element(gManagerWindow, "addon2@tests.mozilla.org");
|
||||
is(item._version.value, "1.0", "Should still show the old version in the normal list");
|
||||
get_tooltip_info(item).then(({ version }) => {
|
||||
is(version, "1.0", "Should still show the old version in the tooltip");
|
||||
maybeRunNext();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,66 +101,61 @@ add_test(function() {
|
||||
var item = list.firstChild;
|
||||
is(item.mAddon.id, "addon2@tests.mozilla.org", "Update item should be for the manually updating addon");
|
||||
|
||||
// for manual update items, update-related properties are updated asynchronously,
|
||||
// so we poll for one of the expected changes to know when its done
|
||||
function waitForAsyncInit() {
|
||||
if (item._version.value == "1.1") {
|
||||
run_next_test();
|
||||
return;
|
||||
}
|
||||
info("Update item not initialized yet, checking again in 100ms");
|
||||
setTimeout(waitForAsyncInit, 100);
|
||||
}
|
||||
waitForAsyncInit();
|
||||
// The item in the list will be checking for update information asynchronously
|
||||
// so we have to wait for it to complete. Doing the same async request should
|
||||
// make our callback be called later.
|
||||
AddonManager.getAllInstalls(run_next_test);
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
var list = gManagerWindow.document.getElementById("updates-list");
|
||||
var item = list.firstChild;
|
||||
is(item._version.value, "1.1", "Update item should have version number of the update");
|
||||
var postfix = gManagerWindow.document.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
is_element_visible(postfix, "'Update' postfix should be visible");
|
||||
is_element_visible(item._updateAvailable, "");
|
||||
is_element_visible(item._relNotesToggle, "Release notes toggle should be visible");
|
||||
is_element_hidden(item._warning, "Incompatible warning should be hidden");
|
||||
is_element_hidden(item._error, "Blocklist error should be hidden");
|
||||
get_tooltip_info(item).then(({ version }) => {
|
||||
is(version, "1.1", "Update item should have version number of the update");
|
||||
var postfix = gManagerWindow.document.getAnonymousElementByAttribute(item, "class", "update-postfix");
|
||||
is_element_visible(postfix, "'Update' postfix should be visible");
|
||||
is_element_visible(item._updateAvailable, "");
|
||||
is_element_visible(item._relNotesToggle, "Release notes toggle should be visible");
|
||||
is_element_hidden(item._warning, "Incompatible warning should be hidden");
|
||||
is_element_hidden(item._error, "Blocklist error should be hidden");
|
||||
|
||||
info("Opening release notes");
|
||||
item.addEventListener("RelNotesToggle", function() {
|
||||
item.removeEventListener("RelNotesToggle", arguments.callee, false);
|
||||
info("Release notes now open");
|
||||
|
||||
is_element_hidden(item._relNotesLoading, "Release notes loading message should be hidden");
|
||||
is_element_visible(item._relNotesError, "Release notes error message should be visible");
|
||||
is(item._relNotes.childElementCount, 0, "Release notes should be empty");
|
||||
|
||||
info("Closing release notes");
|
||||
info("Opening release notes");
|
||||
item.addEventListener("RelNotesToggle", function() {
|
||||
item.removeEventListener("RelNotesToggle", arguments.callee, false);
|
||||
info("Release notes now closed");
|
||||
info("Setting Release notes URI to something that should load");
|
||||
gProvider.installs[0].releaseNotesURI = Services.io.newURI(TESTROOT + "releaseNotes.xhtml", null, null)
|
||||
info("Release notes now open");
|
||||
|
||||
info("Re-opening release notes");
|
||||
is_element_hidden(item._relNotesLoading, "Release notes loading message should be hidden");
|
||||
is_element_visible(item._relNotesError, "Release notes error message should be visible");
|
||||
is(item._relNotes.childElementCount, 0, "Release notes should be empty");
|
||||
|
||||
info("Closing release notes");
|
||||
item.addEventListener("RelNotesToggle", function() {
|
||||
item.removeEventListener("RelNotesToggle", arguments.callee, false);
|
||||
info("Release notes now open");
|
||||
info("Release notes now closed");
|
||||
info("Setting Release notes URI to something that should load");
|
||||
gProvider.installs[0].releaseNotesURI = Services.io.newURI(TESTROOT + "releaseNotes.xhtml", null, null)
|
||||
|
||||
is_element_hidden(item._relNotesLoading, "Release notes loading message should be hidden");
|
||||
is_element_hidden(item._relNotesError, "Release notes error message should be hidden");
|
||||
isnot(item._relNotes.childElementCount, 0, "Release notes should have been inserted into container");
|
||||
run_next_test();
|
||||
info("Re-opening release notes");
|
||||
item.addEventListener("RelNotesToggle", function() {
|
||||
item.removeEventListener("RelNotesToggle", arguments.callee, false);
|
||||
info("Release notes now open");
|
||||
|
||||
is_element_hidden(item._relNotesLoading, "Release notes loading message should be hidden");
|
||||
is_element_hidden(item._relNotesError, "Release notes error message should be hidden");
|
||||
isnot(item._relNotes.childElementCount, 0, "Release notes should have been inserted into container");
|
||||
run_next_test();
|
||||
|
||||
}, false);
|
||||
EventUtils.synthesizeMouseAtCenter(item._relNotesToggle, { }, gManagerWindow);
|
||||
is_element_visible(item._relNotesLoading, "Release notes loading message should be visible");
|
||||
|
||||
}, false);
|
||||
EventUtils.synthesizeMouseAtCenter(item._relNotesToggle, { }, gManagerWindow);
|
||||
is_element_visible(item._relNotesLoading, "Release notes loading message should be visible");
|
||||
|
||||
}, false);
|
||||
EventUtils.synthesizeMouseAtCenter(item._relNotesToggle, { }, gManagerWindow);
|
||||
|
||||
}, false);
|
||||
EventUtils.synthesizeMouseAtCenter(item._relNotesToggle, { }, gManagerWindow);
|
||||
is_element_visible(item._relNotesLoading, "Release notes loading message should be visible");
|
||||
is_element_visible(item._relNotesLoading, "Release notes loading message should be visible");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -49,16 +49,20 @@ add_test(function() {
|
||||
gProvider.installs[0]._addonToInstall = newAddon;
|
||||
|
||||
var item = get_addon_element(gManagerWindow, "addon1@tests.mozilla.org");
|
||||
is(item._version.value, "1.0", "Should still show the old version in the normal list");
|
||||
var name = gManagerWindow.document.getAnonymousElementByAttribute(item, "anonid", "name");
|
||||
is(name.value, "manually updating addon", "Should show the old name in the list");
|
||||
var update = gManagerWindow.document.getAnonymousElementByAttribute(item, "anonid", "update-btn");
|
||||
is_element_visible(update, "Update button should be visible");
|
||||
get_tooltip_info(item).then(({ name, version }) => {
|
||||
is(name, "manually updating addon", "Should show the old name in the tooltip");
|
||||
is(version, "1.0", "Should still show the old version in the tooltip");
|
||||
|
||||
item = get_addon_element(gManagerWindow, "addon2@tests.mozilla.org");
|
||||
is(item, null, "Should not show the new version in the list");
|
||||
var update = gManagerWindow.document.getAnonymousElementByAttribute(item, "anonid", "update-btn");
|
||||
is_element_visible(update, "Update button should be visible");
|
||||
|
||||
run_next_test();
|
||||
item = get_addon_element(gManagerWindow, "addon2@tests.mozilla.org");
|
||||
is(item, null, "Should not show the new version in the list");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -223,6 +223,45 @@ function run_next_test() {
|
||||
executeSoon(() => log_exceptions(test));
|
||||
}
|
||||
|
||||
let get_tooltip_info = Task.async(function*(addon) {
|
||||
let managerWindow = addon.ownerDocument.defaultView;
|
||||
|
||||
// The popup code uses a triggering event's target to set the
|
||||
// document.tooltipNode property.
|
||||
let nameNode = addon.ownerDocument.getAnonymousElementByAttribute(addon, "anonid", "name");
|
||||
let event = new managerWindow.CustomEvent("TriggerEvent");
|
||||
nameNode.dispatchEvent(event);
|
||||
|
||||
let tooltip = managerWindow.document.getElementById("addonitem-tooltip");
|
||||
|
||||
let promise = BrowserTestUtils.waitForEvent(tooltip, "popupshown");
|
||||
tooltip.openPopup(nameNode, "after_start", 0, 0, false, false, event);
|
||||
yield promise;
|
||||
|
||||
let tiptext = tooltip.label;
|
||||
|
||||
promise = BrowserTestUtils.waitForEvent(tooltip, "popuphidden");
|
||||
tooltip.hidePopup();
|
||||
yield promise;
|
||||
|
||||
let expectedName = addon.getAttribute("name");
|
||||
ok(tiptext.substring(0, expectedName.length), expectedName,
|
||||
"Tooltip should always start with the expected name");
|
||||
|
||||
if (expectedName.length == tiptext.length) {
|
||||
return {
|
||||
name: tiptext,
|
||||
version: undefined
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
name: tiptext.substring(0, expectedName.length),
|
||||
version: tiptext.substring(expectedName.length + 1)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
function get_addon_file_url(aFilename) {
|
||||
try {
|
||||
var cr = Cc["@mozilla.org/chrome/chrome-registry;1"].
|
||||
@@ -485,6 +524,11 @@ function is_element_hidden(aElement, aMsg) {
|
||||
ok(is_hidden(aElement), aMsg || (aElement + " should be hidden"));
|
||||
}
|
||||
|
||||
function promiseAddonsByIDs(aIDs) {
|
||||
return new Promise(resolve => {
|
||||
AddonManager.getAddonsByIDs(aIDs, resolve);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Install an add-on and call a callback when complete.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user