tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

web-bluetooth-test.js (24488B)


      1 'use strict';
      2 
      3 const content = {};
      4 const bluetooth = {};
      5 const MOJO_CHOOSER_EVENT_TYPE_MAP = {};
      6 
      7 function toMojoCentralState(state) {
      8  switch (state) {
      9    case 'absent':
     10      return bluetooth.mojom.CentralState.ABSENT;
     11    case 'powered-off':
     12      return bluetooth.mojom.CentralState.POWERED_OFF;
     13    case 'powered-on':
     14      return bluetooth.mojom.CentralState.POWERED_ON;
     15    default:
     16      throw `Unsupported value ${state} for state.`;
     17  }
     18 }
     19 
     20 // Converts bluetooth.mojom.WriteType to a string. If |writeType| is
     21 // invalid, this method will throw.
     22 function writeTypeToString(writeType) {
     23  switch (writeType) {
     24    case bluetooth.mojom.WriteType.kNone:
     25      return 'none';
     26    case bluetooth.mojom.WriteType.kWriteDefaultDeprecated:
     27      return 'default-deprecated';
     28    case bluetooth.mojom.WriteType.kWriteWithResponse:
     29      return 'with-response';
     30    case bluetooth.mojom.WriteType.kWriteWithoutResponse:
     31      return 'without-response';
     32    default:
     33      throw `Unknown bluetooth.mojom.WriteType: ${writeType}`;
     34  }
     35 }
     36 
     37 // Canonicalizes UUIDs and converts them to Mojo UUIDs.
     38 function canonicalizeAndConvertToMojoUUID(uuids) {
     39  let canonicalUUIDs = uuids.map(val => ({uuid: BluetoothUUID.getService(val)}));
     40  return canonicalUUIDs;
     41 }
     42 
     43 // Converts WebIDL a record<DOMString, BufferSource> to a map<K, array<uint8>> to
     44 // use for Mojo, where the value for K is calculated using keyFn.
     45 function convertToMojoMap(record, keyFn, isNumberKey = false) {
     46  let map = new Map();
     47  for (const [key, value] of Object.entries(record)) {
     48    let buffer = ArrayBuffer.isView(value) ? value.buffer : value;
     49    if (isNumberKey) {
     50      let numberKey = parseInt(key);
     51      if (Number.isNaN(numberKey))
     52        throw `Map key ${key} is not a number`;
     53      map.set(keyFn(numberKey), Array.from(new Uint8Array(buffer)));
     54      continue;
     55    }
     56    map.set(keyFn(key), Array.from(new Uint8Array(buffer)));
     57  }
     58  return map;
     59 }
     60 
     61 function ArrayToMojoCharacteristicProperties(arr) {
     62  const struct = {};
     63  arr.forEach(property => { struct[property] = true; });
     64  return struct;
     65 }
     66 
     67 class FakeBluetooth {
     68  constructor() {
     69    this.fake_bluetooth_ptr_ = new bluetooth.mojom.FakeBluetoothRemote();
     70    this.fake_bluetooth_ptr_.$.bindNewPipeAndPassReceiver().bindInBrowser('process');
     71    this.fake_central_ = null;
     72  }
     73 
     74  // Set it to indicate whether the platform supports BLE. For example,
     75  // Windows 7 is a platform that doesn't support Low Energy. On the other
     76  // hand Windows 10 is a platform that does support LE, even if there is no
     77  // Bluetooth radio present.
     78  async setLESupported(supported) {
     79    if (typeof supported !== 'boolean') throw 'Type Not Supported';
     80    await this.fake_bluetooth_ptr_.setLESupported(supported);
     81  }
     82 
     83  // Returns a promise that resolves with a FakeCentral that clients can use
     84  // to simulate events that a device in the Central/Observer role would
     85  // receive as well as monitor the operations performed by the device in the
     86  // Central/Observer role.
     87  // Calls sets LE as supported.
     88  //
     89  // A "Central" object would allow its clients to receive advertising events
     90  // and initiate connections to peripherals i.e. operations of two roles
     91  // defined by the Bluetooth Spec: Observer and Central.
     92  // See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an
     93  // LE Physical Transport".
     94  async simulateCentral({state}) {
     95    if (this.fake_central_)
     96      throw 'simulateCentral() should only be called once';
     97 
     98    await this.setLESupported(true);
     99 
    100    let {fakeCentral: fake_central_ptr} =
    101      await this.fake_bluetooth_ptr_.simulateCentral(
    102        toMojoCentralState(state));
    103    this.fake_central_ = new FakeCentral(fake_central_ptr);
    104    return this.fake_central_;
    105  }
    106 
    107  // Returns true if there are no pending responses.
    108  async allResponsesConsumed() {
    109    let {consumed} = await this.fake_bluetooth_ptr_.allResponsesConsumed();
    110    return consumed;
    111  }
    112 
    113  // Returns a promise that resolves with a FakeChooser that clients can use to
    114  // simulate chooser events.
    115  async getManualChooser() {
    116    if (typeof this.fake_chooser_ === 'undefined') {
    117      this.fake_chooser_ = new FakeChooser();
    118    }
    119    return this.fake_chooser_;
    120  }
    121 }
    122 
    123 // FakeCentral allows clients to simulate events that a device in the
    124 // Central/Observer role would receive as well as monitor the operations
    125 // performed by the device in the Central/Observer role.
    126 class FakeCentral {
    127  constructor(fake_central_ptr) {
    128    this.fake_central_ptr_ = fake_central_ptr;
    129    this.peripherals_ = new Map();
    130  }
    131 
    132  // Simulates a peripheral with |address|, |name|, |manufacturerData| and
    133  // |known_service_uuids| that has already been connected to the system. If the
    134  // peripheral existed already it updates its name, manufacturer data, and
    135  // known UUIDs. |known_service_uuids| should be an array of
    136  // BluetoothServiceUUIDs
    137  // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
    138  //
    139  // Platforms offer methods to retrieve devices that have already been
    140  // connected to the system or weren't connected through the UA e.g. a user
    141  // connected a peripheral through the system's settings. This method is
    142  // intended to simulate peripherals that those methods would return.
    143  async simulatePreconnectedPeripheral(
    144      {address, name, manufacturerData = {}, knownServiceUUIDs = []}) {
    145    await this.fake_central_ptr_.simulatePreconnectedPeripheral(
    146        address, name,
    147        convertToMojoMap(manufacturerData, Number, true /* isNumberKey */),
    148        canonicalizeAndConvertToMojoUUID(knownServiceUUIDs));
    149 
    150    return this.fetchOrCreatePeripheral_(address);
    151  }
    152 
    153  // Simulates an advertisement packet described by |scanResult| being received
    154  // from a device. If central is currently scanning, the device will appear on
    155  // the list of discovered devices.
    156  async simulateAdvertisementReceived(scanResult) {
    157    // Create a deep-copy to prevent the original |scanResult| from being
    158    // modified when the UUIDs, manufacturer, and service data are converted.
    159    let clonedScanResult = JSON.parse(JSON.stringify(scanResult));
    160 
    161    if ('uuids' in scanResult.scanRecord) {
    162      clonedScanResult.scanRecord.uuids =
    163          canonicalizeAndConvertToMojoUUID(scanResult.scanRecord.uuids);
    164    }
    165 
    166    // Convert the optional appearance and txPower fields to the corresponding
    167    // Mojo structures, since Mojo does not support optional interger values. If
    168    // the fields are undefined, set the hasValue field as false and value as 0.
    169    // Otherwise, set the hasValue field as true and value with the field value.
    170    const has_appearance = 'appearance' in scanResult.scanRecord;
    171    clonedScanResult.scanRecord.appearance = {
    172      hasValue: has_appearance,
    173      value: (has_appearance ? scanResult.scanRecord.appearance : 0)
    174    }
    175 
    176    const has_tx_power = 'txPower' in scanResult.scanRecord;
    177    clonedScanResult.scanRecord.txPower = {
    178      hasValue: has_tx_power,
    179      value: (has_tx_power ? scanResult.scanRecord.txPower : 0)
    180    }
    181 
    182    // Convert manufacturerData from a record<DOMString, BufferSource> into a
    183    // map<uint8, array<uint8>> for Mojo.
    184    if ('manufacturerData' in scanResult.scanRecord) {
    185      clonedScanResult.scanRecord.manufacturerData = convertToMojoMap(
    186          scanResult.scanRecord.manufacturerData, Number,
    187          true /* isNumberKey */);
    188    }
    189 
    190    // Convert serviceData from a record<DOMString, BufferSource> into a
    191    // map<string, array<uint8>> for Mojo.
    192    if ('serviceData' in scanResult.scanRecord) {
    193      clonedScanResult.scanRecord.serviceData.serviceData = convertToMojoMap(
    194          scanResult.scanRecord.serviceData, BluetoothUUID.getService,
    195          false /* isNumberKey */);
    196    }
    197 
    198    await this.fake_central_ptr_.simulateAdvertisementReceived(
    199        clonedScanResult);
    200 
    201    return this.fetchOrCreatePeripheral_(clonedScanResult.deviceAddress);
    202  }
    203 
    204  // Simulates a change in the central device described by |state|. For example,
    205  // setState('powered-off') can be used to simulate the central device powering
    206  // off.
    207  //
    208  // This method should be used for any central state changes after
    209  // simulateCentral() has been called to create a FakeCentral object.
    210  async setState(state) {
    211    await this.fake_central_ptr_.setState(toMojoCentralState(state));
    212  }
    213 
    214  // Create a fake_peripheral object from the given address.
    215  fetchOrCreatePeripheral_(address) {
    216    let peripheral = this.peripherals_.get(address);
    217    if (peripheral === undefined) {
    218      peripheral = new FakePeripheral(address, this.fake_central_ptr_);
    219      this.peripherals_.set(address, peripheral);
    220    }
    221    return peripheral;
    222  }
    223 }
    224 
    225 class FakePeripheral {
    226  constructor(address, fake_central_ptr) {
    227    this.address = address;
    228    this.fake_central_ptr_ = fake_central_ptr;
    229  }
    230 
    231  // Adds a fake GATT Service with |uuid| to be discovered when discovering
    232  // the peripheral's GATT Attributes. Returns a FakeRemoteGATTService
    233  // corresponding to this service. |uuid| should be a BluetoothServiceUUIDs
    234  // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
    235  async addFakeService({uuid}) {
    236    let {serviceId: service_id} = await this.fake_central_ptr_.addFakeService(
    237      this.address, {uuid: BluetoothUUID.getService(uuid)});
    238 
    239    if (service_id === null) throw 'addFakeService failed';
    240 
    241    return new FakeRemoteGATTService(
    242      service_id, this.address, this.fake_central_ptr_);
    243  }
    244 
    245  // Sets the next GATT Connection request response to |code|. |code| could be
    246  // an HCI Error Code from BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a
    247  // number outside that range returned by specific platforms e.g. Android
    248  // returns 0x101 to signal a GATT failure
    249  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
    250  async setNextGATTConnectionResponse({code}) {
    251    let {success} =
    252      await this.fake_central_ptr_.setNextGATTConnectionResponse(
    253        this.address, code);
    254 
    255    if (success !== true) throw 'setNextGATTConnectionResponse failed.';
    256  }
    257 
    258  // Sets the next GATT Discovery request response for peripheral with
    259  // |address| to |code|. |code| could be an HCI Error Code from
    260  // BT 4.2 Vol 2 Part D 1.3 List Of Error Codes or a number outside that
    261  // range returned by specific platforms e.g. Android returns 0x101 to signal
    262  // a GATT failure
    263  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
    264  //
    265  // The following procedures defined at BT 4.2 Vol 3 Part G Section 4.
    266  // "GATT Feature Requirements" are used to discover attributes of the
    267  // GATT Server:
    268  //  - Primary Service Discovery
    269  //  - Relationship Discovery
    270  //  - Characteristic Discovery
    271  //  - Characteristic Descriptor Discovery
    272  // This method aims to simulate the response once all of these procedures
    273  // have completed or if there was an error during any of them.
    274  async setNextGATTDiscoveryResponse({code}) {
    275    let {success} =
    276      await this.fake_central_ptr_.setNextGATTDiscoveryResponse(
    277        this.address, code);
    278 
    279    if (success !== true) throw 'setNextGATTDiscoveryResponse failed.';
    280  }
    281 
    282  // Simulates a GATT disconnection from the peripheral with |address|.
    283  async simulateGATTDisconnection() {
    284    let {success} =
    285      await this.fake_central_ptr_.simulateGATTDisconnection(this.address);
    286 
    287    if (success !== true) throw 'simulateGATTDisconnection failed.';
    288  }
    289 
    290  // Simulates an Indication from the peripheral's GATT `Service Changed`
    291  // Characteristic from BT 4.2 Vol 3 Part G 7.1. This Indication is signaled
    292  // when services, characteristics, or descriptors are changed, added, or
    293  // removed.
    294  //
    295  // The value for `Service Changed` is a range of attribute handles that have
    296  // changed. However, this testing specification works at an abstracted
    297  // level and does not expose setting attribute handles when adding
    298  // attributes. Consequently, this simulate method should include the full
    299  // range of all the peripheral's attribute handle values.
    300  async simulateGATTServicesChanged() {
    301    let {success} =
    302      await this.fake_central_ptr_.simulateGATTServicesChanged(this.address);
    303 
    304    if (success !== true) throw 'simulateGATTServicesChanged failed.';
    305  }
    306 }
    307 
    308 class FakeRemoteGATTService {
    309  constructor(service_id, peripheral_address, fake_central_ptr) {
    310    this.service_id_ = service_id;
    311    this.peripheral_address_ = peripheral_address;
    312    this.fake_central_ptr_ = fake_central_ptr;
    313  }
    314 
    315  // Adds a fake GATT Characteristic with |uuid| and |properties|
    316  // to this fake service. The characteristic will be found when discovering
    317  // the peripheral's GATT Attributes. Returns a FakeRemoteGATTCharacteristic
    318  // corresponding to the added characteristic.
    319  async addFakeCharacteristic({uuid, properties}) {
    320    let {characteristicId: characteristic_id} =
    321        await this.fake_central_ptr_.addFakeCharacteristic(
    322            {uuid: BluetoothUUID.getCharacteristic(uuid)},
    323            ArrayToMojoCharacteristicProperties(properties),
    324            this.service_id_,
    325            this.peripheral_address_);
    326 
    327    if (characteristic_id === null) throw 'addFakeCharacteristic failed';
    328 
    329    return new FakeRemoteGATTCharacteristic(
    330      characteristic_id, this.service_id_,
    331      this.peripheral_address_, this.fake_central_ptr_);
    332  }
    333 
    334  // Removes the fake GATT service from its fake peripheral.
    335  async remove() {
    336    let {success} =
    337        await this.fake_central_ptr_.removeFakeService(
    338            this.service_id_,
    339            this.peripheral_address_);
    340 
    341    if (!success) throw 'remove failed';
    342  }
    343 }
    344 
    345 class FakeRemoteGATTCharacteristic {
    346  constructor(characteristic_id, service_id, peripheral_address,
    347      fake_central_ptr) {
    348    this.ids_ = [characteristic_id, service_id, peripheral_address];
    349    this.descriptors_ = [];
    350    this.fake_central_ptr_ = fake_central_ptr;
    351  }
    352 
    353  // Adds a fake GATT Descriptor with |uuid| to be discovered when
    354  // discovering the peripheral's GATT Attributes. Returns a
    355  // FakeRemoteGATTDescriptor corresponding to this descriptor. |uuid| should
    356  // be a BluetoothDescriptorUUID
    357  // https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothdescriptoruuid
    358  async addFakeDescriptor({uuid}) {
    359    let {descriptorId: descriptor_id} =
    360        await this.fake_central_ptr_.addFakeDescriptor(
    361            {uuid: BluetoothUUID.getDescriptor(uuid)}, ...this.ids_);
    362 
    363    if (descriptor_id === null) throw 'addFakeDescriptor failed';
    364 
    365    let fake_descriptor = new FakeRemoteGATTDescriptor(
    366      descriptor_id, ...this.ids_, this.fake_central_ptr_);
    367    this.descriptors_.push(fake_descriptor);
    368 
    369    return fake_descriptor;
    370  }
    371 
    372  // Sets the next read response for characteristic to |code| and |value|.
    373  // |code| could be a GATT Error Response from
    374  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
    375  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
    376  // failure.
    377  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
    378  async setNextReadResponse(gatt_code, value=null) {
    379    if (gatt_code === 0 && value === null) {
    380      throw '|value| can\'t be null if read should success.';
    381    }
    382    if (gatt_code !== 0 && value !== null) {
    383      throw '|value| must be null if read should fail.';
    384    }
    385 
    386    let {success} =
    387      await this.fake_central_ptr_.setNextReadCharacteristicResponse(
    388        gatt_code, value, ...this.ids_);
    389 
    390    if (!success) throw 'setNextReadCharacteristicResponse failed';
    391  }
    392 
    393  // Sets the next write response for this characteristic to |code|. If
    394  // writing to a characteristic that only supports 'write_without_response'
    395  // the set response will be ignored.
    396  // |code| could be a GATT Error Response from
    397  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
    398  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
    399  // failure.
    400  async setNextWriteResponse(gatt_code) {
    401    let {success} =
    402      await this.fake_central_ptr_.setNextWriteCharacteristicResponse(
    403        gatt_code, ...this.ids_);
    404 
    405    if (!success) throw 'setNextWriteCharacteristicResponse failed';
    406  }
    407 
    408  // Sets the next subscribe to notifications response for characteristic with
    409  // |characteristic_id| in |service_id| and in |peripheral_address| to
    410  // |code|. |code| could be a GATT Error Response from BT 4.2 Vol 3 Part F
    411  // 3.4.1.1 Error Response or a number outside that range returned by
    412  // specific platforms e.g. Android returns 0x101 to signal a GATT failure.
    413  async setNextSubscribeToNotificationsResponse(gatt_code) {
    414    let {success} =
    415      await this.fake_central_ptr_.setNextSubscribeToNotificationsResponse(
    416        gatt_code, ...this.ids_);
    417 
    418    if (!success) throw 'setNextSubscribeToNotificationsResponse failed';
    419  }
    420 
    421  // Sets the next unsubscribe to notifications response for characteristic with
    422  // |characteristic_id| in |service_id| and in |peripheral_address| to
    423  // |code|. |code| could be a GATT Error Response from BT 4.2 Vol 3 Part F
    424  // 3.4.1.1 Error Response or a number outside that range returned by
    425  // specific platforms e.g. Android returns 0x101 to signal a GATT failure.
    426  async setNextUnsubscribeFromNotificationsResponse(gatt_code) {
    427    let {success} =
    428      await this.fake_central_ptr_.setNextUnsubscribeFromNotificationsResponse(
    429        gatt_code, ...this.ids_);
    430 
    431    if (!success) throw 'setNextUnsubscribeToNotificationsResponse failed';
    432  }
    433 
    434  // Returns true if notifications from the characteristic have been subscribed
    435  // to.
    436  async isNotifying() {
    437    let {success, isNotifying} =
    438        await this.fake_central_ptr_.isNotifying(...this.ids_);
    439 
    440    if (!success) throw 'isNotifying failed';
    441 
    442    return isNotifying;
    443  }
    444 
    445  // Gets the last successfully written value to the characteristic and its
    446  // write type. Write type is one of 'none', 'default-deprecated',
    447  // 'with-response', 'without-response'. Returns {lastValue: null,
    448  // lastWriteType: 'none'} if no value has yet been written to the
    449  // characteristic.
    450  async getLastWrittenValue() {
    451    let {success, value, writeType} =
    452        await this.fake_central_ptr_.getLastWrittenCharacteristicValue(
    453            ...this.ids_);
    454 
    455    if (!success) throw 'getLastWrittenCharacteristicValue failed';
    456 
    457    return {lastValue: value, lastWriteType: writeTypeToString(writeType)};
    458  }
    459 
    460  // Removes the fake GATT Characteristic from its fake service.
    461  async remove() {
    462    let {success} =
    463        await this.fake_central_ptr_.removeFakeCharacteristic(...this.ids_);
    464 
    465    if (!success) throw 'remove failed';
    466  }
    467 }
    468 
    469 class FakeRemoteGATTDescriptor {
    470  constructor(descriptor_id,
    471              characteristic_id,
    472              service_id,
    473              peripheral_address,
    474              fake_central_ptr) {
    475    this.ids_ = [
    476      descriptor_id, characteristic_id, service_id, peripheral_address];
    477    this.fake_central_ptr_ = fake_central_ptr;
    478  }
    479 
    480  // Sets the next read response for descriptor to |code| and |value|.
    481  // |code| could be a GATT Error Response from
    482  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
    483  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
    484  // failure.
    485  // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#GATT_FAILURE
    486  async setNextReadResponse(gatt_code, value=null) {
    487    if (gatt_code === 0 && value === null) {
    488      throw '|value| cannot be null if read should succeed.';
    489    }
    490    if (gatt_code !== 0 && value !== null) {
    491      throw '|value| must be null if read should fail.';
    492    }
    493 
    494    let {success} =
    495      await this.fake_central_ptr_.setNextReadDescriptorResponse(
    496        gatt_code, value, ...this.ids_);
    497 
    498    if (!success) throw 'setNextReadDescriptorResponse failed';
    499  }
    500 
    501  // Sets the next write response for this descriptor to |code|.
    502  // |code| could be a GATT Error Response from
    503  // BT 4.2 Vol 3 Part F 3.4.1.1 Error Response or a number outside that range
    504  // returned by specific platforms e.g. Android returns 0x101 to signal a GATT
    505  // failure.
    506  async setNextWriteResponse(gatt_code) {
    507    let {success} =
    508      await this.fake_central_ptr_.setNextWriteDescriptorResponse(
    509        gatt_code, ...this.ids_);
    510 
    511    if (!success) throw 'setNextWriteDescriptorResponse failed';
    512  }
    513 
    514  // Gets the last successfully written value to the descriptor.
    515  // Returns null if no value has yet been written to the descriptor.
    516  async getLastWrittenValue() {
    517    let {success, value} =
    518      await this.fake_central_ptr_.getLastWrittenDescriptorValue(
    519          ...this.ids_);
    520 
    521    if (!success) throw 'getLastWrittenDescriptorValue failed';
    522 
    523    return value;
    524  }
    525 
    526  // Removes the fake GATT Descriptor from its fake characteristic.
    527  async remove() {
    528    let {success} =
    529        await this.fake_central_ptr_.removeFakeDescriptor(...this.ids_);
    530 
    531    if (!success) throw 'remove failed';
    532  }
    533 }
    534 
    535 // FakeChooser allows clients to simulate user actions on a Bluetooth chooser,
    536 // and records the events produced by the Bluetooth chooser.
    537 class FakeChooser {
    538  constructor() {
    539    let fakeBluetoothChooserFactoryRemote =
    540        new content.mojom.FakeBluetoothChooserFactoryRemote();
    541    fakeBluetoothChooserFactoryRemote.$.bindNewPipeAndPassReceiver().bindInBrowser('process');
    542 
    543    this.fake_bluetooth_chooser_ptr_ =
    544        new content.mojom.FakeBluetoothChooserRemote();
    545    this.fake_bluetooth_chooser_client_receiver_ =
    546        new content.mojom.FakeBluetoothChooserClientReceiver(this);
    547    fakeBluetoothChooserFactoryRemote.createFakeBluetoothChooser(
    548        this.fake_bluetooth_chooser_ptr_.$.bindNewPipeAndPassReceiver(),
    549        this.fake_bluetooth_chooser_client_receiver_.$.associateAndPassRemote());
    550 
    551    this.events_ = new Array();
    552    this.event_listener_ = null;
    553  }
    554 
    555  // If the chooser has received more events than |numOfEvents| this function
    556  // will reject the promise, else it will wait until |numOfEvents| events are
    557  // received before resolving with an array of |FakeBluetoothChooserEvent|
    558  // objects.
    559  async waitForEvents(numOfEvents) {
    560    return new Promise(resolve => {
    561      if (this.events_.length > numOfEvents) {
    562        throw `Asked for ${numOfEvents} event(s), but received ` +
    563            `${this.events_.length}.`;
    564      }
    565 
    566      this.event_listener_ = () => {
    567         if (this.events_.length === numOfEvents) {
    568          let result = Array.from(this.events_);
    569          this.event_listener_ = null;
    570          this.events_ = [];
    571          resolve(result);
    572        }
    573      };
    574      this.event_listener_();
    575    });
    576  }
    577 
    578  async selectPeripheral(peripheral) {
    579    if (!(peripheral instanceof FakePeripheral)) {
    580      throw '|peripheral| must be an instance of FakePeripheral';
    581    }
    582    await this.fake_bluetooth_chooser_ptr_.selectPeripheral(peripheral.address);
    583  }
    584 
    585  async cancel() {
    586    await this.fake_bluetooth_chooser_ptr_.cancel();
    587  }
    588 
    589  async rescan() {
    590    await this.fake_bluetooth_chooser_ptr_.rescan();
    591  }
    592 
    593  onEvent(chooserEvent) {
    594    chooserEvent.type = MOJO_CHOOSER_EVENT_TYPE_MAP[chooserEvent.type];
    595    this.events_.push(chooserEvent);
    596    if (this.event_listener_ !== null) {
    597      this.event_listener_();
    598    }
    599  }
    600 }
    601 
    602 async function initializeChromiumResources() {
    603  content.mojom = await import(
    604      '/gen/content/web_test/common/fake_bluetooth_chooser.mojom.m.js');
    605  bluetooth.mojom = await import(
    606      '/gen/device/bluetooth/public/mojom/emulation/fake_bluetooth.mojom.m.js');
    607 
    608  const map = MOJO_CHOOSER_EVENT_TYPE_MAP;
    609  const types = content.mojom.ChooserEventType;
    610  map[types.CHOOSER_OPENED] = 'chooser-opened';
    611  map[types.CHOOSER_CLOSED] = 'chooser-closed';
    612  map[types.ADAPTER_REMOVED] = 'adapter-removed';
    613  map[types.ADAPTER_DISABLED] = 'adapter-disabled';
    614  map[types.ADAPTER_ENABLED] = 'adapter-enabled';
    615  map[types.DISCOVERY_FAILED_TO_START] = 'discovery-failed-to-start';
    616  map[types.DISCOVERING] = 'discovering';
    617  map[types.DISCOVERY_IDLE] = 'discovery-idle';
    618  map[types.ADD_OR_UPDATE_DEVICE] = 'add-or-update-device';
    619 
    620  // If this line fails, it means that current environment does not support the
    621  // Web Bluetooth Test API.
    622  try {
    623    navigator.bluetooth.test = new FakeBluetooth();
    624  } catch {
    625    throw 'Web Bluetooth Test API is not implemented on this ' +
    626        'environment. See the bluetooth README at ' +
    627        'https://github.com/web-platform-tests/wpt/blob/master/bluetooth/README.md#web-bluetooth-testing';
    628  }
    629 }