diff options
Diffstat (limited to 'testing/web-platform/tests/resources/chromium/fake-serial.js')
-rw-r--r-- | testing/web-platform/tests/resources/chromium/fake-serial.js | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/testing/web-platform/tests/resources/chromium/fake-serial.js b/testing/web-platform/tests/resources/chromium/fake-serial.js new file mode 100644 index 0000000000..e1e4d57e3e --- /dev/null +++ b/testing/web-platform/tests/resources/chromium/fake-serial.js @@ -0,0 +1,443 @@ +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: + 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; + } + + let token = ++this.nextToken_; + portInfo.token = {high: 0n, low: BigInt(token)}; + + let record = { + portInfo: portInfo, + fakePort: new FakeSerialPort(), + }; + this.ports_.set(token, record); + + for (let client of this.clients_) { + client.onPortAdded(portInfo); + } + + return token; + } + + removePort(token) { + let record = this.ports_.get(token); + if (record === undefined) { + return; + } + + this.ports_.delete(token); + + for (let client of this.clients_) { + client.onPortRemoved(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(); |