From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Sun, 7 Apr 2024 11:22:09 +0200
Subject: Adding upstream version 110.0.1.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 .../forget/connect-after-forget.https.window.js    | 11 +++++
 .../device/forget/detachedIframe.https.window.js   | 27 ++++++++++++
 .../device/forget/getDevices.https.window.js       | 18 ++++++++
 .../disconnected.https.window.js                   | 16 +++++++
 .../disconnected_gc.https.window.js                | 23 ++++++++++
 .../one-event-per-disconnection.https.window.js    | 30 +++++++++++++
 ...nnect-during-disconnected-event.https.window.js | 31 ++++++++++++++
 ...bort-before-watchAdvertisements.https.window.js | 17 ++++++++
 .../abort-pending-operation.https.window.js        | 22 ++++++++++
 .../abort-signal-stops-events.https.window.js      | 27 ++++++++++++
 ...dvertisements-call-stops-events.https.window.js | 26 +++++++++++
 ...vertisementreceived-event-fired.https.window.js | 25 +++++++++++
 ...cturer-data-filtered-from-event.https.window.js | 50 ++++++++++++++++++++++
 ...rrent-watchAdvertisements-calls.https.window.js | 29 +++++++++++++
 .../detachedIframe.https.window.js                 | 27 ++++++++++++
 ...cturer-data-filtered-from-event.https.window.js | 41 ++++++++++++++++++
 ...equent-watchAdvertisements-call.https.window.js | 25 +++++++++++
 ...s-abort-one-watchAdvertisements.https.window.js | 47 ++++++++++++++++++++
 .../watching-two-devices.https.window.js           | 41 ++++++++++++++++++
 19 files changed, 533 insertions(+)
 create mode 100644 testing/web-platform/tests/bluetooth/device/forget/connect-after-forget.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/forget/detachedIframe.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/forget/getDevices.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/disconnected.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/disconnected_gc.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/one-event-per-disconnection.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/gattserverdisconnected-event/reconnect-during-disconnected-event.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-before-watchAdvertisements.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-pending-operation.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-signal-stops-events.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/abort-subsequent-watchAdvertisements-call-stops-events.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/advertisementreceived-event-fired.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/blocklisted-manufacturer-data-filtered-from-event.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/concurrent-watchAdvertisements-calls.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/detachedIframe.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/service-and-manufacturer-data-filtered-from-event.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/subsequent-watchAdvertisements-call.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/watching-two-devices-abort-one-watchAdvertisements.https.window.js
 create mode 100644 testing/web-platform/tests/bluetooth/device/watchAdvertisements/watching-two-devices.https.window.js

(limited to 'testing/web-platform/tests/bluetooth/device')

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);
-- 
cgit v1.2.3