diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/bluetooth | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/bluetooth')
268 files changed, 8885 insertions, 0 deletions
diff --git a/testing/web-platform/tests/bluetooth/META.yml b/testing/web-platform/tests/bluetooth/META.yml new file mode 100644 index 0000000000..607edb6365 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/META.yml @@ -0,0 +1,5 @@ +spec: https://webbluetoothcg.github.io/web-bluetooth/ +suggested_reviewers: + - dougt + - odejesush + - reillyeon diff --git a/testing/web-platform/tests/bluetooth/README.md b/testing/web-platform/tests/bluetooth/README.md new file mode 100644 index 0000000000..027b606e85 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/README.md @@ -0,0 +1,120 @@ +# Web Bluetooth API Tests + +The Web Bluetooth API enables sites to connect to and interact with Bluetooth +Low Energy devices. Please check the [Web Bluetooth specification] for more +details. + +Web Bluetooth testing relies on the [FakeBluetooth][Web Bluetooth +Testing] test API which must be provided by browsers under test. + +TODO([#485]): Update the links to [FakeBluetooth][Web Bluetooth Testing] to +point to the [Testing Web Bluetooth specification]. + +In this test suite `resources/bluetooth-test.js` detects and triggers +the API to be loaded as needed. This file also contains test helper methods, +such as for asserting that Bluetooth events are fired in a specific order. +The `resources/bluetooth-fake-devices.js` contains several helper methods that set +up fake Bluetooth devices. + +[Web Bluetooth specification]: https://WebBluetoothCG.github.io/web-bluetooth +[Web Bluetooth Testing]: +https://docs.google.com/document/d/1Nhv_oVDCodd1pEH_jj9k8gF4rPGb_84VYaZ9IG8M_WY/ +[#485]: https://github.com/WebBluetoothCG/web-bluetooth/issues/485 +[Testing Web Bluetooth specification]: +https://WebBluetoothCG.github.io/web-bluetooth/tests.html + +## Generated Tests + +Several Web Bluetooth tests share common test logic. For these tests, the +`script-tests` directory contains templates that are used by the +`generate.py` script to create several tests from these templates. The templates +are JavaScript files that contain a `CALLS()` keyword with functions delimited by +a `|` character. A test will be created for each function in the `CALLS()` by +`generate.py`. Note that for each subdirectory in `script-tests` there is a +matching directory under `bluetooth`. The generator will expand `CALLS` +functions into the +corresponding directory. + +### Example + +The `./script-tests/server/get-same-object.js` contains the following +code: + +```js +gattServer.CALLS([ + getPrimaryService('heart_rate')| + getPrimaryServices()| + getPrimaryServices('heart_rate')[UUID]]), +``` + +The functions in `CALLS()` will be expanded to generate 3 test files prefixed +with `gen-`: + +``` +bluetooth/server/getPrimaryService/gen-get-same-object.html +bluetooth/server/getPrimaryServices/gen-get-same-object.html +bluetooth/server/getPrimaryServices/gen-get-same-object-with-uuid.html +``` + +### Generate Tests + +To generate the tests in `script-tests`, run the following command from the +source root: + +```sh +$ python bluetooth/generate.py +``` + +To check that generated tests are correct and that there are no obsolete tests, +or tests for which a template does not exist anymore, run: + +```sh +$ python bluetooth/generate_test.py +``` + +More details can be found in `generate.py` and `generate_test.py`. + +## Chromium Implementation +The Chromium implementation is provided by +`../resources/chromium/web-bluetooth-test.js` using [MojoJS]. + +The Chromium implementation is not included in stable Chrome builds since it +would add too much to the binary size. On Chromium infrastructure, it is run +using the `content_shell` executable. + +In the future, Chromium `src/device/bluetooth` may be refactored into a Mojo +service. At this point, it would be possible to add the necessary testing hooks +into stable Chrome without substantially increasing the binary size, similar to +WebUSB. + +These Bluetooth tests are upstreamed here because other browsers can reuse them +by implementing the [Web Bluetooth Testing] API, even if only on their internal +infrastructure. + +For more implementation details, see the [Web Bluetooth Service README]. + +[MojoJS]: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/testing/web_platform_tests.md#mojojs +[Web Bluetooth Service README]: +https://chromium.googlesource.com/chromium/src.git/+/main/content/browser/bluetooth/README.md + +# Resources and Documentation + +For any issues pertaining to the specification, please file a [GitHub] +issue. For issues pertaining to an implementation of Web Bluetooth, please +file an issue with the implementor's bug tracker. + +* [Web Bluetooth specification] +* [Web Bluetooth Testing] +* [Testing Web Bluetooth specification] + +[GitHub]: https://github.com/WebBluetoothCG/web-bluetooth + +## Chromium + +Mailing list: web-bluetooth@chromium.org + +Bug tracker: [Blink>Bluetooth] + +* [Web Bluetooth Service README] + +[Blink>Bluetooth]: https://bugs.chromium.org/p/chromium/issues/list?q=component%3ABlink%3EBluetooth&can=2 diff --git a/testing/web-platform/tests/bluetooth/adapter/adapter-absent-getAvailability.https.window.js b/testing/web-platform/tests/bluetooth/adapter/adapter-absent-getAvailability.https.window.js new file mode 100644 index 0000000000..55f4a675da --- /dev/null +++ b/testing/web-platform/tests/bluetooth/adapter/adapter-absent-getAvailability.https.window.js @@ -0,0 +1,15 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'getAvailability() resolves with false if the system does ' + + 'not have an adapter.'; + +bluetooth_test(async () => { + await navigator.bluetooth.test.simulateCentral({state: 'absent'}); + let availability = await navigator.bluetooth.getAvailability(); + assert_false( + availability, + 'getAvailability() resolves promise with false when adapter is absent.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/adapter/adapter-added-getAvailability.https.window.js b/testing/web-platform/tests/bluetooth/adapter/adapter-added-getAvailability.https.window.js new file mode 100644 index 0000000000..f8e25b2ac2 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/adapter/adapter-added-getAvailability.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'getAvailability() resolves with true after adapter is ' + + 'inserted into a system with a platform that supports Bluetooth LE.'; + +bluetooth_test(async () => { + const fake_central = + await navigator.bluetooth.test.simulateCentral({state: 'absent'}); + let availability = await navigator.bluetooth.getAvailability(); + assert_false( + availability, + 'getAvailability() resolves promise with false when adapter is absent.'); + + await fake_central.setState('powered-on'); + availability = await navigator.bluetooth.getAvailability(); + assert_true( + availability, + 'getAvailability() resolves promise with true after Bluetooth LE ' + + 'capable adapter has been has been added.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/adapter/adapter-powered-off-getAvailability.https.window.js b/testing/web-platform/tests/bluetooth/adapter/adapter-powered-off-getAvailability.https.window.js new file mode 100644 index 0000000000..1ffcd3bb09 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/adapter/adapter-powered-off-getAvailability.https.window.js @@ -0,0 +1,16 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'getAvailability() resolves with true if the Bluetooth ' + + 'radio is powered off, but the platform that supports Bluetooth LE.'; + +bluetooth_test(async () => { + await navigator.bluetooth.test.simulateCentral({state: 'powered-off'}); + let availability = await navigator.bluetooth.getAvailability(); + assert_true( + availability, + 'getAvailability() resolves promise with true when adapter is powered ' + + 'off.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/adapter/adapter-powered-on-getAvailability.https.window.js b/testing/web-platform/tests/bluetooth/adapter/adapter-powered-on-getAvailability.https.window.js new file mode 100644 index 0000000000..84c7982d21 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/adapter/adapter-powered-on-getAvailability.https.window.js @@ -0,0 +1,16 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'getAvailability() resolves with true if the Bluetooth ' + + 'radio is powered on and the platform supports Bluetooth LE.'; + +bluetooth_test(async () => { + await navigator.bluetooth.test.simulateCentral({state: 'powered-on'}); + let availability = await navigator.bluetooth.getAvailability(); + assert_true( + availability, + 'getAvailability() resolves promise with true when adapter is powered ' + + 'on and it supports Bluetooth Low-Energy.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/adapter/adapter-powered-on-off-on-getAvailability.https.window.js b/testing/web-platform/tests/bluetooth/adapter/adapter-powered-on-off-on-getAvailability.https.window.js new file mode 100644 index 0000000000..c4ba9b5f3a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/adapter/adapter-powered-on-off-on-getAvailability.https.window.js @@ -0,0 +1,37 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'getAvailability() is not affected by the powered state of ' + + 'the adapter.'; + +bluetooth_test(async () => { + const fake_central = + await navigator.bluetooth.test.simulateCentral({state: 'powered-on'}); + { + const availability = await navigator.bluetooth.getAvailability(); + assert_true( + availability, + 'getAvailability() resolves promise with true when adapter is ' + + 'powered on and it supports Bluetooth Low-Energy.'); + } + + { + await fake_central.setState('powered-off'); + const availability = await navigator.bluetooth.getAvailability(); + assert_true( + availability, + 'getAvailability() resolves promise with true after adapter powered ' + + 'off.'); + } + + { + await fake_central.setState('powered-on'); + const availability = await navigator.bluetooth.getAvailability(); + assert_true( + availability, + 'getAvailability() resolves promise with true when adapter is ' + + 'powered back on.'); + } +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/adapter/adapter-removed-getAvailability.https.window.js b/testing/web-platform/tests/bluetooth/adapter/adapter-removed-getAvailability.https.window.js new file mode 100644 index 0000000000..ca0b51f47d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/adapter/adapter-removed-getAvailability.https.window.js @@ -0,0 +1,24 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'getAvailability() resolves with false after the powered ' + + 'on adapter is removed.'; + +bluetooth_test(async () => { + const fake_central = + await navigator.bluetooth.test.simulateCentral({state: 'powered-on'}); + let availability = await navigator.bluetooth.getAvailability(); + assert_true( + availability, + 'getAvailability() resolves promise with true when adapter is powered ' + + 'on and it supports Bluetooth Low-Energy.'); + + await fake_central.setState('absent'); + availability = await navigator.bluetooth.getAvailability(); + assert_false( + availability, + 'getAvailability() resolves promise with false after adapter has been ' + + 'has been removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/adapter/cross-origin-iframe-getAvailability.sub.https.window.js b/testing/web-platform/tests/bluetooth/adapter/cross-origin-iframe-getAvailability.sub.https.window.js new file mode 100644 index 0000000000..54abfbb5ce --- /dev/null +++ b/testing/web-platform/tests/bluetooth/adapter/cross-origin-iframe-getAvailability.sub.https.window.js @@ -0,0 +1,31 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'getAvailability() resolves with false if called from a ' + + 'unique origin'; +const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' + + '/bluetooth/resources/health-thermometer-iframe.html' +let iframe = document.createElement('iframe'); + +bluetooth_test(async () => { + await navigator.bluetooth.test.simulateCentral({state: 'powered-on'}); + await new Promise(resolve => { + iframe.src = cross_origin_src; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + await new Promise(resolve => { + callWithTrustedClick( + () => iframe.contentWindow.postMessage({type: 'GetAvailability'}, '*')); + + window.onmessage = messageEvent => { + assert_equals( + messageEvent.data, false, + 'getAvailability resolves to false when called from a unique ' + + 'origin.'); + resolve(); + }; + }); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/characteristicProperties.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/characteristicProperties.https.window.js new file mode 100644 index 0000000000..f7a57a9c4b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/characteristicProperties.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'HeartRate device properties'; + +bluetooth_test(async () => { + const {service} = await getHealthThermometerService() + const [temperature_measurement, measurement_interval] = await Promise.all([ + service.getCharacteristic('temperature_measurement'), + service.getCharacteristic('measurement_interval') + ]); + const tm_expected_properties = new TestCharacteristicProperties(['indicate']); + assert_properties_equal( + temperature_measurement.properties, tm_expected_properties); + + const mi_expected_properties = + new TestCharacteristicProperties(['read', 'write', 'indicate']); + assert_properties_equal( + measurement_interval.properties, mi_expected_properties); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/detachedIframe.https.window.js new file mode 100644 index 0000000000..de7d0b0b7c --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/detachedIframe.https.window.js @@ -0,0 +1,33 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device, fakes} = await getHealthThermometerDeviceFromIframe(iframe); + await fakes.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + let service = await device.gatt.getPrimaryService(health_thermometer.name); + let characteristic = + await service.getCharacteristic(measurement_interval.name); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await characteristic.getDescriptor(user_description.name); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'getDescriptor() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/gen-characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/gen-characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..9e48a7caab --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/gen-characteristic-is-removed.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Characteristic no longer exists.', + 'InvalidStateError'); +let fake_peripheral, characteristic, fake_characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _)) + .then(() => characteristic.getDescriptor(user_description.name)) + .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e)) + .then(() => fake_characteristic.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.getDescriptor(user_description.name), expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/gen-descriptor-get-same-object.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/gen-descriptor-get-same-object.https.window.js new file mode 100644 index 0000000000..708f67da6a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/gen-descriptor-get-same-object.https.window.js @@ -0,0 +1,36 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getDescriptor should return the same object.'; +let characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({characteristic} = _)) + .then(() => Promise.all([ + characteristic.getDescriptor(user_description.alias), + characteristic.getDescriptor(user_description.name), + characteristic.getDescriptor(user_description.uuid) + ])) + .then(descriptors_arrays => { + assert_true(descriptors_arrays.length > 0) + + // Convert to arrays if necessary. + for (let i = 0; i < descriptors_arrays.length; i++) { + descriptors_arrays[i] = [].concat(descriptors_arrays[i]); + } + + for (let i = 1; i < descriptors_arrays.length; i++) { + assert_equals(descriptors_arrays[0].length, + descriptors_arrays[i].length); + } + + let base_set = new Set(descriptors_arrays[0]); + for (let descriptors of descriptors_arrays) { + descriptors.forEach(descriptor => assert_true(base_set.has(descriptor))); + } + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/gen-service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/gen-service-is-removed.https.window.js new file mode 100644 index 0000000000..c256050b0f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptor/gen-service-is-removed.https.window.js @@ -0,0 +1,24 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +// TODO(https://crbug.com/672127) Use this test case to test the rest of +// characteristic functions. +'use strict'; +const test_desc = 'Service is removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let characteristic, fake_peripheral, fake_service; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({characteristic, fake_peripheral, fake_service} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.getDescriptor(user_description.name), + expected, + 'Service got removed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/detachedIframe.https.window.js new file mode 100644 index 0000000000..45dd23752f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/detachedIframe.https.window.js @@ -0,0 +1,33 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device, fakes} = await getHealthThermometerDeviceFromIframe(iframe); + await fakes.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + let service = await device.gatt.getPrimaryService(health_thermometer.name); + let characteristic = + await service.getCharacteristic(measurement_interval.name); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await characteristic.getDescriptors(user_description.name); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'getDescriptors() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed-with-uuid.https.window.js new file mode 100644 index 0000000000..a0424c0110 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed-with-uuid.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Characteristic no longer exists.', + 'InvalidStateError'); +let fake_peripheral, characteristic, fake_characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _)) + .then(() => characteristic.getDescriptor(user_description.name)) + .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e)) + .then(() => fake_characteristic.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.getDescriptors(user_description.name), expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..29325c3bb5 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-characteristic-is-removed.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Characteristic no longer exists.', + 'InvalidStateError'); +let fake_peripheral, characteristic, fake_characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _)) + .then(() => characteristic.getDescriptor(user_description.name)) + .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e)) + .then(() => fake_characteristic.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.getDescriptors(), expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-descriptor-get-same-object.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-descriptor-get-same-object.https.window.js new file mode 100644 index 0000000000..7f1001f3ee --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-descriptor-get-same-object.https.window.js @@ -0,0 +1,36 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getDescriptors should return the same object.'; +let characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({characteristic} = _)) + .then(() => Promise.all([ + characteristic.getDescriptors(user_description.alias), + characteristic.getDescriptors(user_description.name), + characteristic.getDescriptors(user_description.uuid) + ])) + .then(descriptors_arrays => { + assert_true(descriptors_arrays.length > 0) + + // Convert to arrays if necessary. + for (let i = 0; i < descriptors_arrays.length; i++) { + descriptors_arrays[i] = [].concat(descriptors_arrays[i]); + } + + for (let i = 1; i < descriptors_arrays.length; i++) { + assert_equals(descriptors_arrays[0].length, + descriptors_arrays[i].length); + } + + let base_set = new Set(descriptors_arrays[0]); + for (let descriptors of descriptors_arrays) { + descriptors.forEach(descriptor => assert_true(base_set.has(descriptor))); + } + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-service-is-removed-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-service-is-removed-with-uuid.https.window.js new file mode 100644 index 0000000000..b7c4bff32d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-service-is-removed-with-uuid.https.window.js @@ -0,0 +1,24 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +// TODO(https://crbug.com/672127) Use this test case to test the rest of +// characteristic functions. +'use strict'; +const test_desc = 'Service is removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let characteristic, fake_peripheral, fake_service; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({characteristic, fake_peripheral, fake_service} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.getDescriptors(user_description.uuid), + expected, + 'Service got removed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-service-is-removed.https.window.js new file mode 100644 index 0000000000..22dc30f6d8 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/getDescriptors/gen-service-is-removed.https.window.js @@ -0,0 +1,24 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +// TODO(https://crbug.com/672127) Use this test case to test the rest of +// characteristic functions. +'use strict'; +const test_desc = 'Service is removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let characteristic, fake_peripheral, fake_service; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({characteristic, fake_peripheral, fake_service} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.getDescriptors(user_description.name), + expected, + 'Service got removed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/notifications/characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/notifications/characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..9641ad71e9 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/notifications/characteristic-is-removed.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Characteristic is removed. Reject with InvalidStateError.'; +const expected = new DOMException( + 'GATT Characteristic no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + await fake_characteristic.remove(); + await assert_promise_rejects_with_message( + characteristic.startNotifications(), expected, + 'Characteristic got removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/notifications/service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/notifications/service-is-removed.https.window.js new file mode 100644 index 0000000000..a5851fc473 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/notifications/service-is-removed.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Service is removed. Reject with InvalidStateError.'; +const expected = + new DOMException('GATT Service no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_peripheral, fake_service} = + await getMeasurementIntervalCharacteristic(); + await fake_service.remove(); + await fake_peripheral.simulateGATTServicesChanged(); + await assert_promise_rejects_with_message( + characteristic.startNotifications(), expected, 'Service got removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/readValue/add-multiple-event-listeners.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/readValue/add-multiple-event-listeners.https.window.js new file mode 100644 index 0000000000..0eeafd0b79 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/readValue/add-multiple-event-listeners.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Add multiple event listeners then readValue().'; + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + await fake_characteristic.setNextReadResponse(GATT_SUCCESS, [0, 1, 2]); + + // Make sure that |characteristic.readValue()| resolves after + // |characteristicvaluechanged| is fired |3| times. + const results = await assert_promise_resolves_after_event( + characteristic /* object */, 'readValue' /* func */, + 'characteristicvaluechanged' /* event */, 3 /* num_listeners */); + + const read_value = new Uint8Array(results[0].buffer); + const event_values = results.slice(1).map(v => new Uint8Array(v.buffer)); + for (const event_value of event_values) { + assert_equals(event_value.buffer, read_value.buffer); + assert_array_equals(event_value, read_value); + } +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/readValue/characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/readValue/characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..e97b94f736 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/readValue/characteristic-is-removed.https.window.js @@ -0,0 +1,16 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException( + 'GATT Characteristic no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + await fake_characteristic.remove(); + await assert_promise_rejects_with_message( + characteristic.readValue(), expected, 'Characteristic got removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/readValue/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/readValue/detachedIframe.https.window.js new file mode 100644 index 0000000000..6e2510b58d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/readValue/detachedIframe.https.window.js @@ -0,0 +1,33 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device, fakes} = await getHealthThermometerDeviceFromIframe(iframe); + await fakes.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + let service = await device.gatt.getPrimaryService(health_thermometer.name); + let characteristic = + await service.getCharacteristic(measurement_interval.name); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await characteristic.readValue(); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'readValue() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/characteristic/readValue/event-is-fired.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/readValue/event-is-fired.https.window.js new file mode 100644 index 0000000000..52b70e7a08 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/readValue/event-is-fired.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Reading a characteristic should fire an event.'; + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + await fake_characteristic.setNextReadResponse(GATT_SUCCESS, [0, 1, 2]); + + // Make sure that |characteristic.readValue()| resolves after + // |characteristicvaluechanged| is fired. + const results = await assert_promise_resolves_after_event( + characteristic /* object */, 'readValue' /* func */, + 'characteristicvaluechanged' /* event */); + + const read_value = new Uint8Array(results[0].buffer); + const event_value = new Uint8Array(results[1].buffer); + assert_equals(event_value.buffer, read_value.buffer); + assert_array_equals(event_value, read_value); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/readValue/gen-characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/readValue/gen-characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..5bee6db107 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/readValue/gen-characteristic-is-removed.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Characteristic no longer exists.', + 'InvalidStateError'); +let fake_peripheral, characteristic, fake_characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _)) + .then(() => characteristic.getDescriptor(user_description.name)) + .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e)) + .then(() => fake_characteristic.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.readValue(), expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/readValue/read-succeeds.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/readValue/read-succeeds.https.window.js new file mode 100644 index 0000000000..e5ddfb8169 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/readValue/read-succeeds.https.window.js @@ -0,0 +1,16 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A read request succeeds and returns the characteristic\'s ' + + 'value.'; +const EXPECTED_VALUE = [0, 1, 2]; + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + await fake_characteristic.setNextReadResponse(GATT_SUCCESS, EXPECTED_VALUE); + const value = await characteristic.readValue(); + assert_array_equals(new Uint8Array(value.buffer), EXPECTED_VALUE) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/readValue/read-updates-value.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/readValue/read-updates-value.https.window.js new file mode 100644 index 0000000000..bb98aeb18f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/readValue/read-updates-value.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +const test_desc = 'Succesful read should update characteristic.value'; +const EXPECTED_VALUE = [0, 1, 2]; + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + assert_equals(characteristic.value, null); + + await fake_characteristic.setNextReadResponse(GATT_SUCCESS, EXPECTED_VALUE); + await characteristic.readValue(); + assert_array_equals( + new Uint8Array(characteristic.value.buffer), EXPECTED_VALUE) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/readValue/service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/readValue/service-is-removed.https.window.js new file mode 100644 index 0000000000..1f699ca25e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/readValue/service-is-removed.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Service gets removed. Reject with InvalidStateError.'; +const expected = + new DOMException('GATT Service no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_peripheral, fake_service} = + await getMeasurementIntervalCharacteristic(); + await fake_service.remove(); + await fake_peripheral.simulateGATTServicesChanged(); + await assert_promise_rejects_with_message( + characteristic.readValue(), expected, 'Service got removed.') +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/service-same-from-2-characteristics.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/service-same-from-2-characteristics.https.window.js new file mode 100644 index 0000000000..dafd755fd1 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/service-same-from-2-characteristics.https.window.js @@ -0,0 +1,15 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Same parent service returned from multiple characteristics.'; + +bluetooth_test(async () => { + const {service} = await getHealthThermometerService(); + const characteristics = await Promise.all([ + service.getCharacteristic('measurement_interval'), + service.getCharacteristic('temperature_measurement') + ]); + assert_equals(characteristics[0].service, characteristics[1].service); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/service-same-object.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/service-same-object.https.window.js new file mode 100644 index 0000000000..01b3a25e35 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/service-same-object.https.window.js @@ -0,0 +1,12 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = '[SameObject] test for BluetoothRemoteGATTCharacteristic ' + + 'service.'; + +bluetooth_test(async () => { + const {characteristic} = await getMeasurementIntervalCharacteristic(); + assert_equals(characteristic.service, characteristic.service); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/startNotifications/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/startNotifications/detachedIframe.https.window.js new file mode 100644 index 0000000000..f09c52d328 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/startNotifications/detachedIframe.https.window.js @@ -0,0 +1,33 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device, fakes} = await getHealthThermometerDeviceFromIframe(iframe); + await fakes.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + let service = await device.gatt.getPrimaryService(health_thermometer.name); + let characteristic = + await service.getCharacteristic(measurement_interval.name); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await characteristic.startNotifications(); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'startNotifications() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/characteristic/startNotifications/gen-characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/startNotifications/gen-characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..c2a23d7f44 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/startNotifications/gen-characteristic-is-removed.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Characteristic no longer exists.', + 'InvalidStateError'); +let fake_peripheral, characteristic, fake_characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _)) + .then(() => characteristic.getDescriptor(user_description.name)) + .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e)) + .then(() => fake_characteristic.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.startNotifications(), expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/stopNotifications/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/stopNotifications/detachedIframe.https.window.js new file mode 100644 index 0000000000..a459a5b15d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/stopNotifications/detachedIframe.https.window.js @@ -0,0 +1,33 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device, fakes} = await getHealthThermometerDeviceFromIframe(iframe); + await fakes.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + let service = await device.gatt.getPrimaryService(health_thermometer.name); + let characteristic = + await service.getCharacteristic(measurement_interval.name); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await characteristic.stopNotifications(); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'stopNotifications() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValue/buffer-is-detached.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValue/buffer-is-detached.https.window.js new file mode 100644 index 0000000000..5d707775e1 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValue/buffer-is-detached.https.window.js @@ -0,0 +1,24 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'writeValue() fails when passed a detached buffer'; + +function detachBuffer(buffer) { + window.postMessage('', '*', [buffer]); +} + +bluetooth_test(async (t) => { + const {characteristic} = await getMeasurementIntervalCharacteristic(); + + const typed_array = Uint8Array.of(1, 2); + detachBuffer(typed_array.buffer); + await promise_rejects_dom( + t, 'InvalidStateError', characteristic.writeValue(typed_array)); + + const array_buffer = Uint8Array.of(3, 4).buffer; + detachBuffer(array_buffer); + await promise_rejects_dom( + t, 'InvalidStateError', characteristic.writeValue(array_buffer)); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValue/characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValue/characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..6e9da8802c --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValue/characteristic-is-removed.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException( + 'GATT Characteristic no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + await fake_characteristic.remove(); + await assert_promise_rejects_with_message( + characteristic.writeValue(new ArrayBuffer(1 /* length */)), expected, + 'Characteristic got removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValue/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValue/detachedIframe.https.window.js new file mode 100644 index 0000000000..eb243a3508 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValue/detachedIframe.https.window.js @@ -0,0 +1,33 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device, fakes} = await getHealthThermometerDeviceFromIframe(iframe); + await fakes.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + let service = await device.gatt.getPrimaryService(health_thermometer.name); + let characteristic = + await service.getCharacteristic(measurement_interval.name); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await characteristic.writeValue(new DataView(new ArrayBuffer(2))); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'writeValue() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValue/gen-characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValue/gen-characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..5750cb82c7 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValue/gen-characteristic-is-removed.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Characteristic no longer exists.', + 'InvalidStateError'); +let fake_peripheral, characteristic, fake_characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _)) + .then(() => characteristic.getDescriptor(user_description.name)) + .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e)) + .then(() => fake_characteristic.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.writeValue(new Uint8Array(1)), expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValue/service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValue/service-is-removed.https.window.js new file mode 100644 index 0000000000..89c3112475 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValue/service-is-removed.https.window.js @@ -0,0 +1,18 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Service gets removed. Reject with InvalidStateError.'; +const expected = + new DOMException('GATT Service no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_peripheral, fake_service} = + await getMeasurementIntervalCharacteristic(); + await fake_service.remove(); + await fake_peripheral.simulateGATTServicesChanged(); + await assert_promise_rejects_with_message( + characteristic.writeValue(new ArrayBuffer(1 /* length */)), expected, + 'Service got removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValue/write-succeeds.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValue/write-succeeds.https.window.js new file mode 100644 index 0000000000..b57fe941d0 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValue/write-succeeds.https.window.js @@ -0,0 +1,47 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A regular write request to a writable characteristic ' + + 'should succeed.'; + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + + let lastValue, lastWriteType; + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_equals(lastValue, null); + assert_equals(lastWriteType, 'none'); + + await fake_characteristic.setNextWriteResponse(GATT_SUCCESS); + + const typed_array = Uint8Array.of(1, 2); + await characteristic.writeValue(typed_array); + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_array_equals(lastValue, [1, 2]); + assert_equals(lastWriteType, 'default-deprecated'); + + await fake_characteristic.setNextWriteResponse(GATT_SUCCESS); + + const array_buffer = Uint8Array.of(3, 4).buffer; + await characteristic.writeValue(array_buffer); + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_array_equals(lastValue, [3, 4]); + assert_equals(lastWriteType, 'default-deprecated'); + + await fake_characteristic.setNextWriteResponse(GATT_SUCCESS); + + const data_view = new DataView(new ArrayBuffer(2)); + data_view.setUint8(0, 5); + data_view.setUint8(1, 6); + await characteristic.writeValue(data_view); + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_array_equals(lastValue, [5, 6]); + assert_equals(lastWriteType, 'default-deprecated'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/buffer-is-detached.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/buffer-is-detached.https.window.js new file mode 100644 index 0000000000..ebd8aefeca --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/buffer-is-detached.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = + 'writeValueWithResponse() fails when passed a detached buffer'; + +function detachBuffer(buffer) { + window.postMessage('', '*', [buffer]); +} + +bluetooth_test(async (t) => { + const {characteristic} = await getMeasurementIntervalCharacteristic(); + + const typed_array = Uint8Array.of(1, 2); + detachBuffer(typed_array.buffer); + await promise_rejects_dom( + t, 'InvalidStateError', + characteristic.writeValueWithResponse(typed_array)); + + const array_buffer = Uint8Array.of(3, 4).buffer; + detachBuffer(array_buffer); + await promise_rejects_dom( + t, 'InvalidStateError', + characteristic.writeValueWithResponse(array_buffer)); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..9309cd5a3c --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/characteristic-is-removed.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException( + 'GATT Characteristic no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + await fake_characteristic.remove(); + await assert_promise_rejects_with_message( + characteristic.writeValueWithResponse(new ArrayBuffer(1 /* length */)), + expected, 'Characteristic got removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/gen-characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/gen-characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..e202376da7 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/gen-characteristic-is-removed.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Characteristic no longer exists.', + 'InvalidStateError'); +let fake_peripheral, characteristic, fake_characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _)) + .then(() => characteristic.getDescriptor(user_description.name)) + .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e)) + .then(() => fake_characteristic.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.writeValueWithResponse(new Uint8Array(1)), expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/service-is-removed.https.window.js new file mode 100644 index 0000000000..81b2dff44e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/service-is-removed.https.window.js @@ -0,0 +1,18 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Service gets removed. Reject with InvalidStateError.'; +const expected = + new DOMException('GATT Service no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_peripheral, fake_service} = + await getMeasurementIntervalCharacteristic(); + await fake_service.remove(); + await fake_peripheral.simulateGATTServicesChanged(); + await assert_promise_rejects_with_message( + characteristic.writeValueWithResponse(new ArrayBuffer(1 /* length */)), + expected, 'Service got removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/write-succeeds.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/write-succeeds.https.window.js new file mode 100644 index 0000000000..c87e7ac6ab --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithResponse/write-succeeds.https.window.js @@ -0,0 +1,47 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A regular write request to a writable characteristic ' + + 'should succeed.'; + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + + let lastValue, lastWriteType; + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_equals(lastValue, null); + assert_equals(lastWriteType, 'none'); + + await fake_characteristic.setNextWriteResponse(GATT_SUCCESS); + + const typed_array = Uint8Array.of(1, 2); + await characteristic.writeValueWithResponse(typed_array); + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_array_equals(lastValue, [1, 2]); + assert_equals(lastWriteType, 'with-response'); + + await fake_characteristic.setNextWriteResponse(GATT_SUCCESS); + + const array_buffer = Uint8Array.of(3, 4).buffer; + await characteristic.writeValueWithResponse(array_buffer); + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_array_equals(lastValue, [3, 4]); + assert_equals(lastWriteType, 'with-response'); + + await fake_characteristic.setNextWriteResponse(GATT_SUCCESS); + + const data_view = new DataView(new ArrayBuffer(2)); + data_view.setUint8(0, 5); + data_view.setUint8(1, 6); + await characteristic.writeValueWithResponse(data_view); + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_array_equals(lastValue, [5, 6]); + assert_equals(lastWriteType, 'with-response'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/buffer-is-detached.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/buffer-is-detached.https.window.js new file mode 100644 index 0000000000..75d4cf0805 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/buffer-is-detached.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = + 'writeValueWithoutResponse() fails when passed a detached buffer'; + +function detachBuffer(buffer) { + window.postMessage('', '*', [buffer]); +} + +bluetooth_test(async (t) => { + const {characteristic} = await getMeasurementIntervalCharacteristic(); + + const typed_array = Uint8Array.of(1, 2); + detachBuffer(typed_array.buffer); + await promise_rejects_dom( + t, 'InvalidStateError', + characteristic.writeValueWithoutResponse(typed_array)); + + const array_buffer = Uint8Array.of(3, 4).buffer; + detachBuffer(array_buffer); + await promise_rejects_dom( + t, 'InvalidStateError', + characteristic.writeValueWithoutResponse(array_buffer)); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..8d3ed1f663 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/characteristic-is-removed.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException( + 'GATT Characteristic no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + await fake_characteristic.remove(); + await assert_promise_rejects_with_message( + characteristic.writeValueWithoutResponse(new ArrayBuffer(1 /* length */)), + expected, 'Characteristic got removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/gen-characteristic-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/gen-characteristic-is-removed.https.window.js new file mode 100644 index 0000000000..b88246aae8 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/gen-characteristic-is-removed.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Characteristic no longer exists.', + 'InvalidStateError'); +let fake_peripheral, characteristic, fake_characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _)) + .then(() => characteristic.getDescriptor(user_description.name)) + .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e)) + .then(() => fake_characteristic.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.writeValueWithoutResponse(new Uint8Array(1)), expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/service-is-removed.https.window.js new file mode 100644 index 0000000000..feb711c64e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/service-is-removed.https.window.js @@ -0,0 +1,18 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Service gets removed. Reject with InvalidStateError.'; +const expected = + new DOMException('GATT Service no longer exists.', 'InvalidStateError'); + +bluetooth_test(async () => { + const {characteristic, fake_peripheral, fake_service} = + await getMeasurementIntervalCharacteristic(); + await fake_service.remove(); + await fake_peripheral.simulateGATTServicesChanged(); + await assert_promise_rejects_with_message( + characteristic.writeValueWithoutResponse(new ArrayBuffer(1 /* length */)), + expected, 'Service got removed.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/write-succeeds.https.window.js b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/write-succeeds.https.window.js new file mode 100644 index 0000000000..0dcf8ad0b1 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/characteristic/writeValueWithoutResponse/write-succeeds.https.window.js @@ -0,0 +1,41 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A regular write request to a writable characteristic ' + + 'should succeed.'; + +bluetooth_test(async () => { + const {characteristic, fake_characteristic} = + await getMeasurementIntervalCharacteristic(); + + let lastValue, lastWriteType; + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_equals(lastValue, null); + assert_equals(lastWriteType, 'none'); + + const typed_array = Uint8Array.of(1, 2); + await characteristic.writeValueWithoutResponse(typed_array); + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_array_equals(lastValue, [1, 2]); + assert_equals(lastWriteType, 'without-response'); + + const array_buffer = Uint8Array.of(3, 4).buffer; + await characteristic.writeValueWithoutResponse(array_buffer); + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_array_equals(lastValue, [3, 4]); + assert_equals(lastWriteType, 'without-response'); + + const data_view = new DataView(new ArrayBuffer(2)); + data_view.setUint8(0, 5); + data_view.setUint8(1, 6); + await characteristic.writeValueWithoutResponse(data_view); + ({lastValue, lastWriteType} = + await fake_characteristic.getLastWrittenValue()); + assert_array_equals(lastValue, [5, 6]); + assert_equals(lastWriteType, 'without-response'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/descriptor/readValue/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/descriptor/readValue/detachedIframe.https.window.js new file mode 100644 index 0000000000..47765a1315 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/descriptor/readValue/detachedIframe.https.window.js @@ -0,0 +1,34 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device, fakes} = await getHealthThermometerDeviceFromIframe(iframe); + await fakes.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + let service = await device.gatt.getPrimaryService(health_thermometer.name); + let characteristic = + await service.getCharacteristic(measurement_interval.name); + let descriptor = await characteristic.getDescriptor(user_description.name); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await descriptor.readValue(); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'readValue() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/descriptor/readValue/gen-service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/descriptor/readValue/gen-service-is-removed.https.window.js new file mode 100644 index 0000000000..d6c73ba60e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/descriptor/readValue/gen-service-is-removed.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Service gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let descriptor, fake_peripheral, fake_service; + +bluetooth_test(() => getUserDescriptionDescriptor() + .then(_ => ({descriptor, fake_peripheral, fake_service} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + descriptor.readValue(), + expected, + 'Service got removed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/descriptor/readValue/read-succeeds.https.window.js b/testing/web-platform/tests/bluetooth/descriptor/readValue/read-succeeds.https.window.js new file mode 100644 index 0000000000..d81db2f8c0 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/descriptor/readValue/read-succeeds.https.window.js @@ -0,0 +1,16 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = `A read request succeeds and returns the descriptor's value.`; + +bluetooth_test(async () => { + const {descriptor, fake_descriptor} = await getUserDescriptionDescriptor(); + + const EXPECTED_VALUE = [0, 1, 2]; + await fake_descriptor.setNextReadResponse(GATT_SUCCESS, EXPECTED_VALUE); + + const value = await descriptor.readValue(); + assert_array_equals(Array.from(new Uint8Array(value.buffer)), EXPECTED_VALUE); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/descriptor/writeValue/buffer-is-detached.https.window.js b/testing/web-platform/tests/bluetooth/descriptor/writeValue/buffer-is-detached.https.window.js new file mode 100644 index 0000000000..49daf7cf86 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/descriptor/writeValue/buffer-is-detached.https.window.js @@ -0,0 +1,24 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'writeValue() fails when passed a detached buffer'; + +function detachBuffer(buffer) { + window.postMessage('', '*', [buffer]); +} + +bluetooth_test(async (t) => { + const {descriptor, fake_descriptor} = await getUserDescriptionDescriptor(); + + const typed_array = Uint8Array.of(1, 2); + detachBuffer(typed_array.buffer); + await promise_rejects_dom( + t, 'InvalidStateError', descriptor.writeValue(typed_array)); + + const array_buffer = Uint8Array.of(3, 4).buffer; + detachBuffer(array_buffer); + await promise_rejects_dom( + t, 'InvalidStateError', descriptor.writeValue(array_buffer)); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/descriptor/writeValue/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/descriptor/writeValue/detachedIframe.https.window.js new file mode 100644 index 0000000000..aa143ca8e5 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/descriptor/writeValue/detachedIframe.https.window.js @@ -0,0 +1,34 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device, fakes} = await getHealthThermometerDeviceFromIframe(iframe); + await fakes.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + let service = await device.gatt.getPrimaryService(health_thermometer.name); + let characteristic = + await service.getCharacteristic(measurement_interval.name); + let descriptor = await characteristic.getDescriptor(user_description.name); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await descriptor.writeValue(new ArrayBuffer(1 /* length */)); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'writeValue() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/descriptor/writeValue/gen-service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/descriptor/writeValue/gen-service-is-removed.https.window.js new file mode 100644 index 0000000000..c7f6d6efe3 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/descriptor/writeValue/gen-service-is-removed.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Service gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let descriptor, fake_peripheral, fake_service; + +bluetooth_test(() => getUserDescriptionDescriptor() + .then(_ => ({descriptor, fake_peripheral, fake_service} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + descriptor.writeValue(new ArrayBuffer(1 /* length */)), + expected, + 'Service got removed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/device/forget/connect-after-forget.https.window.js b/testing/web-platform/tests/bluetooth/device/forget/connect-after-forget.https.window.js new file mode 100644 index 0000000000..0b15b4d060 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/forget/connect-after-forget.https.window.js @@ -0,0 +1,11 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async (t) => { + const { device } = await getConnectedHealthThermometerDevice(); + await device.forget(); + + await promise_rejects_dom(t, 'SecurityError', device.gatt.connect()); +}, 'gatt.connect() rejects after forget().'); diff --git a/testing/web-platform/tests/bluetooth/device/forget/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/device/forget/detachedIframe.https.window.js new file mode 100644 index 0000000000..f4803542fb --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/forget/detachedIframe.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device} = await getHealthThermometerDeviceFromIframe(iframe); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await device.forget(); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'TypeError'); +}, 'forget() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/device/forget/getDevices.https.window.js b/testing/web-platform/tests/bluetooth/device/forget/getDevices.https.window.js new file mode 100644 index 0000000000..e9ce656319 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/forget/getDevices.https.window.js @@ -0,0 +1,18 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + await getConnectedHealthThermometerDevice(); + const devicesBeforeForget = await navigator.bluetooth.getDevices(); + assert_equals( + devicesBeforeForget.length, 1, 'getDevices() should return the granted device.'); + + const device = devicesBeforeForget[0]; + await device.forget(); + const devicesAfterForget = await navigator.bluetooth.getDevices(); + assert_equals( + devicesAfterForget.length, 0, + 'getDevices() is empty after device.forget().'); +}, 'forget() removes devices from getDevices().'); diff --git a/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/disconnected.https.window.js b/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/disconnected.https.window.js new file mode 100644 index 0000000000..43a11a88cb --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/disconnected.https.window.js @@ -0,0 +1,16 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A device disconnecting while connected should fire the ' + + 'gattserverdisconnected event.'; + +bluetooth_test(async () => { + const {device, fake_peripheral} = await getConnectedHealthThermometerDevice(); + const disconnectPromise = eventPromise(device, 'gattserverdisconnected'); + + await fake_peripheral.simulateGATTDisconnection(); + let disconnectEvent = await disconnectPromise; + assert_true(disconnectEvent.bubbles); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/disconnected_gc.https.window.js b/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/disconnected_gc.https.window.js new file mode 100644 index 0000000000..0cf4973e21 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/disconnected_gc.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A device disconnecting after the BluetoothDevice object ' + + 'has been GC\'ed should not access freed memory.'; + +bluetooth_test(async () => { + let {fake_peripheral} = await getConnectedHealthThermometerDevice(); + + // 1. Disconnect. + await fake_peripheral.simulateGATTDisconnection(); + + // 2. Run garbage collection. + fake_peripheral = undefined; + await garbageCollect(); + + // 3. Wait 50ms after the GC runs for the disconnection event to come back. + // There's nothing to assert other than that only valid memory is used. + await new Promise(resolve => step_timeout(resolve, 50)); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/one-event-per-disconnection.https.window.js b/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/one-event-per-disconnection.https.window.js new file mode 100644 index 0000000000..ab273adbc8 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/one-event-per-disconnection.https.window.js @@ -0,0 +1,30 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'If a site disconnects from a device while the platform is ' + + 'disconnecting that device, only one gattserverdisconnected event should ' + + 'fire.'; + +bluetooth_test(async () => { + const {device, fake_peripheral} = await getConnectedHealthThermometerDevice(); + let num_events = 0; + + // 1. Listen for disconnections. + device.addEventListener('gattserverdisconnected', () => num_events++); + + // 2. Disconnect several times. + await Promise.all([ + eventPromise(device, 'gattserverdisconnected'), + fake_peripheral.simulateGATTDisconnection(), + device.gatt.disconnect(), + device.gatt.disconnect(), + ]); + + // 3. Wait to catch disconnect events. + await new Promise(resolve => step_timeout(resolve, 50)); + + // 4. Ensure there is exactly 1 disconnection recorded. + assert_equals(num_events, 1); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/reconnect-during-disconnected-event.https.window.js b/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/reconnect-during-disconnected-event.https.window.js new file mode 100644 index 0000000000..bdaf47c661 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/reconnect-during-disconnected-event.https.window.js @@ -0,0 +1,31 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A device that reconnects during the ' + + 'gattserverdisconnected event should still receive ' + + 'gattserverdisconnected events after re-connection.'; + +bluetooth_test(async () => { + const {device, fake_peripheral} = await getConnectedHealthThermometerDevice(); + + const reconnectPromise = new Promise(async (resolve) => { + device.addEventListener('gattserverdisconnected', async () => { + // 2. Reconnect. + await fake_peripheral.setNextGATTConnectionResponse({ + code: HCI_SUCCESS, + }); + await device.gatt.connect(); + + // 3. Disconnect after reconnecting. + const disconnectPromise = eventPromise(device, 'gattserverdisconnected'); + fake_peripheral.simulateGATTDisconnection(); + resolve(disconnectPromise); + }, {once: true}); + }); + + // 1. Disconnect. + await fake_peripheral.simulateGATTDisconnection(); + await reconnectPromise; +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-before-watchAdvertisements.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-before-watchAdvertisements.https.window.js new file mode 100644 index 0000000000..e1ac1fb136 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-before-watchAdvertisements.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'watchAdvertisements() rejects if passed an aborted signal.'; + +bluetooth_test(async (t) => { + let {device} = await getDiscoveredHealthThermometerDevice(); + let abortController = new AbortController(); + abortController.abort(); + + await promise_rejects_dom( + t, 'AbortError', + device.watchAdvertisements({signal: abortController.signal})); + assert_false(device.watchingAdvertisements); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-pending-operation.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-pending-operation.https.window.js new file mode 100644 index 0000000000..c1022ff4a9 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-pending-operation.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'AbortController stops a pending watchAdvertisements() ' + + 'operation.'; + +bluetooth_test(async (t) => { + let {device} = await getDiscoveredHealthThermometerDevice(); + const watcher = new EventWatcher(t, device, ['advertisementreceived']); + let abortController = new AbortController(); + + let watchAdvertisementsPromise = + device.watchAdvertisements({signal: abortController.signal}); + abortController.abort(); + assert_false(device.watchingAdvertisements); + await promise_rejects_dom(t, 'AbortError', watchAdvertisementsPromise); + + await fake_central.simulateAdvertisementReceived( + health_thermometer_ad_packet); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-signal-stops-events.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-signal-stops-events.https.window.js new file mode 100644 index 0000000000..21b6883fee --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-signal-stops-events.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = `AbortController stops 'advertisementreceived' ` + + `events from being fired on the device object.`; + +bluetooth_test(async (t) => { + let {device} = await getDiscoveredHealthThermometerDevice(); + const watcher = new EventWatcher(t, device, ['advertisementreceived']); + let abortController = new AbortController(); + + await device.watchAdvertisements({signal: abortController.signal}); + assert_true(device.watchingAdvertisements); + + let advertisementreceivedPromise = watcher.wait_for('advertisementreceived'); + await fake_central.simulateAdvertisementReceived( + health_thermometer_ad_packet); + await advertisementreceivedPromise; + + abortController.abort(); + assert_false(device.watchingAdvertisements); + + await fake_central.simulateAdvertisementReceived( + health_thermometer_ad_packet); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-subsequent-watchAdvertisements-call-stops-events.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-subsequent-watchAdvertisements-call-stops-events.https.window.js new file mode 100644 index 0000000000..a5da75012b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-subsequent-watchAdvertisements-call-stops-events.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'AbortController on subsequent watchAdvertisements() call ' + + 'cancels the watch advertisements operation.'; + +bluetooth_test(async (t) => { + let {device} = await getDiscoveredHealthThermometerDevice(); + const watcher = new EventWatcher(t, device, ['advertisementreceived']); + + // Start a watchAdvertisements() operation. + await device.watchAdvertisements(); + assert_true(device.watchingAdvertisements); + + // Start a second watchAdvertisements() operation after the first one + // resolves. This operation should resolve successfully. + let abortController = new AbortController(); + await device.watchAdvertisements({signal: abortController.signal}); + abortController.abort(); + assert_false(device.watchingAdvertisements); + + // This advertisement packet should not cause the event to be fired. + await fake_central.simulateAdvertisementReceived(heart_rate_ad_packet); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/advertisementreceived-event-fired.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/advertisementreceived-event-fired.https.window.js new file mode 100644 index 0000000000..fff18bc47e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/advertisementreceived-event-fired.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = `watchAdvertisements() enables 'advertisementreceived' ` + + `events to be fired on the device object.`; + +bluetooth_test(async (t) => { + let {device} = await getDiscoveredHealthThermometerDevice(); + const watcher = new EventWatcher(t, device, ['advertisementreceived']); + + await device.watchAdvertisements(); + assert_true(device.watchingAdvertisements); + + // This advertisement packet represents a different device and should not + // cause an event to be fired on |device|. + await fake_central.simulateAdvertisementReceived(heart_rate_ad_packet); + + let advertisementreceivedPromise = watcher.wait_for('advertisementreceived'); + await fake_central.simulateAdvertisementReceived( + health_thermometer_ad_packet); + let evt = await advertisementreceivedPromise; + assert_equals(evt.device, device); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/blocklisted-manufacturer-data-filtered-from-event.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/blocklisted-manufacturer-data-filtered-from-event.https.window.js new file mode 100644 index 0000000000..c73e3dbad1 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/blocklisted-manufacturer-data-filtered-from-event.https.window.js @@ -0,0 +1,50 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = `Blocked manufacturer data is filtered from the ` + + `advertisement event.`; + +const advertisement_packet_with_blocked_manufacturer_data = { + deviceAddress: '07:07:07:07:07:07', + rssi: -10, + scanRecord: { + name: 'LE Device', + uuids: [uuid1234], + manufacturerData: { + [nonBlocklistedManufacturerId]: nonBlocklistedManufacturerData, + [blocklistedManufacturerId]: blocklistedManufacturerData, + }, + } +}; + +bluetooth_test(async (t) => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: { + address: '07:07:07:07:07:07', + knownServiceUUIDs: [uuid1234], + }, + requestDeviceOptions: { + filters: [{services: [uuid1234]}], + optionalManufacturerData: [nonBlocklistedManufacturerId, blocklistedManufacturerId] + } + }); + const watcher = new EventWatcher(t, device, ['advertisementreceived']); + + await device.watchAdvertisements(); + assert_true(device.watchingAdvertisements); + + let advertisementreceivedPromise = watcher.wait_for('advertisementreceived'); + await fake_central.simulateAdvertisementReceived( + advertisement_packet_with_blocked_manufacturer_data); + let evt = await advertisementreceivedPromise; + assert_equals(evt.device, device); + + // Check if block-listed manufacturer data is filtered out properly. + assert_false(evt.manufacturerData.has(blocklistedManufacturerId)); + + // Check if non blocked-listed manufacturer still exists. + assert_data_maps_equal( + evt.manufacturerData, /*expected_key=*/ nonBlocklistedManufacturerId, nonBlocklistedManufacturerData); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/concurrent-watchAdvertisements-calls.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/concurrent-watchAdvertisements-calls.https.window.js new file mode 100644 index 0000000000..cb6532be68 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/concurrent-watchAdvertisements-calls.https.window.js @@ -0,0 +1,29 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'concurrent watchAdvertisements() calls results in the ' + + `second call rejecting with 'InvalidStateError'`; + +bluetooth_test(async (t) => { + let {device} = await getDiscoveredHealthThermometerDevice(); + const watcher = new EventWatcher(t, device, ['advertisementreceived']); + + // Start a watchAdvertisements() operation. + let firstWatchAdvertisementsPromise = device.watchAdvertisements(); + + // Start a second watchAdvertisements() operation. This operation should + // reject with 'InvalidStateError'. + await promise_rejects_dom( + t, 'InvalidStateError', device.watchAdvertisements()); + + // The first watchAdvertisements() operation should resolve successfully. + await firstWatchAdvertisementsPromise; + + let advertisementreceivedPromise = watcher.wait_for('advertisementreceived'); + await fake_central.simulateAdvertisementReceived( + health_thermometer_ad_packet); + let evt = await advertisementreceivedPromise; + assert_equals(evt.device, device); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/detachedIframe.https.window.js new file mode 100644 index 0000000000..202a8dab7d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/detachedIframe.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device} = await getHealthThermometerDeviceFromIframe(iframe); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await device.watchAdvertisements(); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'TypeError'); +}, 'watchAdvertisements() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/service-and-manufacturer-data-filtered-from-event.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/service-and-manufacturer-data-filtered-from-event.https.window.js new file mode 100644 index 0000000000..f6b93ffb4b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/service-and-manufacturer-data-filtered-from-event.https.window.js @@ -0,0 +1,41 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = `Service and Manufacturer that were not granted with ` + + `requestDevice() are filtered from the advertisement event.`; + +bluetooth_test(async (t) => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: { + address: '07:07:07:07:07:07', + knownServiceUUIDs: [uuid1234, uuid5678, uuidABCD], + }, + requestDeviceOptions: { + filters: [{services: [uuid1234]}], + optionalServices: [uuid5678], + optionalManufacturerData: [0x0001] + } + }); + const watcher = new EventWatcher(t, device, ['advertisementreceived']); + + await device.watchAdvertisements(); + assert_true(device.watchingAdvertisements); + + let advertisementreceivedPromise = watcher.wait_for('advertisementreceived'); + await fake_central.simulateAdvertisementReceived( + service_and_manufacturer_data_ad_packet); + let evt = await advertisementreceivedPromise; + assert_equals(evt.device, device); + + // Check that service data is filtered out properly. + assert_data_maps_equal(evt.serviceData, uuid1234, uuid1234Data); + assert_data_maps_equal(evt.serviceData, uuid5678, uuid5678Data); + assert_false(evt.serviceData.has(uuidABCD)); + + // Check that manufacturer data is filtered out properly. + assert_data_maps_equal( + evt.manufacturerData, /*expected_key=*/ 0x0001, manufacturer1Data); + assert_false(evt.manufacturerData.has(0x0002)); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/subsequent-watchAdvertisements-call.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/subsequent-watchAdvertisements-call.https.window.js new file mode 100644 index 0000000000..797bfd1fa0 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/subsequent-watchAdvertisements-call.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'subsequent watchAdvertisements() calls result in the ' + + 'second call resolving successfully.'; + +bluetooth_test(async (t) => { + let {device} = await getDiscoveredHealthThermometerDevice(); + const watcher = new EventWatcher(t, device, ['advertisementreceived']); + + // Start a watchAdvertisements() operation. + await device.watchAdvertisements(); + + // Start a second watchAdvertisements() operation after the first one + // resolves. This operation should resolve successfully. + await device.watchAdvertisements(); + + let advertisementreceivedPromise = watcher.wait_for('advertisementreceived'); + await fake_central.simulateAdvertisementReceived( + health_thermometer_ad_packet); + let evt = await advertisementreceivedPromise; + assert_equals(evt.device, device); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/watching-two-devices-abort-one-watchAdvertisements.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/watching-two-devices-abort-one-watchAdvertisements.https.window.js new file mode 100644 index 0000000000..8be02adb34 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/watching-two-devices-abort-one-watchAdvertisements.https.window.js @@ -0,0 +1,47 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'AbortController while watching advertisements for two ' + + 'devices stops the correct watchAdvertisements() operation.'; + +bluetooth_test(async (t) => { + let health_thermometer_device; + let heart_rate_device; + { + let {device} = await getDiscoveredHealthThermometerDevice(); + health_thermometer_device = device; + } + { + let {device} = await getHeartRateDevice( + {requestDeviceOptions: heartRateRequestDeviceOptionsDefault}); + heart_rate_device = device; + } + const healthThermometerWatcher = + new EventWatcher(t, health_thermometer_device, ['advertisementreceived']); + const heartRateWatcher = + new EventWatcher(t, heart_rate_device, ['advertisementreceived']); + + await health_thermometer_device.watchAdvertisements(); + + let abortController = new AbortController(); + await heart_rate_device.watchAdvertisements({signal: abortController.signal}); + + assert_true(health_thermometer_device.watchingAdvertisements); + assert_true(heart_rate_device.watchingAdvertisements); + + abortController.abort(); + assert_true(health_thermometer_device.watchingAdvertisements); + assert_false(heart_rate_device.watchingAdvertisements); + + // This should not cause |heart_rate_device| to receive an Event. + await fake_central.simulateAdvertisementReceived(heart_rate_ad_packet); + + let advertisementreceivedPromise = + healthThermometerWatcher.wait_for('advertisementreceived'); + await fake_central.simulateAdvertisementReceived( + health_thermometer_ad_packet); + let evt = await advertisementreceivedPromise; + assert_equals(evt.device, health_thermometer_device); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/device/watchAdvertisements/watching-two-devices.https.window.js b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/watching-two-devices.https.window.js new file mode 100644 index 0000000000..32ec89a1eb --- /dev/null +++ b/testing/web-platform/tests/bluetooth/device/watchAdvertisements/watching-two-devices.https.window.js @@ -0,0 +1,41 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = `Events are fired on correct device with multiple ` + + `watchAdvertisements() calls.`; + +bluetooth_test(async (t) => { + let health_thermometer_device; + let heart_rate_device; + { + let {device} = await getDiscoveredHealthThermometerDevice(); + health_thermometer_device = device; + } + { + let {device} = await getHeartRateDevice( + {requestDeviceOptions: heartRateRequestDeviceOptionsDefault}); + heart_rate_device = device; + } + const healthThermometerWatcher = + new EventWatcher(t, health_thermometer_device, ['advertisementreceived']); + const heartRateWatcher = + new EventWatcher(t, heart_rate_device, ['advertisementreceived']); + + await health_thermometer_device.watchAdvertisements(); + await heart_rate_device.watchAdvertisements(); + + let advertisementreceivedPromise = + heartRateWatcher.wait_for('advertisementreceived'); + await fake_central.simulateAdvertisementReceived(heart_rate_ad_packet); + let heartEvt = await advertisementreceivedPromise; + assert_equals(heartEvt.device, heart_rate_device); + + advertisementreceivedPromise = + healthThermometerWatcher.wait_for('advertisementreceived'); + await fake_central.simulateAdvertisementReceived( + health_thermometer_ad_packet); + let healthEvt = await advertisementreceivedPromise; + assert_equals(healthEvt.device, health_thermometer_device); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/generate.py b/testing/web-platform/tests/bluetooth/generate.py new file mode 100644 index 0000000000..505375d55f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/generate.py @@ -0,0 +1,189 @@ +# Copyright 2016 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# TODO(509038): Delete the file in LayoutTests/bluetooth after all the script +# tests have been migrated to this directory. +"""Generator script for Web Bluetooth LayoutTests. + +For each script-tests/X.js creates the following test files depending on the +contents of X.js +- getPrimaryService/X.https.window.js +- getPrimaryServices/X.https.window.js +- getPrimaryServices/X-with-uuid.https.window.js + +script-tests/X.js files should contain "CALLS([variation1 | variation2 | ...])" +tokens that indicate what files to generate. Each variation in CALLS([...]) +should corresponds to a js function call and its arguments. Additionally a +variation can end in [UUID] to indicate that the generated file's name should +have the -with-uuid suffix. + +The PREVIOUS_CALL token will be replaced with the function that replaced CALLS. + +The FUNCTION_NAME token will be replaced with the name of the function that +replaced CALLS. + +For example, for the following template file: + +// script-tests/example.js +promise_test(() => { + return navigator.bluetooth.requestDevice(...) + .then(device => device.gatt.CALLS([ + getPrimaryService('heart_rate')| + getPrimaryServices('heart_rate')[UUID]])) + .then(device => device.gatt.PREVIOUS_CALL); +}, 'example test for FUNCTION_NAME'); + +this script will generate: + +// getPrimaryService/example.https.window.js +promise_test(() => { + return navigator.bluetooth.requestDevice(...) + .then(device => device.gatt.getPrimaryService('heart_rate')) + .then(device => device.gatt.getPrimaryService('heart_rate')); +}, 'example test for getPrimaryService'); + +// getPrimaryServices/example-with-uuid.https.window.js +promise_test(() => { + return navigator.bluetooth.requestDevice(...) + .then(device => device.gatt.getPrimaryServices('heart_rate')) + .then(device => device.gatt.getPrimaryServices('heart_rate')); +}, 'example test for getPrimaryServices'); + +Run +$ python //third_party/WebKit/LayoutTests/bluetooth/generate.py +and commit the generated files. +""" + +import fnmatch +import os +import re +import sys +import logging + +TEMPLATES_DIR = 'script-tests' + + +class GeneratedTest: + + def __init__(self, data, path, template): + self.data = data + self.path = path + self.template = template + + +def GetGeneratedTests(): + """Yields a GeneratedTest for each call in templates in script-tests.""" + bluetooth_tests_dir = os.path.dirname(os.path.realpath(__file__)) + + # Read Base Test Template. + base_template_file_handle = open( + os.path.join( + bluetooth_tests_dir, + TEMPLATES_DIR, + 'base_test_js.template' + ), 'r') + base_template_file_data = base_template_file_handle.read().decode('utf-8') + base_template_file_handle.close() + + # Get Templates. + + template_path = os.path.join(bluetooth_tests_dir, TEMPLATES_DIR) + + available_templates = [] + for root, _, files in os.walk(template_path): + for template in files: + if template.endswith('.js'): + available_templates.append(os.path.join(root, template)) + + # Generate Test Files + for template in available_templates: + # Read template + template_file_handle = open(template, 'r') + template_file_data = template_file_handle.read().decode('utf-8') + template_file_handle.close() + + template_name = os.path.splitext(os.path.basename(template))[0] + + # Find function names in multiline pattern: CALLS( [ function_name,function_name2[UUID] ]) + result = re.search( + r'CALLS\(' + # CALLS( + r'[^\[]*' + # Any characters not [, allowing for new lines. + r'\[' + # [ + r'(.*?)' + # group matching: function_name(), function_name2[UUID] + r'\]\)', # adjacent closing characters: ]) + template_file_data, re.MULTILINE | re.DOTALL) + + if result is None: + raise Exception('Template must contain \'CALLS\' tokens') + + new_test_file_data = base_template_file_data.replace('TEST', + template_file_data) + # Replace CALLS([...]) with CALLS so that we don't have to replace the + # CALLS([...]) for every new test file. + new_test_file_data = new_test_file_data.replace(result.group(), 'CALLS') + + # Replace 'PREVIOUS_CALL' with 'CALLS' so that we can replace it while + # replacing CALLS. + new_test_file_data = new_test_file_data.replace('PREVIOUS_CALL', 'CALLS') + + for call in result.group(1).split('|'): + # Parse call + call = call.strip() + function_name, args, uuid_suffix = re.search(r'(.*?)\((.*)\)(\[UUID\])?', call).groups() + + # Replace template tokens + call_test_file_data = new_test_file_data + call_test_file_data = call_test_file_data.replace('CALLS', '{}({})'.format(function_name, args)) + call_test_file_data = call_test_file_data.replace('FUNCTION_NAME', function_name) + + # Get test file name + group_dir = os.path.basename(os.path.abspath(os.path.join(template, os.pardir))) + + call_test_file_name = 'gen-{}{}.https.window.js'.format(template_name, '-with-uuid' if uuid_suffix else '') + call_test_file_path = os.path.join(bluetooth_tests_dir, group_dir, function_name, call_test_file_name) + + yield GeneratedTest(call_test_file_data, call_test_file_path, template) + +def main(): + logging.basicConfig(level=logging.INFO) + previous_generated_files = set() + current_path = os.path.dirname(os.path.realpath(__file__)) + for root, _, filenames in os.walk(current_path): + for filename in fnmatch.filter(filenames, 'gen-*.https.window.js'): + previous_generated_files.add(os.path.join(root, filename)) + + generated_files = set() + for generated_test in GetGeneratedTests(): + prev_len = len(generated_files) + generated_files.add(generated_test.path) + if prev_len == len(generated_files): + logging.info('Generated the same test twice for template:\n%s', + generated_test.template) + + # Create or open test file + directory = os.path.dirname(generated_test.path) + if not os.path.exists(directory): + os.makedirs(directory) + test_file_handle = open(generated_test.path, 'wb') + + # Write contents + test_file_handle.write(generated_test.data.encode('utf-8')) + test_file_handle.close() + + new_generated_files = generated_files - previous_generated_files + if len(new_generated_files) != 0: + logging.info('Newly generated tests:') + for generated_file in new_generated_files: + logging.info(generated_file) + + obsolete_files = previous_generated_files - generated_files + if len(obsolete_files) != 0: + logging.warning('The following files might be obsolete:') + for generated_file in obsolete_files: + logging.warning(generated_file) + + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/testing/web-platform/tests/bluetooth/generate_test.py b/testing/web-platform/tests/bluetooth/generate_test.py new file mode 100755 index 0000000000..6f46ae913a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/generate_test.py @@ -0,0 +1,56 @@ +#!/usr/bin/python + +# Copyright 2016 The Chromium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# TODO(50903): Delete the file in LayoutTests/bluetooth after all the tests have +# been migrated to this directory. +"""Test that the set of gen-* files is the same as the generated files.""" + +import fnmatch +import os +import sys +import generate +import logging + +UPDATE_TIP = 'To update the generated tests, run:\n' \ + '$ python third_party/WebKit/LayoutTests/bluetooth/generate.py' + + +def main(): + logging.basicConfig(level=logging.INFO) + logging.info(UPDATE_TIP) + generated_files = set() + # Tests data in gen-* files is the same as the data generated. + for generated_test in generate.GetGeneratedTests(): + generated_files.add(generated_test.path) + try: + with open(generated_test.path, 'r') as f: + data = f.read().decode('utf-8') + if data != generated_test.data: + logging.error('%s does not match template', generated_test.path) + return -1 + except IOError as e: + if e.errno == 2: + logging.error('Missing generated test:\n%s\nFor template:\n%s', + generated_test.path, + generated_test.template) + return -1 + + # Tests that there are no obsolete generated files. + previous_generated_files = set() + current_path = os.path.dirname(os.path.realpath(__file__)) + for root, _, filenames in os.walk(current_path): + for filename in fnmatch.filter(filenames, 'gen-*.https.window.js'): + previous_generated_files.add(os.path.join(root, filename)) + + if previous_generated_files != generated_files: + logging.error('There are extra generated tests. Please remove them.') + for test_path in previous_generated_files - generated_files: + logging.error('%s', test_path) + return -1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/testing/web-platform/tests/bluetooth/getAvailability/reject_opaque_origin.https.html b/testing/web-platform/tests/bluetooth/getAvailability/reject_opaque_origin.https.html new file mode 100644 index 0000000000..8745fc9551 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/getAvailability/reject_opaque_origin.https.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + 'use strict'; + + promise_test(async (t) => { + await promise_rejects_dom( + t, 'SecurityError', navigator.bluetooth.getAvailability(), + 'getAvailability() should throw a SecurityError DOMException when called from a context where the top-level document has an opaque origin.'); + }, 'Calls to Bluetooth APIs from an origin with opaque top origin get blocked.'); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/getAvailability/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/bluetooth/getAvailability/reject_opaque_origin.https.html.headers new file mode 100644 index 0000000000..c7e4e7cc5b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/getAvailability/reject_opaque_origin.https.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/getAvailability/sandboxed_iframe.https.window.js b/testing/web-platform/tests/bluetooth/getAvailability/sandboxed_iframe.https.window.js new file mode 100644 index 0000000000..c5e3d1e890 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/getAvailability/sandboxed_iframe.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +'use strict'; + +let iframe = document.createElement('iframe'); + +bluetooth_test(async () => { + await getConnectedHealthThermometerDevice(); + await new Promise(resolve => { + iframe.src = '/bluetooth/resources/health-thermometer-iframe.html'; + iframe.sandbox.add('allow-scripts'); + iframe.allow = 'bluetooth'; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + await new Promise(resolve => { + iframe.contentWindow.postMessage({type: 'GetAvailability'}, '*'); + + window.addEventListener('message', (messageEvent) => { + assert_false(/^FAIL: .*/.test(messageEvent.data)); + resolve(); + }); + }); +}, 'Calls to Bluetooth APIs from a sandboxed iframe are valid.');
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/getDevices/granted-devices-with-services.https.window.js b/testing/web-platform/tests/bluetooth/getDevices/granted-devices-with-services.https.window.js new file mode 100644 index 0000000000..3228543617 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/getDevices/granted-devices-with-services.https.window.js @@ -0,0 +1,72 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'getDevices() resolves with permitted devices that can be ' + + 'GATT connected to.'; + +bluetooth_test(async () => { + // Set up two connectable Bluetooth devices with their services discovered. + // One device is a Health Thermometer device with the 'health_thermometer' + // service while the other is a Heart Rate device with the 'heart_rate' + // service. Both devices contain the 'generic_access' service. + let fake_peripherals = await setUpHealthThermometerAndHeartRateDevices(); + for (let fake_peripheral of fake_peripherals) { + await fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}); + await fake_peripheral.addFakeService({uuid: 'generic_access'}); + if (fake_peripheral.address === '09:09:09:09:09:09') + await fake_peripheral.addFakeService({uuid: 'health_thermometer'}); + else + await fake_peripheral.addFakeService({uuid: 'heart_rate'}); + await fake_peripheral.setNextGATTDiscoveryResponse({code: HCI_SUCCESS}); + } + + // Request the Health Thermometer device with access to its 'generic_access' + // service. + await requestDeviceWithTrustedClick( + {filters: [{name: 'Health Thermometer', services: ['generic_access']}]}); + let devices = await navigator.bluetooth.getDevices(); + assert_equals( + devices.length, 1, + `getDevices() should return the 'Health Thermometer' device.`); + + // Only the 'generic_access' service can be accessed. + try { + await devices[0].gatt.connect(); + await devices[0].gatt.getPrimaryService('generic_access'); + assert_promise_rejects_with_message( + devices[0].gatt.getPrimaryService('health_thermometer'), + {name: 'SecurityError'}); + } catch (err) { + assert_unreached(`${err.name}: ${err.message}`); + } + + // Request the Heart Rate device with access to both of its services. + await requestDeviceWithTrustedClick({ + filters: [{name: 'Heart Rate', services: ['generic_access', 'heart_rate']}] + }); + devices = await navigator.bluetooth.getDevices(); + assert_equals( + devices.length, 2, + `getDevices() should return the 'Health Thermometer' and 'Health ` + + `Monitor' devices`); + + // All of Heart Rate device's services can be accessed, while only the + // 'generic_access' service can be accessed on Health Thermometer. + try { + for (let device of devices) { + await device.gatt.connect(); + await device.gatt.getPrimaryService('generic_access'); + if (device.name === 'Heart Rate') { + await device.gatt.getPrimaryService('heart_rate'); + } else { + assert_promise_rejects_with_message( + devices[0].gatt.getPrimaryService('health_thermometer'), + {name: 'SecurityError'}); + } + } + } catch (err) { + assert_unreached(`${err.name}: ${err.message}`); + } +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/getDevices/no-granted-devices.https.window.js b/testing/web-platform/tests/bluetooth/getDevices/no-granted-devices.https.window.js new file mode 100644 index 0000000000..304aa3820d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/getDevices/no-granted-devices.https.window.js @@ -0,0 +1,15 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'getDevices() resolves with empty array if no device ' + + 'permissions have been granted.'; + +bluetooth_test(async () => { + await navigator.bluetooth.test.simulateCentral({state: 'powered-on'}); + let devices = await navigator.bluetooth.getDevices(); + + assert_equals( + 0, devices.length, 'getDevices() should resolve with an empty array'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/getDevices/reject_opaque_origin.https.html b/testing/web-platform/tests/bluetooth/getDevices/reject_opaque_origin.https.html new file mode 100644 index 0000000000..64b2808fbc --- /dev/null +++ b/testing/web-platform/tests/bluetooth/getDevices/reject_opaque_origin.https.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + 'use strict'; + + promise_test(async (t) => { + await promise_rejects_dom( + t, 'SecurityError', navigator.bluetooth.getDevices(), + 'getDevices() should throw a SecurityError DOMException when called from a context where the top-level document has an opaque origin.'); + }, 'Calls to Bluetooth APIs from an origin with opaque top origin get blocked.'); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/getDevices/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/bluetooth/getDevices/reject_opaque_origin.https.html.headers new file mode 100644 index 0000000000..c7e4e7cc5b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/getDevices/reject_opaque_origin.https.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/getDevices/returns-same-bluetooth-device-object.https.window.js b/testing/web-platform/tests/bluetooth/getDevices/returns-same-bluetooth-device-object.https.window.js new file mode 100644 index 0000000000..81c0f6a97e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/getDevices/returns-same-bluetooth-device-object.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'multiple calls to getDevices() resolves with the same' + + 'BluetoothDevice objects for each granted Bluetooth device.'; + +bluetooth_test(async () => { + await getConnectedHealthThermometerDevice(); + let firstDevices = await navigator.bluetooth.getDevices(); + assert_equals( + firstDevices.length, 1, 'getDevices() should return the granted device.'); + + let secondDevices = await navigator.bluetooth.getDevices(); + assert_equals( + secondDevices.length, 1, + 'getDevices() should return the granted device.'); + assert_equals( + firstDevices[0], secondDevices[0], + 'getDevices() should produce the same BluetoothDevice objects for a ' + + 'given Bluetooth device.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/getDevices/sandboxed_iframe.https.window.js b/testing/web-platform/tests/bluetooth/getDevices/sandboxed_iframe.https.window.js new file mode 100644 index 0000000000..22cfd17d46 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/getDevices/sandboxed_iframe.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +'use strict'; + +let iframe = document.createElement('iframe'); + +bluetooth_test(async () => { + await getConnectedHealthThermometerDevice(); + await new Promise(resolve => { + iframe.src = '/bluetooth/resources/health-thermometer-iframe.html'; + iframe.sandbox.add('allow-scripts'); + iframe.allow = 'bluetooth'; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + await new Promise(resolve => { + iframe.contentWindow.postMessage({type: 'GetDevices'}, '*'); + + window.addEventListener('message', (messageEvent) => { + assert_false(/^FAIL: .*/.test(messageEvent.data)); + resolve(); + }); + }); +}, 'Calls to Bluetooth APIs from a sandboxed iframe are valid.');
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/idl/idl-Bluetooth.https.window.js b/testing/web-platform/tests/bluetooth/idl/idl-Bluetooth.https.window.js new file mode 100644 index 0000000000..2b40eaff49 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/idl/idl-Bluetooth.https.window.js @@ -0,0 +1,18 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +'use strict'; +const test_desc = 'Bluetooth IDL test'; + +test(() => { + assert_throws_js( + TypeError, () => new Bluetooth(), + 'the constructor should not be callable with "new"'); + assert_throws_js( + TypeError, () => Bluetooth(), + 'the constructor should not be callable'); + + // Bluetooth implements BluetoothDiscovery; + assert_true('requestDevice' in navigator.bluetooth); + assert_true('getDevices' in navigator.bluetooth); + assert_equals(navigator.bluetooth.requestDevice.length, 0); +}, test_desc);
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/idl/idl-BluetoothDevice.https.window.js b/testing/web-platform/tests/bluetooth/idl/idl-BluetoothDevice.https.window.js new file mode 100644 index 0000000000..c715926df3 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/idl/idl-BluetoothDevice.https.window.js @@ -0,0 +1,37 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc_idl = 'BluetoothDevice IDL test.'; + +test(() => { + assert_throws_js( + TypeError, () => new BluetoothDevice(), + 'the constructor should not be callable with "new"'); + assert_throws_js( + TypeError, () => BluetoothDevice(), + 'the constructor should not be callable'); +}, test_desc_idl); + +const test_desc_attr = 'BluetoothDevice attributes.'; +let device; + +bluetooth_test(async () => { + let {device} = await getConnectedHealthThermometerDevice(); + + assert_equals(device.constructor.name, 'BluetoothDevice'); + var old_device_id = device.id; + assert_throws_js( + TypeError, () => device.id = 'overwritten', + 'the device id should not be writable'); + assert_throws_js( + TypeError, () => device.name = 'overwritten', + 'the device name should not be writable'); + assert_throws_js( + TypeError, () => device.watchingAdvertisements = true, + 'the device watchingAdvertisements should not be writable'); + assert_equals(device.id, old_device_id); + assert_equals(device.name, 'Health Thermometer'); + assert_equals(device.watchingAdvertisements, false); +}, test_desc_attr); diff --git a/testing/web-platform/tests/bluetooth/idl/idl-BluetoothUUID.window.js b/testing/web-platform/tests/bluetooth/idl/idl-BluetoothUUID.window.js new file mode 100644 index 0000000000..cf9c14bb2c --- /dev/null +++ b/testing/web-platform/tests/bluetooth/idl/idl-BluetoothUUID.window.js @@ -0,0 +1,177 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +'use strict' + +var base_uuid = '00000000-0000-1000-8000-00805f9b34fb' + +test(() => { + let base_alias = 0x0 + assert_equals(BluetoothUUID.getService(base_alias), base_uuid); + assert_equals(BluetoothUUID.getCharacteristic(base_alias), base_uuid); + assert_equals(BluetoothUUID.getDescriptor(base_alias), base_uuid); +}, '0x0 should produce valid UUID.'); + +test(() => { + assert_equals(BluetoothUUID.getService(NaN), base_uuid); + assert_equals(BluetoothUUID.getCharacteristic(NaN), base_uuid); + assert_equals(BluetoothUUID.getDescriptor(NaN), base_uuid); +}, 'NaN returns basic uuid'); + +test( + () => { + let max_uuid = 'ffffffff-0000-1000-8000-00805f9b34fb'; + let nine_digits = 0xfffffffff; + let thirteen_digits = 0xfffffffffffff; + let fourteen_digits = 0xffffffffffffff; + assert_equals(BluetoothUUID.getService(nine_digits), max_uuid); + assert_equals(BluetoothUUID.getCharacteristic(nine_digits), max_uuid); + assert_equals(BluetoothUUID.getDescriptor(nine_digits), max_uuid); + assert_equals(BluetoothUUID.getService(thirteen_digits), max_uuid); + assert_equals(BluetoothUUID.getCharacteristic(thirteen_digits), max_uuid); + assert_equals(BluetoothUUID.getDescriptor(thirteen_digits), max_uuid); + assert_equals(BluetoothUUID.getService(fourteen_digits), base_uuid); + assert_equals( + BluetoothUUID.getCharacteristic(fourteen_digits), base_uuid); + assert_equals(BluetoothUUID.getDescriptor(fourteen_digits), base_uuid); + }, + 'Values between 0xfffffffff (8 digits) and 0xffffffffffffff (14 digits)' + + 'should return max UUID'); + +test(() => { + assert_equals(BluetoothUUID.getService(Infinity), base_uuid); + assert_equals(BluetoothUUID.getCharacteristic(Infinity), base_uuid); + assert_equals(BluetoothUUID.getDescriptor(Infinity), base_uuid); +}, 'Infinity returns base UUID'); + +test(() => { + let deadbeef_alias = 0xDEADBEEF; + let deadbeef_uuid = 'deadbeef-0000-1000-8000-00805f9b34fb'; + assert_equals(BluetoothUUID.getService(deadbeef_alias), deadbeef_uuid); + assert_equals(BluetoothUUID.getCharacteristic(deadbeef_alias), deadbeef_uuid); + assert_equals(BluetoothUUID.getDescriptor(deadbeef_alias), deadbeef_uuid); +}, '0xdeadbeef should produce valid UUID.'); + +test(() => { + let adeadbeef_alias = 0xADEADBEEF; + let adeadbeef_uuid = 'deadbeef-0000-1000-8000-00805f9b34fb'; + assert_equals(BluetoothUUID.getService(adeadbeef_alias), adeadbeef_uuid); + assert_equals( + BluetoothUUID.getCharacteristic(adeadbeef_alias), adeadbeef_uuid); + assert_equals(BluetoothUUID.getDescriptor(adeadbeef_alias), adeadbeef_uuid); +}, 'Only first 32bits should be used.'); + +test(() => { + let basic_uuid = '1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d'; + assert_equals(BluetoothUUID.getService(basic_uuid), basic_uuid); + assert_equals(BluetoothUUID.getCharacteristic(basic_uuid), basic_uuid); + assert_equals(BluetoothUUID.getDescriptor(basic_uuid), basic_uuid); +}, 'A valid UUID String should return the same UUID.'); + +test(() => { + let all_caps_uuid = '1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D'; + assert_throws_js(TypeError, () => BluetoothUUID.getService(all_caps_uuid)); + assert_throws_js( + TypeError, () => BluetoothUUID.getCharacteristic(all_caps_uuid)); + assert_throws_js(TypeError, () => BluetoothUUID.getDescriptor(all_caps_uuid)); +}, 'A UUID String with uppercase letters is an invalid UUID.'); + +test(() => { + let string_alias = 'deadbeef'; + assert_throws_js(TypeError, () => BluetoothUUID.getService(string_alias)); + assert_throws_js( + TypeError, () => BluetoothUUID.getCharacteristic(string_alias)); + assert_throws_js(TypeError, () => BluetoothUUID.getDescriptor(string_alias)); +}, 'A 32bit *String* alias is invalid.'); + +test(() => { + let invalid_character_uuid = '0000000g-0000-1000-8000-00805f9b34fb'; + assert_throws_js( + TypeError, () => BluetoothUUID.getService(invalid_character_uuid)); + assert_throws_js( + TypeError, + () => BluetoothUUID.getCharacteristic(invalid_character_uuid)); + assert_throws_js( + TypeError, () => BluetoothUUID.getDescriptor(invalid_character_uuid)); +}, 'A UUID with invalid characters is an invalid UUID.'); + +test(() => { + assert_equals( + BluetoothUUID.getService('alert_notification'), + '00001811-0000-1000-8000-00805f9b34fb'); + assert_equals( + BluetoothUUID.getCharacteristic('aerobic_heart_rate_lower_limit'), + '00002a7e-0000-1000-8000-00805f9b34fb'); + assert_equals( + BluetoothUUID.getDescriptor('gatt.characteristic_extended_properties'), + '00002900-0000-1000-8000-00805f9b34fb'); +}, 'A valid UUID from a name.'); + +test(() => { + assert_throws_js(TypeError, () => { + BluetoothUUID.getService('aerobic_heart_rate_lower_limit'); + }); + assert_throws_js(TypeError, () => { + BluetoothUUID.getService('gatt.characteristic_extended_properties'); + }); + assert_throws_js(TypeError, () => { + BluetoothUUID.getCharacteristic('alert_notification'); + }); + assert_throws_js(TypeError, () => { + BluetoothUUID.getCharacteristic('gatt.characteristic_extended_properties'); + }); + assert_throws_js(TypeError, () => { + BluetoothUUID.getDescriptor('alert_notification'); + }); + assert_throws_js(TypeError, () => { + BluetoothUUID.getDescriptor('aerobic_heart_rate_lower_limit'); + }); +}, 'Make sure attributes don\'t share a map'); + +test(() => { + let wrong_name = 'wrong_name'; + assert_throws_js(TypeError, () => BluetoothUUID.getService(wrong_name)); + assert_throws_js(TypeError, () => BluetoothUUID.getCharacteristic(wrong_name)); + assert_throws_js(TypeError, () => BluetoothUUID.getDescriptor(wrong_name)); +}, 'Invalid Descriptor name'); + +test(() => { + let object = {}; + let array = []; + let func = () => {}; + + // cannonicalUUID + assert_throws_js(TypeError, () => BluetoothUUID.canonicalUUID(object)); + // [] converts to '', which converts to 0 before the range check. + assert_equals(BluetoothUUID.canonicalUUID(array), base_uuid); + assert_throws_js(TypeError, () => BluetoothUUID.canonicalUUID(func)); + assert_throws_js(TypeError, () => BluetoothUUID.canonicalUUID(undefined)); + assert_equals(BluetoothUUID.canonicalUUID(null), base_uuid); + assert_equals(BluetoothUUID.canonicalUUID(false), base_uuid); + assert_equals( + BluetoothUUID.canonicalUUID(true), BluetoothUUID.canonicalUUID(1)); + assert_throws_js(TypeError, () => BluetoothUUID.canonicalUUID(NaN)); + + // getService + assert_throws_js(TypeError, () => BluetoothUUID.getService(object)); + assert_throws_js(TypeError, () => BluetoothUUID.getService(array)); + assert_throws_js(TypeError, () => BluetoothUUID.getService(func)); + assert_throws_js(TypeError, () => BluetoothUUID.getService(undefined)); + assert_throws_js(TypeError, () => BluetoothUUID.getService(null)); + assert_throws_js(TypeError, () => BluetoothUUID.getService(false)); + + // getCharacteristic + assert_throws_js(TypeError, () => BluetoothUUID.getCharacteristic(object)); + assert_throws_js(TypeError, () => BluetoothUUID.getCharacteristic(array)); + assert_throws_js(TypeError, () => BluetoothUUID.getCharacteristic(func)); + assert_throws_js(TypeError, () => BluetoothUUID.getCharacteristic(undefined)); + assert_throws_js(TypeError, () => BluetoothUUID.getCharacteristic(null)); + assert_throws_js(TypeError, () => BluetoothUUID.getCharacteristic(false)); + + // getDescriptor + assert_throws_js(TypeError, () => BluetoothUUID.getDescriptor(object)); + assert_throws_js(TypeError, () => BluetoothUUID.getDescriptor(array)); + assert_throws_js(TypeError, () => BluetoothUUID.getDescriptor(func)); + assert_throws_js(TypeError, () => BluetoothUUID.getDescriptor(undefined)); + assert_throws_js(TypeError, () => BluetoothUUID.getDescriptor(null)); + assert_throws_js(TypeError, () => BluetoothUUID.getDescriptor(false)); +}, 'Non-number and non-strings'); diff --git a/testing/web-platform/tests/bluetooth/idl/idl-NavigatorBluetooth.https.window.js b/testing/web-platform/tests/bluetooth/idl/idl-NavigatorBluetooth.https.window.js new file mode 100644 index 0000000000..a087d30896 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/idl/idl-NavigatorBluetooth.https.window.js @@ -0,0 +1,12 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +'use strict'; +const test_desc = '[SameObject] test for navigator.bluetooth'; + +test(() => { + assert_true('bluetooth' in navigator, 'navigator.bluetooth exists.'); +}, 'navigator.bluetooth IDL test'); + +test(() => { + assert_equals(navigator.bluetooth, navigator.bluetooth); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/idl/idl-NavigatorBluetooth.window.js b/testing/web-platform/tests/bluetooth/idl/idl-NavigatorBluetooth.window.js new file mode 100644 index 0000000000..db6bf89f9f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/idl/idl-NavigatorBluetooth.window.js @@ -0,0 +1,7 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +'use strict'; + +test(() => { + assert_false('bluetooth' in navigator); +}, 'navigator.bluetooth not available in insecure contexts'); diff --git a/testing/web-platform/tests/bluetooth/idl/idlharness.tentative.https.window.js b/testing/web-platform/tests/bluetooth/idl/idlharness.tentative.https.window.js new file mode 100644 index 0000000000..a632060e20 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/idl/idlharness.tentative.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +'use strict'; + +// https://webbluetoothcg.github.io/web-bluetooth/ + +idl_test( + ['web-bluetooth'], + ['dom', 'html', 'permissions'], + idl_array => { + try { + self.event = new BluetoothAdvertisingEvent('type'); + } catch(e) { + // Surfaced when 'event' is undefined below. + } + + idl_array.add_objects({ + Navigator: ['navigator'], + Bluetooth: ['navigator.bluetooth'], + BluetoothAdvertisingEvent: ['event'], + }); + } +); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/device-with-empty-name.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/device-with-empty-name.https.window.js new file mode 100644 index 0000000000..15bde6a933 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/device-with-empty-name.https.window.js @@ -0,0 +1,19 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Device with empty name and no UUIDs nearby. Should be ' + + 'found if acceptAllDevices is true.'; + +bluetooth_test(async () => { + let { device } = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: { + name: '' + }, + requestDeviceOptions: { + acceptAllDevices: true + } + }); + assert_equals(device.name, ''); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.window.js new file mode 100644 index 0000000000..f3373a6bb6 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/device-with-name.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +const test_desc = + 'A device with name and no UUIDs nearby. Should be found if ' + + 'acceptAllDevices is true.'; +const name = 'LE Device'; + +bluetooth_test(async () => { + let { device } = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: { + name: name + }, + requestDeviceOptions: { + acceptAllDevices: true + } + }); + assert_equals(device.name, name); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.window.js new file mode 100644 index 0000000000..5226a645a8 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/optional-services-missing.https.window.js @@ -0,0 +1,21 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'requestDevice called with acceptAllDevices: true and ' + + 'with no optionalServices. Should not get access to any services.'; +const expected = new DOMException( + 'Origin is not allowed to access any service. ' + + 'Tip: Add the service UUID to \'optionalServices\' in ' + + 'requestDevice() options. https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test( + async () => { + let { device } = await getConnectedHealthThermometerDevice( + { acceptAllDevices: true }); + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(), expected); + }, + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.window.js new file mode 100644 index 0000000000..7c200d03f1 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/acceptAllDevices/optional-services-present.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'requestDevice called with acceptAllDevices: true and with ' + + 'optionalServices. Should get access to services.'; + +bluetooth_test( + async () => { + await getTwoHealthThermometerServicesDevice() + let device = await requestDeviceWithTrustedClick({ + acceptAllDevices: true, + optionalServices: ['health_thermometer'] + }); + let gattServer = await device.gatt.connect(); + let services = await gattServer.getPrimaryServices(); + assert_equals(services.length, 2); + services.forEach(service => { + assert_equals( + service.uuid, + BluetoothUUID.getService('health_thermometer')); + }); + }, + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/blocklisted-manufacturer-data-in-filter.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/blocklisted-manufacturer-data-in-filter.https.window.js new file mode 100644 index 0000000000..2dae7f4cc6 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/blocklisted-manufacturer-data-in-filter.https.window.js @@ -0,0 +1,29 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Reject with SecurityError if requesting a blocklisted ' + + 'manufacturer data.'; + +const expected = new DOMException( + 'requestDevice() called with a filter containing a blocklisted UUID ' + + 'or manufacturer data. https://goo.gl/4NeimX', + 'SecurityError'); + +bluetooth_test(async () => { + await assert_promise_rejects_with_message( + setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {knownServiceUUIDs: ['heart_rate']}, + requestDeviceOptions: { + filters: [{ + services: ['heart_rate'], + manufacturerData: [{ + companyIdentifier: blocklistedManufacturerId, + dataPrefix: blocklistedManufacturerData, + }], + }] + } + }), + expected, 'Requesting blocklisted service rejects.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.js new file mode 100644 index 0000000000..80eaf14447 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/blocklisted-service-in-filter.https.window.js @@ -0,0 +1,21 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Reject with SecurityError if requesting a blocklisted ' + + 'service.'; +const expected = new DOMException( + 'requestDevice() called with a filter containing a blocklisted UUID ' + + 'or manufacturer data. https://goo.gl/4NeimX', + 'SecurityError'); + +bluetooth_test(async () => { + await assert_promise_rejects_with_message( + setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {knownServiceUUIDs: ['human_interface_device']}, + requestDeviceOptions: + {filters: [{services: ['human_interface_device']}]} + }), + expected, 'Requesting blocklisted service rejects.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.window.js new file mode 100644 index 0000000000..4c01974e55 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/blocklisted-service-in-optionalServices.https.window.js @@ -0,0 +1,29 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Blocklisted UUID in optionalServices is removed and ' + + 'access not granted.'; +const expected = new DOMException( + 'Origin is not allowed to access the ' + + 'service. Tip: Add the service UUID to \'optionalServices\' in ' + + 'requestDevice() options. https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(async () => { + let {device, fake_peripheral} = await getDiscoveredHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['human_interface_device'] + }); + await fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}); + await device.gatt.connect(); + Promise.all([ + assert_promise_rejects_with_message( + device.gatt.getPrimaryService('human_interface_device'), expected, + 'Blocklisted service not accessible.'), + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('human_interface_device'), expected, + 'Blocklisted services not accessible.') + ]) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/data-prefix-and-mask-size.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/data-prefix-and-mask-size.https.window.js new file mode 100644 index 0000000000..fa2645093a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/data-prefix-and-mask-size.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = + 'Manufacturer data mask size must be equal to dataPrefix size.'; + +bluetooth_test(async (t) => { + const companyIdentifier = 0x0001; + const dataPrefix = new Uint8Array([0x01, 0x02, 0x03, 0x04]); + const mask = new Uint8Array([0xff]); + + await promise_rejects_js( + t, TypeError, + requestDeviceWithTrustedClick( + {filters: [{manufacturerData: [{companyIdentifier, mask}]}]})); + await promise_rejects_js( + t, TypeError, requestDeviceWithTrustedClick({ + filters: [{manufacturerData: [{companyIdentifier, dataPrefix, mask}]}] + })); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/dataPrefix-buffer-is-detached.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/dataPrefix-buffer-is-detached.https.window.js new file mode 100644 index 0000000000..936ca4735c --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/dataPrefix-buffer-is-detached.https.window.js @@ -0,0 +1,33 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'dataPrefix value buffer must not be detached'; + +function detachBuffer(buffer) { + window.postMessage('', '*', [buffer]); +} + +bluetooth_test(async (t) => { + const companyIdentifier = 0x0001; + + const typed_array = Uint8Array.of(1, 2); + detachBuffer(typed_array.buffer); + + await promise_rejects_dom( + t, 'InvalidStateError', requestDeviceWithTrustedClick({ + filters: + [{manufacturerData: [{companyIdentifier, dataPrefix: typed_array}]}] + })); + + const array_buffer = Uint8Array.of(3, 4).buffer; + detachBuffer(array_buffer); + + await promise_rejects_dom( + t, 'InvalidStateError', requestDeviceWithTrustedClick({ + filters: [ + {manufacturerData: [{companyIdentifier, dataPrefix: array_buffer}]} + ] + })); +}, test_desc);
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.window.js new file mode 100644 index 0000000000..20ed383d39 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/device-name-longer-than-29-bytes.https.window.js @@ -0,0 +1,16 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A device name between 29 and 248 bytes is valid.'; +const DEVICE_NAME = 'a_device_name_that_is_longer_than_29_bytes_but_' + + 'shorter_than_248_bytes'; + +bluetooth_test(async () => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {name: DEVICE_NAME}, + requestDeviceOptions: {filters: [{name: DEVICE_NAME}]} + }); + assert_equals(device.name, DEVICE_NAME) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-dataPrefix.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-dataPrefix.https.window.js new file mode 100644 index 0000000000..75e12219cc --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-dataPrefix.https.window.js @@ -0,0 +1,16 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'dataPrefix when present must be non-empty'; + +bluetooth_test(async (t) => { + await promise_rejects_js( + t, TypeError, requestDeviceWithTrustedClick({ + filters: [{ + manufacturerData: + [{companyIdentifier: 1, dataPrefix: new Uint8Array()}] + }] + })); +}, test_desc);
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.window.js new file mode 100644 index 0000000000..bfe94f2721 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-filter.https.window.js @@ -0,0 +1,12 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A filter must restrict the devices in some way.'; +const expected = new TypeError(); + +bluetooth_test( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick({filters: [{}]}), expected), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-filters-member.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-filters-member.https.window.js new file mode 100644 index 0000000000..3265e54fd8 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-filters-member.https.window.js @@ -0,0 +1,16 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'An empty |filters| member should result in a TypeError'; +const expected = new DOMException( + 'Failed to execute \'requestDevice\' on ' + + '\'Bluetooth\': \'filters\' member must be non-empty to ' + + 'find any devices.', + new TypeError()); + +bluetooth_test( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick({filters: []}), expected), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-manufacturerData-member.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-manufacturerData-member.https.window.js new file mode 100644 index 0000000000..0996137f51 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-manufacturerData-member.https.window.js @@ -0,0 +1,35 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'requestDevice with empty manufacturerData. ' + + 'Should reject with TypeError.'; +const test_specs = [ + {filters: [{manufacturerData: []}]}, + {filters: [{manufacturerData: [], name: 'Name'}]}, + {filters: [{manufacturerData: [], services: ['heart_rate']}]}, + {filters: [{manufacturerData: [], name: 'Name', services: ['heart_rate']}]}, + {filters: [{manufacturerData: []}], optionalServices: ['heart_rate']}, { + filters: [{manufacturerData: [], name: 'Name'}], + optionalServices: ['heart_rate'] + }, + { + filters: [{manufacturerData: [], services: ['heart_rate']}], + optionalServices: ['heart_rate'] + }, + { + filters: [{manufacturerData: [], name: 'Name', services: ['heart_rate']}], + optionalServices: ['heart_rate'] + } +]; + +bluetooth_test((t) => { + let test_promises = Promise.resolve(); + test_specs.forEach(args => { + test_promises = test_promises.then( + () => promise_rejects_js( + t, TypeError, requestDeviceWithTrustedClick(args))); + }); + return test_promises; +}, test_desc);
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-namePrefix.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-namePrefix.https.window.js new file mode 100644 index 0000000000..8ce2e64967 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-namePrefix.https.window.js @@ -0,0 +1,33 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'requestDevice with empty namePrefix. ' + + 'Should reject with TypeError.'; +const expected = new TypeError(); +const test_specs = [ + {filters: [{namePrefix: ''}]}, {filters: [{namePrefix: '', name: 'Name'}]}, + {filters: [{namePrefix: '', services: ['heart_rate']}]}, + {filters: [{namePrefix: '', name: 'Name', services: ['heart_rate']}]}, + {filters: [{namePrefix: ''}], optionalServices: ['heart_rate']}, + {filters: [{namePrefix: '', name: 'Name'}], optionalServices: ['heart_rate']}, + { + filters: [{namePrefix: '', services: ['heart_rate']}], + optionalServices: ['heart_rate'] + }, + { + filters: [{namePrefix: '', name: 'Name', services: ['heart_rate']}], + optionalServices: ['heart_rate'] + } +]; + +bluetooth_test(() => { + let test_promises = Promise.resolve(); + test_specs.forEach(args => { + test_promises = test_promises.then( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick(args), expected)); + }); + return test_promises; +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-services-member.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-services-member.https.window.js new file mode 100644 index 0000000000..a24611631d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/empty-services-member.https.window.js @@ -0,0 +1,18 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Services member must contain at least one service.'; +const expected = new TypeError(); + +bluetooth_test(() => { + let test_promises = Promise.resolve(); + generateRequestDeviceArgsWithServices([]).forEach( + args => { + test_promises = test_promises.then( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick(args), expected, + 'Services member must contain at least one service'))}); + return test_promises; +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/filters-xor-acceptAllDevices.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/filters-xor-acceptAllDevices.https.window.js new file mode 100644 index 0000000000..a6c48f2962 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/filters-xor-acceptAllDevices.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'RequestDeviceOptions should have exactly one of ' + + '\'filters\' or \'acceptAllDevices:true\'. Reject with TypeError if not.'; +const expected = new DOMException( + 'Failed to execute \'requestDevice\' on \'Bluetooth\': ' + + 'Either \'filters\' should be present or ' + + '\'acceptAllDevices\' should be true, but not both.', + new TypeError()); +const test_specs = [ + {}, {optionalServices: ['heart_rate']}, {filters: [], acceptAllDevices: true}, + {filters: [], acceptAllDevices: true, optionalServices: ['heart_rate']} +]; + +bluetooth_test(() => { + let test_promises = Promise.resolve(); + test_specs.forEach( + args => { + test_promises = test_promises.then( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick(args), expected))}); + return test_promises; +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/invalid-companyIdentifier.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/invalid-companyIdentifier.https.window.js new file mode 100644 index 0000000000..18cdbb4b4a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/invalid-companyIdentifier.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'companyIdentifier must be in the [0, 65535] range'; + +bluetooth_test(async (t) => { + await promise_rejects_js( + t, TypeError, + requestDeviceWithTrustedClick( + {filters: [{manufacturerData: [{companyIdentifier: -1}]}]})); + await promise_rejects_js( + t, TypeError, + requestDeviceWithTrustedClick( + {filters: [{manufacturerData: [{companyIdentifier: 65536}]}]})); +}, test_desc);
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/mask-buffer-is-detached.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/mask-buffer-is-detached.https.window.js new file mode 100644 index 0000000000..502e2e4057 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/mask-buffer-is-detached.https.window.js @@ -0,0 +1,36 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'mask value buffer must not be detached'; + +function detachBuffer(buffer) { + window.postMessage('', '*', [buffer]); +} + +bluetooth_test(async (t) => { + const companyIdentifier = 0x0001; + const dataPrefix = Uint8Array.of(1, 2); + + const typed_array = Uint8Array.of(1, 2); + detachBuffer(typed_array.buffer); + + await promise_rejects_dom( + t, 'InvalidStateError', requestDeviceWithTrustedClick({ + filters: [{ + manufacturerData: [{companyIdentifier, dataPrefix, mask: typed_array}] + }] + })); + + const array_buffer = Uint8Array.of(3, 4).buffer; + detachBuffer(array_buffer); + + await promise_rejects_dom( + t, 'InvalidStateError', requestDeviceWithTrustedClick({ + filters: [{ + manufacturerData: + [{companyIdentifier, dataPrefix, mask: array_buffer}] + }] + })); +}, test_desc);
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name-unicode.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name-unicode.https.window.js new file mode 100644 index 0000000000..23de63d293 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name-unicode.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Unicode string with utf8 representation longer than 248 ' + + 'bytes in \'name\' must throw TypeError.'; +const expected = new DOMException( + 'Failed to execute \'requestDevice\' on \'Bluetooth\': ' + + 'A device name can\'t be longer than 248 bytes.', + new TypeError()); +// \u2764's UTF-8 respresentation is 3 bytes long. +// 83 chars * 3 bytes/char = 249 bytes +const unicode_name = '\u2764'.repeat(83); + +bluetooth_test( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick({filters: [{name: unicode_name}]}), + expected), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name.https.window.js new file mode 100644 index 0000000000..f14f78fe7d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-name.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A device name longer than 248 must reject.'; +const expected = new DOMException( + 'Failed to execute \'requestDevice\' on \'Bluetooth\': A device ' + + 'name can\'t be longer than 248 bytes.', + new TypeError()); +const name_too_long = 'a'.repeat(249); + +bluetooth_test( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick({filters: [{name: name_too_long}]}), + expected, 'Device name longer than 248'), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix-unicode.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix-unicode.https.window.js new file mode 100644 index 0000000000..aa832b2e76 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix-unicode.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Unicode string with utf8 representation longer than 248 ' + + 'bytes in \'namePrefix\' must throw NotFoundError.'; +const expected = new DOMException( + 'Failed to execute \'requestDevice\' on \'Bluetooth\': ' + + 'A device name can\'t be longer than 248 bytes.', + new TypeError()); +// \u2764's UTF-8 respresentation is 3 bytes long. +// 83 chars * 3 bytes/char = 249 bytes +const unicode_name = '\u2764'.repeat(83); + +bluetooth_test( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick({filters: [{namePrefix: unicode_name}]}), + expected), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix.https.window.js new file mode 100644 index 0000000000..5d27629eaa --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-exceeded-namePrefix.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A device name prefix longer than 248 must reject.'; +const expected = new DOMException( + 'Failed to execute \'requestDevice\' on \'Bluetooth\': A device ' + + 'name can\'t be longer than 248 bytes.', + new TypeError()); +const name_too_long = 'a'.repeat(249); + +bluetooth_test( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick({filters: [{namePrefix: name_too_long}]}), + expected, 'Device name longer than 248'), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-name-unicode.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-name-unicode.https.window.js new file mode 100644 index 0000000000..1232a030e9 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-name-unicode.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A unicode device name of 248 bytes is valid.'; +// \u00A1's UTF-8 respresentation is 2 bytes long. +// 124 chars * 2 bytes/char = 248 bytes +const DEVICE_NAME = '\u00A1'.repeat(124); + +bluetooth_test(async () => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {name: DEVICE_NAME}, + requestDeviceOptions: {filters: [{name: DEVICE_NAME}]} + }); + device => assert_equals(device.name, DEVICE_NAME) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-name.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-name.https.window.js new file mode 100644 index 0000000000..7ede93ce72 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-name.https.window.js @@ -0,0 +1,15 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A device name of 248 bytes is valid.'; +const DEVICE_NAME = 'a'.repeat(248); + +bluetooth_test(async () => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {name: DEVICE_NAME}, + requestDeviceOptions: {filters: [{name: DEVICE_NAME}]} + }); + device => assert_equals(device.name, DEVICE_NAME) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix-unicode.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix-unicode.https.window.js new file mode 100644 index 0000000000..2932dc1cde --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix-unicode.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A unicode device namePrefix of 248 bytes is valid.'; +// \u00A1's UTF-8 respresentation is 2 bytes long. +// 124 chars * 2 bytes/char = 248 bytes +const DEVICE_NAME = '\u00A1'.repeat(124); + +bluetooth_test(async () => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {name: DEVICE_NAME}, + requestDeviceOptions: {filters: [{namePrefix: DEVICE_NAME}]} + }); + device => assert_equals(device.name, DEVICE_NAME) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix.https.window.js new file mode 100644 index 0000000000..f922bb2f0d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/max-length-namePrefix.https.window.js @@ -0,0 +1,15 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A device namePrefix of 248 bytes is valid.'; +const DEVICE_NAME = 'a'.repeat(248); + +bluetooth_test(async () => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {name: DEVICE_NAME}, + requestDeviceOptions: {filters: [{namePrefix: DEVICE_NAME}]} + }); + device => assert_equals(device.name, DEVICE_NAME) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/no-arguments.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/no-arguments.https.window.js new file mode 100644 index 0000000000..075a97f1a9 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/no-arguments.https.window.js @@ -0,0 +1,12 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'requestDevice() requires an argument.'; +const expected = new TypeError(); + +promise_test( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick(), expected), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/same-company-identifier.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/same-company-identifier.https.window.js new file mode 100644 index 0000000000..41f851adc5 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/same-company-identifier.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Manufacturer data company identifier must be unique.'; +const expected = new TypeError(); + +let filters = [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + }, + { + companyIdentifier: 0x0001, + } + ] +}]; + +bluetooth_test( + (t) => promise_rejects_js( + t, TypeError, requestDeviceWithTrustedClick({filters})), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-name.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-name.https.window.js new file mode 100644 index 0000000000..cd10288ddb --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-name.https.window.js @@ -0,0 +1,18 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A name containing unicode characters whose utf8 length ' + + 'is less than 30 must not throw an error.'; +// \u2764's UTF-8 representation is 3 bytes long. +// 9 chars * 3 bytes/char = 27 bytes +const valid_unicode_name = '\u2764'.repeat(9); + +bluetooth_test(async () => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {name: valid_unicode_name}, + requestDeviceOptions: {filters: [{name: valid_unicode_name}]} + }); + device => assert_equals(device.name, valid_unicode_name) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-namePrefix.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-namePrefix.https.window.js new file mode 100644 index 0000000000..494f324ee2 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/unicode-valid-length-name-namePrefix.https.window.js @@ -0,0 +1,18 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'A namePrefix containing unicode characters whose utf8 ' + + 'length is less than 30 must not throw an error.'; +// \u2764's UTF-8 representation is 3 bytes long. +// 9 chars * 3 bytes/char = 27 bytes +const valid_unicode_name = '\u2764'.repeat(9); + +bluetooth_test(async () => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {name: valid_unicode_name}, + requestDeviceOptions: {filters: [{namePrefix: valid_unicode_name}]} + }); + device => assert_equals(device.name, valid_unicode_name) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-optionalServices-member.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-optionalServices-member.https.window.js new file mode 100644 index 0000000000..bfba220f47 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-optionalServices-member.https.window.js @@ -0,0 +1,37 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Invalid optional service must reject the promise.'; +const expected = new TypeError(); +const test_specs = [ + {optionalServices: ['wrong_service'], filters: [{services: ['heart_rate']}]}, + { + optionalServices: ['wrong_service'], + filters: [{services: ['heart_rate'], name: 'Name'}] + }, + { + optionalServices: ['wrong_service'], + filters: [{services: ['heart_rate'], namePrefix: 'Pre'}] + }, + { + optionalServices: ['wrong_service'], + filters: [{services: ['heart_rate'], name: 'Name', namePrefix: 'Pre'}] + }, + {optionalServices: ['wrong_service'], filters: [{name: 'Name'}]}, { + optionalServices: ['wrong_service'], + filters: [{name: 'Name', namePrefix: 'Pre'}] + }, + {optionalServices: ['wrong_service'], filters: [{namePrefix: 'Pre'}]} +]; + +bluetooth_test(() => { + let test_promises = Promise.resolve(); + test_specs.forEach(args => { + test_promises = test_promises.then( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick(args), expected)); + }); + return test_promises; +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-services-member.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-services-member.https.window.js new file mode 100644 index 0000000000..352437d0e5 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/canonicalizeFilter/wrong-service-in-services-member.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Invalid service must reject the promise.'; +const expected = new TypeError(); + +bluetooth_test(() => { + let test_promises = Promise.resolve(); + generateRequestDeviceArgsWithServices(['wrong_service']).forEach(args => { + test_promises = test_promises.then( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick(args), expected)); + }); + return test_promises; +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/cross-origin-iframe.sub.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/cross-origin-iframe.sub.https.window.js new file mode 100644 index 0000000000..d802a86279 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/cross-origin-iframe.sub.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Request device from a unique origin. ' + + 'Should reject with SecurityError.'; +const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' + + '/bluetooth/resources/health-thermometer-iframe.html' +let iframe = document.createElement('iframe'); + +bluetooth_test(async (t) => { + await setUpHealthThermometerDevice(); + + // 1. Load the iframe. + const iframeWatcher = new EventWatcher(t, iframe, ['load']); + iframe.src = cross_origin_src; + document.body.appendChild(iframe); + await iframeWatcher.wait_for('load'); + + // 2. Request the device from the iframe. + const windowWatcher = new EventWatcher(t, window, ['message']); + iframe.contentWindow.postMessage({type: 'RequestDevice'}, '*'); + const messageEvent = await windowWatcher.wait_for('message'); + assert_equals( + messageEvent.data, + 'FAIL: SecurityError: Failed to execute \'requestDevice\' on \'Bluetooth\': Access to the feature "bluetooth" is disallowed by permissions policy.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/discovery-succeeds.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/discovery-succeeds.https.window.js new file mode 100644 index 0000000000..4941d185ca --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/discovery-succeeds.https.window.js @@ -0,0 +1,31 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Discover a device using alias, name, or UUID.'; + +const test_specs = [ + { + filters: [{services: [health_thermometer.alias]}], + }, + { + filters: [{services: [health_thermometer.name]}], + }, + { + filters: [{services: [health_thermometer.uuid]}], + }, +]; + +bluetooth_test( + () => setUpHealthThermometerDevice().then(() => { + let test_promises = Promise.resolve(); + test_specs.forEach(args => { + test_promises = test_promises.then(async () => { + const device = await requestDeviceWithTrustedClick(args); + assert_equals(device.constructor.name, 'BluetoothDevice'); + }); + }); + return test_promises; + }), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/doesnt-consume-user-gesture.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/doesnt-consume-user-gesture.https.window.js new file mode 100644 index 0000000000..9c742733e1 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/doesnt-consume-user-gesture.https.window.js @@ -0,0 +1,24 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'requestDevice calls do not consume user gestures.'; + +bluetooth_test( + () => setUpHealthThermometerAndHeartRateDevices().then( + () => callWithTrustedClick(() => { + let first = navigator.bluetooth.requestDevice( + {filters: [{services: ['heart_rate']}]}); + let second = navigator.bluetooth.requestDevice( + {filters: [{services: ['heart_rate']}]}); + return Promise.all([ + first.then( + device => + assert_equals(device.constructor.name, 'BluetoothDevice')), + second.then( + device => + assert_equals(device.constructor.name, 'BluetoothDevice')), + ]); + })), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/filter-matches.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/filter-matches.https.window.js new file mode 100644 index 0000000000..1a0f52ac30 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/filter-matches.https.window.js @@ -0,0 +1,76 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Matches a filter if all present members match.'; +let matching_services = [health_thermometer.uuid]; +let matching_name = 'Health Thermometer'; +let matching_namePrefix = 'Health'; +let matching_manufacturerData = [{companyIdentifier: 0x0001}]; + +let test_specs = [ + { + filters: [{ + services: matching_services, + }] + }, + { + filters: [{ + services: matching_services, + name: matching_name, + }] + }, + {filters: [{services: matching_services, namePrefix: matching_namePrefix}]}, { + filters: [ + {services: matching_services, manufacturerData: matching_manufacturerData} + ] + }, + { + filters: [{ + name: matching_name, + }], + optionalServices: matching_services + }, + { + filters: [{namePrefix: matching_namePrefix}], + optionalServices: matching_services + }, + { + filters: [{manufacturerData: matching_manufacturerData}], + optionalServices: matching_services + }, + { + filters: [{ + name: matching_name, + namePrefix: matching_namePrefix, + manufacturerData: matching_manufacturerData + }], + optionalServices: matching_services + }, + { + filters: [{ + services: matching_services, + name: matching_name, + namePrefix: matching_namePrefix, + manufacturerData: matching_manufacturerData + }] + } +]; + +bluetooth_test( + () => setUpHealthThermometerDevice().then(() => { + let test_promises = Promise.resolve(); + test_specs.forEach(args => { + test_promises = + test_promises.then(() => requestDeviceWithTrustedClick(args)) + .then(device => { + // We always have access to the services in matching_services + // because we include them in a filter or in optionalServices. + assert_equals(device.name, matching_name); + assert_true(device.name.startsWith(matching_namePrefix)); + }); + }); + return test_promises; + }), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/le-not-supported.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/le-not-supported.https.window.js new file mode 100644 index 0000000000..c961ab4492 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/le-not-supported.https.window.js @@ -0,0 +1,15 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Reject with NotFoundError if Bluetooth is not supported.'; +const expected = + new DOMException('Bluetooth Low Energy not available.', 'NotFoundError'); + +bluetooth_test( + () => navigator.bluetooth.test.setLESupported(false).then( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick({acceptAllDevices: true}), expected, + 'Bluetooth Low Energy is not supported.')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/manufacturer-data-filter-matches.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/manufacturer-data-filter-matches.https.window.js new file mode 100644 index 0000000000..c4c0e80532 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/manufacturer-data-filter-matches.https.window.js @@ -0,0 +1,139 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Matches a filter when manufacturer data match.'; + +let test_specs = [ + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + }], + }], + }, + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01]), + }], + }], + }, + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01]), + mask: new Uint8Array([0xff]), + }], + }], + }, + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + }], + }], + }, + { + filters: [{ + manufacturerData: [{ + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + } + ], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + dataPrefix: new Uint8Array([0x03]), + } + ], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + dataPrefix: new Uint8Array([0x03]), + mask: new Uint8Array([0xff]), + } + ], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + dataPrefix: new Uint8Array([0x03, 0x04]), + } + ], + }], + }, + { + filters: [{ + manufacturerData: [ + { + companyIdentifier: 0x0001, + dataPrefix: new Uint8Array([0x01, 0x02]), + mask: new Uint8Array([0xff, 0x01]), + }, + { + companyIdentifier: 0x0002, + dataPrefix: new Uint8Array([0x03, 0x04]), + mask: new Uint8Array([0xff, 0xff]) + } + ], + }], + }, +]; + +bluetooth_test( + () => setUpHealthThermometerDevice().then(() => { + let test_promises = Promise.resolve(); + test_specs.forEach(args => { + test_promises = test_promises.then(async () => { + const device = await requestDeviceWithTrustedClick(args); + assert_equals(device.name, 'Health Thermometer'); + }); + }); + return test_promises; + }), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/name-empty-device-from-name-empty-filter.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/name-empty-device-from-name-empty-filter.https.window.js new file mode 100644 index 0000000000..2ff22cb702 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/name-empty-device-from-name-empty-filter.https.window.js @@ -0,0 +1,14 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'An empty name device can be obtained by empty name filter.' + +bluetooth_test(async () => { + let {device} = await setUpPreconnectedFakeDevice({ + fakeDeviceOptions: {name: ''}, + requestDeviceOptions: {filters: [{name: ''}]} + }); + assert_equals(device.name, ''); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/not-processing-user-gesture.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/not-processing-user-gesture.https.window.js new file mode 100644 index 0000000000..a063b61163 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/not-processing-user-gesture.https.window.js @@ -0,0 +1,18 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Requires a user gesture.'; +const expected = new DOMException( + 'Failed to execute \'requestDevice\' on \'Bluetooth\': ' + + 'Must be handling a user gesture to show a permission request.', + 'SecurityError'); + +bluetooth_test( + () => setUpHealthThermometerAndHeartRateDevices().then( + () => assert_promise_rejects_with_message( + navigator.bluetooth.requestDevice( + {filters: [{services: ['heart_rate']}]}), + expected, 'User gesture is required')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/radio-not-present.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/radio-not-present.https.window.js new file mode 100644 index 0000000000..b55d63c6ff --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/radio-not-present.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Reject with NotFoundError if there is no BT radio present.'; +const expected = + new DOMException('Bluetooth adapter not available.', 'NotFoundError'); + +bluetooth_test( + () => navigator.bluetooth.test.simulateCentral({state: 'absent'}) + .then( + () => assert_promise_rejects_with_message( + requestDeviceWithTrustedClick( + {filters: [{services: ['generic_access']}]}), + expected, 'Bluetooth adapter is not present.')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/reject_opaque_origin.https.html b/testing/web-platform/tests/bluetooth/requestDevice/reject_opaque_origin.https.html new file mode 100644 index 0000000000..df348dd39e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/reject_opaque_origin.https.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + 'use strict'; + + promise_test(async (t) => { + await promise_rejects_dom( + t, 'SecurityError', navigator.bluetooth.requestDevice(), + 'requestDevice() should throw a SecurityError DOMException when called from a context where the top-level document has an opaque origin.'); + }, 'Calls to Bluetooth APIs from an origin with opaque top origin get blocked.'); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestDevice/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/bluetooth/requestDevice/reject_opaque_origin.https.html.headers new file mode 100644 index 0000000000..c7e4e7cc5b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/reject_opaque_origin.https.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestDevice/request-from-iframe.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/request-from-iframe.https.window.js new file mode 100644 index 0000000000..d3f3cf897f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/request-from-iframe.https.window.js @@ -0,0 +1,43 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Concurrent requestDevice calls in iframes work.'; +const iframes = []; +for (let i = 0; i < 5; i++) { + iframes.push(document.createElement('iframe')); +} + +bluetooth_test( + () => setUpHealthThermometerAndHeartRateDevices() + // 1. Load the iframes. + .then(() => { + let promises = []; + for (let iframe of iframes) { + iframe.src = + '/bluetooth/resources/health-thermometer-iframe.html'; + document.body.appendChild(iframe); + promises.push(new Promise( + resolve => iframe.addEventListener('load', resolve))); + } + return Promise.all(promises); + }) + // 2. Request the device from the iframes. + .then(() => new Promise(async (resolve) => { + let numMessages = 0; + window.onmessage = + messageEvent => { + assert_equals(messageEvent.data, 'Success'); + if (++numMessages === iframes.length) { + resolve(); + } + } + + for (let iframe of iframes) { + await callWithTrustedClick( + () => iframe.contentWindow.postMessage( + {type: 'RequestDevice'}, '*')); + } + })), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/request-from-sandboxed-iframe.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/request-from-sandboxed-iframe.https.window.js new file mode 100644 index 0000000000..2101cf0d6b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/request-from-sandboxed-iframe.https.window.js @@ -0,0 +1,35 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Request device from a unique origin. ' + + 'Should reject with SecurityError.'; +const expected = + 'FAIL: SecurityError: Failed to execute \'requestDevice\' on ' + + '\'Bluetooth\': Access to the feature "bluetooth" is disallowed by ' + + 'permissions policy.'; + +let iframe = document.createElement('iframe'); + +bluetooth_test( + () => getConnectedHealthThermometerDevice() + // 1. Load the iframe. + .then(() => new Promise(resolve => { + iframe.sandbox.add('allow-scripts'); + iframe.src = + '/bluetooth/resources/health-thermometer-iframe.html'; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + })) + // 2. Request the device from the iframe. + .then(() => new Promise(resolve => { + iframe.contentWindow.postMessage( + {type: 'RequestDevice'}, '*'); + + window.onmessage = messageEvent => { + assert_equals(messageEvent.data, expected); + resolve(); + } + })), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/same-device.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/same-device.https.window.js new file mode 100644 index 0000000000..41a42cf4c8 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/same-device.https.window.js @@ -0,0 +1,19 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Returned device should always be the same.'; +let devices = []; + +bluetooth_test(async () => { + await setUpHealthThermometerAndHeartRateDevices(); + devices.push(await requestDeviceWithTrustedClick( + {filters: [{services: [heart_rate.alias]}]})); + devices.push(await requestDeviceWithTrustedClick( + {filters: [{services: [heart_rate.name]}]})); + devices.push(await requestDeviceWithTrustedClick( + {filters: [{services: [heart_rate.uuid]}]})); + assert_equals(devices[0], devices[1]); + assert_equals(devices[1], devices[2]); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestDevice/sandboxed_iframe.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/sandboxed_iframe.https.window.js new file mode 100644 index 0000000000..e9192a9305 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/sandboxed_iframe.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +'use strict'; + +let iframe = document.createElement('iframe'); + +bluetooth_test(async () => { + await getConnectedHealthThermometerDevice(); + await new Promise(resolve => { + iframe.src = '/bluetooth/resources/health-thermometer-iframe.html'; + iframe.sandbox.add('allow-scripts'); + iframe.allow = 'bluetooth'; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + await new Promise(resolve => { + iframe.contentWindow.postMessage({type: 'RequestDevice'}, '*'); + + window.addEventListener('message', (messageEvent) => { + assert_false(/^FAIL: .*/.test(messageEvent.data)); + resolve(); + }); + }); +}, 'Calls to Bluetooth APIs from a sandboxed iframe are valid.');
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestDevice/single-filter-single-service.https.window.js b/testing/web-platform/tests/bluetooth/requestDevice/single-filter-single-service.https.window.js new file mode 100644 index 0000000000..67afad0b93 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestDevice/single-filter-single-service.https.window.js @@ -0,0 +1,14 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Simple filter selects matching device.'; + +bluetooth_test( + () => setUpHealthThermometerAndHeartRateDevices() + .then( + () => requestDeviceWithTrustedClick( + {filters: [{services: ['health_thermometer']}]})) + .then(device => assert_equals(device.name, 'Health Thermometer')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/requestLEScan/reject_opaque_origin.https.html b/testing/web-platform/tests/bluetooth/requestLEScan/reject_opaque_origin.https.html new file mode 100644 index 0000000000..272c5aa760 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestLEScan/reject_opaque_origin.https.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + 'use strict'; + + promise_test(async (t) => { + await promise_rejects_dom( + t, 'SecurityError', navigator.bluetooth.requestLEScan(), + 'requestLEScan() should throw a SecurityError DOMException when called from a context where the top-level document has an opaque origin.'); + }, 'Calls to Bluetooth APIs from an origin with opaque top origin get blocked.'); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestLEScan/reject_opaque_origin.https.html.headers b/testing/web-platform/tests/bluetooth/requestLEScan/reject_opaque_origin.https.html.headers new file mode 100644 index 0000000000..c7e4e7cc5b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestLEScan/reject_opaque_origin.https.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: sandbox allow-scripts
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/requestLEScan/sandboxed_iframe.https.window.js b/testing/web-platform/tests/bluetooth/requestLEScan/sandboxed_iframe.https.window.js new file mode 100644 index 0000000000..32d1e74b77 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/requestLEScan/sandboxed_iframe.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +'use strict'; + +let iframe = document.createElement('iframe'); + +bluetooth_test(async () => { + await getConnectedHealthThermometerDevice(); + await new Promise(resolve => { + iframe.src = '/bluetooth/resources/health-thermometer-iframe.html'; + iframe.sandbox.add('allow-scripts'); + iframe.allow = 'bluetooth'; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + await new Promise(resolve => { + iframe.contentWindow.postMessage({type: 'RequestLEScan'}, '*'); + + window.addEventListener('message', (messageEvent) => { + assert_false(/^FAIL: .*/.test(messageEvent.data)); + resolve(); + }); + }); +}, 'Calls to Bluetooth APIs from a sandboxed iframe are valid.');
\ No newline at end of file diff --git a/testing/web-platform/tests/bluetooth/resources/bluetooth-fake-devices.js b/testing/web-platform/tests/bluetooth/resources/bluetooth-fake-devices.js new file mode 100644 index 0000000000..b718ab579a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/resources/bluetooth-fake-devices.js @@ -0,0 +1,1203 @@ +'use strict'; + +/* Bluetooth Constants */ + +/** + * HCI Error Codes. + * Used for simulateGATT{Dis}ConnectionResponse. For a complete list of + * possible error codes see BT 4.2 Vol 2 Part D 1.3 List Of Error Codes. + */ +const HCI_SUCCESS = 0x0000; +const HCI_CONNECTION_TIMEOUT = 0x0008; + +/** + * GATT Error codes. + * Used for GATT operations responses. BT 4.2 Vol 3 Part F 3.4.1.1 Error + * Response + */ +const GATT_SUCCESS = 0x0000; +const GATT_INVALID_HANDLE = 0x0001; + +/* Bluetooth UUID Constants */ + +/* Service UUIDs */ +var blocklist_test_service_uuid = '611c954a-263b-4f4a-aab6-01ddb953f985'; +var request_disconnection_service_uuid = '01d7d889-7451-419f-aeb8-d65e7b9277af'; + +/* Characteristic UUIDs */ +var blocklist_exclude_reads_characteristic_uuid = + 'bad1c9a2-9a5b-4015-8b60-1579bbbf2135'; +var request_disconnection_characteristic_uuid = + '01d7d88a-7451-419f-aeb8-d65e7b9277af'; + +/* Descriptor UUIDs */ +var blocklist_test_descriptor_uuid = 'bad2ddcf-60db-45cd-bef9-fd72b153cf7c'; +var blocklist_exclude_reads_descriptor_uuid = + 'bad3ec61-3cc3-4954-9702-7977df514114'; + +/** + * Helper objects that associate Bluetooth names, aliases, and UUIDs. These are + * useful for tests that check that the same result is produces when using all + * three methods of referring to a Bluetooth UUID. + */ +var generic_access = { + alias: 0x1800, + name: 'generic_access', + uuid: '00001800-0000-1000-8000-00805f9b34fb' +}; +var device_name = { + alias: 0x2a00, + name: 'gap.device_name', + uuid: '00002a00-0000-1000-8000-00805f9b34fb' +}; +var reconnection_address = { + alias: 0x2a03, + name: 'gap.reconnection_address', + uuid: '00002a03-0000-1000-8000-00805f9b34fb' +}; +var heart_rate = { + alias: 0x180d, + name: 'heart_rate', + uuid: '0000180d-0000-1000-8000-00805f9b34fb' +}; +var health_thermometer = { + alias: 0x1809, + name: 'health_thermometer', + uuid: '00001809-0000-1000-8000-00805f9b34fb' +}; +var body_sensor_location = { + alias: 0x2a38, + name: 'body_sensor_location', + uuid: '00002a38-0000-1000-8000-00805f9b34fb' +}; +var glucose = { + alias: 0x1808, + name: 'glucose', + uuid: '00001808-0000-1000-8000-00805f9b34fb' +}; +var battery_service = { + alias: 0x180f, + name: 'battery_service', + uuid: '0000180f-0000-1000-8000-00805f9b34fb' +}; +var battery_level = { + alias: 0x2A19, + name: 'battery_level', + uuid: '00002a19-0000-1000-8000-00805f9b34fb' +}; +var user_description = { + alias: 0x2901, + name: 'gatt.characteristic_user_description', + uuid: '00002901-0000-1000-8000-00805f9b34fb' +}; +var client_characteristic_configuration = { + alias: 0x2902, + name: 'gatt.client_characteristic_configuration', + uuid: '00002902-0000-1000-8000-00805f9b34fb' +}; +var measurement_interval = { + alias: 0x2a21, + name: 'measurement_interval', + uuid: '00002a21-0000-1000-8000-00805f9b34fb' +}; + +/** + * An advertisement packet object that simulates a Health Thermometer device. + * @type {ScanResult} + */ +const health_thermometer_ad_packet = { + deviceAddress: '09:09:09:09:09:09', + rssi: -10, + scanRecord: { + name: 'Health Thermometer', + uuids: [health_thermometer.uuid], + }, +}; + +/** + * An advertisement packet object that simulates a Heart Rate device. + * @type {ScanResult} + */ +const heart_rate_ad_packet = { + deviceAddress: '08:08:08:08:08:08', + rssi: -10, + scanRecord: { + name: 'Heart Rate', + uuids: [heart_rate.uuid], + }, +}; + +const uuid1234 = BluetoothUUID.getService(0x1234); +const uuid5678 = BluetoothUUID.getService(0x5678); +const uuidABCD = BluetoothUUID.getService(0xABCD); +const manufacturer1Data = new Uint8Array([1, 2]); +const manufacturer2Data = new Uint8Array([3, 4]); +const uuid1234Data = new Uint8Array([5, 6]); +const uuid5678Data = new Uint8Array([7, 8]); +const uuidABCDData = new Uint8Array([9, 10]); + +// TODO(crbug.com/1163207): Add the blocklist link. +// Fake manufacturer data following iBeacon format listed in +// https://en.wikipedia.org/wiki/IBeacon, which will be blocked according to [TBD blocklist link]. +const blocklistedManufacturerId = 0x4c; +const blocklistedManufacturerData = new Uint8Array([ + 0x02, 0x15, 0xb3, 0xeb, 0x8d, 0xb1, 0x30, 0xa5, 0x44, 0x8d, 0xb4, 0xac, + 0xfb, 0x68, 0xc9, 0x23, 0xa3, 0x0e, 0x00, 0x00, 0x00, 0x00, 0xbf +]); +// Fake manufacturer data that is not in [TBD blocklist link]. +const nonBlocklistedManufacturerId = 0x0001; +const nonBlocklistedManufacturerData = new Uint8Array([1, 2]); + +/** + * An advertisement packet object that simulates a device that advertises + * service and manufacturer data. + * @type {ScanResult} + */ +const service_and_manufacturer_data_ad_packet = { + deviceAddress: '07:07:07:07:07:07', + rssi: -10, + scanRecord: { + name: 'LE Device', + uuids: [uuid1234], + manufacturerData: {0x0001: manufacturer1Data, 0x0002: manufacturer2Data}, + serviceData: { + [uuid1234]: uuid1234Data, + [uuid5678]: uuid5678Data, + [uuidABCD]: uuidABCDData + } + } +}; + +/** Bluetooth Helpers */ + +/** + * Helper class to create a BluetoothCharacteristicProperties object using an + * array of strings corresponding to the property bit to set. + */ +class TestCharacteristicProperties { + /** @param {Array<string>} properties */ + constructor(properties) { + this.broadcast = false; + this.read = false; + this.writeWithoutResponse = false; + this.write = false; + this.notify = false; + this.indicate = false; + this.authenticatedSignedWrites = false; + this.reliableWrite = false; + this.writableAuxiliaries = false; + + properties.forEach(val => { + if (this.hasOwnProperty(val)) + this[val] = true; + else + throw `Invalid member '${val}'`; + }); + } +} + +/** + * Produces an array of BluetoothLEScanFilterInit objects containing the list of + * services in |services| and various permutations of the other + * BluetoothLEScanFilterInit properties. This method is used to test that the + * |services| are valid so the other properties do not matter. + * @param {BluetoothServiceUUID} services + * @returns {Array<RequestDeviceOptions>} A list of options containing + * |services| and various permutations of other options. + */ +function generateRequestDeviceArgsWithServices(services = ['heart_rate']) { + return [ + {filters: [{services: services}]}, + {filters: [{services: services, name: 'Name'}]}, + {filters: [{services: services, namePrefix: 'Pre'}]}, { + filters: [ + {services: services, manufacturerData: [{companyIdentifier: 0x0001}]} + ] + }, + { + filters: [{ + services: services, + name: 'Name', + namePrefix: 'Pre', + manufacturerData: [{companyIdentifier: 0x0001}] + }] + }, + {filters: [{services: services}], optionalServices: ['heart_rate']}, { + filters: [{services: services, name: 'Name'}], + optionalServices: ['heart_rate'] + }, + { + filters: [{services: services, namePrefix: 'Pre'}], + optionalServices: ['heart_rate'] + }, + { + filters: [ + {services: services, manufacturerData: [{companyIdentifier: 0x0001}]} + ], + optionalServices: ['heart_rate'] + }, + { + filters: [{ + services: services, + name: 'Name', + namePrefix: 'Pre', + manufacturerData: [{companyIdentifier: 0x0001}] + }], + optionalServices: ['heart_rate'] + } + ]; +} + +/** + * Causes |fake_peripheral| to disconnect and returns a promise that resolves + * once `gattserverdisconnected` has been fired on |device|. + * @param {BluetoothDevice} device The device to check if the + * `gattserverdisconnected` promise was fired. + * @param {FakePeripheral} fake_peripheral The device fake that represents + * |device|. + * @returns {Promise<Array<Object>>} A promise that resolves when the device has + * successfully disconnected. + */ +function simulateGATTDisconnectionAndWait(device, fake_peripheral) { + return Promise.all([ + eventPromise(device, 'gattserverdisconnected'), + fake_peripheral.simulateGATTDisconnection(), + ]); +} + +/** @type {FakeCentral} The fake adapter for the current test. */ +let fake_central = null; + +async function initializeFakeCentral({state = 'powered-on'}) { + if (!fake_central) { + fake_central = await navigator.bluetooth.test.simulateCentral({state}); + } +} + +/** + * A dictionary for specifying fake Bluetooth device setup options. + * @typedef {{address: !string, name: !string, + * manufacturerData: !Object<uint16,Array<uint8>>, + * knownServiceUUIDs: !Array<string>, connectable: !boolean, + * serviceDiscoveryComplete: !boolean}} + */ +let FakeDeviceOptions; + +/** + * @typedef {{fakeDeviceOptions: FakeDeviceOptions, + * requestDeviceOptions: RequestDeviceOptions}} + */ +let SetupOptions; + +/** + * Default options for setting up a Bluetooth device. + * @type {FakeDeviceOptions} + */ +const fakeDeviceOptionsDefault = { + address: '00:00:00:00:00:00', + name: 'LE Device', + manufacturerData: {}, + knownServiceUUIDs: [], + connectable: false, + serviceDiscoveryComplete: false, +}; + +/** + * A dictionary containing the fake Bluetooth device object. The dictionary can + * optionally contain its fake services and its BluetoothDevice counterpart. + * @typedef {{fake_peripheral: !FakePeripheral, + * fake_services: Object<string, FakeService>, + * device: BluetoothDevice}} + */ +let FakeDevice; + +/** + * Creates a SetupOptions object using |setupOptionsDefault| as the base options + * object with the options from |setupOptionsOverride| overriding these + * defaults. + * @param {SetupOptions} setupOptionsDefault The default options object to use + * as the base. + * @param {SetupOptions} setupOptionsOverride The options to override the + * defaults with. + * @returns {SetupOptions} The merged setup options containing the defaults with + * the overrides applied. + */ +function createSetupOptions(setupOptionsDefault, setupOptionsOverride) { + // Merge the properties of |setupOptionsDefault| and |setupOptionsOverride| + // without modifying |setupOptionsDefault|. + let fakeDeviceOptions = Object.assign( + {...setupOptionsDefault.fakeDeviceOptions}, + setupOptionsOverride.fakeDeviceOptions); + let requestDeviceOptions = Object.assign( + {...setupOptionsDefault.requestDeviceOptions}, + setupOptionsOverride.requestDeviceOptions); + + return {fakeDeviceOptions, requestDeviceOptions}; +} + +/** + * Adds a preconnected device with the given options. A preconnected device is a + * device that has been paired with the system previously. This can be done if, + * for example, the user pairs the device using the OS'es settings. + * + * By default, the preconnected device will be set up using the + * |fakeDeviceOptionsDefault| and will not use a RequestDeviceOption object. + * This means that the device will not be requested during the setup. + * + * If |setupOptionsOverride| is provided, these options will override the + * defaults. If |setupOptionsOverride| includes the requestDeviceOptions + * property, then the device will be requested using those options. + * @param {SetupOptions} setupOptionsOverride An object containing options for + * setting up a fake Bluetooth device and for requesting the device. + * @returns {Promise<FakeDevice>} The device fake initialized with the + * parameter values. + */ +async function setUpPreconnectedFakeDevice(setupOptionsOverride) { + await initializeFakeCentral({state: 'powered-on'}); + + let setupOptions = createSetupOptions( + {fakeDeviceOptions: fakeDeviceOptionsDefault}, setupOptionsOverride); + + // Simulate the fake peripheral. + let preconnectedDevice = {}; + preconnectedDevice.fake_peripheral = + await fake_central.simulatePreconnectedPeripheral({ + address: setupOptions.fakeDeviceOptions.address, + name: setupOptions.fakeDeviceOptions.name, + manufacturerData: setupOptions.fakeDeviceOptions.manufacturerData, + knownServiceUUIDs: setupOptions.fakeDeviceOptions.knownServiceUUIDs, + }); + + if (setupOptions.fakeDeviceOptions.connectable) { + await preconnectedDevice.fake_peripheral.setNextGATTConnectionResponse( + {code: HCI_SUCCESS}); + } + + // Add known services. + preconnectedDevice.fake_services = new Map(); + for (let service of setupOptions.fakeDeviceOptions.knownServiceUUIDs) { + let fake_service = await preconnectedDevice.fake_peripheral.addFakeService( + {uuid: service}); + preconnectedDevice.fake_services.set(service, fake_service); + } + + // Request the device if the request option isn't empty. + if (Object.keys(setupOptions.requestDeviceOptions).length !== 0) { + preconnectedDevice.device = + await requestDeviceWithTrustedClick(setupOptions.requestDeviceOptions); + } + + // Set up services discovered state. + if (setupOptions.fakeDeviceOptions.serviceDiscoveryComplete) { + await preconnectedDevice.fake_peripheral.setNextGATTDiscoveryResponse( + {code: HCI_SUCCESS}); + } + + return preconnectedDevice; +} + +/** Blocklisted GATT Device Helper Methods */ + +/** @type {FakeDeviceOptions} */ +const blocklistFakeDeviceOptionsDefault = { + address: '11:11:11:11:11:11', + name: 'Blocklist Device', + knownServiceUUIDs: ['generic_access', blocklist_test_service_uuid], + connectable: true, + serviceDiscoveryComplete: true +}; + +/** @type {RequestDeviceOptions} */ +const blocklistRequestDeviceOptionsDefault = { + filters: [{services: [blocklist_test_service_uuid]}] +}; + +/** @type {SetupOptions} */ +const blocklistSetupOptionsDefault = { + fakeDeviceOptions: blocklistFakeDeviceOptionsDefault, + requestDeviceOptions: blocklistRequestDeviceOptionsDefault +}; + +/** + * Returns an object containing a BluetoothDevice discovered using |options|, + * its corresponding FakePeripheral and FakeRemoteGATTServices. + * The simulated device is called 'Blocklist Device' and it has one known + * service UUID |blocklist_test_service_uuid|. The |blocklist_test_service_uuid| + * service contains two characteristics: + * - |blocklist_exclude_reads_characteristic_uuid| (read, write) + * - 'gap.peripheral_privacy_flag' (read, write) + * The 'gap.peripheral_privacy_flag' characteristic contains three descriptors: + * - |blocklist_test_descriptor_uuid| + * - |blocklist_exclude_reads_descriptor_uuid| + * - 'gatt.client_characteristic_configuration' + * These are special UUIDs that have been added to the blocklist found at + * https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt + * There are also test UUIDs that have been added to the test environment which + * other implementations should add as test UUIDs as well. + * The device has been connected to and its attributes are ready to be + * discovered. + * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, + * fake_blocklist_test_service: FakeRemoteGATTService, + * fake_blocklist_exclude_reads_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_exclude_writes_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor}>} An + * object containing the BluetoothDevice object and its corresponding + * GATT fake objects. + */ +async function getBlocklistDevice(setupOptionsOverride = {}) { + let setupOptions = + createSetupOptions(blocklistSetupOptionsDefault, setupOptionsOverride); + let fakeDevice = await setUpPreconnectedFakeDevice(setupOptions); + await fakeDevice.device.gatt.connect(); + + let fake_blocklist_test_service = + fakeDevice.fake_services.get(blocklist_test_service_uuid); + + let fake_blocklist_exclude_reads_characteristic = + await fake_blocklist_test_service.addFakeCharacteristic({ + uuid: blocklist_exclude_reads_characteristic_uuid, + properties: ['read', 'write'], + }); + let fake_blocklist_exclude_writes_characteristic = + await fake_blocklist_test_service.addFakeCharacteristic({ + uuid: 'gap.peripheral_privacy_flag', + properties: ['read', 'write'], + }); + + let fake_blocklist_descriptor = + await fake_blocklist_exclude_writes_characteristic.addFakeDescriptor( + {uuid: blocklist_test_descriptor_uuid}); + let fake_blocklist_exclude_reads_descriptor = + await fake_blocklist_exclude_writes_characteristic.addFakeDescriptor( + {uuid: blocklist_exclude_reads_descriptor_uuid}); + let fake_blocklist_exclude_writes_descriptor = + await fake_blocklist_exclude_writes_characteristic.addFakeDescriptor( + {uuid: 'gatt.client_characteristic_configuration'}); + return { + device: fakeDevice.device, + fake_peripheral: fakeDevice.fake_peripheral, + fake_blocklist_test_service, + fake_blocklist_exclude_reads_characteristic, + fake_blocklist_exclude_writes_characteristic, + fake_blocklist_descriptor, + fake_blocklist_exclude_reads_descriptor, + fake_blocklist_exclude_writes_descriptor, + }; +} + +/** + * Returns an object containing a Blocklist Test BluetoothRemoteGattService and + * its corresponding FakeRemoteGATTService. + * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, + * fake_blocklist_test_service: FakeRemoteGATTService, + * fake_blocklist_exclude_reads_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_exclude_writes_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, + * service: BluetoothRemoteGATTService, + * fake_service: FakeBluetoothRemoteGATTService}>} An object containing the + * BluetoothDevice object and its corresponding GATT fake objects. + */ +async function getBlocklistTestService() { + let result = await getBlocklistDevice(); + let service = + await result.device.gatt.getPrimaryService(blocklist_test_service_uuid); + return Object.assign(result, { + service, + fake_service: result.fake_blocklist_test_service, + }); +} + +/** + * Returns an object containing a blocklisted BluetoothRemoteGATTCharacteristic + * that excludes reads and its corresponding FakeRemoteGATTCharacteristic. + * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, + * fake_blocklist_test_service: FakeRemoteGATTService, + * fake_blocklist_exclude_reads_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_exclude_writes_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, + * service: BluetoothRemoteGATTService, + * fake_service: FakeBluetoothRemoteGATTService, + * characteristic: BluetoothRemoteGATTCharacteristic, + * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic}>} An object + * containing the BluetoothDevice object and its corresponding GATT fake + * objects. + */ +async function getBlocklistExcludeReadsCharacteristic() { + let result = await getBlocklistTestService(); + let characteristic = await result.service.getCharacteristic( + blocklist_exclude_reads_characteristic_uuid); + return Object.assign(result, { + characteristic, + fake_characteristic: result.fake_blocklist_exclude_reads_characteristic + }); +} + +/** + * Returns an object containing a blocklisted BluetoothRemoteGATTCharacteristic + * that excludes writes and its corresponding FakeRemoteGATTCharacteristic. + * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, + * fake_blocklist_test_service: FakeRemoteGATTService, + * fake_blocklist_exclude_reads_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_exclude_writes_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, + * service: BluetoothRemoteGATTService, + * fake_service: FakeBluetoothRemoteGATTService, + * characteristic: BluetoothRemoteGATTCharacteristic, + * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic}>} An object + * containing the BluetoothDevice object and its corresponding GATT fake + * objects. + */ +async function getBlocklistExcludeWritesCharacteristic() { + let result = await getBlocklistTestService(); + let characteristic = + await result.service.getCharacteristic('gap.peripheral_privacy_flag'); + return Object.assign(result, { + characteristic, + fake_characteristic: result.fake_blocklist_exclude_writes_characteristic + }); +} + +/** + * Returns an object containing a blocklisted BluetoothRemoteGATTDescriptor that + * excludes reads and its corresponding FakeRemoteGATTDescriptor. + * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, + * fake_blocklist_test_service: FakeRemoteGATTService, + * fake_blocklist_exclude_reads_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_exclude_writes_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, + * service: BluetoothRemoteGATTService, + * fake_service: FakeBluetoothRemoteGATTService, + * characteristic: BluetoothRemoteGATTCharacteristic, + * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic, + * descriptor: BluetoothRemoteGATTDescriptor, + * fake_descriptor: FakeBluetoothRemoteGATTDescriptor}>} An object + * containing the BluetoothDevice object and its corresponding GATT fake + * objects. + */ +async function getBlocklistExcludeReadsDescriptor() { + let result = await getBlocklistExcludeWritesCharacteristic(); + let descriptor = await result.characteristic.getDescriptor( + blocklist_exclude_reads_descriptor_uuid); + return Object.assign(result, { + descriptor, + fake_descriptor: result.fake_blocklist_exclude_reads_descriptor + }); +} + +/** + * Returns an object containing a blocklisted BluetoothRemoteGATTDescriptor that + * excludes writes and its corresponding FakeRemoteGATTDescriptor. + * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, + * fake_blocklist_test_service: FakeRemoteGATTService, + * fake_blocklist_exclude_reads_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_exclude_writes_characteristic: + * FakeRemoteGATTCharacteristic, + * fake_blocklist_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_reads_descriptor: FakeRemoteGATTDescriptor, + * fake_blocklist_exclude_writes_descriptor: FakeRemoteGATTDescriptor, + * service: BluetoothRemoteGATTService, + * fake_service: FakeBluetoothRemoteGATTService, + * characteristic: BluetoothRemoteGATTCharacteristic, + * fake_characteristic: FakeBluetoothRemoteGATTCharacteristic, + * descriptor: BluetoothRemoteGATTDescriptor, + * fake_descriptor: FakeBluetoothRemoteGATTDescriptor}>} An object + * containing the BluetoothDevice object and its corresponding GATT fake + * objects. + */ +async function getBlocklistExcludeWritesDescriptor() { + let result = await getBlocklistExcludeWritesCharacteristic(); + let descriptor = await result.characteristic.getDescriptor( + 'gatt.client_characteristic_configuration'); + return Object.assign(result, { + descriptor: descriptor, + fake_descriptor: result.fake_blocklist_exclude_writes_descriptor, + }); +} + +/** Bluetooth HID Device Helper Methods */ + +/** @type {FakeDeviceOptions} */ +const connectedHIDFakeDeviceOptionsDefault = { + address: '10:10:10:10:10:10', + name: 'HID Device', + knownServiceUUIDs: [ + 'generic_access', + 'device_information', + 'human_interface_device', + ], + connectable: true, + serviceDiscoveryComplete: false +}; + +/** @type {RequestDeviceOptions} */ +const connectedHIDRequestDeviceOptionsDefault = { + filters: [{services: ['device_information']}], + optionalServices: ['human_interface_device'] +}; + +/** @type {SetupOptions} */ +const connectedHIDSetupOptionsDefault = { + fakeDeviceOptions: connectedHIDFakeDeviceOptionsDefault, + requestDeviceOptions: connectedHIDRequestDeviceOptionsDefault +}; + +/** + * Similar to getHealthThermometerDevice except the GATT discovery + * response has not been set yet so more attributes can still be added. + * TODO(crbug.com/719816): Add descriptors. + * @param {RequestDeviceOptions} options The options for requesting a Bluetooth + * Device. + * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object + * containing a requested BluetoothDevice and its fake counter part. + */ +async function getConnectedHIDDevice( + requestDeviceOptionsOverride, fakeDeviceOptionsOverride) { + let setupOptions = createSetupOptions(connectedHIDSetupOptionsDefault, { + fakeDeviceOptions: fakeDeviceOptionsOverride, + requestDeviceOptions: requestDeviceOptionsOverride + }); + + let fakeDevice = await setUpPreconnectedFakeDevice(setupOptions); + await fakeDevice.device.gatt.connect(); + + // Blocklisted Characteristic: + // https://github.com/WebBluetoothCG/registries/blob/master/gatt_blocklist.txt + let dev_info = fakeDevice.fake_services.get('device_information'); + await dev_info.addFakeCharacteristic({ + uuid: 'serial_number_string', + properties: ['read'], + }); + return fakeDevice; +} + +/** + * Returns a BluetoothDevice discovered using |options| and its + * corresponding FakePeripheral. + * The simulated device is called 'HID Device' it has three known service + * UUIDs: 'generic_access', 'device_information', 'human_interface_device'. + * The primary service with 'device_information' UUID has a characteristics + * with UUID 'serial_number_string'. The device has been connected to and its + * attributes are ready to be discovered. + * @param {RequestDeviceOptions} options The options for requesting a Bluetooth + * Device. + * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object + * containing a requested BluetoothDevice and its fake counter part. + */ +async function getHIDDevice(options) { + let result = + await getConnectedHIDDevice(options, {serviceDiscoveryComplete: true}); + return result; +} + +/** Health Thermometer Bluetooth Device Helper Methods */ + +/** @type {FakeDeviceOptions} */ +const healthTherometerFakeDeviceOptionsDefault = { + address: '09:09:09:09:09:09', + name: 'Health Thermometer', + manufacturerData: {0x0001: manufacturer1Data, 0x0002: manufacturer2Data}, + knownServiceUUIDs: ['generic_access', 'health_thermometer'], +}; + +/** + * Returns a FakeDevice that corresponds to a simulated pre-connected device + * called 'Health Thermometer'. The device has two known serviceUUIDs: + * 'generic_access' and 'health_thermometer' and some fake manufacturer data. + * @returns {Promise<FakeDevice>} The device fake initialized as a Health + * Thermometer device. + */ +async function setUpHealthThermometerDevice(setupOptionsOverride = {}) { + let setupOptions = createSetupOptions( + {fakeDeviceOptions: healthTherometerFakeDeviceOptionsDefault}, + setupOptionsOverride); + return await setUpPreconnectedFakeDevice(setupOptions); +} + +/** + * Returns the same fake device as setUpHealthThermometerDevice() except + * that connecting to the peripheral will succeed. + * @returns {Promise<FakeDevice>} The device fake initialized as a + * connectable Health Thermometer device. + */ +async function setUpConnectableHealthThermometerDevice() { + let fake_device = await setUpHealthThermometerDevice( + {fakeDeviceOptions: {connectable: true}}); + return fake_device; +} + +/** + * Populates a fake_device with various fakes appropriate for a health + * thermometer. This resolves to an associative array composed of the fakes, + * including the |fake_peripheral|. + * @param {FakeDevice} fake_device The Bluetooth fake to populate GATT + * services, characteristics, and descriptors on. + * @returns {Promise<{fake_peripheral: FakePeripheral, + * fake_generic_access: FakeRemoteGATTService, + * fake_health_thermometer: FakeRemoteGATTService, + * fake_measurement_interval: FakeRemoteGATTCharacteristic, + * fake_cccd: FakeRemoteGATTDescriptor, + * fake_user_description: FakeRemoteGATTDescriptor, + * fake_temperature_measurement: FakeRemoteGATTCharacteristic, + * fake_temperature_type: FakeRemoteGATTCharacteristic}>} The FakePeripheral + * passed into this method along with the fake GATT services, characteristics, + * and descriptors added to it. + */ +async function populateHealthThermometerFakes(fake_device) { + let fake_peripheral = fake_device.fake_peripheral; + let fake_generic_access = fake_device.fake_services.get('generic_access'); + let fake_health_thermometer = + fake_device.fake_services.get('health_thermometer'); + let fake_measurement_interval = + await fake_health_thermometer.addFakeCharacteristic({ + uuid: 'measurement_interval', + properties: ['read', 'write', 'indicate'], + }); + let fake_user_description = + await fake_measurement_interval.addFakeDescriptor({ + uuid: 'gatt.characteristic_user_description', + }); + let fake_cccd = await fake_measurement_interval.addFakeDescriptor({ + uuid: 'gatt.client_characteristic_configuration', + }); + let fake_temperature_measurement = + await fake_health_thermometer.addFakeCharacteristic({ + uuid: 'temperature_measurement', + properties: ['indicate'], + }); + let fake_temperature_type = + await fake_health_thermometer.addFakeCharacteristic({ + uuid: 'temperature_type', + properties: ['read'], + }); + return { + fake_peripheral, + fake_generic_access, + fake_health_thermometer, + fake_measurement_interval, + fake_cccd, + fake_user_description, + fake_temperature_measurement, + fake_temperature_type, + }; +} + +/** + * Returns the same device and fake peripheral as getHealthThermometerDevice() + * after another frame (an iframe we insert) discovered the device, + * connected to it and discovered its services. + * @param {RequestDeviceOptions} options The options for requesting a Bluetooth + * Device. + * @returns {Promise<{device: BluetoothDevice, fakes: { + * fake_peripheral: FakePeripheral, + * fake_generic_access: FakeRemoteGATTService, + * fake_health_thermometer: FakeRemoteGATTService, + * fake_measurement_interval: FakeRemoteGATTCharacteristic, + * fake_cccd: FakeRemoteGATTDescriptor, + * fake_user_description: FakeRemoteGATTDescriptor, + * fake_temperature_measurement: FakeRemoteGATTCharacteristic, + * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object + * containing a requested BluetoothDevice and all of the GATT fake + * objects. + */ +async function getHealthThermometerDeviceWithServicesDiscovered(options) { + let iframe = document.createElement('iframe'); + let fake_device = await setUpConnectableHealthThermometerDevice(); + let fakes = populateHealthThermometerFakes(fake_device); + await fake_device.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + await new Promise(resolve => { + let src = '/bluetooth/resources/health-thermometer-iframe.html'; + // TODO(509038): Can be removed once LayoutTests/bluetooth/* that + // use health-thermometer-iframe.html have been moved to + // LayoutTests/external/wpt/bluetooth/* + if (window.location.pathname.includes('/LayoutTests/')) { + src = + '../../../external/wpt/bluetooth/resources/health-thermometer-iframe.html'; + } + iframe.src = src; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + await new Promise((resolve, reject) => { + callWithTrustedClick(() => { + iframe.contentWindow.postMessage( + {type: 'DiscoverServices', options: options}, '*'); + }); + + function messageHandler(messageEvent) { + if (messageEvent.data == 'DiscoveryComplete') { + window.removeEventListener('message', messageHandler); + resolve(); + } else { + reject(new Error(`Unexpected message: ${messageEvent.data}`)); + } + } + window.addEventListener('message', messageHandler); + }); + let device = await requestDeviceWithTrustedClick(options); + await device.gatt.connect(); + return Object.assign({device}, fakes); +} + +/** + * Returns the device requested and connected in the given iframe context and + * fakes from populateHealthThermometerFakes(). + * @param {object} iframe The iframe element set up by the caller document. + * @returns {Promise<{device: BluetoothDevice, fakes: { + * fake_peripheral: FakePeripheral, + * fake_generic_access: FakeRemoteGATTService, + * fake_health_thermometer: FakeRemoteGATTService, + * fake_measurement_interval: FakeRemoteGATTCharacteristic, + * fake_cccd: FakeRemoteGATTDescriptor, + * fake_user_description: FakeRemoteGATTDescriptor, + * fake_temperature_measurement: FakeRemoteGATTCharacteristic, + * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object + * containing a requested BluetoothDevice and all of the GATT fake + * objects. + */ +async function getHealthThermometerDeviceFromIframe(iframe) { + const fake_device = await setUpConnectableHealthThermometerDevice(); + const fakes = await populateHealthThermometerFakes(fake_device); + await new Promise(resolve => { + let src = '/bluetooth/resources/health-thermometer-iframe.html'; + iframe.src = src; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve, {once: true}); + }); + await new Promise((resolve, reject) => { + callWithTrustedClick(() => { + iframe.contentWindow.postMessage( + { + type: 'RequestAndConnect', + options: {filters: [{services: [health_thermometer.name]}]} + }, + '*'); + }); + + function messageHandler(messageEvent) { + if (messageEvent.data == 'Connected') { + window.removeEventListener('message', messageHandler); + resolve(); + } else { + reject(new Error(`Unexpected message: ${messageEvent.data}`)); + } + } + window.addEventListener('message', messageHandler, {once: true}); + }); + const devices = await iframe.contentWindow.navigator.bluetooth.getDevices(); + assert_equals(devices.length, 1); + return Object.assign({device: devices[0]}, {fakes}); +} + +/** + * Similar to getHealthThermometerDevice() except the device + * is not connected and thus its services have not been + * discovered. + * @param {RequestDeviceOptions} options The options for requesting a Bluetooth + * Device. + * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object + * containing a requested BluetoothDevice and its fake counter part. + */ +async function getDiscoveredHealthThermometerDevice(options = { + filters: [{services: ['health_thermometer']}] +}) { + return await setUpHealthThermometerDevice({requestDeviceOptions: options}); +} + +/** + * Similar to getHealthThermometerDevice() except the device has no services, + * characteristics, or descriptors. + * @param {RequestDeviceOptions} options The options for requesting a Bluetooth + * Device. + * @returns {device: BluetoothDevice, fake_peripheral: FakePeripheral} An object + * containing a requested BluetoothDevice and its fake counter part. + */ +async function getEmptyHealthThermometerDevice(options) { + let fake_device = await getDiscoveredHealthThermometerDevice(options); + let fake_generic_access = fake_device.fake_services.get('generic_access'); + let fake_health_thermometer = + fake_device.fake_services.get('health_thermometer'); + // Remove services that have been set up by previous steps. + await fake_generic_access.remove(); + await fake_health_thermometer.remove(); + await fake_device.fake_peripheral.setNextGATTConnectionResponse( + {code: HCI_SUCCESS}); + await fake_device.device.gatt.connect(); + await fake_device.fake_peripheral.setNextGATTDiscoveryResponse( + {code: HCI_SUCCESS}); + return fake_device; +} + +/** + * Similar to getHealthThermometerService() except the service has no + * characteristics or included services. + * @param {RequestDeviceOptions} options The options for requesting a Bluetooth + * Device. + * @returns {service: BluetoothRemoteGATTService, + * fake_health_thermometer: FakeRemoteGATTService} An object containing the + * health themometer service object and its corresponding fake. + */ +async function getEmptyHealthThermometerService(options) { + let result = await getDiscoveredHealthThermometerDevice(options); + await result.fake_peripheral.setNextGATTConnectionResponse( + {code: HCI_SUCCESS}); + await result.device.gatt.connect(); + let fake_health_thermometer = + await result.fake_peripheral.addFakeService({uuid: 'health_thermometer'}); + await result.fake_peripheral.setNextGATTDiscoveryResponse( + {code: HCI_SUCCESS}); + let service = + await result.device.gatt.getPrimaryService('health_thermometer'); + return { + service: service, + fake_health_thermometer: fake_health_thermometer, + }; +} + +/** + * Similar to getHealthThermometerDevice except the GATT discovery + * response has not been set yet so more attributes can still be added. + * @param {RequestDeviceOptions} options The options for requesting a Bluetooth + * Device. + * @returns {Promise<{device: BluetoothDevice, fakes: { + * fake_peripheral: FakePeripheral, + * fake_generic_access: FakeRemoteGATTService, + * fake_health_thermometer: FakeRemoteGATTService, + * fake_measurement_interval: FakeRemoteGATTCharacteristic, + * fake_cccd: FakeRemoteGATTDescriptor, + * fake_user_description: FakeRemoteGATTDescriptor, + * fake_temperature_measurement: FakeRemoteGATTCharacteristic, + * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object + * containing a requested BluetoothDevice and all of the GATT fake + * objects. + */ +async function getConnectedHealthThermometerDevice(options) { + let fake_device = await getDiscoveredHealthThermometerDevice(options); + await fake_device.fake_peripheral.setNextGATTConnectionResponse({ + code: HCI_SUCCESS, + }); + let fakes = await populateHealthThermometerFakes(fake_device); + await fake_device.device.gatt.connect(); + return Object.assign({device: fake_device.device}, fakes); +} + +/** + * Returns an object containing a BluetoothDevice discovered using |options|, + * its corresponding FakePeripheral and FakeRemoteGATTServices. + * The simulated device is called 'Health Thermometer' it has two known service + * UUIDs: 'generic_access' and 'health_thermometer' which correspond to two + * services with the same UUIDs. The 'health thermometer' service contains three + * characteristics: + * - 'temperature_measurement' (indicate), + * - 'temperature_type' (read), + * - 'measurement_interval' (read, write, indicate) + * The 'measurement_interval' characteristic contains a + * 'gatt.client_characteristic_configuration' descriptor and a + * 'characteristic_user_description' descriptor. + * The device has been connected to and its attributes are ready to be + * discovered. + * @param {RequestDeviceOptions} options The options for requesting a Bluetooth + * Device. + * @returns {Promise<{device: BluetoothDevice, fakes: { + * fake_peripheral: FakePeripheral, + * fake_generic_access: FakeRemoteGATTService, + * fake_health_thermometer: FakeRemoteGATTService, + * fake_measurement_interval: FakeRemoteGATTCharacteristic, + * fake_cccd: FakeRemoteGATTDescriptor, + * fake_user_description: FakeRemoteGATTDescriptor, + * fake_temperature_measurement: FakeRemoteGATTCharacteristic, + * fake_temperature_type: FakeRemoteGATTCharacteristic}}>} An object + * containing a requested BluetoothDevice and all of the GATT fake + * objects. + */ +async function getHealthThermometerDevice(options) { + let result = await getConnectedHealthThermometerDevice(options); + await result.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + return result; +} + +/** + * Similar to getHealthThermometerDevice except that the peripheral has two + * 'health_thermometer' services. + * @param {RequestDeviceOptions} options The options for requesting a Bluetooth + * Device. + * @returns {Promise<{device: BluetoothDevice, fake_peripheral: FakePeripheral, + * fake_generic_access: FakeRemoteGATTService, fake_health_thermometer1: + * FakeRemoteGATTService, fake_health_thermometer2: FakeRemoteGATTService}>} An + * object containing a requested Bluetooth device and two fake health + * thermometer GATT services. + */ +async function getTwoHealthThermometerServicesDevice(options) { + let result = await getConnectedHealthThermometerDevice(options); + let fake_health_thermometer2 = + await result.fake_peripheral.addFakeService({uuid: 'health_thermometer'}); + await result.fake_peripheral.setNextGATTDiscoveryResponse( + {code: HCI_SUCCESS}); + return { + device: result.device, + fake_peripheral: result.fake_peripheral, + fake_generic_access: result.fake_generic_access, + fake_health_thermometer1: result.fake_health_thermometer, + fake_health_thermometer2: fake_health_thermometer2 + }; +} + +/** + * Returns an object containing a Health Thermometer BluetoothRemoteGattService + * and its corresponding FakeRemoteGATTService. + * @returns {Promise<{device: BluetoothDevice, fakes: { + * fake_peripheral: FakePeripheral, + * fake_generic_access: FakeRemoteGATTService, + * fake_health_thermometer: FakeRemoteGATTService, + * fake_measurement_interval: FakeRemoteGATTCharacteristic, + * fake_cccd: FakeRemoteGATTDescriptor, + * fake_user_description: FakeRemoteGATTDescriptor, + * fake_temperature_measurement: FakeRemoteGATTCharacteristic, + * fake_temperature_type: FakeRemoteGATTCharacteristic, + * service: BluetoothRemoteGATTService, + * fake_service: FakeRemoteGATTService}}>} An object + * containing a requested BluetoothDevice and all of the GATT fake + * objects. + */ +async function getHealthThermometerService() { + let result = await getHealthThermometerDevice(); + let service = + await result.device.gatt.getPrimaryService('health_thermometer'); + return Object.assign(result, { + service, + fake_service: result.fake_health_thermometer, + }); +} + +/** + * Returns an object containing a Measurement Interval + * BluetoothRemoteGATTCharacteristic and its corresponding + * FakeRemoteGATTCharacteristic. + * @returns {Promise<{device: BluetoothDevice, fakes: { + * fake_peripheral: FakePeripheral, + * fake_generic_access: FakeRemoteGATTService, + * fake_health_thermometer: FakeRemoteGATTService, + * fake_measurement_interval: FakeRemoteGATTCharacteristic, + * fake_cccd: FakeRemoteGATTDescriptor, + * fake_user_description: FakeRemoteGATTDescriptor, + * fake_temperature_measurement: FakeRemoteGATTCharacteristic, + * fake_temperature_type: FakeRemoteGATTCharacteristic, + * service: BluetoothRemoteGATTService, + * fake_service: FakeRemoteGATTService, + * characteristic: BluetoothRemoteGATTCharacteristic, + * fake_characteristic: FakeRemoteGATTCharacteristic}}>} An object + * containing a requested BluetoothDevice and all of the GATT fake + * objects. + */ +async function getMeasurementIntervalCharacteristic() { + let result = await getHealthThermometerService(); + let characteristic = + await result.service.getCharacteristic('measurement_interval'); + return Object.assign(result, { + characteristic, + fake_characteristic: result.fake_measurement_interval, + }); +} + +/** + * Returns an object containing a User Description + * BluetoothRemoteGATTDescriptor and its corresponding + * FakeRemoteGATTDescriptor. + * @returns {Promise<{device: BluetoothDevice, fakes: { + * fake_peripheral: FakePeripheral, + * fake_generic_access: FakeRemoteGATTService, + * fake_health_thermometer: FakeRemoteGATTService, + * fake_measurement_interval: FakeRemoteGATTCharacteristic, + * fake_cccd: FakeRemoteGATTDescriptor, + * fake_user_description: FakeRemoteGATTDescriptor, + * fake_temperature_measurement: FakeRemoteGATTCharacteristic, + * fake_temperature_type: FakeRemoteGATTCharacteristic, + * service: BluetoothRemoteGATTService, + * fake_service: FakeRemoteGATTService, + * characteristic: BluetoothRemoteGATTCharacteristic, + * fake_characteristic: FakeRemoteGATTCharacteristic + * descriptor: BluetoothRemoteGATTDescriptor, + * fake_descriptor: FakeRemoteGATTDescriptor}}>} An object + * containing a requested BluetoothDevice and all of the GATT fake + * objects. + */ +async function getUserDescriptionDescriptor() { + let result = await getMeasurementIntervalCharacteristic(); + let descriptor = await result.characteristic.getDescriptor( + 'gatt.characteristic_user_description'); + return Object.assign(result, { + descriptor, + fake_descriptor: result.fake_user_description, + }); +} + +/** Heart Rate Bluetooth Device Helper Methods */ + +/** @type {FakeDeviceOptions} */ +const heartRateFakeDeviceOptionsDefault = { + address: '08:08:08:08:08:08', + name: 'Heart Rate', + knownServiceUUIDs: ['generic_access', 'heart_rate'], + connectable: false, + serviceDiscoveryComplete: false, +}; + +/** @type {RequestDeviceOptions} */ +const heartRateRequestDeviceOptionsDefault = { + filters: [{services: ['heart_rate']}] +}; + +async function getHeartRateDevice(setupOptionsOverride) { + let setupOptions = createSetupOptions( + {fakeDeviceOptions: heartRateFakeDeviceOptionsDefault}, + setupOptionsOverride); + return await setUpPreconnectedFakeDevice(setupOptions); +} + +/** + * Returns an array containing two FakePeripherals corresponding + * to the simulated devices. + * @returns {Promise<Array<FakePeripheral>>} The device fakes initialized as + * Health Thermometer and Heart Rate devices. + */ +async function setUpHealthThermometerAndHeartRateDevices() { + await initializeFakeCentral({state: 'powered-on'}); + return Promise.all([ + fake_central.simulatePreconnectedPeripheral({ + address: '09:09:09:09:09:09', + name: 'Health Thermometer', + manufacturerData: {}, + knownServiceUUIDs: ['generic_access', 'health_thermometer'], + }), + fake_central.simulatePreconnectedPeripheral({ + address: '08:08:08:08:08:08', + name: 'Heart Rate', + manufacturerData: {}, + knownServiceUUIDs: ['generic_access', 'heart_rate'], + }) + ]); +} diff --git a/testing/web-platform/tests/bluetooth/resources/bluetooth-scanning-helpers.js b/testing/web-platform/tests/bluetooth/resources/bluetooth-scanning-helpers.js new file mode 100644 index 0000000000..f474c9c306 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/resources/bluetooth-scanning-helpers.js @@ -0,0 +1,42 @@ +'use strict'; + +const company_id = '224'; +const data = new TextEncoder().encode('foo'); +const manufacturerDataMap = {[company_id]: data}; +const health_uuid = health_thermometer.uuid; +const serviceDataMap = {[health_uuid]: data}; +const scanRecord = { + name: 'Health Thermometer', + uuids: ['generic_access', health_uuid], + txPower: 20, + appearance: 100, + manufacturerData: manufacturerDataMap, + serviceData: serviceDataMap, +}; +const scanResult = { + deviceAddress: '09:09:09:09:09:09', + rssi: 100, + scanRecord: scanRecord, +}; + +function verifyBluetoothAdvertisingEvent(e) { + assert_equals(e.constructor.name, 'BluetoothAdvertisingEvent') + assert_equals(e.device.name, scanRecord.name) + assert_equals(e.name, scanRecord.name) + assert_array_equals(e.uuids, + ["00001800-0000-1000-8000-00805f9b34fb", + "00001809-0000-1000-8000-00805f9b34fb"]) + assert_equals(e.txPower, 20) + assert_equals(e.rssi, 100) + + assert_equals(e.manufacturerData.constructor.name, + 'BluetoothManufacturerDataMap') + assert_equals(data[0], e.manufacturerData.get(224).getUint8(0)) + assert_equals(data[1], e.manufacturerData.get(224).getUint8(1)) + assert_equals(data[2], e.manufacturerData.get(224).getUint8(2)) + + assert_equals(e.serviceData.constructor.name, 'BluetoothServiceDataMap') + assert_equals(data[0], e.serviceData.get(health_uuid).getUint8(0)) + assert_equals(data[1], e.serviceData.get(health_uuid).getUint8(1)) + assert_equals(data[2], e.serviceData.get(health_uuid).getUint8(2)) +} diff --git a/testing/web-platform/tests/bluetooth/resources/bluetooth-test.js b/testing/web-platform/tests/bluetooth/resources/bluetooth-test.js new file mode 100644 index 0000000000..7ad1b937e1 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/resources/bluetooth-test.js @@ -0,0 +1,363 @@ +'use strict'; + +/** + * Test Setup Helpers + */ + +/** + * Loads a script by creating a <script> element pointing to |path|. + * @param {string} path The path of the script to load. + * @returns {Promise<void>} Resolves when the script has finished loading. + */ +function loadScript(path) { + let script = document.createElement('script'); + let promise = new Promise(resolve => script.onload = resolve); + script.src = path; + script.async = false; + document.head.appendChild(script); + return promise; +} + +/** + * Performs the Chromium specific setup necessary to run the tests in the + * Chromium browser. This test file is shared between Web Platform Tests and + * Blink Web Tests, so this method figures out the correct paths to use for + * loading scripts. + * + * TODO(https://crbug.com/569709): Update this description when all Web + * Bluetooth Blink Web Tests have been migrated into this repository. + * @returns {Promise<void>} Resolves when Chromium specific setup is complete. + */ +async function performChromiumSetup() { + // Determine path prefixes. + let resPrefix = '/resources'; + const chromiumResources = ['/resources/chromium/web-bluetooth-test.js']; + const pathname = window.location.pathname; + if (pathname.includes('/wpt_internal/')) { + chromiumResources.push( + '/wpt_internal/bluetooth/resources/bluetooth-fake-adapter.js'); + } + + await loadScript(`${resPrefix}/test-only-api.js`); + if (!isChromiumBased) { + return; + } + + for (const path of chromiumResources) { + await loadScript(path); + } + + await initializeChromiumResources(); + + // Call setBluetoothFakeAdapter() to clean up any fake adapters left over by + // legacy tests. Legacy tests that use setBluetoothFakeAdapter() sometimes + // fail to clean their fake adapter. This is not a problem for these tests + // because the next setBluetoothFakeAdapter() will clean it up anyway but it + // is a problem for the new tests that do not use setBluetoothFakeAdapter(). + // TODO(https://crbug.com/569709): Remove once setBluetoothFakeAdapter is no + // longer used. + if (typeof setBluetoothFakeAdapter !== 'undefined') { + setBluetoothFakeAdapter(''); + } +} + +/** + * These tests rely on the User Agent providing an implementation of the Web + * Bluetooth Testing API. + * https://docs.google.com/document/d/1Nhv_oVDCodd1pEH_jj9k8gF4rPGb_84VYaZ9IG8M_WY/edit?ts=59b6d823#heading=h.7nki9mck5t64 + * @param {function{*}: Promise<*>} test_function The Web Bluetooth test to run. + * @param {string} name The name or description of the test. + * @param {object} properties An object containing extra options for the test. + * @param {Boolean} validate_response_consumed Whether to validate all response + * consumed or not. + * @returns {Promise<void>} Resolves if Web Bluetooth test ran successfully, or + * rejects if the test failed. + */ +function bluetooth_test( + test_function, name, properties, validate_response_consumed = true) { + return promise_test(async (t) => { + assert_implements(navigator.bluetooth, 'missing navigator.bluetooth'); + // Trigger Chromium-specific setup. + await performChromiumSetup(); + assert_implements( + navigator.bluetooth.test, 'missing navigator.bluetooth.test'); + await test_function(t); + if (validate_response_consumed) { + let consumed = await navigator.bluetooth.test.allResponsesConsumed(); + assert_true(consumed); + } + }, name, properties); +} + +/** + * Test Helpers + */ + +/** + * Waits until the document has finished loading. + * @returns {Promise<void>} Resolves if the document is already completely + * loaded or when the 'onload' event is fired. + */ +function waitForDocumentReady() { + return new Promise(resolve => { + if (document.readyState === 'complete') { + resolve(); + } + + window.addEventListener('load', () => { + resolve(); + }, {once: true}); + }); +} + +/** + * Simulates a user activation prior to running |callback|. + * @param {Function} callback The function to run after the user activation. + * @returns {Promise<*>} Resolves when the user activation has been simulated + * with the result of |callback|. + */ +async function callWithTrustedClick(callback) { + await waitForDocumentReady(); + return new Promise(resolve => { + let button = document.createElement('button'); + button.textContent = 'click to continue test'; + button.style.display = 'block'; + button.style.fontSize = '20px'; + button.style.padding = '10px'; + button.onclick = () => { + document.body.removeChild(button); + resolve(callback()); + }; + document.body.appendChild(button); + test_driver.click(button); + }); +} + +/** + * Calls requestDevice() in a context that's 'allowed to show a popup'. + * @returns {Promise<BluetoothDevice>} Resolves with a Bluetooth device if + * successful or rejects with an error. + */ +function requestDeviceWithTrustedClick() { + let args = arguments; + return callWithTrustedClick( + () => navigator.bluetooth.requestDevice.apply(navigator.bluetooth, args)); +} + +/** + * Calls requestLEScan() in a context that's 'allowed to show a popup'. + * @returns {Promise<BluetoothLEScan>} Resolves with the properties of the scan + * if successful or rejects with an error. + */ +function requestLEScanWithTrustedClick() { + let args = arguments; + return callWithTrustedClick( + () => navigator.bluetooth.requestLEScan.apply(navigator.bluetooth, args)); +} + +/** + * Function to test that a promise rejects with the expected error type and + * message. + * @param {Promise} promise + * @param {object} expected + * @param {string} description + * @returns {Promise<void>} Resolves if |promise| rejected with |expected| + * error. + */ +function assert_promise_rejects_with_message(promise, expected, description) { + return promise.then( + () => { + assert_unreached('Promise should have rejected: ' + description); + }, + error => { + assert_equals(error.name, expected.name, 'Unexpected Error Name:'); + if (expected.message) { + assert_equals( + error.message, expected.message, 'Unexpected Error Message:'); + } + }); +} + +/** + * Helper class that can be created to check that an event has fired. + */ +class EventCatcher { + /** + * @param {EventTarget} object The object to listen for events on. + * @param {string} event The type of event to listen for. + */ + constructor(object, event) { + /** @type {boolean} */ + this.eventFired = false; + + /** @type {function()} */ + let event_listener = () => { + object.removeEventListener(event, event_listener); + this.eventFired = true; + }; + object.addEventListener(event, event_listener); + } +} + +/** + * Notifies when the event |type| has fired. + * @param {EventTarget} target The object to listen for the event. + * @param {string} type The type of event to listen for. + * @param {object} options Characteristics about the event listener. + * @returns {Promise<Event>} Resolves when an event of |type| has fired. + */ +function eventPromise(target, type, options) { + return new Promise(resolve => { + let wrapper = function(event) { + target.removeEventListener(type, wrapper); + resolve(event); + }; + target.addEventListener(type, wrapper, options); + }); +} + +/** + * The action that should occur first in assert_promise_event_order_(). + * @enum {string} + */ +const ShouldBeFirst = { + EVENT: 'event', + PROMISE_RESOLUTION: 'promiseresolved', +}; + +/** + * Helper function to assert that events are fired and a promise resolved + * in the correct order. + * 'event' should be passed as |should_be_first| to indicate that the events + * should be fired first, otherwise 'promiseresolved' should be passed. + * Attaches |num_listeners| |event| listeners to |object|. If all events have + * been fired and the promise resolved in the correct order, returns a promise + * that fulfills with the result of |object|.|func()| and |event.target.value| + * of each of event listeners. Otherwise throws an error. + * @param {ShouldBeFirst} should_be_first Indicates whether |func| should + * resolve before |event| is fired. + * @param {EventTarget} object The target object to add event listeners to. + * @param {function(*): Promise<*>} func The function to test the resolution + * order for. + * @param {string} event The event type to listen for. + * @param {number} num_listeners The number of events to listen for. + * @returns {Promise<*>} The return value of |func|. + */ +function assert_promise_event_order_( + should_be_first, object, func, event, num_listeners) { + let order = []; + let event_promises = []; + for (let i = 0; i < num_listeners; i++) { + event_promises.push(new Promise(resolve => { + let event_listener = (e) => { + object.removeEventListener(event, event_listener); + order.push(ShouldBeFirst.EVENT); + resolve(e.target.value); + }; + object.addEventListener(event, event_listener); + })); + } + + let func_promise = object[func]().then(result => { + order.push(ShouldBeFirst.PROMISE_RESOLUTION); + return result; + }); + + return Promise.all([func_promise, ...event_promises]).then((result) => { + if (should_be_first !== order[0]) { + throw should_be_first === ShouldBeFirst.PROMISE_RESOLUTION ? + `'${event}' was fired before promise resolved.` : + `Promise resolved before '${event}' was fired.`; + } + + if (order[0] !== ShouldBeFirst.PROMISE_RESOLUTION && + order[order.length - 1] !== ShouldBeFirst.PROMISE_RESOLUTION) { + throw 'Promise resolved in between event listeners.'; + } + + return result; + }); +} + +/** + * Asserts that the promise returned by |func| resolves before events of type + * |event| are fired |num_listeners| times on |object|. See + * assert_promise_event_order_ above for more details. + * @param {EventTarget} object The target object to add event listeners to. + * @param {function(*): Promise<*>} func The function whose promise should + * resolve first. + * @param {string} event The event type to listen for. + * @param {number} num_listeners The number of events to listen for. + * @returns {Promise<*>} The return value of |func|. + */ +function assert_promise_resolves_before_event( + object, func, event, num_listeners = 1) { + return assert_promise_event_order_( + ShouldBeFirst.PROMISE_RESOLUTION, object, func, event, num_listeners); +} + +/** + * Asserts that the promise returned by |func| resolves after events of type + * |event| are fired |num_listeners| times on |object|. See + * assert_promise_event_order_ above for more details. + * @param {EventTarget} object The target object to add event listeners to. + * @param {function(*): Promise<*>} func The function whose promise should + * resolve first. + * @param {string} event The event type to listen for. + * @param {number} num_listeners The number of events to listen for. + * @returns {Promise<*>} The return value of |func|. + */ +function assert_promise_resolves_after_event( + object, func, event, num_listeners = 1) { + return assert_promise_event_order_( + ShouldBeFirst.EVENT, object, func, event, num_listeners); +} + +/** + * Returns a promise that resolves after 100ms unless the the event is fired on + * the object in which case the promise rejects. + * @param {EventTarget} object The target object to listen for events. + * @param {string} event_name The event type to listen for. + * @returns {Promise<void>} Resolves if no events were fired. + */ +function assert_no_events(object, event_name) { + return new Promise((resolve) => { + let event_listener = (e) => { + object.removeEventListener(event_name, event_listener); + assert_unreached('Object should not fire an event.'); + }; + object.addEventListener(event_name, event_listener); + // TODO: Remove timeout. + // http://crbug.com/543884 + step_timeout(() => { + object.removeEventListener(event_name, event_listener); + resolve(); + }, 100); + }); +} + +/** + * Asserts that |properties| contains the same properties in + * |expected_properties| with equivalent values. + * @param {object} properties Actual object to compare. + * @param {object} expected_properties Expected object to compare with. + */ +function assert_properties_equal(properties, expected_properties) { + for (let key in expected_properties) { + assert_equals(properties[key], expected_properties[key]); + } +} + +/** + * Asserts that |data_map| contains |expected_key|, and that the uint8 values + * for |expected_key| matches |expected_value|. + */ +function assert_data_maps_equal(data_map, expected_key, expected_value) { + assert_true(data_map.has(expected_key)); + + const value = new Uint8Array(data_map.get(expected_key).buffer); + assert_equals(value.length, expected_value.length); + for (let i = 0; i < value.length; ++i) { + assert_equals(value[i], expected_value[i]); + } +} diff --git a/testing/web-platform/tests/bluetooth/resources/health-thermometer-iframe.html b/testing/web-platform/tests/bluetooth/resources/health-thermometer-iframe.html new file mode 100644 index 0000000000..f9f7a6f0d7 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/resources/health-thermometer-iframe.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<body> +<button>Click me!</button> +<script> +let device, gatt; + +test_driver.set_test_context(parent); + +function requestDeviceWithOptionsAndConnect(options) { + return test_driver.click(document.getElementsByTagName("button")[0]) + .then(() => navigator.bluetooth.requestDevice(options)) + .then(device => device.gatt.connect()); +} + +window.addEventListener('message', (messageEvent) => { + switch (messageEvent.data.type) { + case 'GetAvailability': + navigator.bluetooth.getAvailability() + .then(availability => parent.postMessage(availability, '*')) + .catch(err => parent.postMessage(`FAIL: ${err}`, '*')); + break; + case 'GetDevices': + navigator.bluetooth.getDevices() + .then(devices => parent.postMessage('Success', '*')) + .catch(err => parent.postMessage(`FAIL: ${err}`, '*')); + break; + case 'RequestDevice': + test_driver.click(document.getElementsByTagName('button')[0]) + .then( + () => navigator.bluetooth.requestDevice( + {filters: [{services: ['generic_access']}]})) + .then(device => { + if (device.constructor.name === 'BluetoothDevice') { + parent.postMessage('Success', '*'); + } else { + parent.postMessage( + `FAIL: requestDevice in iframe returned ${device.name}`, '*'); + } + }) + .catch(err => parent.postMessage(`FAIL: ${err.name}: ${err.message}`, '*')); + break; + case 'RequestLEScan': + test_driver.click(document.getElementsByTagName('button')[0]) + .then( + () => navigator.bluetooth.requestLEScan( + {filters: [{name: 'Health Thermometer'}]})) + .then(leScan => { + if (leScan.active) { + parent.postMessage('Success', '*'); + leScan.stop(); + } else { + parent.postMessage(`FAIL: the LE scan hasn't been initiated.`, '*'); + } + }) + .catch(err => parent.postMessage(`FAIL: ${err.name}: ${err.message}`, '*')); + break; + case 'RequestAndConnect': + requestDeviceWithOptionsAndConnect(messageEvent.data.options) + .then(_ => { + gatt = _; + device = gatt.device; + parent.postMessage('Connected', '*'); + }) + .catch(err => { + parent.postMessage(`FAIL: ${err}`, '*'); + }); + break; + case 'DiscoverServices': + requestDeviceWithOptionsAndConnect(messageEvent.data.options) + .then(gatt => gatt.getPrimaryServices()) + .then(() => parent.postMessage('DiscoveryComplete', '*')) + .catch(err => { + parent.postMessage(`FAIL: ${err}`, '*'); + }); + break; + case 'GetService': + if (typeof gatt === 'undefined') { + parent.postMessage('FAIL: no GATT server', '*'); + break; + } + gatt.getPrimaryService(messageEvent.data.options) + .then(() => parent.postMessage('ServiceReceived', '*')) + .catch(err => parent.postMessage(`FAIL: ${err}`, '*')); + break; + default: + parent.postMessage( + `FAIL: Bad message type: ${messageEvent.data.type}`, '*'); + } +}); +</script> diff --git a/testing/web-platform/tests/bluetooth/script-tests/base_test_js.template b/testing/web-platform/tests/bluetooth/script-tests/base_test_js.template new file mode 100644 index 0000000000..04c7a70ba4 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/base_test_js.template @@ -0,0 +1,7 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +TEST diff --git a/testing/web-platform/tests/bluetooth/script-tests/characteristic/characteristic-is-removed.js b/testing/web-platform/tests/bluetooth/script-tests/characteristic/characteristic-is-removed.js new file mode 100644 index 0000000000..48aaec3e93 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/characteristic/characteristic-is-removed.js @@ -0,0 +1,24 @@ +'use strict'; +const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Characteristic no longer exists.', + 'InvalidStateError'); +let fake_peripheral, characteristic, fake_characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _)) + .then(() => characteristic.getDescriptor(user_description.name)) + .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e)) + .then(() => fake_characteristic.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.CALLS([ + getDescriptor(user_description.name)| + getDescriptors(user_description.name)[UUID]| + getDescriptors()| + readValue()| + writeValue(new Uint8Array(1))| + writeValueWithResponse(new Uint8Array(1))| + writeValueWithoutResponse(new Uint8Array(1))| + startNotifications() + ]), expected)), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/characteristic/descriptor-get-same-object.js b/testing/web-platform/tests/bluetooth/script-tests/characteristic/descriptor-get-same-object.js new file mode 100644 index 0000000000..4e6bc3519b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/characteristic/descriptor-get-same-object.js @@ -0,0 +1,32 @@ +'use strict'; +const test_desc = 'Calls to FUNCTION_NAME should return the same object.'; +let characteristic; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({characteristic} = _)) + .then(() => Promise.all([ + characteristic.CALLS([ + getDescriptor(user_description.alias)| + getDescriptors(user_description.alias) + ]), + characteristic.FUNCTION_NAME(user_description.name), + characteristic.FUNCTION_NAME(user_description.uuid) + ])) + .then(descriptors_arrays => { + assert_true(descriptors_arrays.length > 0) + + // Convert to arrays if necessary. + for (let i = 0; i < descriptors_arrays.length; i++) { + descriptors_arrays[i] = [].concat(descriptors_arrays[i]); + } + + for (let i = 1; i < descriptors_arrays.length; i++) { + assert_equals(descriptors_arrays[0].length, + descriptors_arrays[i].length); + } + + let base_set = new Set(descriptors_arrays[0]); + for (let descriptors of descriptors_arrays) { + descriptors.forEach(descriptor => assert_true(base_set.has(descriptor))); + } + }), test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/characteristic/service-is-removed.js b/testing/web-platform/tests/bluetooth/script-tests/characteristic/service-is-removed.js new file mode 100644 index 0000000000..2f5824082b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/characteristic/service-is-removed.js @@ -0,0 +1,20 @@ +// TODO(https://crbug.com/672127) Use this test case to test the rest of +// characteristic functions. +'use strict'; +const test_desc = 'Service is removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let characteristic, fake_peripheral, fake_service; + +bluetooth_test(() => getMeasurementIntervalCharacteristic() + .then(_ => ({characteristic, fake_peripheral, fake_service} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + characteristic.CALLS([ + getDescriptor(user_description.name)| + getDescriptors(user_description.uuid)[UUID]| + getDescriptors(user_description.name)]), + expected, + 'Service got removed.')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/descriptor/service-is-removed.js b/testing/web-platform/tests/bluetooth/script-tests/descriptor/service-is-removed.js new file mode 100644 index 0000000000..5373364399 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/descriptor/service-is-removed.js @@ -0,0 +1,18 @@ +'use strict'; +const test_desc = 'Service gets removed. Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let descriptor, fake_peripheral, fake_service; + +bluetooth_test(() => getUserDescriptionDescriptor() + .then(_ => ({descriptor, fake_peripheral, fake_service} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + descriptor.CALLS([ + readValue()| + writeValue(new ArrayBuffer(1 /* length */)) + ]), + expected, + 'Service got removed.')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-before.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-before.js new file mode 100644 index 0000000000..57704ee299 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-before.js @@ -0,0 +1,22 @@ +'use strict'; +const test_desc = 'disconnect() called before FUNCTION_NAME. ' + + 'Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); +let device; + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.disconnect()) + .then(() => assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('health_thermometer')| + getPrimaryServices()| + getPrimaryServices('health_thermometer')[UUID]]), + expected)), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-error.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-error.js new file mode 100644 index 0000000000..edabb07bcc --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-error.js @@ -0,0 +1,22 @@ +'use strict'; +const test_desc = 'disconnect() called during a FUNCTION_NAME ' + + 'call that fails. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', 'NetworkError'); +let device; + +bluetooth_test(() => getEmptyHealthThermometerDevice() + .then(_ => ({device} = _)) + .then(() => { + let promise = assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('health_thermometer')| + getPrimaryServices()| + getPrimaryServices('health_thermometer')[UUID] + ]), + expected) + device.gatt.disconnect(); + return promise; + }), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-success.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-success.js new file mode 100644 index 0000000000..84157a0693 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-success.js @@ -0,0 +1,23 @@ +'use strict'; +const test_desc = 'disconnect() called during a FUNCTION_NAME call that ' + + 'succeeds. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(({device}) => { + let promise = assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('health_thermometer')| + getPrimaryServices()| + getPrimaryServices('health_thermometer')[UUID] + ]), + expected); + device.gatt.disconnect(); + return promise; + }), test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-discovery-timeout.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-discovery-timeout.js new file mode 100644 index 0000000000..718e290950 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-discovery-timeout.js @@ -0,0 +1,42 @@ +'use strict'; +const test_desc = + 'Calls to FUNCTION_NAME when device disconnects and discovery' + + ' times out should reject promise rather than get stuck.'; +let device; + +bluetooth_test( + async (t) => { + let {device, fake_peripheral} = + await getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }); + + await fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_CONNECTION_TIMEOUT, + }); + await Promise.all([ + fake_peripheral.simulateGATTDisconnection({ + code: HCI_SUCCESS, + }), + // Using promise_rejects_dom here rather than + // assert_promise_rejects_with_message as the race between + // simulateGATTDisconnection and getPrimaryServices might end up giving + // slightly different exception message (i.e has "Failed to execute ... + // on + // ... " prefix when disconnected state is reflected on the renderer + // side). The point of the test is no matter how race between them, the + // promise will be rejected as opposed to get stuck. + promise_rejects_dom(t, 'NetworkError', device.gatt.CALLS([ + getPrimaryService('health_thermometer') | getPrimaryServices() | + getPrimaryServices('health_thermometer')[UUID] + ])), + ]); + }, + test_desc, '', + // As specified above there is a race condition between + // simulateGATTDisconnection and getPrimaryServices, the artificial + // GATTDiscoveryResponse might not be consumed in case + // simulateGATTDisconnection happens first. As a result explicitly skip + // all response consumed validation at the end of the test. + /*validate_response_consumed=*/ false); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-invalidates-objects.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-invalidates-objects.js new file mode 100644 index 0000000000..995fda3441 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-invalidates-objects.js @@ -0,0 +1,39 @@ +'use strict'; +const test_desc = 'Calls on services after we disconnect and connect again. '+ + 'Should reject with InvalidStateError.'; +let device, services; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.CALLS([ + getPrimaryService('health_thermometer')| + getPrimaryServices()| + getPrimaryServices('health_thermometer')[UUID]])) + // Convert to array if necessary. + .then(s => services = [].concat(s)) + .then(() => device.gatt.disconnect()) + .then(() => device.gatt.connect()) + .then(() => { + let promises = Promise.resolve(); + for (let service of services) { + let error = new DOMException( + `Service with UUID ${service.uuid} is no longer valid. Remember ` + + `to retrieve the service again after reconnecting.`, + 'InvalidStateError'); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristic('measurement_interval'), + error)); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristics(), + error)); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristics('measurement_interval'), + error)); + } + return promises; + }), test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnected-device.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnected-device.js new file mode 100644 index 0000000000..2b6011642b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnected-device.js @@ -0,0 +1,20 @@ +'use strict'; +const test_desc = 'FUNCTION_NAME called before connecting. Reject with ' + + 'NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); + +bluetooth_test(() => getDiscoveredHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('health_thermometer')| + getPrimaryServices()| + getPrimaryServices('health_thermometer')[UUID] + ]), + expected)), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js b/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js new file mode 100644 index 0000000000..e9e972359a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js @@ -0,0 +1,25 @@ +'use strict'; +const test_desc = 'Request for absent service without permission. Should ' + + 'Reject with SecurityError even if services have been discovered already.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service ' + + 'UUID to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); +let device; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: ['health_thermometer']}] + }) + .then(_ => ({device} = _)) + .then(() => Promise.all([ + assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService(glucose.alias)| + getPrimaryServices(glucose.alias)[UUID] + ]), expected), + assert_promise_rejects_with_message( + device.gatt.FUNCTION_NAME(glucose.name), expected), + assert_promise_rejects_with_message( + device.gatt.FUNCTION_NAME(glucose.uuid), expected)])), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-service-not-found.js b/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-service-not-found.js new file mode 100644 index 0000000000..6b745d7e2a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-service-not-found.js @@ -0,0 +1,16 @@ +'use strict'; +const test_desc = 'Request for absent service. Must reject with ' + + 'NotFoundError even when the services have previously been discovered.'; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['glucose']}) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('glucose')| + getPrimaryServices('glucose')[UUID] + ]), + new DOMException( + `No Services matching UUID ${glucose.uuid} found in Device.`, + 'NotFoundError'))), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js b/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js new file mode 100644 index 0000000000..cf508a928e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js @@ -0,0 +1,25 @@ +'use strict'; +const test_desc = 'Garbage Collection ran during a FUNCTION_NAME ' + + 'call that failed. Should not crash.' +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect first ' + + 'with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getEmptyHealthThermometerDevice() + .then(({device}) => { + promise = assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('health_thermometer')| + getPrimaryServices()| + getPrimaryServices('health_thermometer')[UUID] + ]), + expected); + // Disconnect called to clear attributeInstanceMap and allow the + // object to get garbage collected. + device.gatt.disconnect(); + return garbageCollect(); + }) + .then(() => promise), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js b/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js new file mode 100644 index 0000000000..bb472fcca4 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js @@ -0,0 +1,24 @@ +'use strict'; +const test_desc = 'Garbage Collection ran during a FUNCTION_NAME call that ' + + 'succeeds. Should not crash.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => { + promise = assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('health_thermometer') | + getPrimaryServices() | + getPrimaryServices('health_thermometer')[UUID]]), + expected); + device.gatt.disconnect(); + return garbageCollect(); + }) + .then(() => promise), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/get-different-service-after-reconnection.js b/testing/web-platform/tests/bluetooth/script-tests/server/get-different-service-after-reconnection.js new file mode 100644 index 0000000000..e72128a76f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/get-different-service-after-reconnection.js @@ -0,0 +1,35 @@ +'use strict'; +const test_desc = 'Calls to FUNCTION_NAME after a disconnection should return ' + + 'a different object.'; +let device, services_first_connection, services_second_connection; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.CALLS([ + getPrimaryService('health_thermometer')| + getPrimaryServices()| + getPrimaryServices('health_thermometer')[UUID]])) + .then(services => services_first_connection = services) + .then(() => device.gatt.disconnect()) + .then(() => device.gatt.connect()) + .then(() => device.gatt.PREVIOUS_CALL) + .then(services => services_second_connection = services) + .then(() => { + // Convert to arrays if necessary. + services_first_connection = [].concat(services_first_connection); + services_second_connection = [].concat(services_second_connection); + + assert_equals(services_first_connection.length, + services_second_connection.length); + + let first_connection_set = new Set(services_first_connection); + let second_connection_set = new Set(services_second_connection); + + // The two sets should be disjoint. + let common_services = services_first_connection.filter( + val => second_connection_set.has(val)); + assert_equals(common_services.length, 0); + }), test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/get-same-object.js b/testing/web-platform/tests/bluetooth/script-tests/server/get-same-object.js new file mode 100644 index 0000000000..3b3bdd19d2 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/get-same-object.js @@ -0,0 +1,33 @@ +'use strict'; +const test_desc = 'Calls to FUNCTION_NAME should return the same object.'; +let device; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access']}) + .then(({device}) => Promise.all([ + device.gatt.CALLS([ + getPrimaryService('health_thermometer')| + getPrimaryServices()| + getPrimaryServices('health_thermometer')[UUID]]), + device.gatt.PREVIOUS_CALL])) + .then(([services_first_call, services_second_call]) => { + // Convert to arrays if necessary. + services_first_call = [].concat(services_first_call); + services_second_call = [].concat(services_second_call); + + assert_equals(services_first_call.length, services_second_call.length); + + let first_call_set = new Set(services_first_call); + assert_equals(services_first_call.length, first_call_set.size); + let second_call_set = new Set(services_second_call); + assert_equals(services_second_call.length, second_call_set.size); + + services_first_call.forEach(service => { + assert_true(second_call_set.has(service)) + }); + + services_second_call.forEach(service => { + assert_true(first_call_set.has(service)); + }); + }), test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/invalid-service-name.js b/testing/web-platform/tests/bluetooth/script-tests/server/invalid-service-name.js new file mode 100644 index 0000000000..52cbb24f4a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/invalid-service-name.js @@ -0,0 +1,22 @@ +'use strict'; +const test_desc = 'Wrong Service name. Reject with TypeError.'; +const expected = new DOMException( + "Failed to execute 'FUNCTION_NAME' on " + + "'BluetoothRemoteGATTServer': Invalid Service name: " + + "'wrong_name'. It must be a valid UUID alias (e.g. 0x1234), " + + "UUID (lowercase hex characters e.g. " + + "'00001234-0000-1000-8000-00805f9b34fb'), " + + "or recognized standard name from " + + "https://www.bluetooth.com/specifications/gatt/services" + + " e.g. 'alert_notification'.", + 'TypeError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice() + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('wrong_name')| + getPrimaryServices('wrong_name') + ]), + expected, + 'Wrong Service name passed.')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-absent-service.js b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-absent-service.js new file mode 100644 index 0000000000..200dab3e93 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-absent-service.js @@ -0,0 +1,23 @@ +'use strict'; +const test_desc = 'Request for absent service without permission. ' + + 'Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service UUID ' + + 'to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => Promise.all([ + assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService(glucose.alias)| + getPrimaryServices(glucose.alias)[UUID] + ]), expected), + assert_promise_rejects_with_message( + device.gatt.FUNCTION_NAME(glucose.name), expected), + assert_promise_rejects_with_message( + device.gatt.FUNCTION_NAME(glucose.uuid), expected)])), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-for-any-service.js b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-for-any-service.js new file mode 100644 index 0000000000..60e3ef0080 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-for-any-service.js @@ -0,0 +1,17 @@ +'use strict'; +const test_desc = 'Request for present service without permission to access ' + + 'any service. Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access any service. Tip: Add the service ' + + 'UUID to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true}) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('heart_rate')| + getPrimaryServices()| + getPrimaryServices('heart_rate')[UUID]]), + expected)), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-present-service.js b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-present-service.js new file mode 100644 index 0000000000..3257410685 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-present-service.js @@ -0,0 +1,22 @@ +'use strict'; +const test_desc = 'Request for present service without permission. ' + + 'Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service UUID ' + + 'to \'optionalServices\' in requestDevice() options. https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => Promise.all([ + assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService(generic_access.alias)| + getPrimaryServices(generic_access.alias)[UUID] + ]), expected), + assert_promise_rejects_with_message( + device.gatt.FUNCTION_NAME(generic_access.name), expected), + assert_promise_rejects_with_message( + device.gatt.FUNCTION_NAME(generic_access.uuid), expected)])), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/service-not-found.js b/testing/web-platform/tests/bluetooth/script-tests/server/service-not-found.js new file mode 100644 index 0000000000..0fd2dace78 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/server/service-not-found.js @@ -0,0 +1,16 @@ +'use strict'; +const test_desc = 'Request for absent service. Reject with NotFoundError.'; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['glucose'] + }) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.CALLS([ + getPrimaryService('glucose')| + getPrimaryServices('glucose')[UUID] + ]), + new DOMException( + `No Services matching UUID ${glucose.uuid} found in Device.`, + 'NotFoundError'))), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/blocklisted-characteristic.js b/testing/web-platform/tests/bluetooth/script-tests/service/blocklisted-characteristic.js new file mode 100644 index 0000000000..b26f039a70 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/service/blocklisted-characteristic.js @@ -0,0 +1,19 @@ +'use strict'; +const test_desc = 'Serial Number String characteristic is blocklisted. ' + + 'Should reject with SecurityError.'; +const expected = new DOMException( + 'getCharacteristic(s) called with blocklisted UUID. https://goo.gl/4NeimX', + 'SecurityError'); + +bluetooth_test(() => getHIDDevice({ + filters: [{services: ['device_information']}] +}) + .then(({device}) => device.gatt.getPrimaryService('device_information')) + .then(service => assert_promise_rejects_with_message( + service.CALLS([ + getCharacteristic('serial_number_string')| + getCharacteristics('serial_number_string')[UUID] + ]), + expected, + 'Serial Number String characteristic is blocklisted.')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/characteristic-not-found.js b/testing/web-platform/tests/bluetooth/script-tests/service/characteristic-not-found.js new file mode 100644 index 0000000000..366e046774 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/service/characteristic-not-found.js @@ -0,0 +1,15 @@ +'use strict'; +const test_desc = 'Request for absent characteristics with UUID. ' + + 'Reject with NotFoundError.'; + +bluetooth_test(() => getEmptyHealthThermometerService() + .then(({service}) => assert_promise_rejects_with_message( + service.CALLS([ + getCharacteristic('battery_level')| + getCharacteristics('battery_level')[UUID] + ]), + new DOMException( + `No Characteristics matching UUID ${battery_level.uuid} found ` + + `in Service with UUID ${health_thermometer.uuid}.`, + 'NotFoundError'))), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/garbage-collection-ran-during-error.js b/testing/web-platform/tests/bluetooth/script-tests/service/garbage-collection-ran-during-error.js new file mode 100644 index 0000000000..7ed4aaa962 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/service/garbage-collection-ran-during-error.js @@ -0,0 +1,24 @@ +'use strict'; +const test_desc = 'Garbage Collection ran during FUNCTION_NAME ' + + 'call that fails. Should not crash'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve characteristics. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => { + promise = assert_promise_rejects_with_message( + service.CALLS([ + getCharacteristic('measurement_interval')| + getCharacteristics()| + getCharacteristics('measurement_interval')[UUID] + ]), expected); + // Disconnect called to clear attributeInstanceMap and allow the object to + // get garbage collected. + service.device.gatt.disconnect(); + }) + .then(garbageCollect) + .then(() => promise), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/get-same-object.js b/testing/web-platform/tests/bluetooth/script-tests/service/get-same-object.js new file mode 100644 index 0000000000..db9d740c83 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/service/get-same-object.js @@ -0,0 +1,24 @@ +'use strict'; +const test_desc = 'Calls to FUNCTION_NAME should return the same object.'; + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => Promise.all([ + service.CALLS([ + getCharacteristic('measurement_interval')| + getCharacteristics()| + getCharacteristics('measurement_interval')[UUID]]), + service.PREVIOUS_CALL])) + .then(([characteristics_first_call, characteristics_second_call]) => { + // Convert to arrays if necessary. + characteristics_first_call = [].concat(characteristics_first_call); + characteristics_second_call = [].concat(characteristics_second_call); + + let first_call_set = new Set(characteristics_first_call); + assert_equals(characteristics_first_call.length, first_call_set.size); + let second_call_set = new Set(characteristics_second_call); + assert_equals(characteristics_second_call.length, second_call_set.size); + + characteristics_first_call.forEach(characteristic => { + assert_true(second_call_set.has(characteristic)); + }); + }), test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/invalid-characteristic-name.js b/testing/web-platform/tests/bluetooth/script-tests/service/invalid-characteristic-name.js new file mode 100644 index 0000000000..74cba7ec43 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/service/invalid-characteristic-name.js @@ -0,0 +1,23 @@ +'use strict'; +const test_desc = 'Wrong Characteristic name. Reject with TypeError.'; +const expected = new DOMException( + "Failed to execute 'FUNCTION_NAME' on " + + "'BluetoothRemoteGATTService': Invalid Characteristic name: " + + "'wrong_name'. " + + "It must be a valid UUID alias (e.g. 0x1234), " + + "UUID (lowercase hex characters e.g. " + + "'00001234-0000-1000-8000-00805f9b34fb'), " + + "or recognized standard name from " + + "https://www.bluetooth.com/specifications/gatt/characteristics" + + " e.g. 'aerobic_heart_rate_lower_limit'.", + 'TypeError'); + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => assert_promise_rejects_with_message( + service.CALLS([ + getCharacteristic('wrong_name')| + getCharacteristics('wrong_name') + ]), + expected, + 'Wrong Characteristic name passed.')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/reconnect-during.js b/testing/web-platform/tests/bluetooth/script-tests/service/reconnect-during.js new file mode 100644 index 0000000000..cc71547ac2 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/service/reconnect-during.js @@ -0,0 +1,36 @@ +'use strict'; +const test_desc = 'disconnect() and connect() called during ' + + 'FUNCTION_NAME. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve characteristics. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let device; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: [health_thermometer.name]}], +}) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryService(health_thermometer.name)) + .then(service => Promise.all([ + // 1. Make a call to service.FUNCTION_NAME, while the service is still + // valid. + assert_promise_rejects_with_message(service.CALLS([ + getCharacteristic(measurement_interval.name)| + getCharacteristics()| + getCharacteristics(measurement_interval.name)[UUID] + ]), expected), + + // 2. disconnect() and connect before the initial call completes. + // This is accomplished by making the calls without waiting for the + // earlier promises to resolve. + // connect() guarantees on OS-level connection, but disconnect() + // only disconnects the current instance. + // getHealthThermometerDeviceWithServicesDiscovered holds another + // connection in an iframe, so disconnect() and connect() are certain to + // reconnect. However, disconnect() will invalidate the service object so + // the subsequent calls made to it will fail, even after reconnecting. + device.gatt.disconnect(), + device.gatt.connect() + ])), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/service-is-removed.js b/testing/web-platform/tests/bluetooth/script-tests/service/service-is-removed.js new file mode 100644 index 0000000000..aaf0f14436 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/script-tests/service/service-is-removed.js @@ -0,0 +1,20 @@ +'use strict'; +const test_desc = 'Service is removed before FUNCTION_NAME call. ' + + 'Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let service, fake_service, fake_peripheral; + +bluetooth_test(() => getHealthThermometerService() + .then(_ => ({service, fake_service, fake_peripheral} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + service.CALLS([ + getCharacteristic('measurement_interval')| + getCharacteristics()| + getCharacteristics('measurement_interval')[UUID] + ]), + expected, + 'Service got removed.')), + test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/connect/connection-succeeds.https.window.js b/testing/web-platform/tests/bluetooth/server/connect/connection-succeeds.https.window.js new file mode 100644 index 0000000000..90b62b9265 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/connect/connection-succeeds.https.window.js @@ -0,0 +1,13 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Device will connect'; + +bluetooth_test(async () => { + let {device, fake_peripheral} = await getDiscoveredHealthThermometerDevice(); + await fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}); + let gatt = await device.gatt.connect(); + assert_true(gatt.connected); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/connect/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/server/connect/detachedIframe.https.window.js new file mode 100644 index 0000000000..2332cef707 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/connect/detachedIframe.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device} = await getHealthThermometerDeviceFromIframe(iframe); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await device.gatt.connect(); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'connect() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/server/connect/garbage-collection-ran-during-success.https.window.js b/testing/web-platform/tests/bluetooth/server/connect/garbage-collection-ran-during-success.https.window.js new file mode 100644 index 0000000000..2d2211dec3 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/connect/garbage-collection-ran-during-success.https.window.js @@ -0,0 +1,19 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Garbage Collection ran during a connect call that ' + + 'succeeds. Should not crash.'; + +bluetooth_test(async () => { + let connectPromise; + { + let {device, fake_peripheral} = + await getDiscoveredHealthThermometerDevice(); + await fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}); + connectPromise = device.gatt.connect(); + } + await Promise.all([connectPromise, garbageCollect()]); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/connect/get-same-gatt-server.https.window.js b/testing/web-platform/tests/bluetooth/server/connect/get-same-gatt-server.https.window.js new file mode 100644 index 0000000000..59d7243a65 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/connect/get-same-gatt-server.https.window.js @@ -0,0 +1,19 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Multiple connects should return the same gatt object.'; + +bluetooth_test(async () => { + let {device, fake_peripheral} = await getDiscoveredHealthThermometerDevice(); + await fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}); + // No second response is necessary because an ATT Bearer + // already exists from the first connection. + // See + // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothremotegattserver-connect + // step 5.1. + let gatt1 = await device.gatt.connect(); + let gatt2 = await device.gatt.connect(); + assert_equals(gatt1, gatt2); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/device-same-object.https.window.js b/testing/web-platform/tests/bluetooth/server/device-same-object.https.window.js new file mode 100644 index 0000000000..f9a66d9b69 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/device-same-object.https.window.js @@ -0,0 +1,13 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = '[SameObject] test for BluetoothRemoteGATTServer\'s device.'; + +bluetooth_test(async () => { + let {device, fake_peripheral} = await getDiscoveredHealthThermometerDevice(); + await fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}); + let gatt = await device.gatt.connect(); + assert_equals(gatt.device, device); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/disconnect/connect-disconnect-twice.https.window.js b/testing/web-platform/tests/bluetooth/server/disconnect/connect-disconnect-twice.https.window.js new file mode 100644 index 0000000000..5d9908df4c --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/disconnect/connect-disconnect-twice.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Connect + Disconnect twice still results in ' + + '\'connected\' being false.'; +let device, fake_peripheral; + +// TODO(569716): Test that the disconnect signal was sent to the device. +bluetooth_test(async () => { + let {device, fake_peripheral} = await getDiscoveredHealthThermometerDevice(); + await fake_peripheral.setNextGATTConnectionResponse({ + code: HCI_SUCCESS, + }); + let gattServer = await device.gatt.connect(); + await gattServer.disconnect(); + assert_false(gattServer.connected); + + gattServer = await device.gatt.connect(); + await gattServer.disconnect(); + assert_false(gattServer.connected); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/disconnect/detach-gc.https.window.js b/testing/web-platform/tests/bluetooth/server/disconnect/detach-gc.https.window.js new file mode 100644 index 0000000000..b934b37973 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/disconnect/detach-gc.https.window.js @@ -0,0 +1,34 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Detach frame then garbage collect. We shouldn\'t crash.'; +let iframe = document.createElement('iframe'); + +bluetooth_test(async () => { + await setUpConnectableHealthThermometerDevice(); + // 1. Load the iframe. + await new Promise(resolve => { + iframe.src = '/bluetooth/resources/health-thermometer-iframe.html'; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + // 2. Connect device, detach the iframe, and run garbage collection. + await new Promise(resolve => { + callWithTrustedClick(() => { + iframe.contentWindow.postMessage( + { + type: 'RequestAndConnect', + options: {filters: [{services: ['health_thermometer']}]} + }, + '*'); + }); + window.onmessage = messageEvent => { + assert_equals(messageEvent.data, 'Connected'); + iframe.remove(); + garbageCollect().then(resolve); + } + }) +}, test_desc) diff --git a/testing/web-platform/tests/bluetooth/server/disconnect/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/server/disconnect/detachedIframe.https.window.js new file mode 100644 index 0000000000..04e0ca0117 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/disconnect/detachedIframe.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device} = await getHealthThermometerDeviceFromIframe(iframe); + await device.gatt.connect(); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await device.gatt.disconnect(); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'disconnect() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/server/disconnect/disconnect-twice-in-a-row.https.window.js b/testing/web-platform/tests/bluetooth/server/disconnect/disconnect-twice-in-a-row.https.window.js new file mode 100644 index 0000000000..acca9796d5 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/disconnect/disconnect-twice-in-a-row.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Calling disconnect twice in a row still results in ' + + '\'connected\' being false.'; + +// TODO(569716): Test that the disconnect signal was sent to the device. +bluetooth_test(async () => { + let {device, fake_peripheral} = await getDiscoveredHealthThermometerDevice(); + await fake_peripheral.setNextGATTConnectionResponse({ + code: HCI_SUCCESS, + }); + let gattServer = await device.gatt.connect(); + await gattServer.disconnect(); + assert_false(gattServer.connected); + await gattServer.disconnect(); + assert_false(gattServer.connected); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/disconnect/gc-detach.https.window.js b/testing/web-platform/tests/bluetooth/server/disconnect/gc-detach.https.window.js new file mode 100644 index 0000000000..1c062a7759 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/disconnect/gc-detach.https.window.js @@ -0,0 +1,36 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Garbage collect then detach frame. We shouldn\'t crash.'; +let iframe = document.createElement('iframe'); + +bluetooth_test(async () => { + await setUpConnectableHealthThermometerDevice(); + // 1. Load the iframe. + await new Promise(resolve => { + iframe.src = '/bluetooth/resources/health-thermometer-iframe.html'; + document.body.appendChild(iframe); + iframe.addEventListener('load', resolve); + }); + // 2. Connect device, run garbage collection, and detach iframe. + await new Promise(resolve => { + callWithTrustedClick(() => { + iframe.contentWindow.postMessage( + { + type: 'RequestAndConnect', + options: {filters: [{services: ['health_thermometer']}]} + }, + '*'); + }); + window.onmessage = messageEvent => { + assert_equals(messageEvent.data, 'Connected'); + garbageCollect().then(() => { + iframe.remove(); + resolve(); + }); + } + }) +}, test_desc) diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-called-before.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-called-before.https.window.js new file mode 100644 index 0000000000..631545a385 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-called-before.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() called before getPrimaryService. ' + + 'Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); +let device; + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.disconnect()) + .then(() => assert_promise_rejects_with_message( + device.gatt.getPrimaryService('health_thermometer'), + expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-called-during-error.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-called-during-error.https.window.js new file mode 100644 index 0000000000..bcf19665d5 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-called-during-error.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() called during a getPrimaryService ' + + 'call that fails. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', 'NetworkError'); +let device; + +bluetooth_test(() => getEmptyHealthThermometerDevice() + .then(_ => ({device} = _)) + .then(() => { + let promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryService('health_thermometer'), + expected) + device.gatt.disconnect(); + return promise; + }), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-called-during-success.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-called-during-success.https.window.js new file mode 100644 index 0000000000..0d2fc1044a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-called-during-success.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() called during a getPrimaryService call that ' + + 'succeeds. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(({device}) => { + let promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryService('health_thermometer'), + expected); + device.gatt.disconnect(); + return promise; + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-discovery-timeout.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-discovery-timeout.https.window.js new file mode 100644 index 0000000000..03b0c9d0f3 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-discovery-timeout.https.window.js @@ -0,0 +1,46 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = + 'Calls to getPrimaryService when device disconnects and discovery' + + ' times out should reject promise rather than get stuck.'; +let device; + +bluetooth_test( + async (t) => { + let {device, fake_peripheral} = + await getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }); + + await fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_CONNECTION_TIMEOUT, + }); + await Promise.all([ + fake_peripheral.simulateGATTDisconnection({ + code: HCI_SUCCESS, + }), + // Using promise_rejects_dom here rather than + // assert_promise_rejects_with_message as the race between + // simulateGATTDisconnection and getPrimaryServices might end up giving + // slightly different exception message (i.e has "Failed to execute ... + // on + // ... " prefix when disconnected state is reflected on the renderer + // side). The point of the test is no matter how race between them, the + // promise will be rejected as opposed to get stuck. + promise_rejects_dom(t, 'NetworkError', device.gatt.getPrimaryService('health_thermometer')), + ]); + }, + test_desc, '', + // As specified above there is a race condition between + // simulateGATTDisconnection and getPrimaryServices, the artificial + // GATTDiscoveryResponse might not be consumed in case + // simulateGATTDisconnection happens first. As a result explicitly skip + // all response consumed validation at the end of the test. + /*validate_response_consumed=*/ false); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-invalidates-objects.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-invalidates-objects.https.window.js new file mode 100644 index 0000000000..56468b24ea --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnect-invalidates-objects.https.window.js @@ -0,0 +1,43 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls on services after we disconnect and connect again. '+ + 'Should reject with InvalidStateError.'; +let device, services; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryService('health_thermometer')) + // Convert to array if necessary. + .then(s => services = [].concat(s)) + .then(() => device.gatt.disconnect()) + .then(() => device.gatt.connect()) + .then(() => { + let promises = Promise.resolve(); + for (let service of services) { + let error = new DOMException( + `Service with UUID ${service.uuid} is no longer valid. Remember ` + + `to retrieve the service again after reconnecting.`, + 'InvalidStateError'); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristic('measurement_interval'), + error)); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristics(), + error)); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristics('measurement_interval'), + error)); + } + return promises; + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnected-device.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnected-device.https.window.js new file mode 100644 index 0000000000..741b2db5ee --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-disconnected-device.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'getPrimaryService called before connecting. Reject with ' + + 'NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); + +bluetooth_test(() => getDiscoveredHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryService('health_thermometer'), + expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-discovery-complete-no-permission-absent-service.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-discovery-complete-no-permission-absent-service.https.window.js new file mode 100644 index 0000000000..e2f5c87630 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-discovery-complete-no-permission-absent-service.https.window.js @@ -0,0 +1,29 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent service without permission. Should ' + + 'Reject with SecurityError even if services have been discovered already.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service ' + + 'UUID to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); +let device; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: ['health_thermometer']}] + }) + .then(_ => ({device} = _)) + .then(() => Promise.all([ + assert_promise_rejects_with_message( + device.gatt.getPrimaryService(glucose.alias), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryService(glucose.name), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryService(glucose.uuid), expected)])), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-discovery-complete-service-not-found.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-discovery-complete-service-not-found.https.window.js new file mode 100644 index 0000000000..8e9166b41a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-discovery-complete-service-not-found.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent service. Must reject with ' + + 'NotFoundError even when the services have previously been discovered.'; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['glucose']}) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryService('glucose'), + new DOMException( + `No Services matching UUID ${glucose.uuid} found in Device.`, + 'NotFoundError'))), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.https.window.js new file mode 100644 index 0000000000..df182fe8ff --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-error.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Garbage Collection ran during a getPrimaryService ' + + 'call that failed. Should not crash.' +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect first ' + + 'with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getEmptyHealthThermometerDevice() + .then(({device}) => { + promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryService('health_thermometer'), + expected); + // Disconnect called to clear attributeInstanceMap and allow the + // object to get garbage collected. + device.gatt.disconnect(); + return garbageCollect(); + }) + .then(() => promise), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.https.window.js new file mode 100644 index 0000000000..8e278af224 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-garbage-collection-ran-during-success.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Garbage Collection ran during a getPrimaryService call that ' + + 'succeeds. Should not crash.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => { + promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryService('health_thermometer'), + expected); + device.gatt.disconnect(); + return garbageCollect(); + }) + .then(() => promise), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-get-different-service-after-reconnection.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-get-different-service-after-reconnection.https.window.js new file mode 100644 index 0000000000..d4557f6753 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-get-different-service-after-reconnection.https.window.js @@ -0,0 +1,39 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getPrimaryService after a disconnection should return ' + + 'a different object.'; +let device, services_first_connection, services_second_connection; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryService('health_thermometer')) + .then(services => services_first_connection = services) + .then(() => device.gatt.disconnect()) + .then(() => device.gatt.connect()) + .then(() => device.gatt.getPrimaryService('health_thermometer')) + .then(services => services_second_connection = services) + .then(() => { + // Convert to arrays if necessary. + services_first_connection = [].concat(services_first_connection); + services_second_connection = [].concat(services_second_connection); + + assert_equals(services_first_connection.length, + services_second_connection.length); + + let first_connection_set = new Set(services_first_connection); + let second_connection_set = new Set(services_second_connection); + + // The two sets should be disjoint. + let common_services = services_first_connection.filter( + val => second_connection_set.has(val)); + assert_equals(common_services.length, 0); + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-get-same-object.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-get-same-object.https.window.js new file mode 100644 index 0000000000..b43cefb567 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-get-same-object.https.window.js @@ -0,0 +1,37 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getPrimaryService should return the same object.'; +let device; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access']}) + .then(({device}) => Promise.all([ + device.gatt.getPrimaryService('health_thermometer'), + device.gatt.getPrimaryService('health_thermometer')])) + .then(([services_first_call, services_second_call]) => { + // Convert to arrays if necessary. + services_first_call = [].concat(services_first_call); + services_second_call = [].concat(services_second_call); + + assert_equals(services_first_call.length, services_second_call.length); + + let first_call_set = new Set(services_first_call); + assert_equals(services_first_call.length, first_call_set.size); + let second_call_set = new Set(services_second_call); + assert_equals(services_second_call.length, second_call_set.size); + + services_first_call.forEach(service => { + assert_true(second_call_set.has(service)) + }); + + services_second_call.forEach(service => { + assert_true(first_call_set.has(service)); + }); + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-invalid-service-name.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-invalid-service-name.https.window.js new file mode 100644 index 0000000000..cf4ab6c665 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-invalid-service-name.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Wrong Service name. Reject with TypeError.'; +const expected = new DOMException( + "Failed to execute 'getPrimaryService' on " + + "'BluetoothRemoteGATTServer': Invalid Service name: " + + "'wrong_name'. It must be a valid UUID alias (e.g. 0x1234), " + + "UUID (lowercase hex characters e.g. " + + "'00001234-0000-1000-8000-00805f9b34fb'), " + + "or recognized standard name from " + + "https://www.bluetooth.com/specifications/gatt/services" + + " e.g. 'alert_notification'.", + 'TypeError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice() + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryService('wrong_name'), + expected, + 'Wrong Service name passed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-no-permission-absent-service.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-no-permission-absent-service.https.window.js new file mode 100644 index 0000000000..3466ded4f9 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-no-permission-absent-service.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent service without permission. ' + + 'Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service UUID ' + + 'to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => Promise.all([ + assert_promise_rejects_with_message( + device.gatt.getPrimaryService(glucose.alias), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryService(glucose.name), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryService(glucose.uuid), expected)])), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.https.window.js new file mode 100644 index 0000000000..6576ef20a3 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-no-permission-for-any-service.https.window.js @@ -0,0 +1,21 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for present service without permission to access ' + + 'any service. Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access any service. Tip: Add the service ' + + 'UUID to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true}) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryService('heart_rate'), + expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-no-permission-present-service.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-no-permission-present-service.https.window.js new file mode 100644 index 0000000000..3d0b460bc3 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-no-permission-present-service.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for present service without permission. ' + + 'Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service UUID ' + + 'to \'optionalServices\' in requestDevice() options. https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => Promise.all([ + assert_promise_rejects_with_message( + device.gatt.getPrimaryService(generic_access.alias), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryService(generic_access.name), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryService(generic_access.uuid), expected)])), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-service-not-found.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-service-not-found.https.window.js new file mode 100644 index 0000000000..6e0d2c446b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/gen-service-not-found.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent service. Reject with NotFoundError.'; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['glucose'] + }) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryService('glucose'), + new DOMException( + `No Services matching UUID ${glucose.uuid} found in Device.`, + 'NotFoundError'))), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/service-found.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/service-found.https.window.js new file mode 100644 index 0000000000..b8a930d10c --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/service-found.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Request for service. Should return right service'; + +bluetooth_test(async () => { + let {device} = await getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }); + let services = await Promise.all([ + device.gatt.getPrimaryService(generic_access.alias), + device.gatt.getPrimaryService(generic_access.name), + device.gatt.getPrimaryService(generic_access.uuid) + ]); + services.forEach(service => { + assert_equals( + service.uuid, generic_access.uuid, + 'Service UUID should be the same as requested UUID.'); + assert_true( + service.isPrimary, + 'getPrimaryService should return a primary service.'); + assert_equals( + service.device, device, 'Service device should be the same as device.'); + }) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryService/two-iframes-from-same-origin.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryService/two-iframes-from-same-origin.https.window.js new file mode 100644 index 0000000000..b7f23a1491 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryService/two-iframes-from-same-origin.https.window.js @@ -0,0 +1,88 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Two iframes in the same origin should be able to access ' + + 'each other\'s services'; + +const iframe1 = document.createElement('iframe'); +const iframe2 = document.createElement('iframe'); + +function add_iframe(iframe) { + let promise = + new Promise(resolve => iframe.addEventListener('load', resolve)); + iframe.src = '/bluetooth/resources/health-thermometer-iframe.html'; + document.body.appendChild(iframe); + return promise; +} + +function send_message(iframe, command, arg, assert_func) { + let promise = new Promise((resolve, reject) => { + window.addEventListener('message', (messageEvent) => { + try { + assert_func(messageEvent.data); + } catch (e) { + reject(e); + } + resolve(); + }, {once: true}); + }); + if (command === 'RequestAndConnect') { + arg = {filters: [{services: [arg]}]}; + } + callWithTrustedClick( + () => iframe.contentWindow.postMessage( + { + type: command, + options: arg, + }, + '*')); + return promise; +} + +bluetooth_test(async () => { + await getHealthThermometerDevice(); + // 1. Add the first iframe. + await add_iframe(iframe1); + // 2. Connect with the first iframe, requesting the health + // thermometer service. + await send_message( + iframe1, 'RequestAndConnect', 'health_thermometer', + msg => assert_equals(msg, 'Connected')); + // 3. Access the health thermometer service with the first iframe + // (successfully). + await send_message( + iframe1, 'GetService', 'health_thermometer', + msg => assert_equals(msg, 'ServiceReceived')); + // 4. Access the generic access service with the first iframe + // (unsuccessfully). + await send_message(iframe1, 'GetService', 'generic_access', msg => { + let split_msg = msg.split(': '); + assert_equals(split_msg[0], 'FAIL'); + assert_equals(split_msg[1], 'SecurityError'); + }); + // 5. Add the second iframe. + await add_iframe(iframe2); + // 6. Connect with the second iframe, requesting the generic + // access service. + await send_message( + iframe2, 'RequestAndConnect', 'generic_access', + msg => assert_equals(msg, 'Connected')); + // 7. Access the health thermometer service with the second iframe + // (successfully). Both iframes should have access to both + // services at this point since they have the same origin. + await send_message( + iframe2, 'GetService', 'health_thermometer', + msg => assert_equals(msg, 'ServiceReceived')); + // 8. Access the generic access service with the second iframe + // (unsuccessfully). + await send_message( + iframe2, 'GetService', 'generic_access', + msg => assert_equals(msg, 'ServiceReceived')); + // 9. Access the generic access service with the first iframe + // (successfully). + await send_message( + iframe1, 'GetService', 'generic_access', + msg => assert_equals(msg, 'ServiceReceived')); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/blocklisted-services-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/blocklisted-services-with-uuid.https.window.js new file mode 100644 index 0000000000..ccc913e5bf --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/blocklisted-services-with-uuid.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Request for services. Does not return blocklisted service.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service ' + + 'UUID to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(async () => { + let {device} = await getConnectedHIDDevice({ + filters: [{services: ['device_information']}], + optionalServices: ['human_interface_device'] + }); + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('human_interface_device'), expected) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/blocklisted-services.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/blocklisted-services.https.window.js new file mode 100644 index 0000000000..ae6be90994 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/blocklisted-services.https.window.js @@ -0,0 +1,22 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Request for services. Does not return blocklisted service.'; + +bluetooth_test(async () => { + let {device} = await getHIDDevice({ + filters: [{services: ['device_information']}], + optionalServices: ['generic_access', 'human_interface_device'] + }) + let services = await device.gatt.getPrimaryServices(); + assert_equals(services.length, 2); + let uuid_set = new Set(services.map(s => s.uuid)); + + assert_equals(uuid_set.size, 2); + assert_true(uuid_set.has(BluetoothUUID.getService('generic_access'))); + assert_true(uuid_set.has(BluetoothUUID.getService('device_information'))); + assert_false( + uuid_set.has(BluetoothUUID.getService('human_interface_device'))); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/correct-services.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/correct-services.https.window.js new file mode 100644 index 0000000000..f3d883dd2e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/correct-services.https.window.js @@ -0,0 +1,30 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Find correct services with UUID.'; +let device, fake_peripheral; + +bluetooth_test(async () => { + let {device, fake_peripheral} = await getConnectedHealthThermometerDevice( + {filters: [{services: ['health_thermometer']}]}); + let fake_service = + await fake_peripheral.addFakeService({uuid: 'health_thermometer'}); + await Promise.all([ + fake_service.addFakeCharacteristic( + {uuid: 'temperature_measurement', properties: ['indicate']}), + fake_service.addFakeCharacteristic( + {uuid: 'temperature_measurement', properties: ['indicate']}) + ]); + await fake_peripheral.setNextGATTDiscoveryResponse({code: HCI_SUCCESS}); + let services = await device.gatt.getPrimaryServices('health_thermometer'); + let [characteristics1, characteristics2] = await Promise.all( + [services[0].getCharacteristics(), services[1].getCharacteristics()]); + if (characteristics1.length === 2) + assert_equals(characteristics2.length, 3); + else if (characteristics2.length === 2) + assert_equals(characteristics1.length, 3); + else + assert_unreached('Invalid lengths.'); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-before-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-before-with-uuid.https.window.js new file mode 100644 index 0000000000..21b561375d --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-before-with-uuid.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() called before getPrimaryServices. ' + + 'Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); +let device; + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.disconnect()) + .then(() => assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('health_thermometer'), + expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-before.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-before.https.window.js new file mode 100644 index 0000000000..8e5fea83ab --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-before.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() called before getPrimaryServices. ' + + 'Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); +let device; + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.disconnect()) + .then(() => assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(), + expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error-with-uuid.https.window.js new file mode 100644 index 0000000000..5c28716b90 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error-with-uuid.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() called during a getPrimaryServices ' + + 'call that fails. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', 'NetworkError'); +let device; + +bluetooth_test(() => getEmptyHealthThermometerDevice() + .then(_ => ({device} = _)) + .then(() => { + let promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('health_thermometer'), + expected) + device.gatt.disconnect(); + return promise; + }), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error.https.window.js new file mode 100644 index 0000000000..ddc3124791 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-error.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() called during a getPrimaryServices ' + + 'call that fails. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', 'NetworkError'); +let device; + +bluetooth_test(() => getEmptyHealthThermometerDevice() + .then(_ => ({device} = _)) + .then(() => { + let promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(), + expected) + device.gatt.disconnect(); + return promise; + }), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success-with-uuid.https.window.js new file mode 100644 index 0000000000..13e3806d31 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success-with-uuid.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() called during a getPrimaryServices call that ' + + 'succeeds. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(({device}) => { + let promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('health_thermometer'), + expected); + device.gatt.disconnect(); + return promise; + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success.https.window.js new file mode 100644 index 0000000000..d6b31936c6 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-called-during-success.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() called during a getPrimaryServices call that ' + + 'succeeds. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(({device}) => { + let promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(), + expected); + device.gatt.disconnect(); + return promise; + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout-with-uuid.https.window.js new file mode 100644 index 0000000000..77f7bc81d9 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout-with-uuid.https.window.js @@ -0,0 +1,46 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = + 'Calls to getPrimaryServices when device disconnects and discovery' + + ' times out should reject promise rather than get stuck.'; +let device; + +bluetooth_test( + async (t) => { + let {device, fake_peripheral} = + await getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }); + + await fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_CONNECTION_TIMEOUT, + }); + await Promise.all([ + fake_peripheral.simulateGATTDisconnection({ + code: HCI_SUCCESS, + }), + // Using promise_rejects_dom here rather than + // assert_promise_rejects_with_message as the race between + // simulateGATTDisconnection and getPrimaryServices might end up giving + // slightly different exception message (i.e has "Failed to execute ... + // on + // ... " prefix when disconnected state is reflected on the renderer + // side). The point of the test is no matter how race between them, the + // promise will be rejected as opposed to get stuck. + promise_rejects_dom(t, 'NetworkError', device.gatt.getPrimaryServices('health_thermometer')), + ]); + }, + test_desc, '', + // As specified above there is a race condition between + // simulateGATTDisconnection and getPrimaryServices, the artificial + // GATTDiscoveryResponse might not be consumed in case + // simulateGATTDisconnection happens first. As a result explicitly skip + // all response consumed validation at the end of the test. + /*validate_response_consumed=*/ false); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout.https.window.js new file mode 100644 index 0000000000..ea55b7b495 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-discovery-timeout.https.window.js @@ -0,0 +1,46 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = + 'Calls to getPrimaryServices when device disconnects and discovery' + + ' times out should reject promise rather than get stuck.'; +let device; + +bluetooth_test( + async (t) => { + let {device, fake_peripheral} = + await getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }); + + await fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_CONNECTION_TIMEOUT, + }); + await Promise.all([ + fake_peripheral.simulateGATTDisconnection({ + code: HCI_SUCCESS, + }), + // Using promise_rejects_dom here rather than + // assert_promise_rejects_with_message as the race between + // simulateGATTDisconnection and getPrimaryServices might end up giving + // slightly different exception message (i.e has "Failed to execute ... + // on + // ... " prefix when disconnected state is reflected on the renderer + // side). The point of the test is no matter how race between them, the + // promise will be rejected as opposed to get stuck. + promise_rejects_dom(t, 'NetworkError', device.gatt.getPrimaryServices()), + ]); + }, + test_desc, '', + // As specified above there is a race condition between + // simulateGATTDisconnection and getPrimaryServices, the artificial + // GATTDiscoveryResponse might not be consumed in case + // simulateGATTDisconnection happens first. As a result explicitly skip + // all response consumed validation at the end of the test. + /*validate_response_consumed=*/ false); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects-with-uuid.https.window.js new file mode 100644 index 0000000000..8cdb83e3ad --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects-with-uuid.https.window.js @@ -0,0 +1,43 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls on services after we disconnect and connect again. '+ + 'Should reject with InvalidStateError.'; +let device, services; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryServices('health_thermometer')) + // Convert to array if necessary. + .then(s => services = [].concat(s)) + .then(() => device.gatt.disconnect()) + .then(() => device.gatt.connect()) + .then(() => { + let promises = Promise.resolve(); + for (let service of services) { + let error = new DOMException( + `Service with UUID ${service.uuid} is no longer valid. Remember ` + + `to retrieve the service again after reconnecting.`, + 'InvalidStateError'); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristic('measurement_interval'), + error)); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristics(), + error)); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristics('measurement_interval'), + error)); + } + return promises; + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects.https.window.js new file mode 100644 index 0000000000..9fd536f051 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnect-invalidates-objects.https.window.js @@ -0,0 +1,43 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls on services after we disconnect and connect again. '+ + 'Should reject with InvalidStateError.'; +let device, services; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryServices()) + // Convert to array if necessary. + .then(s => services = [].concat(s)) + .then(() => device.gatt.disconnect()) + .then(() => device.gatt.connect()) + .then(() => { + let promises = Promise.resolve(); + for (let service of services) { + let error = new DOMException( + `Service with UUID ${service.uuid} is no longer valid. Remember ` + + `to retrieve the service again after reconnecting.`, + 'InvalidStateError'); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristic('measurement_interval'), + error)); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristics(), + error)); + promises = promises.then(() => + assert_promise_rejects_with_message( + service.getCharacteristics('measurement_interval'), + error)); + } + return promises; + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnected-device-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnected-device-with-uuid.https.window.js new file mode 100644 index 0000000000..e0393d5e69 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnected-device-with-uuid.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'getPrimaryServices called before connecting. Reject with ' + + 'NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); + +bluetooth_test(() => getDiscoveredHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('health_thermometer'), + expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnected-device.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnected-device.https.window.js new file mode 100644 index 0000000000..87d74c6ab1 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-disconnected-device.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'getPrimaryServices called before connecting. Reject with ' + + 'NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' + + 'first with `device.gatt.connect`.', + 'NetworkError'); + +bluetooth_test(() => getDiscoveredHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(), + expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-discovery-complete-no-permission-absent-service-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-discovery-complete-no-permission-absent-service-with-uuid.https.window.js new file mode 100644 index 0000000000..6e179dc5d9 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-discovery-complete-no-permission-absent-service-with-uuid.https.window.js @@ -0,0 +1,29 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent service without permission. Should ' + + 'Reject with SecurityError even if services have been discovered already.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service ' + + 'UUID to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); +let device; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: ['health_thermometer']}] + }) + .then(_ => ({device} = _)) + .then(() => Promise.all([ + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(glucose.alias), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(glucose.name), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(glucose.uuid), expected)])), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-discovery-complete-service-not-found-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-discovery-complete-service-not-found-with-uuid.https.window.js new file mode 100644 index 0000000000..66cfb491c0 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-discovery-complete-service-not-found-with-uuid.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent service. Must reject with ' + + 'NotFoundError even when the services have previously been discovered.'; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['glucose']}) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('glucose'), + new DOMException( + `No Services matching UUID ${glucose.uuid} found in Device.`, + 'NotFoundError'))), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.https.window.js new file mode 100644 index 0000000000..a235cf5d18 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error-with-uuid.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Garbage Collection ran during a getPrimaryServices ' + + 'call that failed. Should not crash.' +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect first ' + + 'with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getEmptyHealthThermometerDevice() + .then(({device}) => { + promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('health_thermometer'), + expected); + // Disconnect called to clear attributeInstanceMap and allow the + // object to get garbage collected. + device.gatt.disconnect(); + return garbageCollect(); + }) + .then(() => promise), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.https.window.js new file mode 100644 index 0000000000..f174d4aef9 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-error.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Garbage Collection ran during a getPrimaryServices ' + + 'call that failed. Should not crash.' +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. (Re)connect first ' + + 'with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getEmptyHealthThermometerDevice() + .then(({device}) => { + promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(), + expected); + // Disconnect called to clear attributeInstanceMap and allow the + // object to get garbage collected. + device.gatt.disconnect(); + return garbageCollect(); + }) + .then(() => promise), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.https.window.js new file mode 100644 index 0000000000..cf5dfb246f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success-with-uuid.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Garbage Collection ran during a getPrimaryServices call that ' + + 'succeeds. Should not crash.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => { + promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('health_thermometer'), + expected); + device.gatt.disconnect(); + return garbageCollect(); + }) + .then(() => promise), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.https.window.js new file mode 100644 index 0000000000..f1c080a946 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-garbage-collection-ran-during-success.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Garbage Collection ran during a getPrimaryServices call that ' + + 'succeeds. Should not crash.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve services. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => { + promise = assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(), + expected); + device.gatt.disconnect(); + return garbageCollect(); + }) + .then(() => promise), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection-with-uuid.https.window.js new file mode 100644 index 0000000000..2e40d580f3 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection-with-uuid.https.window.js @@ -0,0 +1,39 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getPrimaryServices after a disconnection should return ' + + 'a different object.'; +let device, services_first_connection, services_second_connection; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryServices('health_thermometer')) + .then(services => services_first_connection = services) + .then(() => device.gatt.disconnect()) + .then(() => device.gatt.connect()) + .then(() => device.gatt.getPrimaryServices('health_thermometer')) + .then(services => services_second_connection = services) + .then(() => { + // Convert to arrays if necessary. + services_first_connection = [].concat(services_first_connection); + services_second_connection = [].concat(services_second_connection); + + assert_equals(services_first_connection.length, + services_second_connection.length); + + let first_connection_set = new Set(services_first_connection); + let second_connection_set = new Set(services_second_connection); + + // The two sets should be disjoint. + let common_services = services_first_connection.filter( + val => second_connection_set.has(val)); + assert_equals(common_services.length, 0); + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection.https.window.js new file mode 100644 index 0000000000..ee1fc971bf --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-different-service-after-reconnection.https.window.js @@ -0,0 +1,39 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getPrimaryServices after a disconnection should return ' + + 'a different object.'; +let device, services_first_connection, services_second_connection; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryServices()) + .then(services => services_first_connection = services) + .then(() => device.gatt.disconnect()) + .then(() => device.gatt.connect()) + .then(() => device.gatt.getPrimaryServices()) + .then(services => services_second_connection = services) + .then(() => { + // Convert to arrays if necessary. + services_first_connection = [].concat(services_first_connection); + services_second_connection = [].concat(services_second_connection); + + assert_equals(services_first_connection.length, + services_second_connection.length); + + let first_connection_set = new Set(services_first_connection); + let second_connection_set = new Set(services_second_connection); + + // The two sets should be disjoint. + let common_services = services_first_connection.filter( + val => second_connection_set.has(val)); + assert_equals(common_services.length, 0); + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-same-object-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-same-object-with-uuid.https.window.js new file mode 100644 index 0000000000..b589056a23 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-same-object-with-uuid.https.window.js @@ -0,0 +1,37 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getPrimaryServices should return the same object.'; +let device; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access']}) + .then(({device}) => Promise.all([ + device.gatt.getPrimaryServices('health_thermometer'), + device.gatt.getPrimaryServices('health_thermometer')])) + .then(([services_first_call, services_second_call]) => { + // Convert to arrays if necessary. + services_first_call = [].concat(services_first_call); + services_second_call = [].concat(services_second_call); + + assert_equals(services_first_call.length, services_second_call.length); + + let first_call_set = new Set(services_first_call); + assert_equals(services_first_call.length, first_call_set.size); + let second_call_set = new Set(services_second_call); + assert_equals(services_second_call.length, second_call_set.size); + + services_first_call.forEach(service => { + assert_true(second_call_set.has(service)) + }); + + services_second_call.forEach(service => { + assert_true(first_call_set.has(service)); + }); + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-same-object.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-same-object.https.window.js new file mode 100644 index 0000000000..63739add91 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-get-same-object.https.window.js @@ -0,0 +1,37 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getPrimaryServices should return the same object.'; +let device; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access']}) + .then(({device}) => Promise.all([ + device.gatt.getPrimaryServices(), + device.gatt.getPrimaryServices()])) + .then(([services_first_call, services_second_call]) => { + // Convert to arrays if necessary. + services_first_call = [].concat(services_first_call); + services_second_call = [].concat(services_second_call); + + assert_equals(services_first_call.length, services_second_call.length); + + let first_call_set = new Set(services_first_call); + assert_equals(services_first_call.length, first_call_set.size); + let second_call_set = new Set(services_second_call); + assert_equals(services_second_call.length, second_call_set.size); + + services_first_call.forEach(service => { + assert_true(second_call_set.has(service)) + }); + + services_second_call.forEach(service => { + assert_true(first_call_set.has(service)); + }); + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-invalid-service-name.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-invalid-service-name.https.window.js new file mode 100644 index 0000000000..a9b1262e6a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-invalid-service-name.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Wrong Service name. Reject with TypeError.'; +const expected = new DOMException( + "Failed to execute 'getPrimaryServices' on " + + "'BluetoothRemoteGATTServer': Invalid Service name: " + + "'wrong_name'. It must be a valid UUID alias (e.g. 0x1234), " + + "UUID (lowercase hex characters e.g. " + + "'00001234-0000-1000-8000-00805f9b34fb'), " + + "or recognized standard name from " + + "https://www.bluetooth.com/specifications/gatt/services" + + " e.g. 'alert_notification'.", + 'TypeError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice() + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('wrong_name'), + expected, + 'Wrong Service name passed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-absent-service-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-absent-service-with-uuid.https.window.js new file mode 100644 index 0000000000..27ad9f008e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-absent-service-with-uuid.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent service without permission. ' + + 'Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service UUID ' + + 'to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => Promise.all([ + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(glucose.alias), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(glucose.name), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(glucose.uuid), expected)])), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.https.window.js new file mode 100644 index 0000000000..d5f06c23da --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service-with-uuid.https.window.js @@ -0,0 +1,21 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for present service without permission to access ' + + 'any service. Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access any service. Tip: Add the service ' + + 'UUID to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true}) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('heart_rate'), + expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.https.window.js new file mode 100644 index 0000000000..8aa730d2ed --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-for-any-service.https.window.js @@ -0,0 +1,21 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for present service without permission to access ' + + 'any service. Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access any service. Tip: Add the service ' + + 'UUID to \'optionalServices\' in requestDevice() options. ' + + 'https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true}) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(), + expected)), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-present-service-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-present-service-with-uuid.https.window.js new file mode 100644 index 0000000000..a2047a0e8f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-no-permission-present-service-with-uuid.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for present service without permission. ' + + 'Reject with SecurityError.'; +const expected = new DOMException( + 'Origin is not allowed to access the service. Tip: Add the service UUID ' + + 'to \'optionalServices\' in requestDevice() options. https://goo.gl/HxfxSQ', + 'SecurityError'); + +bluetooth_test(() => getConnectedHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}] + }) + .then(({device}) => Promise.all([ + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(generic_access.alias), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(generic_access.name), expected), + assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(generic_access.uuid), expected)])), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-service-not-found-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-service-not-found-with-uuid.https.window.js new file mode 100644 index 0000000000..a2db1edc4b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/gen-service-not-found-with-uuid.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent service. Reject with NotFoundError.'; + +bluetooth_test(() => getHealthThermometerDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['glucose'] + }) + .then(({device}) => assert_promise_rejects_with_message( + device.gatt.getPrimaryServices('glucose'), + new DOMException( + `No Services matching UUID ${glucose.uuid} found in Device.`, + 'NotFoundError'))), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/services-found-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/services-found-with-uuid.https.window.js new file mode 100644 index 0000000000..972e6a75ca --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/services-found-with-uuid.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Request for services. Should return right number of ' + + 'services.'; + +bluetooth_test(async () => { + let {device} = await getTwoHealthThermometerServicesDevice( + {filters: [{services: ['health_thermometer']}]}); + let services_arrays = await Promise.all([ + device.gatt.getPrimaryServices(health_thermometer.alias), + device.gatt.getPrimaryServices(health_thermometer.name), + device.gatt.getPrimaryServices(health_thermometer.uuid) + ]); + services_arrays.forEach(services => { + assert_equals(services.length, 2); + services.forEach(service => { + assert_equals( + service.uuid, BluetoothUUID.getService('health_thermometer')); + assert_true(service.isPrimary); + }); + }) +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/services-found.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/services-found.https.window.js new file mode 100644 index 0000000000..46861175c6 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/services-found.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Find all services in a device.'; + +bluetooth_test(async () => { + let {device} = await getTwoHealthThermometerServicesDevice({ + filters: [{services: ['health_thermometer']}], + optionalServices: ['generic_access'] + }); + let services = await device.gatt.getPrimaryServices(); + // Expect three service instances. + assert_equals(services.length, 3); + services.forEach(s => assert_true(s.isPrimary)); + + let uuid_set = new Set(services.map(s => s.uuid)); + // Two of the expected services are 'health_thermometer', so + // only 2 unique UUIDs. + assert_equals(uuid_set.size, 2); + + assert_true(uuid_set.has(BluetoothUUID.getService('generic_access'))); + assert_true(uuid_set.has(BluetoothUUID.getService('health_thermometer'))); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/server/getPrimaryServices/services-not-found.https.window.js b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/services-not-found.https.window.js new file mode 100644 index 0000000000..6350328241 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/server/getPrimaryServices/services-not-found.https.window.js @@ -0,0 +1,15 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Request for services in a device with no services. Reject ' + + 'with NotFoundError.'; +const expected = + new DOMException('No Services found in device.', 'NotFoundError'); + +bluetooth_test(async () => { + let {device} = await getEmptyHealthThermometerDevice(); + return assert_promise_rejects_with_message( + device.gatt.getPrimaryServices(), expected); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/service/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/service/detachedIframe.https.window.js new file mode 100644 index 0000000000..f75fc225a7 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/detachedIframe.https.window.js @@ -0,0 +1,26 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + const {device} = await getHealthThermometerDeviceFromIframe(iframe); + let error; + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await device.gatt.getPrimaryService(health_thermometer.name); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'getPrimaryService() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/service/device-same-from-2-services.https.window.js b/testing/web-platform/tests/bluetooth/service/device-same-from-2-services.https.window.js new file mode 100644 index 0000000000..5b2ba310d3 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/device-same-from-2-services.https.window.js @@ -0,0 +1,14 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Same parent device returned from multiple services.'; + +bluetooth_test(async () => { + let {device} = await getTwoHealthThermometerServicesDevice( + {filters: [{services: ['health_thermometer']}]}); + let [service1, service2] = + await device.gatt.getPrimaryServices('health_thermometer'); + assert_equals(service1.device, service2.device); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/service/device-same-object.https.window.js b/testing/web-platform/tests/bluetooth/service/device-same-object.https.window.js new file mode 100644 index 0000000000..97da769a9e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/device-same-object.https.window.js @@ -0,0 +1,13 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = '[SameObject] test for BluetoothRemoteGATTService device.'; + +bluetooth_test(async () => { + let {device} = await getHealthThermometerDevice( + {filters: [{services: ['health_thermometer']}]}); + let service = await device.gatt.getPrimaryService('health_thermometer'); + assert_equals(service.device, device); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristic/characteristic-found.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristic/characteristic-found.https.window.js new file mode 100644 index 0000000000..807852ae13 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristic/characteristic-found.https.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Request for characteristic. Should return right ' + + 'characteristic.'; + +bluetooth_test(async () => { + let {device} = await getHealthThermometerDevice(); + let service = await device.gatt.getPrimaryService('health_thermometer'); + let characteristics = await Promise.all([ + service.getCharacteristic(measurement_interval.alias), + service.getCharacteristic(measurement_interval.name), + service.getCharacteristic(measurement_interval.uuid) + ]); + characteristics.forEach(characteristic => { + assert_equals( + characteristic.uuid, measurement_interval.uuid, + 'Characteristic UUID should be the same as requested UUID.'); + assert_equals( + characteristic.service, service, + 'Characteristic service should be the same as service.'); + }); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristic/detachedIframe.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristic/detachedIframe.https.window.js new file mode 100644 index 0000000000..ea8c96160f --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristic/detachedIframe.https.window.js @@ -0,0 +1,31 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js + +bluetooth_test(async () => { + let iframe = document.createElement('iframe'); + let error; + + const {device, fakes} = await getHealthThermometerDeviceFromIframe(iframe); + await fakes.fake_peripheral.setNextGATTDiscoveryResponse({ + code: HCI_SUCCESS, + }); + let service = await device.gatt.getPrimaryService(health_thermometer.name); + + iframe.remove(); + // Set iframe to null to ensure that the GC cleans up as much as possible. + iframe = null; + await garbageCollect(); + + try { + await service.getCharacteristic(measurement_interval.alias); + } catch (e) { + // Cannot use promise_rejects_dom() because |e| is thrown from a different + // global. + error = e; + } + assert_not_equals(error, undefined); + assert_equals(error.name, 'NetworkError'); +}, 'getCharacteristic() rejects in a detached context'); diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-blocklisted-characteristic.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-blocklisted-characteristic.https.window.js new file mode 100644 index 0000000000..cce302d650 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-blocklisted-characteristic.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Serial Number String characteristic is blocklisted. ' + + 'Should reject with SecurityError.'; +const expected = new DOMException( + 'getCharacteristic(s) called with blocklisted UUID. https://goo.gl/4NeimX', + 'SecurityError'); + +bluetooth_test(() => getHIDDevice({ + filters: [{services: ['device_information']}] +}) + .then(({device}) => device.gatt.getPrimaryService('device_information')) + .then(service => assert_promise_rejects_with_message( + service.getCharacteristic('serial_number_string'), + expected, + 'Serial Number String characteristic is blocklisted.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-characteristic-not-found.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-characteristic-not-found.https.window.js new file mode 100644 index 0000000000..2ed48eb5c6 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-characteristic-not-found.https.window.js @@ -0,0 +1,19 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent characteristics with UUID. ' + + 'Reject with NotFoundError.'; + +bluetooth_test(() => getEmptyHealthThermometerService() + .then(({service}) => assert_promise_rejects_with_message( + service.getCharacteristic('battery_level'), + new DOMException( + `No Characteristics matching UUID ${battery_level.uuid} found ` + + `in Service with UUID ${health_thermometer.uuid}.`, + 'NotFoundError'))), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-error.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-error.https.window.js new file mode 100644 index 0000000000..1fd70c8fad --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-garbage-collection-ran-during-error.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Garbage Collection ran during getCharacteristic ' + + 'call that fails. Should not crash'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve characteristics. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => { + promise = assert_promise_rejects_with_message( + service.getCharacteristic('measurement_interval'), expected); + // Disconnect called to clear attributeInstanceMap and allow the object to + // get garbage collected. + service.device.gatt.disconnect(); + }) + .then(garbageCollect) + .then(() => promise), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-get-same-object.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-get-same-object.https.window.js new file mode 100644 index 0000000000..c5176cdc5e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-get-same-object.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getCharacteristic should return the same object.'; + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => Promise.all([ + service.getCharacteristic('measurement_interval'), + service.getCharacteristic('measurement_interval')])) + .then(([characteristics_first_call, characteristics_second_call]) => { + // Convert to arrays if necessary. + characteristics_first_call = [].concat(characteristics_first_call); + characteristics_second_call = [].concat(characteristics_second_call); + + let first_call_set = new Set(characteristics_first_call); + assert_equals(characteristics_first_call.length, first_call_set.size); + let second_call_set = new Set(characteristics_second_call); + assert_equals(characteristics_second_call.length, second_call_set.size); + + characteristics_first_call.forEach(characteristic => { + assert_true(second_call_set.has(characteristic)); + }); + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-invalid-characteristic-name.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-invalid-characteristic-name.https.window.js new file mode 100644 index 0000000000..da0f5bda28 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-invalid-characteristic-name.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Wrong Characteristic name. Reject with TypeError.'; +const expected = new DOMException( + "Failed to execute 'getCharacteristic' on " + + "'BluetoothRemoteGATTService': Invalid Characteristic name: " + + "'wrong_name'. " + + "It must be a valid UUID alias (e.g. 0x1234), " + + "UUID (lowercase hex characters e.g. " + + "'00001234-0000-1000-8000-00805f9b34fb'), " + + "or recognized standard name from " + + "https://www.bluetooth.com/specifications/gatt/characteristics" + + " e.g. 'aerobic_heart_rate_lower_limit'.", + 'TypeError'); + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => assert_promise_rejects_with_message( + service.getCharacteristic('wrong_name'), + expected, + 'Wrong Characteristic name passed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-reconnect-during.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-reconnect-during.https.window.js new file mode 100644 index 0000000000..8801c152e9 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-reconnect-during.https.window.js @@ -0,0 +1,39 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() and connect() called during ' + + 'getCharacteristic. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve characteristics. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let device; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: [health_thermometer.name]}], +}) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryService(health_thermometer.name)) + .then(service => Promise.all([ + // 1. Make a call to service.getCharacteristic, while the service is still + // valid. + assert_promise_rejects_with_message(service.getCharacteristic(measurement_interval.name), expected), + + // 2. disconnect() and connect before the initial call completes. + // This is accomplished by making the calls without waiting for the + // earlier promises to resolve. + // connect() guarantees on OS-level connection, but disconnect() + // only disconnects the current instance. + // getHealthThermometerDeviceWithServicesDiscovered holds another + // connection in an iframe, so disconnect() and connect() are certain to + // reconnect. However, disconnect() will invalidate the service object so + // the subsequent calls made to it will fail, even after reconnecting. + device.gatt.disconnect(), + device.gatt.connect() + ])), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-service-is-removed.https.window.js new file mode 100644 index 0000000000..bfeb318c46 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristic/gen-service-is-removed.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Service is removed before getCharacteristic call. ' + + 'Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let service, fake_service, fake_peripheral; + +bluetooth_test(() => getHealthThermometerService() + .then(_ => ({service, fake_service, fake_peripheral} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + service.getCharacteristic('measurement_interval'), + expected, + 'Service got removed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/blocklisted-characteristics.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/blocklisted-characteristics.https.window.js new file mode 100644 index 0000000000..408943585a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/blocklisted-characteristics.https.window.js @@ -0,0 +1,17 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'The Device Information service is composed of blocklisted ' + + 'characteristics so we shouldn\'t find any.'; +const expected = + new DOMException('No Characteristics found in service.', 'NotFoundError'); + +bluetooth_test(async () => { + let {device} = + await getHIDDevice({filters: [{services: ['device_information']}]}); + let service = await device.gatt.getPrimaryService('device_information'); + return assert_promise_rejects_with_message( + service.getCharacteristics(), expected); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/characteristics-found-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/characteristics-found-with-uuid.https.window.js new file mode 100644 index 0000000000..f11c69c92e --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/characteristics-found-with-uuid.https.window.js @@ -0,0 +1,39 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Find characteristics with UUID in service.'; + +bluetooth_test(async () => { + let {device, fake_peripheral, fake_services} = await getDiscoveredHealthThermometerDevice(); + // Setup a device with two measurement intervals. + await fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}); + await device.gatt.connect(); + let fake_health_thermometer = fake_services.get('health_thermometer'); + await Promise.all([ + fake_health_thermometer.addFakeCharacteristic({ + uuid: 'measurement_interval', + properties: ['read', 'write', 'indicate'] + }), + fake_health_thermometer.addFakeCharacteristic({ + uuid: 'measurement_interval', + properties: ['read', 'write', 'indicate'] + }), + fake_health_thermometer.addFakeCharacteristic( + {uuid: 'temperature_measurement', properties: ['indicate']}) + ]); + await fake_peripheral.setNextGATTDiscoveryResponse({code: HCI_SUCCESS}); + let service = await device.gatt.getPrimaryService('health_thermometer'); + // Actual test starts. + let characteristics_arrays = await Promise.all([ + service.getCharacteristics(measurement_interval.alias), + service.getCharacteristics(measurement_interval.name), + service.getCharacteristics(measurement_interval.uuid) + ]); + characteristics_arrays.forEach(characteristics => { + assert_equals(characteristics.length, 2); + assert_equals(characteristics[0].uuid, measurement_interval.uuid); + assert_equals(characteristics[1].uuid, measurement_interval.uuid); + }); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/characteristics-found.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/characteristics-found.https.window.js new file mode 100644 index 0000000000..3244dd3e17 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/characteristics-found.https.window.js @@ -0,0 +1,41 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Find all characteristics in a service.'; + +bluetooth_test(async () => { + let {device, fake_peripheral, fake_services} = await getDiscoveredHealthThermometerDevice(); + // Setup a device with two measurement intervals. + await fake_peripheral.setNextGATTConnectionResponse({code: HCI_SUCCESS}); + await device.gatt.connect(); + let fake_health_thermometer = fake_services.get('health_thermometer'); + await Promise.all([ + fake_health_thermometer.addFakeCharacteristic({ + uuid: 'measurement_interval', + properties: ['read', 'write', 'indicate'] + }), + fake_health_thermometer.addFakeCharacteristic({ + uuid: 'measurement_interval', + properties: ['read', 'write', 'indicate'] + }), + fake_health_thermometer.addFakeCharacteristic( + {uuid: 'temperature_measurement', properties: ['indicate']}) + ]); + await fake_peripheral.setNextGATTDiscoveryResponse({code: HCI_SUCCESS}); + let service = await device.gatt.getPrimaryService('health_thermometer'); + // Actual test starts. + let characteristics = await service.getCharacteristics(); + // Expect three characteristic instances. + assert_equals(characteristics.length, 3); + + let uuid_set = new Set(characteristics.map(c => c.uuid)); + // Two of the expected characteristics are + // 'measurement_interval', so only 2 unique UUID. + assert_equals(uuid_set.size, 2); + assert_true( + uuid_set.has(BluetoothUUID.getCharacteristic('measurement_interval'))); + assert_true( + uuid_set.has(BluetoothUUID.getCharacteristic('temperature_measurement'))); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/characteristics-not-found.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/characteristics-not-found.https.window.js new file mode 100644 index 0000000000..5b0c1896d6 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/characteristics-not-found.https.window.js @@ -0,0 +1,15 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +'use strict'; +const test_desc = 'Request for absent characteristics. Reject with ' + + 'NotFoundError.'; +const expected = + new DOMException('No Characteristics found in service.', 'NotFoundError'); + +bluetooth_test(async () => { + let {service} = await getEmptyHealthThermometerService(); + return assert_promise_rejects_with_message( + service.getCharacteristics(), expected); +}, test_desc); diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-blocklisted-characteristic-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-blocklisted-characteristic-with-uuid.https.window.js new file mode 100644 index 0000000000..79cd01032b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-blocklisted-characteristic-with-uuid.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Serial Number String characteristic is blocklisted. ' + + 'Should reject with SecurityError.'; +const expected = new DOMException( + 'getCharacteristic(s) called with blocklisted UUID. https://goo.gl/4NeimX', + 'SecurityError'); + +bluetooth_test(() => getHIDDevice({ + filters: [{services: ['device_information']}] +}) + .then(({device}) => device.gatt.getPrimaryService('device_information')) + .then(service => assert_promise_rejects_with_message( + service.getCharacteristics('serial_number_string'), + expected, + 'Serial Number String characteristic is blocklisted.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-characteristic-not-found-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-characteristic-not-found-with-uuid.https.window.js new file mode 100644 index 0000000000..8a5e2ab4e4 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-characteristic-not-found-with-uuid.https.window.js @@ -0,0 +1,19 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Request for absent characteristics with UUID. ' + + 'Reject with NotFoundError.'; + +bluetooth_test(() => getEmptyHealthThermometerService() + .then(({service}) => assert_promise_rejects_with_message( + service.getCharacteristics('battery_level'), + new DOMException( + `No Characteristics matching UUID ${battery_level.uuid} found ` + + `in Service with UUID ${health_thermometer.uuid}.`, + 'NotFoundError'))), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error-with-uuid.https.window.js new file mode 100644 index 0000000000..683b93e352 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error-with-uuid.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Garbage Collection ran during getCharacteristics ' + + 'call that fails. Should not crash'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve characteristics. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => { + promise = assert_promise_rejects_with_message( + service.getCharacteristics('measurement_interval'), expected); + // Disconnect called to clear attributeInstanceMap and allow the object to + // get garbage collected. + service.device.gatt.disconnect(); + }) + .then(garbageCollect) + .then(() => promise), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error.https.window.js new file mode 100644 index 0000000000..c964781ab4 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-garbage-collection-ran-during-error.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Garbage Collection ran during getCharacteristics ' + + 'call that fails. Should not crash'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve characteristics. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let promise; + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => { + promise = assert_promise_rejects_with_message( + service.getCharacteristics(), expected); + // Disconnect called to clear attributeInstanceMap and allow the object to + // get garbage collected. + service.device.gatt.disconnect(); + }) + .then(garbageCollect) + .then(() => promise), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-get-same-object-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-get-same-object-with-uuid.https.window.js new file mode 100644 index 0000000000..64b53f4eb3 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-get-same-object-with-uuid.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getCharacteristics should return the same object.'; + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => Promise.all([ + service.getCharacteristics('measurement_interval'), + service.getCharacteristics('measurement_interval')])) + .then(([characteristics_first_call, characteristics_second_call]) => { + // Convert to arrays if necessary. + characteristics_first_call = [].concat(characteristics_first_call); + characteristics_second_call = [].concat(characteristics_second_call); + + let first_call_set = new Set(characteristics_first_call); + assert_equals(characteristics_first_call.length, first_call_set.size); + let second_call_set = new Set(characteristics_second_call); + assert_equals(characteristics_second_call.length, second_call_set.size); + + characteristics_first_call.forEach(characteristic => { + assert_true(second_call_set.has(characteristic)); + }); + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-get-same-object.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-get-same-object.https.window.js new file mode 100644 index 0000000000..6aad17c1e6 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-get-same-object.https.window.js @@ -0,0 +1,28 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Calls to getCharacteristics should return the same object.'; + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => Promise.all([ + service.getCharacteristics(), + service.getCharacteristics()])) + .then(([characteristics_first_call, characteristics_second_call]) => { + // Convert to arrays if necessary. + characteristics_first_call = [].concat(characteristics_first_call); + characteristics_second_call = [].concat(characteristics_second_call); + + let first_call_set = new Set(characteristics_first_call); + assert_equals(characteristics_first_call.length, first_call_set.size); + let second_call_set = new Set(characteristics_second_call); + assert_equals(characteristics_second_call.length, second_call_set.size); + + characteristics_first_call.forEach(characteristic => { + assert_true(second_call_set.has(characteristic)); + }); + }), test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-invalid-characteristic-name.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-invalid-characteristic-name.https.window.js new file mode 100644 index 0000000000..c7d439e13a --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-invalid-characteristic-name.https.window.js @@ -0,0 +1,27 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Wrong Characteristic name. Reject with TypeError.'; +const expected = new DOMException( + "Failed to execute 'getCharacteristics' on " + + "'BluetoothRemoteGATTService': Invalid Characteristic name: " + + "'wrong_name'. " + + "It must be a valid UUID alias (e.g. 0x1234), " + + "UUID (lowercase hex characters e.g. " + + "'00001234-0000-1000-8000-00805f9b34fb'), " + + "or recognized standard name from " + + "https://www.bluetooth.com/specifications/gatt/characteristics" + + " e.g. 'aerobic_heart_rate_lower_limit'.", + 'TypeError'); + +bluetooth_test(() => getHealthThermometerService() + .then(({service}) => assert_promise_rejects_with_message( + service.getCharacteristics('wrong_name'), + expected, + 'Wrong Characteristic name passed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-reconnect-during-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-reconnect-during-with-uuid.https.window.js new file mode 100644 index 0000000000..db373fbca1 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-reconnect-during-with-uuid.https.window.js @@ -0,0 +1,39 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() and connect() called during ' + + 'getCharacteristics. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve characteristics. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let device; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: [health_thermometer.name]}], +}) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryService(health_thermometer.name)) + .then(service => Promise.all([ + // 1. Make a call to service.getCharacteristics, while the service is still + // valid. + assert_promise_rejects_with_message(service.getCharacteristics(measurement_interval.name), expected), + + // 2. disconnect() and connect before the initial call completes. + // This is accomplished by making the calls without waiting for the + // earlier promises to resolve. + // connect() guarantees on OS-level connection, but disconnect() + // only disconnects the current instance. + // getHealthThermometerDeviceWithServicesDiscovered holds another + // connection in an iframe, so disconnect() and connect() are certain to + // reconnect. However, disconnect() will invalidate the service object so + // the subsequent calls made to it will fail, even after reconnecting. + device.gatt.disconnect(), + device.gatt.connect() + ])), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-reconnect-during.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-reconnect-during.https.window.js new file mode 100644 index 0000000000..8b3ba7cc6b --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-reconnect-during.https.window.js @@ -0,0 +1,39 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'disconnect() and connect() called during ' + + 'getCharacteristics. Reject with NetworkError.'; +const expected = new DOMException( + 'GATT Server is disconnected. Cannot retrieve characteristics. ' + + '(Re)connect first with `device.gatt.connect`.', + 'NetworkError'); +let device; + +bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({ + filters: [{services: [health_thermometer.name]}], +}) + .then(_ => ({device} = _)) + .then(() => device.gatt.getPrimaryService(health_thermometer.name)) + .then(service => Promise.all([ + // 1. Make a call to service.getCharacteristics, while the service is still + // valid. + assert_promise_rejects_with_message(service.getCharacteristics(), expected), + + // 2. disconnect() and connect before the initial call completes. + // This is accomplished by making the calls without waiting for the + // earlier promises to resolve. + // connect() guarantees on OS-level connection, but disconnect() + // only disconnects the current instance. + // getHealthThermometerDeviceWithServicesDiscovered holds another + // connection in an iframe, so disconnect() and connect() are certain to + // reconnect. However, disconnect() will invalidate the service object so + // the subsequent calls made to it will fail, even after reconnecting. + device.gatt.disconnect(), + device.gatt.connect() + ])), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-service-is-removed-with-uuid.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-service-is-removed-with-uuid.https.window.js new file mode 100644 index 0000000000..2d4db52822 --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-service-is-removed-with-uuid.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Service is removed before getCharacteristics call. ' + + 'Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let service, fake_service, fake_peripheral; + +bluetooth_test(() => getHealthThermometerService() + .then(_ => ({service, fake_service, fake_peripheral} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + service.getCharacteristics('measurement_interval'), + expected, + 'Service got removed.')), + test_desc); + diff --git a/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-service-is-removed.https.window.js b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-service-is-removed.https.window.js new file mode 100644 index 0000000000..f922b45cdc --- /dev/null +++ b/testing/web-platform/tests/bluetooth/service/getCharacteristics/gen-service-is-removed.https.window.js @@ -0,0 +1,23 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/common/gc.js +// META: script=/bluetooth/resources/bluetooth-test.js +// META: script=/bluetooth/resources/bluetooth-fake-devices.js +// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py +'use strict'; +const test_desc = 'Service is removed before getCharacteristics call. ' + + 'Reject with InvalidStateError.'; +const expected = new DOMException('GATT Service no longer exists.', + 'InvalidStateError'); +let service, fake_service, fake_peripheral; + +bluetooth_test(() => getHealthThermometerService() + .then(_ => ({service, fake_service, fake_peripheral} = _)) + .then(() => fake_service.remove()) + .then(() => fake_peripheral.simulateGATTServicesChanged()) + .then(() => assert_promise_rejects_with_message( + service.getCharacteristics(), + expected, + 'Service got removed.')), + test_desc); + |