chromium/third_party/blink/web_tests/wpt_internal/bluetooth/characteristic/notifications/gc-with-event-listener.https.html

<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/bluetooth/resources/bluetooth-test.js"></script>
<script src="/bluetooth/resources/bluetooth-fake-devices.js"></script>
<script src="/common/gc.js"></script>
<script>
'use strict';
bluetooth_test(async () => {
  let expectingEvent = false;
  let eventHandler;
  let eventPromise = new Promise((resolve, reject) => {
    eventHandler = () => {
      if (expectingEvent) {
        resolve();
      } else {
        reject('Event fired before garbage collection.');
      }
    };
  });

  await (async () => {
    await setBluetoothFakeAdapter('HeartRateAdapter');
    let device = await requestDeviceWithTrustedClick({
        filters: [{services: ['heart_rate']}]});
    let gattServer = await device.gatt.connect();
    let service = await gattServer.getPrimaryService('heart_rate');
    let characteristic =
        await service.getCharacteristic('heart_rate_measurement');
    await characteristic.startNotifications();

    characteristic.addEventListener(
        'characteristicvaluechanged', eventHandler, { once: true });

    // The characteristic must hold the last reference to the event handler.
    eventHandler = undefined;
  })();

  // At this point all the variables above should be out of scope and so this
  // will free them.
  garbageCollect();

  // The event listener should still be active.
  expectingEvent = true;
  await eventPromise;
}, 'Event listeners remain registered after garbage collection.');
</script>