1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 define('serial_service', [
6 'content/public/renderer/service_provider',
9 'device/serial/serial.mojom',
10 'device/serial/serial_serialization.mojom',
11 'mojo/public/js/core',
12 'mojo/public/js/router',
14 ], function(serviceProvider,
23 * A Javascript client for the serial service and connection Mojo services.
25 * This provides a thick client around the Mojo services, exposing a JS-style
26 * interface to serial connections and information about serial devices. This
27 * converts parameters and result between the Apps serial API types and the
31 var service = new serialMojom.SerialService.proxyClass(
32 new routerModule.Router(
33 serviceProvider.connectToService(serialMojom.SerialService.name)));
35 function getDevices() {
36 return service.getDevices().then(function(response) {
37 return $Array.map(response.devices, function(device) {
38 var result = {path: device.path};
39 if (device.has_vendor_id)
40 result.vendorId = device.vendor_id;
41 if (device.has_product_id)
42 result.productId = device.product_id;
43 if (device.display_name)
44 result.displayName = device.display_name;
50 var DATA_BITS_TO_MOJO = {
51 undefined: serialMojom.DataBits.NONE,
52 'seven': serialMojom.DataBits.SEVEN,
53 'eight': serialMojom.DataBits.EIGHT,
55 var STOP_BITS_TO_MOJO = {
56 undefined: serialMojom.StopBits.NONE,
57 'one': serialMojom.StopBits.ONE,
58 'two': serialMojom.StopBits.TWO,
60 var PARITY_BIT_TO_MOJO = {
61 undefined: serialMojom.ParityBit.NONE,
62 'no': serialMojom.ParityBit.NO,
63 'odd': serialMojom.ParityBit.ODD,
64 'even': serialMojom.ParityBit.EVEN,
66 var SEND_ERROR_TO_MOJO = {
67 undefined: serialMojom.SendError.NONE,
68 'disconnected': serialMojom.SendError.DISCONNECTED,
69 'pending': serialMojom.SendError.PENDING,
70 'timeout': serialMojom.SendError.TIMEOUT,
71 'system_error': serialMojom.SendError.SYSTEM_ERROR,
73 var RECEIVE_ERROR_TO_MOJO = {
74 undefined: serialMojom.ReceiveError.NONE,
75 'disconnected': serialMojom.ReceiveError.DISCONNECTED,
76 'device_lost': serialMojom.ReceiveError.DEVICE_LOST,
77 'timeout': serialMojom.ReceiveError.TIMEOUT,
78 'break': serialMojom.ReceiveError.BREAK,
79 'frame_error': serialMojom.ReceiveError.FRAME_ERROR,
80 'overrun': serialMojom.ReceiveError.OVERRUN,
81 'buffer_overflow': serialMojom.ReceiveError.BUFFER_OVERFLOW,
82 'parity_error': serialMojom.ReceiveError.PARITY_ERROR,
83 'system_error': serialMojom.ReceiveError.SYSTEM_ERROR,
86 function invertMap(input) {
88 for (var key in input) {
89 if (key == 'undefined')
90 output[input[key]] = undefined;
92 output[input[key]] = key;
96 var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO);
97 var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO);
98 var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO);
99 var SEND_ERROR_FROM_MOJO = invertMap(SEND_ERROR_TO_MOJO);
100 var RECEIVE_ERROR_FROM_MOJO = invertMap(RECEIVE_ERROR_TO_MOJO);
102 function getServiceOptions(options) {
104 if (options.dataBits)
105 out.data_bits = DATA_BITS_TO_MOJO[options.dataBits];
106 if (options.stopBits)
107 out.stop_bits = STOP_BITS_TO_MOJO[options.stopBits];
108 if (options.parityBit)
109 out.parity_bit = PARITY_BIT_TO_MOJO[options.parityBit];
110 if ('ctsFlowControl' in options) {
111 out.has_cts_flow_control = true;
112 out.cts_flow_control = options.ctsFlowControl;
114 if ('bitrate' in options)
115 out.bitrate = options.bitrate;
119 function convertServiceInfo(result) {
121 throw new Error('Failed to get ConnectionInfo.');
123 ctsFlowControl: !!result.info.cts_flow_control,
124 bitrate: result.info.bitrate || undefined,
125 dataBits: DATA_BITS_FROM_MOJO[result.info.data_bits],
126 stopBits: STOP_BITS_FROM_MOJO[result.info.stop_bits],
127 parityBit: PARITY_BIT_FROM_MOJO[result.info.parity_bit],
131 // Update client-side options |clientOptions| from the user-provided
133 function updateClientOptions(clientOptions, options) {
134 if ('name' in options)
135 clientOptions.name = options.name;
136 if ('receiveTimeout' in options)
137 clientOptions.receiveTimeout = options.receiveTimeout;
138 if ('sendTimeout' in options)
139 clientOptions.sendTimeout = options.sendTimeout;
140 if ('bufferSize' in options)
141 clientOptions.bufferSize = options.bufferSize;
142 if ('persistent' in options)
143 clientOptions.persistent = options.persistent;
146 function Connection(connection, router, receivePipe, receiveClientPipe,
147 sendPipe, id, options) {
148 var state = new serialization.ConnectionState();
149 state.connectionId = id;
150 updateClientOptions(state, options);
151 var receiver = new dataReceiver.DataReceiver(
152 receivePipe, receiveClientPipe, state.bufferSize,
153 serialMojom.ReceiveError.DISCONNECTED);
154 var sender = new dataSender.DataSender(sendPipe, state.bufferSize,
155 serialMojom.SendError.DISCONNECTED);
162 serialMojom.ReceiveError.NONE);
163 connections_.set(id, this);
164 this.startReceive_();
167 // Initializes this Connection from the provided args.
168 Connection.prototype.init_ = function(state,
174 queuedReceiveError) {
177 // queuedReceiveData_ or queuedReceiveError_ will store the receive result
178 // or error, respectively, if a receive completes or fails while this
179 // connection is paused. At most one of the the two may be non-null: a
180 // receive completed while paused will only set one of them, no further
181 // receives will be performed while paused and a queued result is dispatched
182 // before any further receives are initiated when unpausing.
183 if (queuedReceiveError != serialMojom.ReceiveError.NONE)
184 this.queuedReceiveError_ = {error: queuedReceiveError};
185 if (queuedReceiveData) {
186 this.queuedReceiveData_ = new ArrayBuffer(queuedReceiveData.length);
187 new Int8Array(this.queuedReceiveData_).set(queuedReceiveData);
189 this.router_ = router;
190 this.remoteConnection_ = connection;
191 this.receivePipe_ = receiver;
192 this.sendPipe_ = sender;
193 this.sendInProgress_ = false;
196 Connection.create = function(path, options) {
197 options = options || {};
198 var serviceOptions = getServiceOptions(options);
199 var pipe = core.createMessagePipe();
200 var sendPipe = core.createMessagePipe();
201 var receivePipe = core.createMessagePipe();
202 var receivePipeClient = core.createMessagePipe();
203 service.connect(path,
208 receivePipeClient.handle0);
209 var router = new routerModule.Router(pipe.handle1);
210 var connection = new serialMojom.Connection.proxyClass(router);
211 return connection.getInfo().then(convertServiceInfo).then(function(info) {
212 return Promise.all([info, allocateConnectionId()]);
213 }).catch(function(e) {
215 core.close(sendPipe.handle1);
216 core.close(receivePipe.handle1);
217 core.close(receivePipeClient.handle1);
219 }).then(function(results) {
220 var info = results[0];
222 var serialConnectionClient = new Connection(connection,
225 receivePipeClient.handle1,
229 var clientInfo = serialConnectionClient.getClientInfo_();
230 for (var key in clientInfo) {
231 info[key] = clientInfo[key];
234 connection: serialConnectionClient,
240 Connection.prototype.close = function() {
241 this.router_.close();
242 this.receivePipe_.close();
243 this.sendPipe_.close();
244 clearTimeout(this.receiveTimeoutId_);
245 clearTimeout(this.sendTimeoutId_);
246 connections_.delete(this.state_.connectionId);
250 Connection.prototype.getClientInfo_ = function() {
252 connectionId: this.state_.connectionId,
253 paused: this.state_.paused,
254 persistent: this.state_.persistent,
255 name: this.state_.name,
256 receiveTimeout: this.state_.receiveTimeout,
257 sendTimeout: this.state_.sendTimeout,
258 bufferSize: this.state_.bufferSize,
262 Connection.prototype.getInfo = function() {
263 var info = this.getClientInfo_();
264 return this.remoteConnection_.getInfo().then(convertServiceInfo).then(
266 for (var key in result) {
267 info[key] = result[key];
270 }).catch(function() {
275 Connection.prototype.setOptions = function(options) {
276 updateClientOptions(this.state_, options);
277 var serviceOptions = getServiceOptions(options);
278 if ($Object.keys(serviceOptions).length == 0)
280 return this.remoteConnection_.setOptions(serviceOptions).then(
282 return !!result.success;
283 }).catch(function() {
288 Connection.prototype.getControlSignals = function() {
289 return this.remoteConnection_.getControlSignals().then(function(result) {
291 throw new Error('Failed to get control signals.');
292 var signals = result.signals;
302 Connection.prototype.setControlSignals = function(signals) {
303 var controlSignals = {};
304 if ('dtr' in signals) {
305 controlSignals.has_dtr = true;
306 controlSignals.dtr = signals.dtr;
308 if ('rts' in signals) {
309 controlSignals.has_rts = true;
310 controlSignals.rts = signals.rts;
312 return this.remoteConnection_.setControlSignals(controlSignals).then(
314 return !!result.success;
318 Connection.prototype.flush = function() {
319 return this.remoteConnection_.flush().then(function(result) {
320 return !!result.success;
324 Connection.prototype.setPaused = function(paused) {
325 this.state_.paused = paused;
327 clearTimeout(this.receiveTimeoutId_);
328 this.receiveTimeoutId_ = null;
329 } else if (!this.receiveInProgress_) {
330 this.startReceive_();
334 Connection.prototype.send = function(data) {
335 if (this.sendInProgress_)
336 return Promise.resolve({bytesSent: 0, error: 'pending'});
338 if (this.state_.sendTimeout) {
339 this.sendTimeoutId_ = setTimeout(function() {
340 this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT);
341 }.bind(this), this.state_.sendTimeout);
343 this.sendInProgress_ = true;
344 return this.sendPipe_.send(data).then(function(bytesSent) {
345 return {bytesSent: bytesSent};
346 }).catch(function(e) {
348 bytesSent: e.bytesSent,
349 error: SEND_ERROR_FROM_MOJO[e.error],
351 }).then(function(result) {
352 if (this.sendTimeoutId_)
353 clearTimeout(this.sendTimeoutId_);
354 this.sendTimeoutId_ = null;
355 this.sendInProgress_ = false;
360 Connection.prototype.startReceive_ = function() {
361 this.receiveInProgress_ = true;
362 var receivePromise = null;
363 // If we have a queued receive result, dispatch it immediately instead of
364 // starting a new receive.
365 if (this.queuedReceiveData_) {
366 receivePromise = Promise.resolve(this.queuedReceiveData_);
367 this.queuedReceiveData_ = null;
368 } else if (this.queuedReceiveError_) {
369 receivePromise = Promise.reject(this.queuedReceiveError_);
370 this.queuedReceiveError_ = null;
372 receivePromise = this.receivePipe_.receive();
374 receivePromise.then(this.onDataReceived_.bind(this)).catch(
375 this.onReceiveError_.bind(this));
376 this.startReceiveTimeoutTimer_();
379 Connection.prototype.onDataReceived_ = function(data) {
380 this.startReceiveTimeoutTimer_();
381 this.receiveInProgress_ = false;
382 if (this.state_.paused) {
383 this.queuedReceiveData_ = data;
389 if (!this.state_.paused) {
390 this.startReceive_();
394 Connection.prototype.onReceiveError_ = function(e) {
395 clearTimeout(this.receiveTimeoutId_);
396 this.receiveInProgress_ = false;
397 if (this.state_.paused) {
398 this.queuedReceiveError_ = e;
402 this.state_.paused = true;
404 this.onError(RECEIVE_ERROR_FROM_MOJO[error]);
407 Connection.prototype.startReceiveTimeoutTimer_ = function() {
408 clearTimeout(this.receiveTimeoutId_);
409 if (this.state_.receiveTimeout && !this.state_.paused) {
410 this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this),
411 this.state_.receiveTimeout);
415 Connection.prototype.onReceiveTimeout_ = function() {
417 this.onError('timeout');
418 this.startReceiveTimeoutTimer_();
421 Connection.prototype.serialize = function() {
422 connections_.delete(this.state_.connectionId);
425 var handle = this.router_.connector_.handle_;
426 this.router_.connector_.handle_ = null;
427 this.router_.close();
428 clearTimeout(this.receiveTimeoutId_);
429 clearTimeout(this.sendTimeoutId_);
431 // Serializing receivePipe_ will cancel an in-progress receive, which would
432 // pause the connection, so save it ahead of time.
433 var paused = this.state_.paused;
435 this.receivePipe_.serialize(),
436 this.sendPipe_.serialize(),
437 ]).then(function(serializedComponents) {
438 var queuedReceiveError = serialMojom.ReceiveError.NONE;
439 if (this.queuedReceiveError_)
440 queuedReceiveError = this.queuedReceiveError_.error;
441 this.state_.paused = paused;
442 var serialized = new serialization.SerializedConnection();
443 serialized.state = this.state_;
444 serialized.queuedReceiveError = queuedReceiveError;
445 serialized.queuedReceiveData =
446 this.queuedReceiveData_ ? new Int8Array(this.queuedReceiveData_) :
448 serialized.connection = handle;
449 serialized.receiver = serializedComponents[0];
450 serialized.sender = serializedComponents[1];
455 Connection.deserialize = function(serialized) {
456 var serialConnection = $Object.create(Connection.prototype);
457 var router = new routerModule.Router(serialized.connection);
458 var connection = new serialMojom.Connection.proxyClass(router);
459 var receiver = dataReceiver.DataReceiver.deserialize(serialized.receiver);
460 var sender = dataSender.DataSender.deserialize(serialized.sender);
462 // Ensure that paused and persistent are booleans.
463 serialized.state.paused = !!serialized.state.paused;
464 serialized.state.persistent = !!serialized.state.persistent;
465 serialConnection.init_(serialized.state,
470 serialized.queuedReceiveData,
471 serialized.queuedReceiveError);
472 serialConnection.awaitingResume_ = true;
473 var connectionId = serialized.state.connectionId;
474 connections_.set(connectionId, serialConnection);
475 if (connectionId >= nextConnectionId_)
476 nextConnectionId_ = connectionId + 1;
477 return serialConnection;
480 // Resume receives on a deserialized connection.
481 Connection.prototype.resumeReceives = function() {
482 if (!this.awaitingResume_)
484 this.awaitingResume_ = false;
485 if (!this.state_.paused)
486 this.startReceive_();
489 // All accesses to connections_ and nextConnectionId_ other than those
490 // involved in deserialization should ensure that
491 // connectionDeserializationComplete_ has resolved first.
492 var connectionDeserializationComplete_ = stashClient.retrieve(
493 'serial', serialization.SerializedConnection).then(function(decoded) {
496 return Promise.all($Array.map(decoded, Connection.deserialize));
499 // The map of connection ID to connection object.
500 var connections_ = new Map();
502 // The next connection ID to be allocated.
503 var nextConnectionId_ = 0;
505 function getConnections() {
506 return connectionDeserializationComplete_.then(function() {
507 return new Map(connections_);
511 function getConnection(id) {
512 return getConnections().then(function(connections) {
513 if (!connections.has(id))
514 throw new Error('Serial connection not found.');
515 return connections.get(id);
519 function allocateConnectionId() {
520 return connectionDeserializationComplete_.then(function() {
521 return nextConnectionId_++;
525 stashClient.registerClient(
526 'serial', serialization.SerializedConnection, function() {
527 return connectionDeserializationComplete_.then(function() {
528 var clientPromises = [];
529 for (var connection of connections_.values()) {
530 if (connection.state_.persistent)
531 clientPromises.push(connection.serialize());
535 return Promise.all($Array.map(clientPromises, function(promise) {
536 return promise.then(function(serialization) {
538 serialization: serialization,
539 monitorHandles: !serialization.paused,
547 getDevices: getDevices,
548 createConnection: Connection.create,
549 getConnection: getConnection,
550 getConnections: getConnections,
552 Connection: Connection,