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();