tor-browser

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

fake-serial.js (11418B)


      1 import {SerialPortFlushMode, SerialPortRemote, SerialReceiveError, SerialPortReceiver, SerialSendError} from '/gen/services/device/public/mojom/serial.mojom.m.js';
      2 import {SerialService, SerialServiceReceiver} from '/gen/third_party/blink/public/mojom/serial/serial.mojom.m.js';
      3 
      4 // Implementation of an UnderlyingSource to create a ReadableStream from a Mojo
      5 // data pipe consumer handle.
      6 class DataPipeSource {
      7  constructor(consumer) {
      8    this.consumer_ = consumer;
      9  }
     10 
     11  async pull(controller) {
     12    let chunk = new ArrayBuffer(64);
     13    let {result, numBytes} = this.consumer_.readData(chunk);
     14    if (result == Mojo.RESULT_OK) {
     15      controller.enqueue(new Uint8Array(chunk, 0, numBytes));
     16      return;
     17    } else if (result == Mojo.RESULT_FAILED_PRECONDITION) {
     18      controller.close();
     19      return;
     20    } else if (result == Mojo.RESULT_SHOULD_WAIT) {
     21      await this.readable();
     22      return this.pull(controller);
     23    }
     24  }
     25 
     26  cancel() {
     27    if (this.watcher_)
     28      this.watcher_.cancel();
     29    this.consumer_.close();
     30  }
     31 
     32  readable() {
     33    return new Promise((resolve) => {
     34      this.watcher_ =
     35          this.consumer_.watch({ readable: true, peerClosed: true }, () => {
     36            this.watcher_.cancel();
     37            this.watcher_ = undefined;
     38            resolve();
     39          });
     40    });
     41  }
     42 }
     43 
     44 // Implementation of an UnderlyingSink to create a WritableStream from a Mojo
     45 // data pipe producer handle.
     46 class DataPipeSink {
     47  constructor(producer) {
     48    this._producer = producer;
     49  }
     50 
     51  async write(chunk, controller) {
     52    while (true) {
     53      let {result, numBytes} = this._producer.writeData(chunk);
     54      if (result == Mojo.RESULT_OK) {
     55        if (numBytes == chunk.byteLength) {
     56          return;
     57        }
     58        chunk = chunk.slice(numBytes);
     59      } else if (result == Mojo.RESULT_FAILED_PRECONDITION) {
     60        throw new DOMException('The pipe is closed.', 'InvalidStateError');
     61      } else if (result == Mojo.RESULT_SHOULD_WAIT) {
     62        await this.writable();
     63      }
     64    }
     65  }
     66 
     67  close() {
     68    assert_equals(undefined, this._watcher);
     69    this._producer.close();
     70  }
     71 
     72  abort(reason) {
     73    if (this._watcher)
     74      this._watcher.cancel();
     75    this._producer.close();
     76  }
     77 
     78  writable() {
     79    return new Promise((resolve) => {
     80      this._watcher =
     81          this._producer.watch({ writable: true, peerClosed: true }, () => {
     82            this._watcher.cancel();
     83            this._watcher = undefined;
     84            resolve();
     85          });
     86    });
     87  }
     88 }
     89 
     90 // Implementation of device.mojom.SerialPort.
     91 class FakeSerialPort {
     92  constructor() {
     93    this.inputSignals_ = {
     94      dataCarrierDetect: false,
     95      clearToSend: false,
     96      ringIndicator: false,
     97      dataSetReady: false
     98    };
     99    this.inputSignalFailure_ = false;
    100    this.outputSignals_ = {
    101      dataTerminalReady: false,
    102      requestToSend: false,
    103      break: false
    104    };
    105    this.outputSignalFailure_ = false;
    106  }
    107 
    108  open(options, client) {
    109    if (this.receiver_ !== undefined) {
    110      // Port already open.
    111      return null;
    112    }
    113 
    114    let port = new SerialPortRemote();
    115    this.receiver_ = new SerialPortReceiver(this);
    116    this.receiver_.$.bindHandle(port.$.bindNewPipeAndPassReceiver().handle);
    117 
    118    this.options_ = options;
    119    this.client_ = client;
    120    // OS typically sets DTR on open.
    121    this.outputSignals_.dataTerminalReady = true;
    122 
    123    return port;
    124  }
    125 
    126  write(data) {
    127    return this.writer_.write(data);
    128  }
    129 
    130  read() {
    131    return this.reader_.read();
    132  }
    133 
    134  // Reads from the port until at least |targetLength| is read or the stream is
    135  // closed. The data is returned as a combined Uint8Array.
    136  readWithLength(targetLength) {
    137    return readWithLength(this.reader_, targetLength);
    138  }
    139 
    140  simulateReadError(error) {
    141    this.writer_.close();
    142    this.writer_.releaseLock();
    143    this.writer_ = undefined;
    144    this.writable_ = undefined;
    145    this.client_.onReadError(error);
    146  }
    147 
    148  simulateParityError() {
    149    this.simulateReadError(SerialReceiveError.PARITY_ERROR);
    150  }
    151 
    152  simulateDisconnectOnRead() {
    153    this.simulateReadError(SerialReceiveError.DISCONNECTED);
    154  }
    155 
    156  simulateWriteError(error) {
    157    this.reader_.cancel();
    158    this.reader_ = undefined;
    159    this.readable_ = undefined;
    160    this.client_.onSendError(error);
    161  }
    162 
    163  simulateSystemErrorOnWrite() {
    164    this.simulateWriteError(SerialSendError.SYSTEM_ERROR);
    165  }
    166 
    167  simulateDisconnectOnWrite() {
    168    this.simulateWriteError(SerialSendError.DISCONNECTED);
    169  }
    170 
    171  simulateInputSignals(signals) {
    172    this.inputSignals_ = signals;
    173  }
    174 
    175  simulateInputSignalFailure(fail) {
    176    this.inputSignalFailure_ = fail;
    177  }
    178 
    179  get outputSignals() {
    180    return this.outputSignals_;
    181  }
    182 
    183  simulateOutputSignalFailure(fail) {
    184    this.outputSignalFailure_ = fail;
    185  }
    186 
    187  writable() {
    188    if (this.writable_)
    189      return Promise.resolve();
    190 
    191    if (!this.writablePromise_) {
    192      this.writablePromise_ = new Promise((resolve) => {
    193        this.writableResolver_ = resolve;
    194      });
    195    }
    196 
    197    return this.writablePromise_;
    198  }
    199 
    200  readable() {
    201    if (this.readable_)
    202      return Promise.resolve();
    203 
    204    if (!this.readablePromise_) {
    205      this.readablePromise_ = new Promise((resolve) => {
    206        this.readableResolver_ = resolve;
    207      });
    208    }
    209 
    210    return this.readablePromise_;
    211  }
    212 
    213  async startWriting(in_stream) {
    214    this.readable_ = new ReadableStream(new DataPipeSource(in_stream));
    215    this.reader_ = this.readable_.getReader();
    216    if (this.readableResolver_) {
    217      this.readableResolver_();
    218      this.readableResolver_ = undefined;
    219      this.readablePromise_ = undefined;
    220    }
    221  }
    222 
    223  async startReading(out_stream) {
    224    this.writable_ = new WritableStream(new DataPipeSink(out_stream));
    225    this.writer_ = this.writable_.getWriter();
    226    if (this.writableResolver_) {
    227      this.writableResolver_();
    228      this.writableResolver_ = undefined;
    229      this.writablePromise_ = undefined;
    230    }
    231  }
    232 
    233  async flush(mode) {
    234    switch (mode) {
    235      case SerialPortFlushMode.kReceive:
    236        this.writer_.abort();
    237        this.writer_.releaseLock();
    238        this.writer_ = undefined;
    239        this.writable_ = undefined;
    240        break;
    241      case SerialPortFlushMode.kTransmit:
    242        if (this.reader_) {
    243          this.reader_.cancel();
    244          this.reader_ = undefined;
    245        }
    246        this.readable_ = undefined;
    247        break;
    248    }
    249  }
    250 
    251  async drain() {
    252    await this.reader_.closed;
    253  }
    254 
    255  async getControlSignals() {
    256    if (this.inputSignalFailure_) {
    257      return {signals: null};
    258    }
    259 
    260    const signals = {
    261      dcd: this.inputSignals_.dataCarrierDetect,
    262      cts: this.inputSignals_.clearToSend,
    263      ri: this.inputSignals_.ringIndicator,
    264      dsr: this.inputSignals_.dataSetReady
    265    };
    266    return {signals};
    267  }
    268 
    269  async setControlSignals(signals) {
    270    if (this.outputSignalFailure_) {
    271      return {success: false};
    272    }
    273 
    274    if (signals.hasDtr) {
    275      this.outputSignals_.dataTerminalReady = signals.dtr;
    276    }
    277    if (signals.hasRts) {
    278      this.outputSignals_.requestToSend = signals.rts;
    279    }
    280    if (signals.hasBrk) {
    281      this.outputSignals_.break = signals.brk;
    282    }
    283    return { success: true };
    284  }
    285 
    286  async configurePort(options) {
    287    this.options_ = options;
    288    return { success: true };
    289  }
    290 
    291  async getPortInfo() {
    292    return {
    293      bitrate: this.options_.bitrate,
    294      dataBits: this.options_.datBits,
    295      parityBit: this.options_.parityBit,
    296      stopBits: this.options_.stopBits,
    297      ctsFlowControl:
    298          this.options_.hasCtsFlowControl && this.options_.ctsFlowControl,
    299    };
    300  }
    301 
    302  async close() {
    303    // OS typically clears DTR on close.
    304    this.outputSignals_.dataTerminalReady = false;
    305    if (this.writer_) {
    306      this.writer_.close();
    307      this.writer_.releaseLock();
    308      this.writer_ = undefined;
    309    }
    310    this.writable_ = undefined;
    311 
    312    // Close the receiver asynchronously so the reply to this message can be
    313    // sent first.
    314    const receiver = this.receiver_;
    315    this.receiver_ = undefined;
    316    setTimeout(() => {
    317      receiver.$.close();
    318    }, 0);
    319 
    320    return {};
    321  }
    322 }
    323 
    324 // Implementation of blink.mojom.SerialService.
    325 class FakeSerialService {
    326  constructor() {
    327    this.interceptor_ =
    328        new MojoInterfaceInterceptor(SerialService.$interfaceName);
    329    this.interceptor_.oninterfacerequest = e => this.bind(e.handle);
    330    this.receiver_ = new SerialServiceReceiver(this);
    331    this.clients_ = [];
    332    this.nextToken_ = 0;
    333    this.reset();
    334  }
    335 
    336  start() {
    337    this.interceptor_.start();
    338  }
    339 
    340  stop() {
    341    this.interceptor_.stop();
    342  }
    343 
    344  reset() {
    345    this.ports_ = new Map();
    346    this.selectedPort_ = null;
    347  }
    348 
    349  addPort(info) {
    350    let portInfo = {};
    351    if (info?.usbVendorId !== undefined) {
    352      portInfo.hasUsbVendorId = true;
    353      portInfo.usbVendorId = info.usbVendorId;
    354    }
    355    if (info?.usbProductId !== undefined) {
    356      portInfo.hasUsbProductId = true;
    357      portInfo.usbProductId = info.usbProductId;
    358    }
    359    portInfo.connected = true;
    360    if (info?.connected !== undefined) {
    361      portInfo.connected = info.connected;
    362    }
    363 
    364    let token = ++this.nextToken_;
    365    portInfo.token = {high: 0n, low: BigInt(token)};
    366 
    367    let record = {
    368      portInfo: portInfo,
    369      fakePort: new FakeSerialPort(),
    370    };
    371    this.ports_.set(token, record);
    372 
    373    if (portInfo.connected) {
    374      for (let client of this.clients_) {
    375        client.onPortConnectedStateChanged(portInfo);
    376      }
    377    }
    378 
    379    return token;
    380  }
    381 
    382  removePort(token) {
    383    let record = this.ports_.get(token);
    384    if (record === undefined) {
    385      return;
    386    }
    387 
    388    this.ports_.delete(token);
    389 
    390    record.portInfo.connected = false;
    391    for (let client of this.clients_) {
    392      client.onPortConnectedStateChanged(record.portInfo);
    393    }
    394  }
    395 
    396  setPortConnectedState(token, connected) {
    397    let record = this.ports_.get(token);
    398    if (record === undefined) {
    399      return;
    400    }
    401 
    402    let was_connected = record.portInfo.connected;
    403    if (was_connected === connected) {
    404      return;
    405    }
    406 
    407    record.portInfo.connected = connected;
    408    for (let client of this.clients_) {
    409      client.onPortConnectedStateChanged(record.portInfo);
    410    }
    411  }
    412 
    413  setSelectedPort(token) {
    414    this.selectedPort_ = this.ports_.get(token);
    415  }
    416 
    417  getFakePort(token) {
    418    let record = this.ports_.get(token);
    419    if (record === undefined)
    420      return undefined;
    421    return record.fakePort;
    422  }
    423 
    424  bind(handle) {
    425    this.receiver_.$.bindHandle(handle);
    426  }
    427 
    428  async setClient(client_remote) {
    429    this.clients_.push(client_remote);
    430  }
    431 
    432  async getPorts() {
    433    return {
    434      ports: Array.from(this.ports_, ([token, record]) => record.portInfo)
    435    };
    436  }
    437 
    438  async requestPort(filters) {
    439    if (this.selectedPort_)
    440      return { port: this.selectedPort_.portInfo };
    441    else
    442      return { port: null };
    443  }
    444 
    445  async openPort(token, options, client) {
    446    let record = this.ports_.get(Number(token.low));
    447    if (record !== undefined) {
    448      return {port: record.fakePort.open(options, client)};
    449    } else {
    450      return {port: null};
    451    }
    452  }
    453 
    454  async forgetPort(token) {
    455    let record = this.ports_.get(Number(token.low));
    456    if (record === undefined) {
    457      return {success: false};
    458    }
    459 
    460    this.ports_.delete(Number(token.low));
    461    if (record.fakePort.receiver_) {
    462      record.fakePort.receiver_.$.close();
    463      record.fakePort.receiver_ = undefined;
    464    }
    465    return {success: true};
    466  }
    467 }
    468 
    469 export const fakeSerialService = new FakeSerialService();