Chromium Code Reviews| 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 |