Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / extensions / renderer / resources / serial_service.js
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.
4
5 define('serial_service', [
6     'content/public/renderer/service_provider',
7     'data_receiver',
8     'data_sender',
9     'device/serial/serial.mojom',
10     'device/serial/serial_serialization.mojom',
11     'mojo/public/js/core',
12     'mojo/public/js/router',
13 ], function(serviceProvider,
14             dataReceiver,
15             dataSender,
16             serialMojom,
17             serialization,
18             core,
19             routerModule) {
20   /**
21    * A Javascript client for the serial service and connection Mojo services.
22    *
23    * This provides a thick client around the Mojo services, exposing a JS-style
24    * interface to serial connections and information about serial devices. This
25    * converts parameters and result between the Apps serial API types and the
26    * Mojo types.
27    */
28
29   var service = new serialMojom.SerialService.proxyClass(
30       new routerModule.Router(
31           serviceProvider.connectToService(serialMojom.SerialService.name)));
32
33   function getDevices() {
34     return service.getDevices().then(function(response) {
35       return $Array.map(response.devices, function(device) {
36         var result = {path: device.path};
37         if (device.has_vendor_id)
38           result.vendorId = device.vendor_id;
39         if (device.has_product_id)
40           result.productId = device.product_id;
41         if (device.display_name)
42           result.displayName = device.display_name;
43         return result;
44       });
45     });
46   }
47
48   var DATA_BITS_TO_MOJO = {
49     undefined: serialMojom.DataBits.NONE,
50     'seven': serialMojom.DataBits.SEVEN,
51     'eight': serialMojom.DataBits.EIGHT,
52   };
53   var STOP_BITS_TO_MOJO = {
54     undefined: serialMojom.StopBits.NONE,
55     'one': serialMojom.StopBits.ONE,
56     'two': serialMojom.StopBits.TWO,
57   };
58   var PARITY_BIT_TO_MOJO = {
59     undefined: serialMojom.ParityBit.NONE,
60     'no': serialMojom.ParityBit.NO,
61     'odd': serialMojom.ParityBit.ODD,
62     'even': serialMojom.ParityBit.EVEN,
63   };
64   var SEND_ERROR_TO_MOJO = {
65     undefined: serialMojom.SendError.NONE,
66     'disconnected': serialMojom.SendError.DISCONNECTED,
67     'pending': serialMojom.SendError.PENDING,
68     'timeout': serialMojom.SendError.TIMEOUT,
69     'system_error': serialMojom.SendError.SYSTEM_ERROR,
70   };
71   var RECEIVE_ERROR_TO_MOJO = {
72     undefined: serialMojom.ReceiveError.NONE,
73     'disconnected': serialMojom.ReceiveError.DISCONNECTED,
74     'device_lost': serialMojom.ReceiveError.DEVICE_LOST,
75     'timeout': serialMojom.ReceiveError.TIMEOUT,
76     'system_error': serialMojom.ReceiveError.SYSTEM_ERROR,
77   };
78
79   function invertMap(input) {
80     var output = {};
81     for (var key in input) {
82       if (key == 'undefined')
83         output[input[key]] = undefined;
84       else
85         output[input[key]] = key;
86     }
87     return output;
88   }
89   var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO);
90   var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO);
91   var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO);
92   var SEND_ERROR_FROM_MOJO = invertMap(SEND_ERROR_TO_MOJO);
93   var RECEIVE_ERROR_FROM_MOJO = invertMap(RECEIVE_ERROR_TO_MOJO);
94
95   function getServiceOptions(options) {
96     var out = {};
97     if (options.dataBits)
98       out.data_bits = DATA_BITS_TO_MOJO[options.dataBits];
99     if (options.stopBits)
100       out.stop_bits = STOP_BITS_TO_MOJO[options.stopBits];
101     if (options.parityBit)
102       out.parity_bit = PARITY_BIT_TO_MOJO[options.parityBit];
103     if ('ctsFlowControl' in options) {
104       out.has_cts_flow_control = true;
105       out.cts_flow_control = options.ctsFlowControl;
106     }
107     if ('bitrate' in options)
108       out.bitrate = options.bitrate;
109     return out;
110   }
111
112   function convertServiceInfo(result) {
113     if (!result.info)
114       throw new Error('Failed to get ConnectionInfo.');
115     return {
116       ctsFlowControl: !!result.info.cts_flow_control,
117       bitrate: result.info.bitrate || undefined,
118       dataBits: DATA_BITS_FROM_MOJO[result.info.data_bits],
119       stopBits: STOP_BITS_FROM_MOJO[result.info.stop_bits],
120       parityBit: PARITY_BIT_FROM_MOJO[result.info.parity_bit],
121     };
122   }
123
124   // Update client-side options |clientOptions| from the user-provided
125   // |options|.
126   function updateClientOptions(clientOptions, options) {
127     if ('name' in options)
128       clientOptions.name = options.name;
129     if ('receiveTimeout' in options)
130       clientOptions.receiveTimeout = options.receiveTimeout;
131     if ('sendTimeout' in options)
132       clientOptions.sendTimeout = options.sendTimeout;
133     if ('bufferSize' in options)
134       clientOptions.bufferSize = options.bufferSize;
135   };
136
137   function Connection(connection, router, receivePipe, sendPipe, id, options) {
138     var state = new serialization.ConnectionState();
139     state.connectionId = id;
140     updateClientOptions(state, options);
141     var receiver = new dataReceiver.DataReceiver(
142         receivePipe, state.bufferSize, serialMojom.ReceiveError.DISCONNECTED);
143     var sender = new dataSender.DataSender(
144         sendPipe, state.bufferSize, serialMojom.SendError.DISCONNECTED);
145     this.init_(state,
146                connection,
147                router,
148                receiver,
149                sender,
150                null,
151                serialMojom.ReceiveError.NONE);
152     connections_.set(id, this);
153     this.startReceive_();
154   }
155
156   // Initializes this Connection from the provided args.
157   Connection.prototype.init_ = function(state,
158                                         connection,
159                                         router,
160                                         receiver,
161                                         sender,
162                                         queuedReceiveData,
163                                         queuedReceiveError) {
164     this.state_ = state;
165
166     // queuedReceiveData_ or queuedReceiveError_ will store the receive result
167     // or error, respectively, if a receive completes or fails while this
168     // connection is paused. At most one of the the two may be non-null: a
169     // receive completed while paused will only set one of them, no further
170     // receives will be performed while paused and a queued result is dispatched
171     // before any further receives are initiated when unpausing.
172     if (queuedReceiveError != serialMojom.ReceiveError.NONE)
173       this.queuedReceiveError_ = {error: queuedReceiveError};
174     if (queuedReceiveData) {
175       this.queuedReceiveData_ = new ArrayBuffer(queuedReceiveData.length);
176       new Int8Array(this.queuedReceiveData_).set(queuedReceiveData);
177     }
178     this.router_ = router;
179     this.remoteConnection_ = connection;
180     this.receivePipe_ = receiver;
181     this.sendPipe_ = sender;
182     this.sendInProgress_ = false;
183   };
184
185   Connection.create = function(path, options) {
186     options = options || {};
187     var serviceOptions = getServiceOptions(options);
188     var pipe = core.createMessagePipe();
189     var sendPipe = core.createMessagePipe();
190     var receivePipe = core.createMessagePipe();
191     service.connect(path,
192                     serviceOptions,
193                     pipe.handle0,
194                     sendPipe.handle0,
195                     receivePipe.handle0);
196     var router = new routerModule.Router(pipe.handle1);
197     var connection = new serialMojom.Connection.proxyClass(router);
198     return connection.getInfo().then(convertServiceInfo).then(function(info) {
199       return Promise.all([info, allocateConnectionId()]);
200     }).catch(function(e) {
201       router.close();
202       core.close(sendPipe.handle1);
203       core.close(receivePipe.handle1);
204       throw e;
205     }).then(function(results) {
206       var info = results[0];
207       var id = results[1];
208       var serialConnectionClient = new Connection(connection,
209                                                   router,
210                                                   receivePipe.handle1,
211                                                   sendPipe.handle1,
212                                                   id,
213                                                   options);
214       var clientInfo = serialConnectionClient.getClientInfo_();
215       for (var key in clientInfo) {
216         info[key] = clientInfo[key];
217       }
218       return {
219         connection: serialConnectionClient,
220         info: info,
221       };
222     });
223   };
224
225   Connection.prototype.close = function() {
226     this.router_.close();
227     this.receivePipe_.close();
228     this.sendPipe_.close();
229     clearTimeout(this.receiveTimeoutId_);
230     clearTimeout(this.sendTimeoutId_);
231     connections_.delete(this.state_.connectionId);
232     return true;
233   };
234
235   Connection.prototype.getClientInfo_ = function() {
236     return {
237       connectionId: this.state_.connectionId,
238       paused: this.state_.paused,
239       persistent: this.state_.persistent,
240       name: this.state_.name,
241       receiveTimeout: this.state_.receiveTimeout,
242       sendTimeout: this.state_.sendTimeout,
243       bufferSize: this.state_.bufferSize,
244     };
245   };
246
247   Connection.prototype.getInfo = function() {
248     var info = this.getClientInfo_();
249     return this.remoteConnection_.getInfo().then(convertServiceInfo).then(
250         function(result) {
251       for (var key in result) {
252         info[key] = result[key];
253       }
254       return info;
255     }).catch(function() {
256       return info;
257     });
258   };
259
260   Connection.prototype.setOptions = function(options) {
261     updateClientOptions(this.state_, options);
262     var serviceOptions = getServiceOptions(options);
263     if ($Object.keys(serviceOptions).length == 0)
264       return true;
265     return this.remoteConnection_.setOptions(serviceOptions).then(
266         function(result) {
267       return !!result.success;
268     }).catch(function() {
269       return false;
270     });
271   };
272
273   Connection.prototype.getControlSignals = function() {
274     return this.remoteConnection_.getControlSignals().then(function(result) {
275       if (!result.signals)
276         throw new Error('Failed to get control signals.');
277       var signals = result.signals;
278       return {
279         dcd: !!signals.dcd,
280         cts: !!signals.cts,
281         ri: !!signals.ri,
282         dsr: !!signals.dsr,
283       };
284     });
285   };
286
287   Connection.prototype.setControlSignals = function(signals) {
288     var controlSignals = {};
289     if ('dtr' in signals) {
290       controlSignals.has_dtr = true;
291       controlSignals.dtr = signals.dtr;
292     }
293     if ('rts' in signals) {
294       controlSignals.has_rts = true;
295       controlSignals.rts = signals.rts;
296     }
297     return this.remoteConnection_.setControlSignals(controlSignals).then(
298         function(result) {
299       return !!result.success;
300     });
301   };
302
303   Connection.prototype.flush = function() {
304     return this.remoteConnection_.flush().then(function(result) {
305       return !!result.success;
306     });
307   };
308
309   Connection.prototype.setPaused = function(paused) {
310     this.state_.paused = paused;
311     if (paused) {
312       clearTimeout(this.receiveTimeoutId_);
313       this.receiveTimeoutId_ = null;
314     } else if (!this.receiveInProgress_) {
315       this.startReceive_();
316     }
317   };
318
319   Connection.prototype.send = function(data) {
320     if (this.sendInProgress_)
321       return Promise.resolve({bytesSent: 0, error: 'pending'});
322
323     if (this.state_.sendTimeout) {
324       this.sendTimeoutId_ = setTimeout(function() {
325         this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT);
326       }.bind(this), this.state_.sendTimeout);
327     }
328     this.sendInProgress_ = true;
329     return this.sendPipe_.send(data).then(function(bytesSent) {
330       return {bytesSent: bytesSent};
331     }).catch(function(e) {
332       return {
333         bytesSent: e.bytesSent,
334         error: SEND_ERROR_FROM_MOJO[e.error],
335       };
336     }).then(function(result) {
337       if (this.sendTimeoutId_)
338         clearTimeout(this.sendTimeoutId_);
339       this.sendTimeoutId_ = null;
340       this.sendInProgress_ = false;
341       return result;
342     }.bind(this));
343   };
344
345   Connection.prototype.startReceive_ = function() {
346     this.receiveInProgress_ = true;
347     var receivePromise = null;
348     // If we have a queued receive result, dispatch it immediately instead of
349     // starting a new receive.
350     if (this.queuedReceiveData_) {
351       receivePromise = Promise.resolve(this.queuedReceiveData_);
352       this.queuedReceiveData_ = null;
353     } else if (this.queuedReceiveError_) {
354       receivePromise = Promise.reject(this.queuedReceiveError_);
355       this.queuedReceiveError_ = null;
356     } else {
357       receivePromise = this.receivePipe_.receive();
358     }
359     receivePromise.then(this.onDataReceived_.bind(this)).catch(
360         this.onReceiveError_.bind(this));
361     this.startReceiveTimeoutTimer_();
362   };
363
364   Connection.prototype.onDataReceived_ = function(data) {
365     this.startReceiveTimeoutTimer_();
366     this.receiveInProgress_ = false;
367     if (this.state_.paused) {
368       this.queuedReceiveData_ = data;
369       return;
370     }
371     if (this.onData) {
372       this.onData(data);
373     }
374     if (!this.state_.paused) {
375       this.startReceive_();
376     }
377   };
378
379   Connection.prototype.onReceiveError_ = function(e) {
380     clearTimeout(this.receiveTimeoutId_);
381     this.receiveInProgress_ = false;
382     if (this.state_.paused) {
383       this.queuedReceiveError_ = e;
384       return;
385     }
386     var error = e.error;
387     this.state_.paused = true;
388     if (this.onError)
389       this.onError(RECEIVE_ERROR_FROM_MOJO[error]);
390   };
391
392   Connection.prototype.startReceiveTimeoutTimer_ = function() {
393     clearTimeout(this.receiveTimeoutId_);
394     if (this.state_.receiveTimeout && !this.state_.paused) {
395       this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this),
396                                           this.state_.receiveTimeout);
397     }
398   };
399
400   Connection.prototype.onReceiveTimeout_ = function() {
401     if (this.onError)
402       this.onError('timeout');
403     this.startReceiveTimeoutTimer_();
404   };
405
406   Connection.prototype.serialize = function() {
407     connections_.delete(this.state_.connectionId);
408     this.onData = null;
409     this.onError = null;
410     var handle = this.router_.connector_.handle_;
411     this.router_.connector_.handle_ = null;
412     this.router_.close();
413     clearTimeout(this.receiveTimeoutId_);
414     clearTimeout(this.sendTimeoutId_);
415
416     // Serializing receivePipe_ will cancel an in-progress receive, which would
417     // pause the connection, so save it ahead of time.
418     var paused = this.state_.paused;
419     return Promise.all([
420         this.receivePipe_.serialize(),
421         this.sendPipe_.serialize(),
422     ]).then(function(serializedComponents) {
423       var queuedReceiveError = serialMojom.ReceiveError.NONE;
424       if (this.queuedReceiveError_)
425         queuedReceiveError = this.queuedReceiveError_.error;
426       this.state_.paused = paused;
427       var serialized = new serialization.SerializedConnection();
428       serialized.state = this.state_;
429       serialized.queuedReceiveError = queuedReceiveError;
430       serialized.queuedReceiveData =
431           this.queuedReceiveData_ ? new Int8Array(this.queuedReceiveData_) :
432                                     null;
433       serialized.connection = handle;
434       serialized.receiver = serializedComponents[0];
435       serialized.sender = serializedComponents[1];
436       return serialized;
437     }.bind(this));
438   };
439
440   Connection.deserialize = function(serialized) {
441     var serialConnection = $Object.create(Connection.prototype);
442     var router = new routerModule.Router(serialized.connection);
443     var connection = new serialMojom.ConnectionProxy(router);
444     var receiver = dataReceiver.DataReceiver.deserialize(serialized.receiver);
445     var sender = dataSender.DataSender.deserialize(serialized.sender);
446
447     // Ensure that paused and persistent are booleans.
448     serialized.state.paused = !!serialized.state.paused;
449     serialized.state.persistent = !!serialized.state.persistent;
450     serialConnection.init_(serialized.state,
451                            connection,
452                            router,
453                            receiver,
454                            sender,
455                            serialized.queuedReceiveData,
456                            serialized.queuedReceiveError);
457     serialConnection.awaitingResume_ = true;
458     var connectionId = serialized.state.connectionId;
459     connections_.set(connectionId, serialConnection);
460     if (connectionId >= nextConnectionId_)
461       nextConnectionId_ = connectionId + 1;
462     return serialConnection;
463   };
464
465   // Resume receives on a deserialized connection.
466   Connection.prototype.resumeReceives = function() {
467     if (!this.awaitingResume_)
468       return;
469     this.awaitingResume_ = false;
470     if (!this.state_.paused)
471       this.startReceive_();
472   };
473
474   // All accesses to connections_ and nextConnectionId_ other than those
475   // involved in deserialization should ensure that
476   // connectionDeserializationComplete_ has resolved first.
477   // Note: this will not immediately resolve once serial connection stashing and
478   // restoring is implemented.
479   var connectionDeserializationComplete_ = Promise.resolve();
480
481   // The map of connection ID to connection object.
482   var connections_ = new Map();
483
484   // The next connection ID to be allocated.
485   var nextConnectionId_ = 0;
486
487   function getConnections() {
488     return connectionDeserializationComplete_.then(function() {
489       return new Map(connections_);
490     });
491   }
492
493   function getConnection(id) {
494     return getConnections().then(function(connections) {
495       if (!connections.has(id))
496         throw new Error('Serial connection not found.');
497       return connections.get(id);
498     });
499   }
500
501   function allocateConnectionId() {
502     return connectionDeserializationComplete_.then(function() {
503       return nextConnectionId_++;
504     });
505   }
506
507   return {
508     getDevices: getDevices,
509     createConnection: Connection.create,
510     getConnection: getConnection,
511     getConnections: getConnections,
512     // For testing.
513     Connection: Connection,
514   };
515 });