import {SerialPortFlushMode, SerialPortRemote, SerialReceiveError, SerialPortReceiver, SerialSendError} from '/gen/services/device/public/mojom/serial.mojom.m.js'; import {SerialService, SerialServiceReceiver} from '/gen/third_party/blink/public/mojom/serial/serial.mojom.m.js'; // Implementation of an UnderlyingSource to create a ReadableStream from a Mojo // data pipe consumer handle. class DataPipeSource { constructor(consumer) { this.consumer_ = consumer; } async pull(controller) { let chunk = new ArrayBuffer(64); let {result, numBytes} = this.consumer_.readData(chunk); if (result == Mojo.RESULT_OK) { controller.enqueue(new Uint8Array(chunk, 0, numBytes)); return; } else if (result == Mojo.RESULT_FAILED_PRECONDITION) { controller.close(); return; } else if (result == Mojo.RESULT_SHOULD_WAIT) { await this.readable(); return this.pull(controller); } } cancel() { if (this.watcher_) this.watcher_.cancel(); this.consumer_.close(); } readable() { return new Promise((resolve) => { this.watcher_ = this.consumer_.watch({ readable: true, peerClosed: true }, () => { this.watcher_.cancel(); this.watcher_ = undefined; resolve(); }); }); } } // Implementation of an UnderlyingSink to create a WritableStream from a Mojo // data pipe producer handle. class DataPipeSink { constructor(producer) { this._producer = producer; } async write(chunk, controller) { while (true) { let {result, numBytes} = this._producer.writeData(chunk); if (result == Mojo.RESULT_OK) { if (numBytes == chunk.byteLength) { return; } chunk = chunk.slice(numBytes); } else if (result == Mojo.RESULT_FAILED_PRECONDITION) { throw new DOMException('The pipe is closed.', 'InvalidStateError'); } else if (result == Mojo.RESULT_SHOULD_WAIT) { await this.writable(); } } } close() { assert_equals(undefined, this._watcher); this._producer.close(); } abort(reason) { if (this._watcher) this._watcher.cancel(); this._producer.close(); } writable() { return new Promise((resolve) => { this._watcher = this._producer.watch({ writable: true, peerClosed: true }, () => { this._watcher.cancel(); this._watcher = undefined; resolve(); }); }); } } // Implementation of device.mojom.SerialPort. class FakeSerialPort { constructor() { this.inputSignals_ = { dataCarrierDetect: false, clearToSend: false, ringIndicator: false, dataSetReady: false }; this.inputSignalFailure_ = false; this.outputSignals_ = { dataTerminalReady: false, requestToSend: false, break: false }; this.outputSignalFailure_ = false; } open(options, client) { if (this.receiver_ !== undefined) { // Port already open. return null; } let port = new SerialPortRemote(); this.receiver_ = new SerialPortReceiver(this); this.receiver_.$.bindHandle(port.$.bindNewPipeAndPassReceiver().handle); this.options_ = options; this.client_ = client; // OS typically sets DTR on open. this.outputSignals_.dataTerminalReady = true; return port; } write(data) { return this.writer_.write(data); } read() { return this.reader_.read(); } // Reads from the port until at least |targetLength| is read or the stream is // closed. The data is returned as a combined Uint8Array. readWithLength(targetLength) { return readWithLength(this.reader_, targetLength); } simulateReadError(error) { this.writer_.close(); this.writer_.releaseLock(); this.writer_ = undefined; this.writable_ = undefined; this.client_.onReadError(error); } simulateParityError() { this.simulateReadError(SerialReceiveError.PARITY_ERROR); } simulateDisconnectOnRead() { this.simulateReadError(SerialReceiveError.DISCONNECTED); } simulateWriteError(error) { this.reader_.cancel(); this.reader_ = undefined; this.readable_ = undefined; this.client_.onSendError(error); } simulateSystemErrorOnWrite() { this.simulateWriteError(SerialSendError.SYSTEM_ERROR); } simulateDisconnectOnWrite() { this.simulateWriteError(SerialSendError.DISCONNECTED); } simulateInputSignals(signals) { this.inputSignals_ = signals; } simulateInputSignalFailure(fail) { this.inputSignalFailure_ = fail; } get outputSignals() { return this.outputSignals_; } simulateOutputSignalFailure(fail) { this.outputSignalFailure_ = fail; } writable() { if (this.writable_) return Promise.resolve(); if (!this.writablePromise_) { this.writablePromise_ = new Promise((resolve) => { this.writableResolver_ = resolve; }); } return this.writablePromise_; } readable() { if (this.readable_) return Promise.resolve(); if (!this.readablePromise_) { this.readablePromise_ = new Promise((resolve) => { this.readableResolver_ = resolve; }); } return this.readablePromise_; } async startWriting(in_stream) { this.readable_ = new ReadableStream(new DataPipeSource(in_stream)); this.reader_ = this.readable_.getReader(); if (this.readableResolver_) { this.readableResolver_(); this.readableResolver_ = undefined; this.readablePromise_ = undefined; } } async startReading(out_stream) { this.writable_ = new WritableStream(new DataPipeSink(out_stream)); this.writer_ = this.writable_.getWriter(); if (this.writableResolver_) { this.writableResolver_(); this.writableResolver_ = undefined; this.writablePromise_ = undefined; } } async flush(mode) { switch (mode) { case SerialPortFlushMode.kReceive: this.writer_.abort(); this.writer_.releaseLock(); this.writer_ = undefined; this.writable_ = undefined; break; case SerialPortFlushMode.kTransmit: if (this.reader_) { this.reader_.cancel(); this.reader_ = undefined; } this.readable_ = undefined; break; } } async drain() { await this.reader_.closed; } async getControlSignals() { if (this.inputSignalFailure_) { return {signals: null}; } const signals = { dcd: this.inputSignals_.dataCarrierDetect, cts: this.inputSignals_.clearToSend, ri: this.inputSignals_.ringIndicator, dsr: this.inputSignals_.dataSetReady }; return {signals}; } async setControlSignals(signals) { if (this.outputSignalFailure_) { return {success: false}; } if (signals.hasDtr) { this.outputSignals_.dataTerminalReady = signals.dtr; } if (signals.hasRts) { this.outputSignals_.requestToSend = signals.rts; } if (signals.hasBrk) { this.outputSignals_.break = signals.brk; } return { success: true }; } async configurePort(options) { this.options_ = options; return { success: true }; } async getPortInfo() { return { bitrate: this.options_.bitrate, dataBits: this.options_.datBits, parityBit: this.options_.parityBit, stopBits: this.options_.stopBits, ctsFlowControl: this.options_.hasCtsFlowControl && this.options_.ctsFlowControl, }; } async close() { // OS typically clears DTR on close. this.outputSignals_.dataTerminalReady = false; if (this.writer_) { this.writer_.close(); this.writer_.releaseLock(); this.writer_ = undefined; } this.writable_ = undefined; // Close the receiver asynchronously so the reply to this message can be // sent first. const receiver = this.receiver_; this.receiver_ = undefined; setTimeout(() => { receiver.$.close(); }, 0); return {}; } } // Implementation of blink.mojom.SerialService. class FakeSerialService { constructor() { this.interceptor_ = new MojoInterfaceInterceptor(SerialService.$interfaceName); this.interceptor_.oninterfacerequest = e => this.bind(e.handle); this.receiver_ = new SerialServiceReceiver(this); this.clients_ = []; this.nextToken_ = 0; this.reset(); } start() { this.interceptor_.start(); } stop() { this.interceptor_.stop(); } reset() { this.ports_ = new Map(); this.selectedPort_ = null; } addPort(info) { let portInfo = {}; if (info?.usbVendorId !== undefined) { portInfo.hasUsbVendorId = true; portInfo.usbVendorId = info.usbVendorId; } if (info?.usbProductId !== undefined) { portInfo.hasUsbProductId = true; portInfo.usbProductId = info.usbProductId; } portInfo.connected = true; if (info?.connected !== undefined) { portInfo.connected = info.connected; } let token = ++this.nextToken_; portInfo.token = {high: 0n, low: BigInt(token)}; let record = { portInfo: portInfo, fakePort: new FakeSerialPort(), }; this.ports_.set(token, record); if (portInfo.connected) { for (let client of this.clients_) { client.onPortConnectedStateChanged(portInfo); } } return token; } removePort(token) { let record = this.ports_.get(token); if (record === undefined) { return; } this.ports_.delete(token); record.portInfo.connected = false; for (let client of this.clients_) { client.onPortConnectedStateChanged(record.portInfo); } } setPortConnectedState(token, connected) { let record = this.ports_.get(token); if (record === undefined) { return; } let was_connected = record.portInfo.connected; if (was_connected === connected) { return; } record.portInfo.connected = connected; for (let client of this.clients_) { client.onPortConnectedStateChanged(record.portInfo); } } setSelectedPort(token) { this.selectedPort_ = this.ports_.get(token); } getFakePort(token) { let record = this.ports_.get(token); if (record === undefined) return undefined; return record.fakePort; } bind(handle) { this.receiver_.$.bindHandle(handle); } async setClient(client_remote) { this.clients_.push(client_remote); } async getPorts() { return { ports: Array.from(this.ports_, ([token, record]) => record.portInfo) }; } async requestPort(filters) { if (this.selectedPort_) return { port: this.selectedPort_.portInfo }; else return { port: null }; } async openPort(token, options, client) { let record = this.ports_.get(Number(token.low)); if (record !== undefined) { return {port: record.fakePort.open(options, client)}; } else { return {port: null}; } } async forgetPort(token) { let record = this.ports_.get(Number(token.low)); if (record === undefined) { return {success: false}; } this.ports_.delete(Number(token.low)); if (record.fakePort.receiver_) { record.fakePort.receiver_.$.close(); record.fakePort.receiver_ = undefined; } return {success: true}; } } export const fakeSerialService = new FakeSerialService();