bluetooth: Add BluetoothDevice.forget()

This CL adds the forget method to the BluetoothDevice interface so that
developers can revoke permission access to a paired BluetoothDevice.
It is available behind the WebBluetoothGetDevices blink runtime feature.

Spec: https://github.com/WebBluetoothCG/web-bluetooth/pull/574
Test: https://bluetoothdevice-forget.glitch.me/
Change-Id: I0a562607cc473ecc95fdb269eb40194d81c1b8ed
Bug: 1302328
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3497377
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Dominick Ng <dominickn@chromium.org>
Commit-Queue: Fr <beaufort.francois@gmail.com>
Cr-Commit-Position: refs/heads/main@{#979716}
diff --git a/bluetooth/device/forget/connect-after-forget.https.window.js b/bluetooth/device/forget/connect-after-forget.https.window.js
new file mode 100644
index 0000000..0b15b4d
--- /dev/null
+++ b/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/bluetooth/device/forget/detachedIframe.https.window.js b/bluetooth/device/forget/detachedIframe.https.window.js
new file mode 100644
index 0000000..c34a3c4
--- /dev/null
+++ b/bluetooth/device/forget/detachedIframe.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
+
+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 runGarbageCollection();
+
+  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/bluetooth/device/forget/getDevices.https.window.js b/bluetooth/device/forget/getDevices.https.window.js
new file mode 100644
index 0000000..e9ce656
--- /dev/null
+++ b/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().');