OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 define('serial_service', [ | 5 define('serial_service', [ |
6 'content/public/renderer/service_provider', | 6 'content/public/renderer/service_provider', |
7 'data_receiver', | |
8 'data_sender', | |
7 'device/serial/serial.mojom', | 9 'device/serial/serial.mojom', |
8 'mojo/public/js/bindings/core', | 10 'mojo/public/js/bindings/core', |
9 'mojo/public/js/bindings/router', | 11 'mojo/public/js/bindings/router', |
10 ], function(serviceProvider, serialMojom, core, routerModule) { | 12 ], function(serviceProvider, |
13 dataReceiver, | |
14 dataSender, | |
15 serialMojom, | |
16 core, | |
17 routerModule) { | |
11 /** | 18 /** |
12 * A Javascript client for the serial service and connection Mojo services. | 19 * A Javascript client for the serial service and connection Mojo services. |
13 * | 20 * |
14 * This provides a thick client around the Mojo services, exposing a JS-style | 21 * This provides a thick client around the Mojo services, exposing a JS-style |
15 * interface to serial connections and information about serial devices. This | 22 * interface to serial connections and information about serial devices. This |
16 * converts parameters and result between the Apps serial API types and the | 23 * converts parameters and result between the Apps serial API types and the |
17 * Mojo types. | 24 * Mojo types. |
18 */ | 25 */ |
19 | 26 |
20 var service = new serialMojom.SerialServiceProxy(new routerModule.Router( | 27 var service = new serialMojom.SerialServiceProxy(new routerModule.Router( |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
52 undefined: serialMojom.StopBits.NONE, | 59 undefined: serialMojom.StopBits.NONE, |
53 'one': serialMojom.StopBits.ONE, | 60 'one': serialMojom.StopBits.ONE, |
54 'two': serialMojom.StopBits.TWO, | 61 'two': serialMojom.StopBits.TWO, |
55 }; | 62 }; |
56 var PARITY_BIT_TO_MOJO = { | 63 var PARITY_BIT_TO_MOJO = { |
57 undefined: serialMojom.ParityBit.NONE, | 64 undefined: serialMojom.ParityBit.NONE, |
58 'no': serialMojom.ParityBit.NO, | 65 'no': serialMojom.ParityBit.NO, |
59 'odd': serialMojom.ParityBit.ODD, | 66 'odd': serialMojom.ParityBit.ODD, |
60 'even': serialMojom.ParityBit.EVEN, | 67 'even': serialMojom.ParityBit.EVEN, |
61 }; | 68 }; |
69 var SEND_ERROR_TO_MOJO = { | |
70 undefined: serialMojom.SendError.NONE, | |
71 'disconnected': serialMojom.SendError.DISCONNECTED, | |
72 'pending': serialMojom.SendError.PENDING, | |
73 'timeout': serialMojom.SendError.TIMEOUT, | |
74 'system_error': serialMojom.SendError.SYSTEM_ERROR, | |
75 }; | |
76 var RECEIVE_ERROR_TO_MOJO = { | |
77 undefined: serialMojom.ReceiveError.NONE, | |
78 'disconnected': serialMojom.ReceiveError.DISCONNECTED, | |
79 'device_lost': serialMojom.ReceiveError.DEVICE_LOST, | |
80 'timeout': serialMojom.ReceiveError.TIMEOUT, | |
81 'system_error': serialMojom.ReceiveError.SYSTEM_ERROR, | |
82 }; | |
62 | 83 |
63 function invertMap(input) { | 84 function invertMap(input) { |
64 var output = {}; | 85 var output = {}; |
65 for (var key in input) { | 86 for (var key in input) { |
66 if (key == 'undefined') | 87 if (key == 'undefined') |
67 output[input[key]] = undefined; | 88 output[input[key]] = undefined; |
68 else | 89 else |
69 output[input[key]] = key; | 90 output[input[key]] = key; |
70 } | 91 } |
71 return output; | 92 return output; |
72 } | 93 } |
73 var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO); | 94 var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO); |
74 var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO); | 95 var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO); |
75 var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO); | 96 var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO); |
97 var SEND_ERROR_FROM_MOJO = invertMap(SEND_ERROR_TO_MOJO); | |
98 var RECEIVE_ERROR_FROM_MOJO = invertMap(RECEIVE_ERROR_TO_MOJO); | |
76 | 99 |
77 function getServiceOptions(options) { | 100 function getServiceOptions(options) { |
78 var out = {}; | 101 var out = {}; |
79 if (options.dataBits) | 102 if (options.dataBits) |
80 out.data_bits = DATA_BITS_TO_MOJO[options.dataBits]; | 103 out.data_bits = DATA_BITS_TO_MOJO[options.dataBits]; |
81 if (options.stopBits) | 104 if (options.stopBits) |
82 out.stop_bits = STOP_BITS_TO_MOJO[options.stopBits]; | 105 out.stop_bits = STOP_BITS_TO_MOJO[options.stopBits]; |
83 if (options.parityBit) | 106 if (options.parityBit) |
84 out.parity_bit = PARITY_BIT_TO_MOJO[options.parityBit]; | 107 out.parity_bit = PARITY_BIT_TO_MOJO[options.parityBit]; |
85 if ('ctsFlowControl' in options) { | 108 if ('ctsFlowControl' in options) { |
(...skipping 10 matching lines...) Expand all Loading... | |
96 throw new Error('Failed to get ConnectionInfo.'); | 119 throw new Error('Failed to get ConnectionInfo.'); |
97 return { | 120 return { |
98 ctsFlowControl: !!result.info.cts_flow_control, | 121 ctsFlowControl: !!result.info.cts_flow_control, |
99 bitrate: result.info.bitrate || undefined, | 122 bitrate: result.info.bitrate || undefined, |
100 dataBits: DATA_BITS_FROM_MOJO[result.info.data_bits], | 123 dataBits: DATA_BITS_FROM_MOJO[result.info.data_bits], |
101 stopBits: STOP_BITS_FROM_MOJO[result.info.stop_bits], | 124 stopBits: STOP_BITS_FROM_MOJO[result.info.stop_bits], |
102 parityBit: PARITY_BIT_FROM_MOJO[result.info.parity_bit], | 125 parityBit: PARITY_BIT_FROM_MOJO[result.info.parity_bit], |
103 }; | 126 }; |
104 } | 127 } |
105 | 128 |
106 function Connection(remoteConnection, router, id, options) { | 129 function Connection( |
130 remoteConnection, router, receivePipe, sendPipe, id, options) { | |
107 this.remoteConnection_ = remoteConnection; | 131 this.remoteConnection_ = remoteConnection; |
108 this.router_ = router; | 132 this.router_ = router; |
133 this.options_ = {}; | |
134 for (var key in DEFAULT_CLIENT_OPTIONS) { | |
135 this.options_[key] = DEFAULT_CLIENT_OPTIONS[key]; | |
136 } | |
137 this.setClientOptions_(options); | |
138 this.receivePipe_ = | |
139 new dataReceiver.DataReceiver(receivePipe, | |
140 this.options_.bufferSize, | |
141 serialMojom.ReceiveError.DISCONNECTED); | |
142 this.sendPipe_ = new dataSender.DataSender( | |
143 sendPipe, this.options_.bufferSize, serialMojom.SendError.DISCONNECTED); | |
109 this.id_ = id; | 144 this.id_ = id; |
110 getConnections().then(function(connections) { | 145 getConnections().then(function(connections) { |
111 connections[this.id_] = this; | 146 connections[this.id_] = this; |
112 }.bind(this)); | 147 }.bind(this)); |
113 this.paused_ = false; | 148 this.paused_ = false; |
114 this.options_ = {}; | 149 this.sendInProgress_ = false; |
115 for (var key in DEFAULT_CLIENT_OPTIONS) { | 150 this.pendingReceive_ = null; |
116 this.options_[key] = DEFAULT_CLIENT_OPTIONS[key]; | 151 this.pendingError_ = null; |
117 } | 152 this.startReceive_(); |
118 this.setClientOptions_(options); | |
119 } | 153 } |
120 | 154 |
121 Connection.create = function(path, options) { | 155 Connection.create = function(path, options) { |
122 options = options || {}; | 156 options = options || {}; |
123 var serviceOptions = getServiceOptions(options); | 157 var serviceOptions = getServiceOptions(options); |
124 var pipe = core.createMessagePipe(); | 158 var pipe = core.createMessagePipe(); |
125 // Note: These two are created and closed because the service implementation | |
126 // requires that we provide valid message pipes for the data source and | |
127 // sink. Currently the client handles are immediately closed; the real | |
128 // implementation will come later. | |
129 var sendPipe = core.createMessagePipe(); | 159 var sendPipe = core.createMessagePipe(); |
130 var receivePipe = core.createMessagePipe(); | 160 var receivePipe = core.createMessagePipe(); |
131 service.connect(path, | 161 service.connect(path, |
132 serviceOptions, | 162 serviceOptions, |
133 pipe.handle0, | 163 pipe.handle0, |
134 sendPipe.handle0, | 164 sendPipe.handle0, |
135 receivePipe.handle0); | 165 receivePipe.handle0); |
136 core.close(sendPipe.handle1); | |
137 core.close(receivePipe.handle1); | |
138 var router = new routerModule.Router(pipe.handle1); | 166 var router = new routerModule.Router(pipe.handle1); |
139 var connection = new serialMojom.ConnectionProxy(router); | 167 var connection = new serialMojom.ConnectionProxy(router); |
140 return connection.getInfo().then(convertServiceInfo).then( | 168 return connection.getInfo().then(convertServiceInfo).then(function(info) { |
141 function(info) { | |
142 return Promise.all([info, allocateConnectionId()]); | 169 return Promise.all([info, allocateConnectionId()]); |
143 }).catch(function(e) { | 170 }).catch(function(e) { |
144 router.close(); | 171 router.close(); |
172 core.close(sendPipe.handle1); | |
173 core.close(receivePipe.handle1); | |
145 throw e; | 174 throw e; |
146 }).then(function(results) { | 175 }).then(function(results) { |
147 var info = results[0]; | 176 var info = results[0]; |
148 var id = results[1]; | 177 var id = results[1]; |
149 var serialConnectionClient = new Connection( | 178 var serialConnectionClient = new Connection(connection, |
150 connection, router, id, options); | 179 router, |
180 receivePipe.handle1, | |
181 sendPipe.handle1, | |
182 id, | |
183 options); | |
151 var clientInfo = serialConnectionClient.getClientInfo_(); | 184 var clientInfo = serialConnectionClient.getClientInfo_(); |
152 for (var key in clientInfo) { | 185 for (var key in clientInfo) { |
153 info[key] = clientInfo[key]; | 186 info[key] = clientInfo[key]; |
154 } | 187 } |
155 return { | 188 return { |
156 connection: serialConnectionClient, | 189 connection: serialConnectionClient, |
157 info: info, | 190 info: info, |
158 }; | 191 }; |
159 }); | 192 }); |
160 }; | 193 }; |
161 | 194 |
162 Connection.prototype.close = function() { | 195 Connection.prototype.close = function() { |
163 this.router_.close(); | 196 this.router_.close(); |
197 this.receivePipe_.close(); | |
198 this.sendPipe_.close(); | |
199 clearTimeout(this.receiveTimeoutId_); | |
200 clearTimeout(this.sendTimeoutId_); | |
164 return getConnections().then(function(connections) { | 201 return getConnections().then(function(connections) { |
165 delete connections[this.id_] | 202 delete connections[this.id_]; |
166 return true; | 203 return true; |
167 }.bind(this)); | 204 }.bind(this)); |
168 }; | 205 }; |
169 | 206 |
170 Connection.prototype.getClientInfo_ = function() { | 207 Connection.prototype.getClientInfo_ = function() { |
171 var info = { | 208 var info = { |
172 connectionId: this.id_, | 209 connectionId: this.id_, |
173 paused: this.paused_, | 210 paused: this.paused_, |
174 } | 211 }; |
175 for (var key in this.options_) { | 212 for (var key in this.options_) { |
176 info[key] = this.options_[key]; | 213 info[key] = this.options_[key]; |
177 } | 214 } |
178 return info; | 215 return info; |
179 }; | 216 }; |
180 | 217 |
181 Connection.prototype.getInfo = function() { | 218 Connection.prototype.getInfo = function() { |
182 var info = this.getClientInfo_(); | 219 var info = this.getClientInfo_(); |
183 return this.remoteConnection_.getInfo().then(convertServiceInfo).then( | 220 return this.remoteConnection_.getInfo().then(convertServiceInfo).then( |
184 function(result) { | 221 function(result) { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
246 }; | 283 }; |
247 | 284 |
248 Connection.prototype.flush = function() { | 285 Connection.prototype.flush = function() { |
249 return this.remoteConnection_.flush().then(function(result) { | 286 return this.remoteConnection_.flush().then(function(result) { |
250 return !!result.success; | 287 return !!result.success; |
251 }); | 288 }); |
252 }; | 289 }; |
253 | 290 |
254 Connection.prototype.setPaused = function(paused) { | 291 Connection.prototype.setPaused = function(paused) { |
255 this.paused_ = paused; | 292 this.paused_ = paused; |
293 if (paused) { | |
294 clearTimeout(this.receiveTimeoutId_); | |
295 this.receiveTimeoutId_ = null; | |
296 return; | |
297 } | |
raymes
2014/08/29 06:07:16
Using if/else here probably makes this slightly cl
Sam McNally
2014/09/01 06:35:17
Done.
| |
298 if (!this.receiveInProgress_) | |
299 this.startReceive_(); | |
300 }; | |
301 | |
302 Connection.prototype.send = function(data) { | |
303 if (this.sendInProgress_) | |
304 return Promise.resolve({bytesSent: 0, error: 'pending'}); | |
305 | |
306 if (this.options_.sendTimeout) { | |
307 this.sendTimeoutId_ = setTimeout(function() { | |
308 this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT); | |
309 }.bind(this), this.options_.sendTimeout); | |
310 } | |
311 this.sendInProgress_ = true; | |
312 return this.sendPipe_.send(data).then(function(bytesSent) { | |
313 return {bytesSent: bytesSent}; | |
314 }).catch(function(e) { | |
315 return { | |
316 bytesSent: e.bytesSent || 0, | |
317 error: SEND_ERROR_FROM_MOJO[e.error] || 'system_error', | |
raymes
2014/08/29 06:07:15
In what case will it return 'system_error'?
Sam McNally
2014/09/01 06:35:18
It shouldn't hit that case.
| |
318 }; | |
319 }).then(function(result) { | |
320 if (this.sendTimeoutId_) | |
321 clearTimeout(this.sendTimeoutId_); | |
322 this.sendTimeoutId_ = null; | |
323 this.sendInProgress_ = false; | |
324 return result; | |
325 }.bind(this)); | |
326 }; | |
327 | |
328 Connection.prototype.startReceive_ = function() { | |
329 this.receiveInProgress_ = true; | |
330 var receivePromise = null; | |
331 if (this.pendingReceive_) { | |
raymes
2014/08/29 06:07:15
Some comments describing how pendingReceive_/pendi
Sam McNally
2014/09/01 06:35:17
Done.
| |
332 receivePromise = Promise.resolve(this.pendingReceive_); | |
333 this.pendingReceive_ = null; | |
334 } else if (this.pendingError_) { | |
335 receivePromise = Promise.reject(this.pendingError_); | |
336 this.pendingError_ = null; | |
337 } else { | |
338 receivePromise = this.receivePipe_.receive(); | |
339 } | |
340 receivePromise.then(this.onDataReceived_.bind(this)).catch( | |
341 this.onReceiveError_.bind(this)); | |
342 this.startReceiveTimeoutTimer_(); | |
343 }; | |
344 | |
345 Connection.prototype.onDataReceived_ = function(data) { | |
346 this.startReceiveTimeoutTimer_(); | |
347 this.receiveInProgress_ = false; | |
348 if (this.paused_) { | |
349 this.pendingReceive_ = data; | |
350 return; | |
351 } | |
352 if (this.onData) { | |
353 this.onData(data); | |
354 } | |
355 if (!this.paused_) { | |
356 this.startReceive_(); | |
357 } | |
358 }; | |
359 | |
360 Connection.prototype.onReceiveError_ = function(e) { | |
361 clearTimeout(this.receiveTimeoutId_); | |
362 this.receiveInProgress_ = false; | |
363 if (this.paused_) { | |
364 this.pendingError_ = e; | |
365 return; | |
366 } | |
367 var error = e.error || serialMojom.ReceiveError.SYSTEM_ERROR; | |
368 this.paused_ = true; | |
369 if (this.onError) | |
370 this.onError(RECEIVE_ERROR_FROM_MOJO[error]); | |
371 }; | |
372 | |
373 Connection.prototype.startReceiveTimeoutTimer_ = function() { | |
374 clearTimeout(this.receiveTimeoutId_); | |
375 if (this.options_.receiveTimeout && !this.paused_) { | |
376 this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this), | |
377 this.options_.receiveTimeout); | |
378 } | |
379 }; | |
380 | |
381 Connection.prototype.onReceiveTimeout_ = function() { | |
382 if (this.onError) | |
383 this.onError('timeout'); | |
384 this.startReceiveTimeoutTimer_(); | |
256 }; | 385 }; |
257 | 386 |
258 var connections_ = {}; | 387 var connections_ = {}; |
259 var nextConnectionId_ = 0; | 388 var nextConnectionId_ = 0; |
260 | 389 |
261 // Wrap all access to |connections_| through getConnections to avoid adding | 390 // Wrap all access to |connections_| through getConnections to avoid adding |
262 // any synchronous dependencies on it. This will likely be important when | 391 // any synchronous dependencies on it. This will likely be important when |
263 // supporting persistent connections by stashing them. | 392 // supporting persistent connections by stashing them. |
264 function getConnections() { | 393 function getConnections() { |
265 return Promise.resolve(connections_); | 394 return Promise.resolve(connections_); |
266 } | 395 } |
267 | 396 |
268 function getConnection(id) { | 397 function getConnection(id) { |
269 return getConnections().then(function(connections) { | 398 return getConnections().then(function(connections) { |
270 if (!connections[id]) | 399 if (!connections[id]) |
271 throw new Error ('Serial connection not found.'); | 400 throw new Error('Serial connection not found.'); |
272 return connections[id]; | 401 return connections[id]; |
273 }); | 402 }); |
274 } | 403 } |
275 | 404 |
276 function allocateConnectionId() { | 405 function allocateConnectionId() { |
277 return Promise.resolve(nextConnectionId_++); | 406 return Promise.resolve(nextConnectionId_++); |
278 } | 407 } |
279 | 408 |
280 return { | 409 return { |
281 getDevices: getDevices, | 410 getDevices: getDevices, |
282 createConnection: Connection.create, | 411 createConnection: Connection.create, |
283 getConnection: getConnection, | 412 getConnection: getConnection, |
284 getConnections: getConnections, | 413 getConnections: getConnections, |
414 // For testing. | |
415 Connection: Connection, | |
285 }; | 416 }; |
286 }); | 417 }); |
OLD | NEW |