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
,