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 'system_error': serialMojom
.ReceiveError
.SYSTEM_ERROR
,
81 function invertMap(input
) {
83 for (var key
in input
) {
84 if (key
== 'undefined')
85 output
[input
[key
]] = undefined;
87 output
[input
[key
]] = key
;
91 var DATA_BITS_FROM_MOJO
= invertMap(DATA_BITS_TO_MOJO
);
92 var STOP_BITS_FROM_MOJO
= invertMap(STOP_BITS_TO_MOJO
);
93 var PARITY_BIT_FROM_MOJO
= invertMap(PARITY_BIT_TO_MOJO
);
94 var SEND_ERROR_FROM_MOJO
= invertMap(SEND_ERROR_TO_MOJO
);
95 var RECEIVE_ERROR_FROM_MOJO
= invertMap(RECEIVE_ERROR_TO_MOJO
);
97 function getServiceOptions(options
) {
100 out
.data_bits
= DATA_BITS_TO_MOJO
[options
.dataBits
];
101 if (options
.stopBits
)
102 out
.stop_bits
= STOP_BITS_TO_MOJO
[options
.stopBits
];
103 if (options
.parityBit
)
104 out
.parity_bit
= PARITY_BIT_TO_MOJO
[options
.parityBit
];
105 if ('ctsFlowControl' in options
) {
106 out
.has_cts_flow_control
= true;
107 out
.cts_flow_control
= options
.ctsFlowControl
;
109 if ('bitrate' in options
)
110 out
.bitrate
= options
.bitrate
;
114 function convertServiceInfo(result
) {
116 throw new Error('Failed to get ConnectionInfo.');
118 ctsFlowControl
: !!result
.info
.cts_flow_control
,
119 bitrate
: result
.info
.bitrate
|| undefined,
120 dataBits
: DATA_BITS_FROM_MOJO
[result
.info
.data_bits
],
121 stopBits
: STOP_BITS_FROM_MOJO
[result
.info
.stop_bits
],
122 parityBit
: PARITY_BIT_FROM_MOJO
[result
.info
.parity_bit
],
126 // Update client-side options |clientOptions| from the user-provided
128 function updateClientOptions(clientOptions
, options
) {
129 if ('name' in options
)
130 clientOptions
.name
= options
.name
;
131 if ('receiveTimeout' in options
)
132 clientOptions
.receiveTimeout
= options
.receiveTimeout
;
133 if ('sendTimeout' in options
)
134 clientOptions
.sendTimeout
= options
.sendTimeout
;
135 if ('bufferSize' in options
)
136 clientOptions
.bufferSize
= options
.bufferSize
;
137 if ('persistent' in options
)
138 clientOptions
.persistent
= options
.persistent
;
141 function Connection(connection
, router
, receivePipe
, receiveClientPipe
,
142 sendPipe
, id
, options
) {
143 var state
= new serialization
.ConnectionState();
144 state
.connectionId
= id
;
145 updateClientOptions(state
, options
);
146 var receiver
= new dataReceiver
.DataReceiver(
147 receivePipe
, receiveClientPipe
, state
.bufferSize
,
148 serialMojom
.ReceiveError
.DISCONNECTED
);
149 var sender
= new dataSender
.DataSender(sendPipe
, state
.bufferSize
,
150 serialMojom
.SendError
.DISCONNECTED
);
157 serialMojom
.ReceiveError
.NONE
);
158 connections_
.set(id
, this);
159 this.startReceive_();
162 // Initializes this Connection from the provided args.
163 Connection
.prototype.init_ = function(state
,
169 queuedReceiveError
) {
172 // queuedReceiveData_ or queuedReceiveError_ will store the receive result
173 // or error, respectively, if a receive completes or fails while this
174 // connection is paused. At most one of the the two may be non-null: a
175 // receive completed while paused will only set one of them, no further
176 // receives will be performed while paused and a queued result is dispatched
177 // before any further receives are initiated when unpausing.
178 if (queuedReceiveError
!= serialMojom
.ReceiveError
.NONE
)
179 this.queuedReceiveError_
= {error
: queuedReceiveError
};
180 if (queuedReceiveData
) {
181 this.queuedReceiveData_
= new ArrayBuffer(queuedReceiveData
.length
);
182 new Int8Array(this.queuedReceiveData_
).set(queuedReceiveData
);
184 this.router_
= router
;
185 this.remoteConnection_
= connection
;
186 this.receivePipe_
= receiver
;
187 this.sendPipe_
= sender
;
188 this.sendInProgress_
= false;
191 Connection
.create = function(path
, options
) {
192 options
= options
|| {};
193 var serviceOptions
= getServiceOptions(options
);
194 var pipe
= core
.createMessagePipe();
195 var sendPipe
= core
.createMessagePipe();
196 var receivePipe
= core
.createMessagePipe();
197 var receivePipeClient
= core
.createMessagePipe();
198 service
.connect(path
,
203 receivePipeClient
.handle0
);
204 var router
= new routerModule
.Router(pipe
.handle1
);
205 var connection
= new serialMojom
.Connection
.proxyClass(router
);
206 return connection
.getInfo().then(convertServiceInfo
).then(function(info
) {
207 return Promise
.all([info
, allocateConnectionId()]);
208 }).catch(function(e
) {
210 core
.close(sendPipe
.handle1
);
211 core
.close(receivePipe
.handle1
);
212 core
.close(receivePipeClient
.handle1
);
214 }).then(function(results
) {
215 var info
= results
[0];
217 var serialConnectionClient
= new Connection(connection
,
220 receivePipeClient
.handle1
,
224 var clientInfo
= serialConnectionClient
.getClientInfo_();
225 for (var key
in clientInfo
) {
226 info
[key
] = clientInfo
[key
];
229 connection
: serialConnectionClient
,
235 Connection
.prototype.close = function() {
236 this.router_
.close();
237 this.receivePipe_
.close();
238 this.sendPipe_
.close();
239 clearTimeout(this.receiveTimeoutId_
);
240 clearTimeout(this.sendTimeoutId_
);
241 connections_
.delete(this.state_
.connectionId
);
245 Connection
.prototype.getClientInfo_ = function() {
247 connectionId
: this.state_
.connectionId
,
248 paused
: this.state_
.paused
,
249 persistent
: this.state_
.persistent
,
250 name
: this.state_
.name
,
251 receiveTimeout
: this.state_
.receiveTimeout
,
252 sendTimeout
: this.state_
.sendTimeout
,
253 bufferSize
: this.state_
.bufferSize
,
257 Connection
.prototype.getInfo = function() {
258 var info
= this.getClientInfo_();
259 return this.remoteConnection_
.getInfo().then(convertServiceInfo
).then(
261 for (var key
in result
) {
262 info
[key
] = result
[key
];
265 }).catch(function() {
270 Connection
.prototype.setOptions = function(options
) {
271 updateClientOptions(this.state_
, options
);
272 var serviceOptions
= getServiceOptions(options
);
273 if ($Object
.keys(serviceOptions
).length
== 0)
275 return this.remoteConnection_
.setOptions(serviceOptions
).then(
277 return !!result
.success
;
278 }).catch(function() {
283 Connection
.prototype.getControlSignals = function() {
284 return this.remoteConnection_
.getControlSignals().then(function(result
) {
286 throw new Error('Failed to get control signals.');
287 var signals
= result
.signals
;
297 Connection
.prototype.setControlSignals = function(signals
) {
298 var controlSignals
= {};
299 if ('dtr' in signals
) {
300 controlSignals
.has_dtr
= true;
301 controlSignals
.dtr
= signals
.dtr
;
303 if ('rts' in signals
) {
304 controlSignals
.has_rts
= true;
305 controlSignals
.rts
= signals
.rts
;
307 return this.remoteConnection_
.setControlSignals(controlSignals
).then(
309 return !!result
.success
;
313 Connection
.prototype.flush = function() {
314 return this.remoteConnection_
.flush().then(function(result
) {
315 return !!result
.success
;
319 Connection
.prototype.setPaused = function(paused
) {
320 this.state_
.paused
= paused
;
322 clearTimeout(this.receiveTimeoutId_
);
323 this.receiveTimeoutId_
= null;
324 } else if (!this.receiveInProgress_
) {
325 this.startReceive_();
329 Connection
.prototype.send = function(data
) {
330 if (this.sendInProgress_
)
331 return Promise
.resolve({bytesSent
: 0, error
: 'pending'});
333 if (this.state_
.sendTimeout
) {
334 this.sendTimeoutId_
= setTimeout(function() {
335 this.sendPipe_
.cancel(serialMojom
.SendError
.TIMEOUT
);
336 }.bind(this), this.state_
.sendTimeout
);
338 this.sendInProgress_
= true;
339 return this.sendPipe_
.send(data
).then(function(bytesSent
) {
340 return {bytesSent
: bytesSent
};
341 }).catch(function(e
) {
343 bytesSent
: e
.bytesSent
,
344 error
: SEND_ERROR_FROM_MOJO
[e
.error
],
346 }).then(function(result
) {
347 if (this.sendTimeoutId_
)
348 clearTimeout(this.sendTimeoutId_
);
349 this.sendTimeoutId_
= null;
350 this.sendInProgress_
= false;
355 Connection
.prototype.startReceive_ = function() {
356 this.receiveInProgress_
= true;
357 var receivePromise
= null;
358 // If we have a queued receive result, dispatch it immediately instead of
359 // starting a new receive.
360 if (this.queuedReceiveData_
) {
361 receivePromise
= Promise
.resolve(this.queuedReceiveData_
);
362 this.queuedReceiveData_
= null;
363 } else if (this.queuedReceiveError_
) {
364 receivePromise
= Promise
.reject(this.queuedReceiveError_
);
365 this.queuedReceiveError_
= null;
367 receivePromise
= this.receivePipe_
.receive();
369 receivePromise
.then(this.onDataReceived_
.bind(this)).catch(
370 this.onReceiveError_
.bind(this));
371 this.startReceiveTimeoutTimer_();
374 Connection
.prototype.onDataReceived_ = function(data
) {
375 this.startReceiveTimeoutTimer_();
376 this.receiveInProgress_
= false;
377 if (this.state_
.paused
) {
378 this.queuedReceiveData_
= data
;
384 if (!this.state_
.paused
) {
385 this.startReceive_();
389 Connection
.prototype.onReceiveError_ = function(e
) {
390 clearTimeout(this.receiveTimeoutId_
);
391 this.receiveInProgress_
= false;
392 if (this.state_
.paused
) {
393 this.queuedReceiveError_
= e
;
397 this.state_
.paused
= true;
399 this.onError(RECEIVE_ERROR_FROM_MOJO
[error
]);
402 Connection
.prototype.startReceiveTimeoutTimer_ = function() {
403 clearTimeout(this.receiveTimeoutId_
);
404 if (this.state_
.receiveTimeout
&& !this.state_
.paused
) {
405 this.receiveTimeoutId_
= setTimeout(this.onReceiveTimeout_
.bind(this),
406 this.state_
.receiveTimeout
);
410 Connection
.prototype.onReceiveTimeout_ = function() {
412 this.onError('timeout');
413 this.startReceiveTimeoutTimer_();
416 Connection
.prototype.serialize = function() {
417 connections_
.delete(this.state_
.connectionId
);
420 var handle
= this.router_
.connector_
.handle_
;
421 this.router_
.connector_
.handle_
= null;
422 this.router_
.close();
423 clearTimeout(this.receiveTimeoutId_
);
424 clearTimeout(this.sendTimeoutId_
);
426 // Serializing receivePipe_ will cancel an in-progress receive, which would
427 // pause the connection, so save it ahead of time.
428 var paused
= this.state_
.paused
;
430 this.receivePipe_
.serialize(),
431 this.sendPipe_
.serialize(),
432 ]).then(function(serializedComponents
) {
433 var queuedReceiveError
= serialMojom
.ReceiveError
.NONE
;
434 if (this.queuedReceiveError_
)
435 queuedReceiveError
= this.queuedReceiveError_
.error
;
436 this.state_
.paused
= paused
;
437 var serialized
= new serialization
.SerializedConnection();
438 serialized
.state
= this.state_
;
439 serialized
.queuedReceiveError
= queuedReceiveError
;
440 serialized
.queuedReceiveData
=
441 this.queuedReceiveData_
? new Int8Array(this.queuedReceiveData_
) :
443 serialized
.connection
= handle
;
444 serialized
.receiver
= serializedComponents
[0];
445 serialized
.sender
= serializedComponents
[1];
450 Connection
.deserialize = function(serialized
) {
451 var serialConnection
= $Object
.create(Connection
.prototype);
452 var router
= new routerModule
.Router(serialized
.connection
);
453 var connection
= new serialMojom
.Connection
.proxyClass(router
);
454 var receiver
= dataReceiver
.DataReceiver
.deserialize(serialized
.receiver
);
455 var sender
= dataSender
.DataSender
.deserialize(serialized
.sender
);
457 // Ensure that paused and persistent are booleans.
458 serialized
.state
.paused
= !!serialized
.state
.paused
;
459 serialized
.state
.persistent
= !!serialized
.state
.persistent
;
460 serialConnection
.init_(serialized
.state
,
465 serialized
.queuedReceiveData
,
466 serialized
.queuedReceiveError
);
467 serialConnection
.awaitingResume_
= true;
468 var connectionId
= serialized
.state
.connectionId
;
469 connections_
.set(connectionId
, serialConnection
);
470 if (connectionId
>= nextConnectionId_
)
471 nextConnectionId_
= connectionId
+ 1;
472 return serialConnection
;
475 // Resume receives on a deserialized connection.
476 Connection
.prototype.resumeReceives = function() {
477 if (!this.awaitingResume_
)
479 this.awaitingResume_
= false;
480 if (!this.state_
.paused
)
481 this.startReceive_();
484 // All accesses to connections_ and nextConnectionId_ other than those
485 // involved in deserialization should ensure that
486 // connectionDeserializationComplete_ has resolved first.
487 var connectionDeserializationComplete_
= stashClient
.retrieve(
488 'serial', serialization
.SerializedConnection
).then(function(decoded
) {
491 return Promise
.all($Array
.map(decoded
, Connection
.deserialize
));
494 // The map of connection ID to connection object.
495 var connections_
= new Map();
497 // The next connection ID to be allocated.
498 var nextConnectionId_
= 0;
500 function getConnections() {
501 return connectionDeserializationComplete_
.then(function() {
502 return new Map(connections_
);
506 function getConnection(id
) {
507 return getConnections().then(function(connections
) {
508 if (!connections
.has(id
))
509 throw new Error('Serial connection not found.');
510 return connections
.get(id
);
514 function allocateConnectionId() {
515 return connectionDeserializationComplete_
.then(function() {
516 return nextConnectionId_
++;
520 stashClient
.registerClient(
521 'serial', serialization
.SerializedConnection
, function() {
522 return connectionDeserializationComplete_
.then(function() {
523 var clientPromises
= [];
524 for (var connection
of connections_
.values()) {
525 if (connection
.state_
.persistent
)
526 clientPromises
.push(connection
.serialize());
530 return Promise
.all($Array
.map(clientPromises
, function(promise
) {
531 return promise
.then(function(serialization
) {
533 serialization
: serialization
,
534 monitorHandles
: !serialization
.paused
,
542 getDevices
: getDevices
,
543 createConnection
: Connection
.create
,
544 getConnection
: getConnection
,
545 getConnections
: getConnections
,
547 Connection
: Connection
,