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 |
116 this.options_[key] = DEFAULT_CLIENT_OPTIONS[key]; | 151 // queuedReceiveData_ or queuedReceiveError will store the receive result or |
117 } | 152 // error, respectively, if a receive completes or fails while this |
118 this.setClientOptions_(options); | 153 // connection is paused. At most one of the the two may be non-null: a |
| 154 // receive completed while paused will only set one of them, no further |
| 155 // receives will be performed while paused and a queued result is dispatched |
| 156 // before any further receives are initiated when unpausing. |
| 157 this.queuedReceiveData_ = null; |
| 158 this.queuedReceiveError = null; |
| 159 |
| 160 this.startReceive_(); |
119 } | 161 } |
120 | 162 |
121 Connection.create = function(path, options) { | 163 Connection.create = function(path, options) { |
122 options = options || {}; | 164 options = options || {}; |
123 var serviceOptions = getServiceOptions(options); | 165 var serviceOptions = getServiceOptions(options); |
124 var pipe = core.createMessagePipe(); | 166 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(); | 167 var sendPipe = core.createMessagePipe(); |
130 var receivePipe = core.createMessagePipe(); | 168 var receivePipe = core.createMessagePipe(); |
131 service.connect(path, | 169 service.connect(path, |
132 serviceOptions, | 170 serviceOptions, |
133 pipe.handle0, | 171 pipe.handle0, |
134 sendPipe.handle0, | 172 sendPipe.handle0, |
135 receivePipe.handle0); | 173 receivePipe.handle0); |
136 core.close(sendPipe.handle1); | |
137 core.close(receivePipe.handle1); | |
138 var router = new routerModule.Router(pipe.handle1); | 174 var router = new routerModule.Router(pipe.handle1); |
139 var connection = new serialMojom.ConnectionProxy(router); | 175 var connection = new serialMojom.ConnectionProxy(router); |
140 return connection.getInfo().then(convertServiceInfo).then( | 176 return connection.getInfo().then(convertServiceInfo).then(function(info) { |
141 function(info) { | |
142 return Promise.all([info, allocateConnectionId()]); | 177 return Promise.all([info, allocateConnectionId()]); |
143 }).catch(function(e) { | 178 }).catch(function(e) { |
144 router.close(); | 179 router.close(); |
| 180 core.close(sendPipe.handle1); |
| 181 core.close(receivePipe.handle1); |
145 throw e; | 182 throw e; |
146 }).then(function(results) { | 183 }).then(function(results) { |
147 var info = results[0]; | 184 var info = results[0]; |
148 var id = results[1]; | 185 var id = results[1]; |
149 var serialConnectionClient = new Connection( | 186 var serialConnectionClient = new Connection(connection, |
150 connection, router, id, options); | 187 router, |
| 188 receivePipe.handle1, |
| 189 sendPipe.handle1, |
| 190 id, |
| 191 options); |
151 var clientInfo = serialConnectionClient.getClientInfo_(); | 192 var clientInfo = serialConnectionClient.getClientInfo_(); |
152 for (var key in clientInfo) { | 193 for (var key in clientInfo) { |
153 info[key] = clientInfo[key]; | 194 info[key] = clientInfo[key]; |
154 } | 195 } |
155 return { | 196 return { |
156 connection: serialConnectionClient, | 197 connection: serialConnectionClient, |
157 info: info, | 198 info: info, |
158 }; | 199 }; |
159 }); | 200 }); |
160 }; | 201 }; |
161 | 202 |
162 Connection.prototype.close = function() { | 203 Connection.prototype.close = function() { |
163 this.router_.close(); | 204 this.router_.close(); |
| 205 this.receivePipe_.close(); |
| 206 this.sendPipe_.close(); |
| 207 clearTimeout(this.receiveTimeoutId_); |
| 208 clearTimeout(this.sendTimeoutId_); |
164 return getConnections().then(function(connections) { | 209 return getConnections().then(function(connections) { |
165 delete connections[this.id_] | 210 delete connections[this.id_]; |
166 return true; | 211 return true; |
167 }.bind(this)); | 212 }.bind(this)); |
168 }; | 213 }; |
169 | 214 |
170 Connection.prototype.getClientInfo_ = function() { | 215 Connection.prototype.getClientInfo_ = function() { |
171 var info = { | 216 var info = { |
172 connectionId: this.id_, | 217 connectionId: this.id_, |
173 paused: this.paused_, | 218 paused: this.paused_, |
174 } | 219 }; |
175 for (var key in this.options_) { | 220 for (var key in this.options_) { |
176 info[key] = this.options_[key]; | 221 info[key] = this.options_[key]; |
177 } | 222 } |
178 return info; | 223 return info; |
179 }; | 224 }; |
180 | 225 |
181 Connection.prototype.getInfo = function() { | 226 Connection.prototype.getInfo = function() { |
182 var info = this.getClientInfo_(); | 227 var info = this.getClientInfo_(); |
183 return this.remoteConnection_.getInfo().then(convertServiceInfo).then( | 228 return this.remoteConnection_.getInfo().then(convertServiceInfo).then( |
184 function(result) { | 229 function(result) { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
246 }; | 291 }; |
247 | 292 |
248 Connection.prototype.flush = function() { | 293 Connection.prototype.flush = function() { |
249 return this.remoteConnection_.flush().then(function(result) { | 294 return this.remoteConnection_.flush().then(function(result) { |
250 return !!result.success; | 295 return !!result.success; |
251 }); | 296 }); |
252 }; | 297 }; |
253 | 298 |
254 Connection.prototype.setPaused = function(paused) { | 299 Connection.prototype.setPaused = function(paused) { |
255 this.paused_ = paused; | 300 this.paused_ = paused; |
| 301 if (paused) { |
| 302 clearTimeout(this.receiveTimeoutId_); |
| 303 this.receiveTimeoutId_ = null; |
| 304 } else if (!this.receiveInProgress_) { |
| 305 this.startReceive_(); |
| 306 } |
| 307 }; |
| 308 |
| 309 Connection.prototype.send = function(data) { |
| 310 if (this.sendInProgress_) |
| 311 return Promise.resolve({bytesSent: 0, error: 'pending'}); |
| 312 |
| 313 if (this.options_.sendTimeout) { |
| 314 this.sendTimeoutId_ = setTimeout(function() { |
| 315 this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT); |
| 316 }.bind(this), this.options_.sendTimeout); |
| 317 } |
| 318 this.sendInProgress_ = true; |
| 319 return this.sendPipe_.send(data).then(function(bytesSent) { |
| 320 return {bytesSent: bytesSent}; |
| 321 }).catch(function(e) { |
| 322 return { |
| 323 bytesSent: e.bytesSent, |
| 324 error: SEND_ERROR_FROM_MOJO[e.error], |
| 325 }; |
| 326 }).then(function(result) { |
| 327 if (this.sendTimeoutId_) |
| 328 clearTimeout(this.sendTimeoutId_); |
| 329 this.sendTimeoutId_ = null; |
| 330 this.sendInProgress_ = false; |
| 331 return result; |
| 332 }.bind(this)); |
| 333 }; |
| 334 |
| 335 Connection.prototype.startReceive_ = function() { |
| 336 this.receiveInProgress_ = true; |
| 337 var receivePromise = null; |
| 338 // If we have a queued receive result, dispatch it immediately instead of |
| 339 // starting a new receive. |
| 340 if (this.queuedReceiveData_) { |
| 341 receivePromise = Promise.resolve(this.queuedReceiveData_); |
| 342 this.queuedReceiveData_ = null; |
| 343 } else if (this.queuedReceiveError) { |
| 344 receivePromise = Promise.reject(this.queuedReceiveError); |
| 345 this.queuedReceiveError = null; |
| 346 } else { |
| 347 receivePromise = this.receivePipe_.receive(); |
| 348 } |
| 349 receivePromise.then(this.onDataReceived_.bind(this)).catch( |
| 350 this.onReceiveError_.bind(this)); |
| 351 this.startReceiveTimeoutTimer_(); |
| 352 }; |
| 353 |
| 354 Connection.prototype.onDataReceived_ = function(data) { |
| 355 this.startReceiveTimeoutTimer_(); |
| 356 this.receiveInProgress_ = false; |
| 357 if (this.paused_) { |
| 358 this.queuedReceiveData_ = data; |
| 359 return; |
| 360 } |
| 361 if (this.onData) { |
| 362 this.onData(data); |
| 363 } |
| 364 if (!this.paused_) { |
| 365 this.startReceive_(); |
| 366 } |
| 367 }; |
| 368 |
| 369 Connection.prototype.onReceiveError_ = function(e) { |
| 370 clearTimeout(this.receiveTimeoutId_); |
| 371 this.receiveInProgress_ = false; |
| 372 if (this.paused_) { |
| 373 this.queuedReceiveError = e; |
| 374 return; |
| 375 } |
| 376 var error = e.error; |
| 377 this.paused_ = true; |
| 378 if (this.onError) |
| 379 this.onError(RECEIVE_ERROR_FROM_MOJO[error]); |
| 380 }; |
| 381 |
| 382 Connection.prototype.startReceiveTimeoutTimer_ = function() { |
| 383 clearTimeout(this.receiveTimeoutId_); |
| 384 if (this.options_.receiveTimeout && !this.paused_) { |
| 385 this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this), |
| 386 this.options_.receiveTimeout); |
| 387 } |
| 388 }; |
| 389 |
| 390 Connection.prototype.onReceiveTimeout_ = function() { |
| 391 if (this.onError) |
| 392 this.onError('timeout'); |
| 393 this.startReceiveTimeoutTimer_(); |
256 }; | 394 }; |
257 | 395 |
258 var connections_ = {}; | 396 var connections_ = {}; |
259 var nextConnectionId_ = 0; | 397 var nextConnectionId_ = 0; |
260 | 398 |
261 // Wrap all access to |connections_| through getConnections to avoid adding | 399 // Wrap all access to |connections_| through getConnections to avoid adding |
262 // any synchronous dependencies on it. This will likely be important when | 400 // any synchronous dependencies on it. This will likely be important when |
263 // supporting persistent connections by stashing them. | 401 // supporting persistent connections by stashing them. |
264 function getConnections() { | 402 function getConnections() { |
265 return Promise.resolve(connections_); | 403 return Promise.resolve(connections_); |
266 } | 404 } |
267 | 405 |
268 function getConnection(id) { | 406 function getConnection(id) { |
269 return getConnections().then(function(connections) { | 407 return getConnections().then(function(connections) { |
270 if (!connections[id]) | 408 if (!connections[id]) |
271 throw new Error ('Serial connection not found.'); | 409 throw new Error('Serial connection not found.'); |
272 return connections[id]; | 410 return connections[id]; |
273 }); | 411 }); |
274 } | 412 } |
275 | 413 |
276 function allocateConnectionId() { | 414 function allocateConnectionId() { |
277 return Promise.resolve(nextConnectionId_++); | 415 return Promise.resolve(nextConnectionId_++); |
278 } | 416 } |
279 | 417 |
280 return { | 418 return { |
281 getDevices: getDevices, | 419 getDevices: getDevices, |
282 createConnection: Connection.create, | 420 createConnection: Connection.create, |
283 getConnection: getConnection, | 421 getConnection: getConnection, |
284 getConnections: getConnections, | 422 getConnections: getConnections, |
| 423 // For testing. |
| 424 Connection: Connection, |
285 }; | 425 }; |
286 }); | 426 }); |
OLD | NEW |