Index: extensions/renderer/resources/serial_service.js |
diff --git a/extensions/renderer/resources/serial_service.js b/extensions/renderer/resources/serial_service.js |
index b8b0970db71ab70e1921064d4570c6ed204a6ae6..60b0afd6c0a0cca18c1d9808935ae821feecec61 100644 |
--- a/extensions/renderer/resources/serial_service.js |
+++ b/extensions/renderer/resources/serial_service.js |
@@ -4,10 +4,17 @@ |
define('serial_service', [ |
'content/public/renderer/service_provider', |
+ 'data_receiver', |
+ 'data_sender', |
'device/serial/serial.mojom', |
'mojo/public/js/bindings/core', |
'mojo/public/js/bindings/router', |
-], function(serviceProvider, serialMojom, core, routerModule) { |
+], function(serviceProvider, |
+ dataReceiver, |
+ dataSender, |
+ serialMojom, |
+ core, |
+ routerModule) { |
/** |
* A Javascript client for the serial service and connection Mojo services. |
* |
@@ -59,6 +66,20 @@ define('serial_service', [ |
'odd': serialMojom.ParityBit.ODD, |
'even': serialMojom.ParityBit.EVEN, |
}; |
+ var SEND_ERROR_TO_MOJO = { |
+ undefined: serialMojom.SendError.NONE, |
+ 'disconnected': serialMojom.SendError.DISCONNECTED, |
+ 'pending': serialMojom.SendError.PENDING, |
+ 'timeout': serialMojom.SendError.TIMEOUT, |
+ 'system_error': serialMojom.SendError.SYSTEM_ERROR, |
+ }; |
+ var RECEIVE_ERROR_TO_MOJO = { |
+ undefined: serialMojom.ReceiveError.NONE, |
+ 'disconnected': serialMojom.ReceiveError.DISCONNECTED, |
+ 'device_lost': serialMojom.ReceiveError.DEVICE_LOST, |
+ 'timeout': serialMojom.ReceiveError.TIMEOUT, |
+ 'system_error': serialMojom.ReceiveError.SYSTEM_ERROR, |
+ }; |
function invertMap(input) { |
var output = {}; |
@@ -73,6 +94,8 @@ define('serial_service', [ |
var DATA_BITS_FROM_MOJO = invertMap(DATA_BITS_TO_MOJO); |
var STOP_BITS_FROM_MOJO = invertMap(STOP_BITS_TO_MOJO); |
var PARITY_BIT_FROM_MOJO = invertMap(PARITY_BIT_TO_MOJO); |
+ var SEND_ERROR_FROM_MOJO = invertMap(SEND_ERROR_TO_MOJO); |
+ var RECEIVE_ERROR_FROM_MOJO = invertMap(RECEIVE_ERROR_TO_MOJO); |
function getServiceOptions(options) { |
var out = {}; |
@@ -103,29 +126,44 @@ define('serial_service', [ |
}; |
} |
- function Connection(remoteConnection, router, id, options) { |
+ function Connection( |
+ remoteConnection, router, receivePipe, sendPipe, id, options) { |
this.remoteConnection_ = remoteConnection; |
this.router_ = router; |
- this.id_ = id; |
- getConnections().then(function(connections) { |
- connections[this.id_] = this; |
- }.bind(this)); |
- this.paused_ = false; |
this.options_ = {}; |
for (var key in DEFAULT_CLIENT_OPTIONS) { |
this.options_[key] = DEFAULT_CLIENT_OPTIONS[key]; |
} |
this.setClientOptions_(options); |
+ this.receivePipe_ = |
+ new dataReceiver.DataReceiver(receivePipe, |
+ this.options_.bufferSize, |
+ serialMojom.ReceiveError.DISCONNECTED); |
+ this.sendPipe_ = new dataSender.DataSender( |
+ sendPipe, this.options_.bufferSize, serialMojom.SendError.DISCONNECTED); |
+ this.id_ = id; |
+ getConnections().then(function(connections) { |
+ connections[this.id_] = this; |
+ }.bind(this)); |
+ this.paused_ = false; |
+ this.sendInProgress_ = false; |
+ |
+ // queuedReceiveData_ or queuedReceiveError will store the receive result or |
+ // error, respectively, if a receive completes or fails while this |
+ // connection is paused. At most one of the the two may be non-null: a |
+ // receive completed while paused will only set one of them, no further |
+ // receives will be performed while paused and a queued result is dispatched |
+ // before any further receives are initiated when unpausing. |
+ this.queuedReceiveData_ = null; |
+ this.queuedReceiveError = null; |
+ |
+ this.startReceive_(); |
} |
Connection.create = function(path, options) { |
options = options || {}; |
var serviceOptions = getServiceOptions(options); |
var pipe = core.createMessagePipe(); |
- // Note: These two are created and closed because the service implementation |
- // requires that we provide valid message pipes for the data source and |
- // sink. Currently the client handles are immediately closed; the real |
- // implementation will come later. |
var sendPipe = core.createMessagePipe(); |
var receivePipe = core.createMessagePipe(); |
service.connect(path, |
@@ -133,21 +171,24 @@ define('serial_service', [ |
pipe.handle0, |
sendPipe.handle0, |
receivePipe.handle0); |
- core.close(sendPipe.handle1); |
- core.close(receivePipe.handle1); |
var router = new routerModule.Router(pipe.handle1); |
var connection = new serialMojom.ConnectionProxy(router); |
- return connection.getInfo().then(convertServiceInfo).then( |
- function(info) { |
+ return connection.getInfo().then(convertServiceInfo).then(function(info) { |
return Promise.all([info, allocateConnectionId()]); |
}).catch(function(e) { |
router.close(); |
+ core.close(sendPipe.handle1); |
+ core.close(receivePipe.handle1); |
throw e; |
}).then(function(results) { |
var info = results[0]; |
var id = results[1]; |
- var serialConnectionClient = new Connection( |
- connection, router, id, options); |
+ var serialConnectionClient = new Connection(connection, |
+ router, |
+ receivePipe.handle1, |
+ sendPipe.handle1, |
+ id, |
+ options); |
var clientInfo = serialConnectionClient.getClientInfo_(); |
for (var key in clientInfo) { |
info[key] = clientInfo[key]; |
@@ -161,8 +202,12 @@ define('serial_service', [ |
Connection.prototype.close = function() { |
this.router_.close(); |
+ this.receivePipe_.close(); |
+ this.sendPipe_.close(); |
+ clearTimeout(this.receiveTimeoutId_); |
+ clearTimeout(this.sendTimeoutId_); |
return getConnections().then(function(connections) { |
- delete connections[this.id_] |
+ delete connections[this.id_]; |
return true; |
}.bind(this)); |
}; |
@@ -171,7 +216,7 @@ define('serial_service', [ |
var info = { |
connectionId: this.id_, |
paused: this.paused_, |
- } |
+ }; |
for (var key in this.options_) { |
info[key] = this.options_[key]; |
} |
@@ -253,6 +298,99 @@ define('serial_service', [ |
Connection.prototype.setPaused = function(paused) { |
this.paused_ = paused; |
+ if (paused) { |
+ clearTimeout(this.receiveTimeoutId_); |
+ this.receiveTimeoutId_ = null; |
+ } else if (!this.receiveInProgress_) { |
+ this.startReceive_(); |
+ } |
+ }; |
+ |
+ Connection.prototype.send = function(data) { |
+ if (this.sendInProgress_) |
+ return Promise.resolve({bytesSent: 0, error: 'pending'}); |
+ |
+ if (this.options_.sendTimeout) { |
+ this.sendTimeoutId_ = setTimeout(function() { |
+ this.sendPipe_.cancel(serialMojom.SendError.TIMEOUT); |
+ }.bind(this), this.options_.sendTimeout); |
+ } |
+ this.sendInProgress_ = true; |
+ return this.sendPipe_.send(data).then(function(bytesSent) { |
+ return {bytesSent: bytesSent}; |
+ }).catch(function(e) { |
+ return { |
+ bytesSent: e.bytesSent, |
+ error: SEND_ERROR_FROM_MOJO[e.error], |
+ }; |
+ }).then(function(result) { |
+ if (this.sendTimeoutId_) |
+ clearTimeout(this.sendTimeoutId_); |
+ this.sendTimeoutId_ = null; |
+ this.sendInProgress_ = false; |
+ return result; |
+ }.bind(this)); |
+ }; |
+ |
+ Connection.prototype.startReceive_ = function() { |
+ this.receiveInProgress_ = true; |
+ var receivePromise = null; |
+ // If we have a queued receive result, dispatch it immediately instead of |
+ // starting a new receive. |
+ if (this.queuedReceiveData_) { |
+ receivePromise = Promise.resolve(this.queuedReceiveData_); |
+ this.queuedReceiveData_ = null; |
+ } else if (this.queuedReceiveError) { |
+ receivePromise = Promise.reject(this.queuedReceiveError); |
+ this.queuedReceiveError = null; |
+ } else { |
+ receivePromise = this.receivePipe_.receive(); |
+ } |
+ receivePromise.then(this.onDataReceived_.bind(this)).catch( |
+ this.onReceiveError_.bind(this)); |
+ this.startReceiveTimeoutTimer_(); |
+ }; |
+ |
+ Connection.prototype.onDataReceived_ = function(data) { |
+ this.startReceiveTimeoutTimer_(); |
+ this.receiveInProgress_ = false; |
+ if (this.paused_) { |
+ this.queuedReceiveData_ = data; |
+ return; |
+ } |
+ if (this.onData) { |
+ this.onData(data); |
+ } |
+ if (!this.paused_) { |
+ this.startReceive_(); |
+ } |
+ }; |
+ |
+ Connection.prototype.onReceiveError_ = function(e) { |
+ clearTimeout(this.receiveTimeoutId_); |
+ this.receiveInProgress_ = false; |
+ if (this.paused_) { |
+ this.queuedReceiveError = e; |
+ return; |
+ } |
+ var error = e.error; |
+ this.paused_ = true; |
+ if (this.onError) |
+ this.onError(RECEIVE_ERROR_FROM_MOJO[error]); |
+ }; |
+ |
+ Connection.prototype.startReceiveTimeoutTimer_ = function() { |
+ clearTimeout(this.receiveTimeoutId_); |
+ if (this.options_.receiveTimeout && !this.paused_) { |
+ this.receiveTimeoutId_ = setTimeout(this.onReceiveTimeout_.bind(this), |
+ this.options_.receiveTimeout); |
+ } |
+ }; |
+ |
+ Connection.prototype.onReceiveTimeout_ = function() { |
+ if (this.onError) |
+ this.onError('timeout'); |
+ this.startReceiveTimeoutTimer_(); |
}; |
var connections_ = {}; |
@@ -268,7 +406,7 @@ define('serial_service', [ |
function getConnection(id) { |
return getConnections().then(function(connections) { |
if (!connections[id]) |
- throw new Error ('Serial connection not found.'); |
+ throw new Error('Serial connection not found.'); |
return connections[id]; |
}); |
} |
@@ -282,5 +420,7 @@ define('serial_service', [ |
createConnection: Connection.create, |
getConnection: getConnection, |
getConnections: getConnections, |
+ // For testing. |
+ Connection: Connection, |
}; |
}); |