From 9479dc0adcab6d97b7c32766e28f37d963a6aa56 Mon Sep 17 00:00:00 2001 From: Chengwei Hsieh Date: Mon, 4 Aug 2025 15:13:59 -0700 Subject: [PATCH 1/4] Algorithms for bluetooth.descriptor commands --- index.bs | 181 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 154 insertions(+), 27 deletions(-) diff --git a/index.bs b/index.bs index 8e15a42..44d41d4 100644 --- a/index.bs +++ b/index.bs @@ -4102,6 +4102,30 @@ slots described in the following table: has been removed or otherwise invalidated. + + \[[automatedDescriptorReadResponse]] + "not-expected" + + The simulated GATT descriptor response code for a GATT descriptor + read attempt. + + + + \[[automatedDescriptorReadResponseData]] + Empty byte sequence + + The simulated GATT descriptor response data for a GATT descriptor + read attempt. + + + + \[[automatedDescriptorWriteResponse]] + "not-expected" + + The simulated GATT descriptor response code for a GATT descriptor + write attempt. + +
@@ -4146,18 +4170,40 @@ readValue() method, when invoked, MUST run the following steps: "{{InvalidStateError}}" {{DOMException}}. 1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=] |promise| and run the following steps [=in parallel=]: - 1. If the UA is currently using the Bluetooth system, it MAY [=queue a - global task=] on the [=Bluetooth task source=] given |global| to - [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and - abort these steps. + 1. If |global|'s [=Window/navigable=]'s + [=navigable/top-level traversable=]'s simulated Bluetooth adapter + is not empty, run the following steps: + 1. If [=this=].{{[[automatedDescriptorReadResponse]]}} is not `"not-expected"`, + [=queue a global task=] on the [=Bluetooth task source=] given |global| to + [=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort + these steps. + 1. [=Trigger a simulated descriptor event=] given |global|'s + [=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}}, + |descriptor|, and `read`. + 1. Set [=this=].{{[[automatedDescriptorReadResponse]]}} to `"expected"`, + and wait for it to change. + 1. Let |response| be [=this=].{{[[automatedDescriptorReadResponse]]}}. + 1. Set [=this=].{{[[automatedDescriptorReadResponse]]}} to `"not-expected"`. + 1. If |response| is not `0`, do the following sub-steps: + 1. [=Queue a global task=] on the [=Bluetooth task source=] given + |global| to [=reject=] |promise| with a "{{NetworkError}}" + {{DOMException}} and abort these steps. + 1. Otherwise, let |buffer| be a [=new=] {{ArrayBuffer}} containing + [=this=].{{[[automatedDescriptorReadResponseData]]}}. + 1. Otherwise, run the following steps: + 1. If the UA is currently using the Bluetooth system, it MAY [=queue a + global task=] on the [=Bluetooth task source=] given |global| to + [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and + abort these steps. - Issue(188): Implementations may be able to avoid this {{NetworkError}}, - but for now sites need to serialize their use of this API - and/or give the user a way to retry failed operations. - 1. Use either the [=Read Characteristic Descriptors=] or the [=Read Long - Characteristic Descriptors=] sub-procedure to retrieve the value of - |descriptor|. Handle errors as described in - . + Issue(188): Implementations may be able to avoid this {{NetworkError}}, + but for now sites need to serialize their use of this API + and/or give the user a way to retry failed operations. + 1. Use either the [=Read Characteristic Descriptors=] or the [=Read Long + Characteristic Descriptors=] sub-procedure to retrieve the value of + |descriptor| and let |buffer| be a [=new=] {{ArrayBuffer}} holding the + retrieved value. Handle errors as described in + . 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps: 1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=] @@ -4165,8 +4211,7 @@ readValue() method, when invoked, MUST run the following steps: these steps. 1. If the sub-procedure above returned an error, [=reject=] |promise| with that error and abort these steps. - 1. Let |buffer| be a [=new=] {{ArrayBuffer}} holding the retrieved - value, and assign a [=new=] {{DataView}} created with |buffer| to + 1. Assign a [=new=] {{DataView}} created with |buffer| to [=this=].{{BluetoothRemoteGATTDescriptor/value}}. 1. [=Resolve=] |promise| with [=this=].{{BluetoothRemoteGATTDescriptor/value}}. @@ -4194,18 +4239,37 @@ following steps: "{{InvalidStateError}}" {{DOMException}}. 1. Return a |gatt|-[=connection-checking wrapper=] around [=a new promise=] |promise| and run the following steps [=in parallel=]. - 1. If the UA is currently using the Bluetooth system, it MAY [=queue a - global task=] on the [=Bluetooth task source=] given |global| to - [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and - abort these steps. + 1. If |global|'s [=Window/navigable=]'s + [=navigable/top-level traversable=]'s simulated Bluetooth adapter + is not empty, run the following steps: + 1. If [=this=].{{[[automatedDescriptorWriteResponse]]}} is not `"not-expected"`, + [=queue a global task=] on the [=Bluetooth task source=] given |global| to + [=reject=] |promise| with a "{{InvalidStateError}}" {{DOMException}} and abort + these steps. + 1. [=Trigger a simulated descriptor event=] given |global|'s + [=Window/navigable=], [=this=].{{BluetoothRemoteGATTServer/device}}, + |descriptor|, `write`, and |bytes|. + 1. Set [=this=].{{[[automatedDescriptorWriteResponse]]}} to `"expected"`, + and wait for it to change. + 1. Let |response| be [=this=].{{[[automatedDescriptorWriteResponse]]}}. + 1. Set [=this=].{{[[automatedDescriptorWriteResponse]]}} to `"not-expected"`. + 1. If |response| is not `0`, do the following sub-steps: + 1. [=Queue a global task=] on the [=Bluetooth task source=] given + |global| to [=reject=] |promise| with a "{{NetworkError}}" + {{DOMException}} and abort these steps. + 1. Otherwise, run the following steps: + 1. If the UA is currently using the Bluetooth system, it MAY [=queue a + global task=] on the [=Bluetooth task source=] given |global| to + [=reject=] |promise| with a "{{NetworkError}}" {{DOMException}} and + abort these steps. - Issue(188): Implementations may be able to avoid this {{NetworkError}}, - but for now sites need to serialize their use of this API - and/or give the user a way to retry failed operations. - 1. Use either the [=Write Characteristic Descriptors=] or the [=Write Long - Characteristic Descriptors=] sub-procedure to write |bytes| to - |descriptor|. Handle errors as described in - . + Issue(188): Implementations may be able to avoid this {{NetworkError}}, + but for now sites need to serialize their use of this API + and/or give the user a way to retry failed operations. + 1. Use either the [=Write Characteristic Descriptors=] or the [=Write Long + Characteristic Descriptors=] sub-procedure to write |bytes| to + |descriptor|. Handle errors as described in + . 1. [=Queue a global task=] on the [=Bluetooth task source=] given |global| to perform the following steps: 1. If |promise| is not in |gatt|.{{[[activeAlgorithms]]}}, [=reject=] @@ -5139,11 +5203,15 @@ is an ordered map of Bluetooth UUID strings to simulated GATT c A simulated GATT characteristic is a software defined [=Characteristic=] that belongs to a simulated GATT service, has a property of UUID, a property of Characteristic Properties, -and is known-present in the Bluetooth cache. +is known-present in the Bluetooth cache, and has a simulated GATT descriptor mapping, which +is an ordered map of Bluetooth UUID strings to simulated GATT descriptors. Simulated GATT characteristic properties are software defined [=Characteristic Properties=] that belong to a simulated GATT characteristic and are known-present in the Bluetooth cache. +A simulated GATT descriptor is a software defined [=Descriptor=] that belongs to a +simulated GATT characteristic, has a property of UUID, and is known-present in the Bluetooth cache. + Issue: CDDL snippetes use the "text" type instead of "browsingContext.BrowsingContext" to allow indepedent programmatic processing of CDDL snippets. Currently, other modules cannot be @@ -6025,7 +6093,46 @@ bluetooth.SimulateDescriptorParameters = {
-Issue: TODO: Finish the algorithm of bluetooth.simulateDescriptor. +1. Let |contextId| be |params|[`"context"`]. +1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. +1. Let |deviceAddress| be |params|[`"address"`]. +1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. +1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. +1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. +1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. +1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. +1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing + |simulatedDevice| inside |navigable|'s active window's associated Navigator's + [=associated Bluetooth=]. +1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping. +1. Let |serviceUuid| be |params|[`"serviceUuid"`]. +1. If |serviceMapping|[|serviceUuid|] [=map/exists=], let |simulatedService| be |serviceMapping|[|serviceUuid|]. +1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. +1. Let |characteristicMapping| be |simulatedService|'s simulated GATT characteristic mapping. +1. Let |characteristicUuid| be |params|[`"characteristicUuid"`]. +1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], let |simulatedCharacteristic| be + |characteristicMapping|[|characteristicUuid|]. +1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. +1. Let |descriptorMapping| be |simulatedCharacteristic|'s simulated GATT descriptor mapping. +1. Let |descriptorUuid| be |params|[`"descriptorUuid"`]. +1. If |params|[`"type"`] is `"add"`: + 1. If |descriptorMapping|[|descriptorUuid|] [=map/exists=], return [=error=] with + [=error code=] [=invalid element state=]. + 1. Let |simulatedGattDescriptor| be a new simulated GATT descriptor. + 1. Set |simulatedGattDescriptor|'s UUID to |descriptorUuid|. + 1. Set |descriptorMapping|[|descriptorUuid|] to |simulatedGattDescriptor|. + 1. Create a BluetoothRemoteGATTDescriptor representing |simulatedGattDescriptor| + and add a mapping from |simulatedGattDescriptor| to the resulting {{Promise}} in + |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. + 1. Return [=success=] with data `null`. +1. Else if |params|[`"type"`] is `"remove"`: + 1. If |descriptorMapping|[|descriptorUuid|] [=map/exists=], let |simulatedGattDescriptor| + be |descriptorMapping|[|descriptorUuid|]. + 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. + 1. Remove |simulatedGattDescriptor| from |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. + 1. Remove |descriptorUuid| from |descriptorMapping|. + 1. Return [=success=] with data `null`. +1. Return [=error=] with [=error code=] [=invalid argument=].
@@ -6243,7 +6350,27 @@ bluetooth.DescriptorEventGeneratedParameters = {
-Issue: TODO: Finish the algorithm of bluetooth.descriptorEventGenerated. +To trigger a simulated descriptor event given a [=navigable=] |navigable|, a {{BluetoothDevice}} |device|, a +simulated GATT descriptor |descriptor|, string |type|, and an optional byte sequence |bytes|: + +1. Let |navigableId| be |navigable|'s [=navigable id=]. +1. Let |params| be a [=map=] matching the bluetooth.DescriptorEventGeneratedParameters production and run + the following steps: + 1. Set |params|[`"context"`] to |navigableId|. + 1. Set |params|[`"address"`] to |device|.{{[[representedDevice]]}}'s address. + 1. Let |characteristic| be the simulated GATT characteristic containing |descriptor|. + 1. Let |service| be the simulated GATT service containing |characteristic|. + 1. Set |params|[`"serviceUuid"`] to |service|'s UUID. + 1. Set |params|[`"characteristicUuid"`] to |characteristic|'s UUID. + 1. Set |params|[`"descriptorUuid"`] to |descriptor|'s UUID. + 1. Set |params|[`"type"`] to |type|. + 1. If |type| is `write`, set |params|[`"data"`] to a [=new=] {{Uint8Array}} wrapping a [=new=] {{ArrayBuffer}} containing |bytes|. +1. Let |body| be a [=map=] matching the bluetooth.DescriptorEventGenerated production, with the + params field set to |params|. +1. Let |relatedNavigables| be a [=/set=] containing |navigable|. +1. For each |session| in the [=set of sessions for which an event is enabled=] given + "bluetooth.descriptorEventGenerated" and |relatedNavigables|: + 1. [=Emit an event=] with |session| and |body|.
From 4014bedadd6eb05317bf3dd62999e35ba4235ea3 Mon Sep 17 00:00:00 2001 From: Chengwei Hsieh Date: Mon, 4 Aug 2025 15:42:30 -0700 Subject: [PATCH 2/4] Finish algorithm for bluetooth.simulateDescriptorResponse --- index.bs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index 44d41d4..bb6e216 100644 --- a/index.bs +++ b/index.bs @@ -6194,7 +6194,44 @@ bluetooth.SimulateDescriptorResponseParameters = {
-Issue: TODO: Finish the algorithm of bluetooth.simulateDescriptorResponse. +1. Let |contextId| be |params|[`"context"`]. +1. Let |navigable| be the result of [=trying=] to [=get a navigable=] with |contextId|. +1. Let |deviceAddress| be |params|[`"address"`]. +1. Let |simulatedBluetoothAdapter| be |navigable|'s simulated Bluetooth adapter. +1. If |simulatedBluetoothAdapter| is empty, return [=error=] with [=error code=] [=invalid argument=]. +1. Let |deviceMapping| be |simulatedBluetoothAdapter|'s simulated Bluetooth device mapping. +1. If |deviceMapping|[|deviceAddress|] [=map/exists=], let |simulatedDevice| be |deviceMapping|[|deviceAddress|]. + Otherwise, return [=error=] with [=error code=] [=invalid argument=]. +1. Let |serviceMapping| be |simulatedDevice|'s simulated GATT service mapping. +1. Let |serviceUuid| be |params|[`"serviceUuid"`]. +1. If |serviceMapping|[|serviceUuid|] [=map/exists=], let |simulatedService| be |serviceMapping|[|serviceUuid|]. +1. Otherwise, return [=error=] with [=error code=] [=invalid argument=]. +1. Let |characteristicMapping| be |simulatedService|'s simulated GATT characteristic mapping. +1. Let |characteristicUuid| be |params|[`"characteristicUuid"`]. +1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], let |simulatedCharacteristic| + be |characteristicMapping|[|characteristicUuid|]. +1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. +1. Let |descriptorMapping| be |simulatedCharacteristic|'s simulated GATT descriptor mapping. +1. Let |descriptorUuid| be |params|[`"descriptorUuid"`]. +1. If |descriptorMapping|[|descriptorUuid|] [=map/exists=], let |simulatedDescriptor| + be |descriptorMapping|[|descriptorUuid|]. +1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. +1. Let |simulatedDeviceInstance| be the result of get the BluetoothDevice representing + |simulatedDevice| inside |navigable|'s active window's associated Navigator's + [=associated Bluetooth=]. +1. Let |promise| be |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}[|simulatedDescriptor|]. +1. Upon fulfillment of |promise| with |descriptor|, run the following steps: + 1. If |params|[`"type"`] is `read`, run the following steps: + 1. If |descriptor|.{{[[automatedDescriptorReadResponse]]}} is `expected`, + set |descriptor|.{{[[automatedDescriptorReadResponse]]}} to |params|[`"code"`] and + |descriptor|.{{[[automatedDescriptorReadResponseData]]}} to [=a copy of the bytes held=] + by |params|[`"data"`]. + 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. + 1. If |params|[`"type"`] is `write`, run the following steps: + 1. If |characteristic|.{{[[automatedDescriptorWriteResponse]]}} is `expected`, + set |characteristic|.{{[[automatedDescriptorWriteResponse]]}} to |params|[`"code"`]. + 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. + 1. Otherwise, return [=error=] with [=error code=] [=invalid argument=].
From bb1513089465aaff8a22b713d5824dea03df5d28 Mon Sep 17 00:00:00 2001 From: Chengwei Hsieh Date: Tue, 5 Aug 2025 00:22:26 -0700 Subject: [PATCH 3/4] Revise sentence for updating data field --- index.bs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/index.bs b/index.bs index bb6e216..e032302 100644 --- a/index.bs +++ b/index.bs @@ -6356,7 +6356,11 @@ To trigger a simulated characteristic event given a [=navigable=] |n 1. Set |params|[`"serviceUuid"`] to |service|'s UUID. 1. Set |params|[`"characteristicUuid"`] to |characteristic|'s UUID. 1. Set |params|[`"type"`] to |type|. - 1. If |type| is `write`, set |params|[`"data"`] to a [=new=] {{Uint8Array}} wrapping a [=new=] {{ArrayBuffer}} containing |bytes|. + 1. If |type| is `write`, run the following steps: + 1. Let |data| be an empty list. + 1. For each |byte| in |bytes|: + 1. Append |byte|'s [=byte/value=] to |data|. + 1. Set |params|[`"data"`] to |data|. 1. Let |body| be a [=map=] matching the bluetooth.CharacteristicEventGenerated production, with the params field set to |params|. 1. Let |relatedNavigables| be a [=/set=] containing |navigable|. @@ -6401,7 +6405,11 @@ To trigger a simulated descriptor event given a [=navigable=] |navig 1. Set |params|[`"characteristicUuid"`] to |characteristic|'s UUID. 1. Set |params|[`"descriptorUuid"`] to |descriptor|'s UUID. 1. Set |params|[`"type"`] to |type|. - 1. If |type| is `write`, set |params|[`"data"`] to a [=new=] {{Uint8Array}} wrapping a [=new=] {{ArrayBuffer}} containing |bytes|. + 1. If |type| is `write`, run the following steps: + 1. Let |data| be an empty list. + 1. For each |byte| in |bytes|: + 1. Append |byte|'s [=byte/value=] to |data|. + 1. Set |params|[`"data"`] to |data|. 1. Let |body| be a [=map=] matching the bluetooth.DescriptorEventGenerated production, with the params field set to |params|. 1. Let |relatedNavigables| be a [=/set=] containing |navigable|. From 0a2c94d722d38d9427ef97852cf6d5da2b3eb32f Mon Sep 17 00:00:00 2001 From: Chengwei Hsieh Date: Tue, 5 Aug 2025 00:28:16 -0700 Subject: [PATCH 4/4] Else if to if --- index.bs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.bs b/index.bs index e032302..864ea96 100644 --- a/index.bs +++ b/index.bs @@ -5825,7 +5825,7 @@ The [=remote end steps=] with command parameters |params| are: and add a mapping from |simulatedGattService| to the resulting {{Promise}} in |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Return [=success=] with data `null`. -1. Else if |params|[`"type"`] is `"remove"`: +1. If |params|[`"type"`] is `"remove"`: 1. If |serviceMapping|[|uuid|] [=map/exists=], let |simulatedGattService| be |serviceMapping|[|uuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=]. 1. Remove |simulatedGattService| from |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. @@ -5935,7 +5935,7 @@ The [=remote end steps=] with command parameters |params| are: and add a mapping from |simulatedGattCharacteristic| to the resulting {{Promise}} in |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Return [=success=] with data `null`. -1. Else if |params|[`"type"`] is `"remove"`: +1. If |params|[`"type"`] is `"remove"`: 1. If |params|[`"characteristicProperties"`] [=map/exists=], return [=error=] with [=error code=] [=invalid argument=]. 1. If |characteristicMapping|[|characteristicUuid|] [=map/exists=], let |simulatedGattCharacteristic| be |characteristicMapping|[|characteristicUuid|]. @@ -6125,7 +6125,7 @@ bluetooth.SimulateDescriptorParameters = { and add a mapping from |simulatedGattDescriptor| to the resulting {{Promise}} in |simulatedDeviceInstance|.{{[[context]]}}.{{Bluetooth/[[attributeInstanceMap]]}}. 1. Return [=success=] with data `null`. -1. Else if |params|[`"type"`] is `"remove"`: +1. If |params|[`"type"`] is `"remove"`: 1. If |descriptorMapping|[|descriptorUuid|] [=map/exists=], let |simulatedGattDescriptor| be |descriptorMapping|[|descriptorUuid|]. 1. Otherwise, return [=error=] with [=error code=] [=invalid element state=].