tor-browser

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

fake-hid.js (10471B)


      1 import {HidConnectionReceiver, HidDeviceInfo} from '/gen/services/device/public/mojom/hid.mojom.m.js';
      2 import {HidService, HidServiceReceiver} from '/gen/third_party/blink/public/mojom/hid/hid.mojom.m.js';
      3 
      4 // Fake implementation of device.mojom.HidConnection. HidConnection represents
      5 // an open connection to a HID device and can be used to send and receive
      6 // reports.
      7 class FakeHidConnection {
      8  constructor(client) {
      9    this.client_ = client;
     10    this.receiver_ = new HidConnectionReceiver(this);
     11    this.expectedWrites_ = [];
     12    this.expectedGetFeatureReports_ = [];
     13    this.expectedSendFeatureReports_ = [];
     14  }
     15 
     16  bindNewPipeAndPassRemote() {
     17    return this.receiver_.$.bindNewPipeAndPassRemote();
     18  }
     19 
     20  // Simulate an input report sent from the device to the host. The connection
     21  // client's onInputReport method will be called with the provided |reportId|
     22  // and |buffer|.
     23  simulateInputReport(reportId, reportData) {
     24    if (this.client_) {
     25      this.client_.onInputReport(reportId, reportData);
     26    }
     27  }
     28 
     29  // Specify the result for an expected call to write. If |success| is true the
     30  // write will be successful, otherwise it will simulate a failure. The
     31  // parameters of the next write call must match |reportId| and |buffer|.
     32  queueExpectedWrite(success, reportId, reportData) {
     33    this.expectedWrites_.push({
     34      params: {reportId, data: reportData},
     35      result: {success},
     36    });
     37  }
     38 
     39  // Specify the result for an expected call to getFeatureReport. If |success|
     40  // is true the operation is successful, otherwise it will simulate a failure.
     41  // The parameter of the next getFeatureReport call must match |reportId|.
     42  queueExpectedGetFeatureReport(success, reportId, reportData) {
     43    this.expectedGetFeatureReports_.push({
     44      params: {reportId},
     45      result: {success, buffer: reportData},
     46    });
     47  }
     48 
     49  // Specify the result for an expected call to sendFeatureReport. If |success|
     50  // is true the operation is successful, otherwise it will simulate a failure.
     51  // The parameters of the next sendFeatureReport call must match |reportId| and
     52  // |buffer|.
     53  queueExpectedSendFeatureReport(success, reportId, reportData) {
     54    this.expectedSendFeatureReports_.push({
     55      params: {reportId, data: reportData},
     56      result: {success},
     57    });
     58  }
     59 
     60  // Asserts that there are no more expected operations.
     61  assertExpectationsMet() {
     62    assert_equals(this.expectedWrites_.length, 0);
     63    assert_equals(this.expectedGetFeatureReports_.length, 0);
     64    assert_equals(this.expectedSendFeatureReports_.length, 0);
     65  }
     66 
     67  read() {}
     68 
     69  // Implementation of HidConnection::Write. Causes an assertion failure if
     70  // there are no expected write operations, or if the parameters do not match
     71  // the expected call.
     72  async write(reportId, buffer) {
     73    let expectedWrite = this.expectedWrites_.shift();
     74    assert_not_equals(expectedWrite, undefined);
     75    assert_equals(reportId, expectedWrite.params.reportId);
     76    let actual = new Uint8Array(buffer);
     77    compareDataViews(
     78        new DataView(actual.buffer, actual.byteOffset),
     79        new DataView(
     80            expectedWrite.params.data.buffer,
     81            expectedWrite.params.data.byteOffset));
     82    return expectedWrite.result;
     83  }
     84 
     85  // Implementation of HidConnection::GetFeatureReport. Causes an assertion
     86  // failure if there are no expected write operations, or if the parameters do
     87  // not match the expected call.
     88  async getFeatureReport(reportId) {
     89    let expectedGetFeatureReport = this.expectedGetFeatureReports_.shift();
     90    assert_not_equals(expectedGetFeatureReport, undefined);
     91    assert_equals(reportId, expectedGetFeatureReport.params.reportId);
     92    return expectedGetFeatureReport.result;
     93  }
     94 
     95  // Implementation of HidConnection::SendFeatureReport. Causes an assertion
     96  // failure if there are no expected write operations, or if the parameters do
     97  // not match the expected call.
     98  async sendFeatureReport(reportId, buffer) {
     99    let expectedSendFeatureReport = this.expectedSendFeatureReports_.shift();
    100    assert_not_equals(expectedSendFeatureReport, undefined);
    101    assert_equals(reportId, expectedSendFeatureReport.params.reportId);
    102    let actual = new Uint8Array(buffer);
    103    compareDataViews(
    104        new DataView(actual.buffer, actual.byteOffset),
    105        new DataView(
    106            expectedSendFeatureReport.params.data.buffer,
    107            expectedSendFeatureReport.params.data.byteOffset));
    108    return expectedSendFeatureReport.result;
    109  }
    110 }
    111 
    112 
    113 // A fake implementation of the HidService mojo interface. HidService manages
    114 // HID device access for clients in the render process. Typically, when a client
    115 // requests access to a HID device a chooser dialog is shown with a list of
    116 // available HID devices. Selecting a device from the chooser also grants
    117 // permission for the client to access that device.
    118 //
    119 // The fake implementation allows tests to simulate connected devices. It also
    120 // skips the chooser dialog and instead allows tests to specify which device
    121 // should be selected. All devices are treated as if the user had already
    122 // granted permission. It is possible to revoke permission with forget() later.
    123 class FakeHidService {
    124  constructor() {
    125    this.interceptor_ = new MojoInterfaceInterceptor(HidService.$interfaceName);
    126    this.interceptor_.oninterfacerequest = e => this.bind(e.handle);
    127    this.receiver_ = new HidServiceReceiver(this);
    128    this.nextGuidValue_ = 0;
    129    this.simulateConnectFailure_ = false;
    130    this.reset();
    131  }
    132 
    133  start() {
    134    this.interceptor_.start();
    135  }
    136 
    137  stop() {
    138    this.interceptor_.stop();
    139  }
    140 
    141  reset() {
    142    this.devices_ = new Map();
    143    this.allowedDevices_ = new Map();
    144    this.fakeConnections_ = new Map();
    145    this.selectedDevices_ = [];
    146  }
    147 
    148  // Creates and returns a HidDeviceInfo with the specified device IDs.
    149  makeDevice(vendorId, productId) {
    150    let guidValue = ++this.nextGuidValue_;
    151    let info = new HidDeviceInfo();
    152    info.guid = 'guid-' + guidValue.toString();
    153    info.physicalDeviceId = 'physical-device-id-' + guidValue.toString();
    154    info.vendorId = vendorId;
    155    info.productId = productId;
    156    info.productName = 'product name';
    157    info.serialNumber = '0';
    158    info.reportDescriptor = new Uint8Array();
    159    info.collections = [];
    160    info.deviceNode = 'device node';
    161    return info;
    162  }
    163 
    164  // Simulates a connected device the client has already been granted permission
    165  // to. Returns the key used to store the device in the map. The key is either
    166  // the physical device ID, or the device GUID if it has no physical device ID.
    167  addDevice(deviceInfo, grantPermission = true) {
    168    let key = deviceInfo.physicalDeviceId;
    169    if (key.length === 0)
    170      key = deviceInfo.guid;
    171 
    172    let devices = this.devices_.get(key) || [];
    173    devices.push(deviceInfo);
    174    this.devices_.set(key, devices);
    175 
    176    if (grantPermission) {
    177      let allowedDevices = this.allowedDevices_.get(key) || [];
    178      allowedDevices.push(deviceInfo);
    179      this.allowedDevices_.set(key, allowedDevices);
    180    }
    181 
    182    if (this.client_)
    183      this.client_.deviceAdded(deviceInfo);
    184    return key;
    185  }
    186 
    187  // Simulates disconnecting a connected device.
    188  removeDevice(key) {
    189    let devices = this.devices_.get(key);
    190    this.devices_.delete(key);
    191    if (this.client_ && devices) {
    192      devices.forEach(deviceInfo => {
    193        this.client_.deviceRemoved(deviceInfo);
    194      });
    195    }
    196  }
    197 
    198  // Simulates updating the device information for a connected device.
    199  changeDevice(deviceInfo) {
    200    let key = deviceInfo.physicalDeviceId;
    201    if (key.length === 0)
    202      key = deviceInfo.guid;
    203 
    204    let devices = this.devices_.get(key) || [];
    205    let i = devices.length;
    206    while (i--) {
    207      if (devices[i].guid == deviceInfo.guid)
    208        devices.splice(i, 1);
    209    }
    210    devices.push(deviceInfo);
    211    this.devices_.set(key, devices);
    212 
    213    let allowedDevices = this.allowedDevices_.get(key) || [];
    214    let j = allowedDevices.length;
    215    while (j--) {
    216      if (allowedDevices[j].guid == deviceInfo.guid)
    217        allowedDevices.splice(j, 1);
    218    }
    219    allowedDevices.push(deviceInfo);
    220    this.allowedDevices_.set(key, allowedDevices);
    221 
    222    if (this.client_)
    223      this.client_.deviceChanged(deviceInfo);
    224    return key;
    225  }
    226 
    227  // Sets a flag that causes the next call to connect() to fail.
    228  simulateConnectFailure() {
    229    this.simulateConnectFailure_ = true;
    230  }
    231 
    232  // Sets the key of the device that will be returned as the selected item the
    233  // next time requestDevice is called. The device with this key must have been
    234  // previously added with addDevice.
    235  setSelectedDevice(key) {
    236    this.selectedDevices_ = this.devices_.get(key);
    237  }
    238 
    239  // Returns the fake HidConnection object for this device, if there is one. A
    240  // connection is created once the device is opened.
    241  getFakeConnection(guid) {
    242    return this.fakeConnections_.get(guid);
    243  }
    244 
    245  bind(handle) {
    246    this.receiver_.$.bindHandle(handle);
    247  }
    248 
    249  registerClient(client) {
    250    this.client_ = client;
    251  }
    252 
    253  // Returns an array of connected devices the client has already been granted
    254  // permission to access.
    255  async getDevices() {
    256    let devices = [];
    257    this.allowedDevices_.forEach((value) => {
    258      devices = devices.concat(value);
    259    });
    260    return {devices};
    261  }
    262 
    263  // Simulates a device chooser prompt, returning |selectedDevices_| as the
    264  // simulated selection. |options| is ignored.
    265  async requestDevice(options) {
    266    return {devices: this.selectedDevices_};
    267  }
    268 
    269  // Returns a fake connection to the device with the specified GUID. If
    270  // |connectionClient| is not null, its onInputReport method will be called
    271  // when input reports are received. If simulateConnectFailure() was called
    272  // then a null connection is returned instead, indicating failure.
    273  async connect(guid, connectionClient) {
    274    if (this.simulateConnectFailure_) {
    275      this.simulateConnectFailure_ = false;
    276      return {connection: null};
    277    }
    278    const fakeConnection = new FakeHidConnection(connectionClient);
    279    this.fakeConnections_.set(guid, fakeConnection);
    280    return {connection: fakeConnection.bindNewPipeAndPassRemote()};
    281  }
    282 
    283  // Removes the allowed device.
    284  async forget(deviceInfo) {
    285    for (const [key, value] of this.allowedDevices_) {
    286      for (const device of value) {
    287        if (device.guid == deviceInfo.guid) {
    288          this.allowedDevices_.delete(key);
    289          break;
    290        }
    291      }
    292    }
    293    return {success: true};
    294  }
    295 }
    296 
    297 export const fakeHidService = new FakeHidService();