469 lines
11 KiB
JavaScript
469 lines
11 KiB
JavaScript
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();
|