Reland the ULONG -> SIZE_T change from 317177
[chromium-blink-merge.git] / extensions / renderer / resources / data_sender.js
blob5fa8708ba3338f53dffc6cc55c9b506a153addc8
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.
5 define('data_sender', [
6     'device/serial/data_stream.mojom',
7     'device/serial/data_stream_serialization.mojom',
8     'mojo/public/js/core',
9     'mojo/public/js/router',
10 ], function(dataStreamMojom, serialization, core, routerModule) {
11   /**
12    * @module data_sender
13    */
15   /**
16    * A pending send operation.
17    * @param {!ArrayBuffer} data The data to be sent.
18    * @constructor
19    * @alias module:data_sender~PendingSend
20    * @private
21    */
22   function PendingSend(data) {
23     /**
24      * The data to be sent.
25      * @type {ArrayBuffer}
26      * @private
27      */
28     this.data_ = data;
29     /**
30      * The total length of data to be sent.
31      * @type {number}
32      * @private
33      */
34     this.length_ = data.byteLength;
35     /**
36      * The promise that will be resolved or rejected when this send completes
37      * or fails, respectively.
38      * @type {!Promise<number>}
39      * @private
40      */
41     this.promise_ = new Promise(function(resolve, reject) {
42       /**
43        * The callback to call on success.
44        * @type {Function}
45        * @private
46        */
47       this.successCallback_ = resolve;
48       /**
49        * The callback to call with the error on failure.
50        * @type {Function}
51        * @private
52        */
53       this.errorCallback_ = reject;
54     }.bind(this));
55   }
57   /**
58    * Returns the promise that will be resolved when this operation completes or
59    * rejected if an error occurs.
60    * @return {!Promise<number>} A promise to the number of bytes sent.
61    */
62   PendingSend.prototype.getPromise = function() {
63     return this.promise_;
64   };
66   /**
67    * Invoked when the DataSink reports that bytes have been sent. Resolves the
68    * promise returned by
69    * [getPromise()]{@link module:data_sender~PendingSend#getPromise} once all
70    * bytes have been reported as sent.
71    */
72   PendingSend.prototype.reportBytesSent = function() {
73     this.successCallback_(this.length_);
74   };
76   /**
77    * Invoked when the DataSink reports an error. Rejects the promise returned by
78    * [getPromise()]{@link module:data_sender~PendingSend#getPromise} unless the
79    * error occurred after this send, that is, unless numBytes is greater than
80    * the nubmer of outstanding bytes.
81    * @param {number} numBytes The number of bytes sent.
82    * @param {number} error The error reported by the DataSink.
83    */
84   PendingSend.prototype.reportBytesSentAndError = function(numBytes, error) {
85     var e = new Error();
86     e.error = error;
87     e.bytesSent = numBytes;
88     this.errorCallback_(e);
89   };
91   /**
92    * Writes pending data into the data pipe.
93    * @param {!DataSink} sink The DataSink to receive the data.
94    * @return {!Object} result The send result.
95    * @return {boolean} result.completed Whether all of the pending data was
96    *     sent.
97    */
98   PendingSend.prototype.sendData = function(sink) {
99     var dataSent = sink.onData(new Uint8Array(this.data_));
100     this.data_ = null;
101     return dataSent;
102   };
104   /**
105    * A DataSender that sends data to a DataSink.
106    * @param {!MojoHandle} sink The handle to the DataSink.
107    * @param {number} bufferSize How large a buffer to use for data.
108    * @param {number} fatalErrorValue The send error value to report in the
109    *     event of a fatal error.
110    * @constructor
111    * @alias module:data_sender.DataSender
112    */
113   function DataSender(sink, bufferSize, fatalErrorValue) {
114     this.init_(sink, fatalErrorValue);
115   }
117   /**
118    * Closes this DataSender.
119    */
120   DataSender.prototype.close = function() {
121     if (this.shutDown_)
122       return;
123     this.shutDown_ = true;
124     this.router_.close();
125     while (this.sendsAwaitingAck_.length) {
126       this.sendsAwaitingAck_.pop().reportBytesSentAndError(
127           0, this.fatalErrorValue_);
128     }
129     this.callCancelCallback_();
130   };
132   /**
133    * Initialize this DataSender.
134    * @param {!MojoHandle} sink A handle to the DataSink.
135    * @param {number} fatalErrorValue The error to dispatch in the event of a
136    *     fatal error.
137    * @private
138    */
139   DataSender.prototype.init_ = function(sink, fatalErrorValue) {
140     /**
141      * The error to be dispatched in the event of a fatal error.
142      * @const {number}
143      * @private
144      */
145     this.fatalErrorValue_ = fatalErrorValue;
146     /**
147      * Whether this DataSender has shut down.
148      * @type {boolean}
149      * @private
150      */
151     this.shutDown_ = false;
152     /**
153      * The [Router]{@link module:mojo/public/js/router.Router} for the
154      * connection to the DataSink.
155      * @private
156      */
157     this.router_ = new routerModule.Router(sink);
158     /**
159      * The connection to the DataSink.
160      * @private
161      */
162     this.sink_ = new dataStreamMojom.DataSink.proxyClass(this.router_);
163     /**
164      * A queue of sends that have sent their data to the DataSink, but have not
165      * been received by the DataSink.
166      * @type {!module:data_sender~PendingSend[]}
167      * @private
168      */
169     this.sendsAwaitingAck_ = [];
171     /**
172      * The callback that will resolve a pending cancel if one is in progress.
173      * @type {?Function}
174      * @private
175      */
176     this.pendingCancel_ = null;
178     /**
179      * The promise that will be resolved when a pending cancel completes if one
180      * is in progress.
181      * @type {Promise}
182      * @private
183      */
184     this.cancelPromise_ = null;
185   };
187   /**
188    * Serializes this DataSender.
189    * This will cancel any sends in progress before the returned promise
190    * resolves.
191    * @return {!Promise<SerializedDataSender>} A promise that will resolve to
192    *     the serialization of this DataSender. If this DataSender has shut down,
193    *     the promise will resolve to null.
194    */
195   DataSender.prototype.serialize = function() {
196     if (this.shutDown_)
197       return Promise.resolve(null);
199     var readyToSerialize = Promise.resolve();
200     if (this.sendsAwaitingAck_.length) {
201       if (this.pendingCancel_)
202         readyToSerialize = this.cancelPromise_;
203       else
204         readyToSerialize = this.cancel(this.fatalErrorValue_);
205     }
206     return readyToSerialize.then(function() {
207       var serialized = new serialization.SerializedDataSender();
208       serialized.sink = this.router_.connector_.handle_;
209       serialized.fatal_error_value = this.fatalErrorValue_;
210       this.router_.connector_.handle_ = null;
211       this.router_.close();
212       this.shutDown_ = true;
213       return serialized;
214     }.bind(this));
215   };
217   /**
218    * Deserializes a SerializedDataSender.
219    * @param {SerializedDataSender} serialized The serialized DataSender.
220    * @return {!DataSender} The deserialized DataSender.
221    */
222   DataSender.deserialize = function(serialized) {
223     var sender = $Object.create(DataSender.prototype);
224     sender.deserialize_(serialized);
225     return sender;
226   };
228   /**
229    * Deserializes a SerializedDataSender into this DataSender.
230    * @param {SerializedDataSender} serialized The serialized DataSender.
231    * @private
232    */
233   DataSender.prototype.deserialize_ = function(serialized) {
234     if (!serialized) {
235       this.shutDown_ = true;
236       return;
237     }
238     this.init_(serialized.sink, serialized.fatal_error_value,
239                serialized.buffer_size);
240   };
242   /**
243    * Sends data to the DataSink.
244    * @return {!Promise<number>} A promise to the number of bytes sent. If an
245    *     error occurs, the promise will reject with an Error object with a
246    *     property error containing the error code.
247    * @throws Will throw if this has encountered a fatal error or a cancel is in
248    *     progress.
249    */
250   DataSender.prototype.send = function(data) {
251     if (this.shutDown_)
252       throw new Error('DataSender has been closed');
253     if (this.pendingCancel_)
254       throw new Error('Cancel in progress');
255     var send = new PendingSend(data);
256     this.sendsAwaitingAck_.push(send);
257     send.sendData(this.sink_).then(this.reportBytesSentAndError.bind(this));
258     return send.getPromise();
259   };
261   /**
262    * Requests the cancellation of any in-progress sends. Calls to
263    * [send()]{@link module:data_sender.DataSender#send} will fail until the
264    * cancel has completed.
265    * @param {number} error The error to report for cancelled sends.
266    * @return {!Promise} A promise that will resolve when the cancel completes.
267    * @throws Will throw if this has encountered a fatal error or another cancel
268    *     is in progress.
269    */
270   DataSender.prototype.cancel = function(error) {
271     if (this.shutDown_)
272       throw new Error('DataSender has been closed');
273     if (this.pendingCancel_)
274       throw new Error('Cancel already in progress');
275     if (this.sendsAwaitingAck_.length == 0)
276       return Promise.resolve();
278     this.sink_.cancel(error);
279     this.cancelPromise_ = new Promise(function(resolve) {
280       this.pendingCancel_ = resolve;
281     }.bind(this));
282     return this.cancelPromise_;
283   };
285   /**
286    * Calls and clears the pending cancel callback if one is pending.
287    * @private
288    */
289   DataSender.prototype.callCancelCallback_ = function() {
290     if (this.pendingCancel_) {
291       this.cancelPromise_ = null;
292       this.pendingCancel_();
293       this.pendingCancel_ = null;
294     }
295   };
297   /**
298    * Invoked by the DataSink to report that data has been successfully sent.
299    * @private
300    */
301   DataSender.prototype.reportBytesSent = function() {
302     var result = this.sendsAwaitingAck_[0].reportBytesSent();
303     this.sendsAwaitingAck_.shift();
305     // A cancel is completed when all of the sends that were in progress have
306     // completed or failed. This is the case where all sends complete
307     // successfully.
308     if (this.sendsAwaitingAck_.length == 0)
309       this.callCancelCallback_();
310   };
312   /**
313    * Invoked by the DataSink to report an error in sending data.
314    * @param {number} numBytes The number of bytes sent.
315    * @param {number} error The error reported by the DataSink.
316    * @private
317    */
318   DataSender.prototype.reportBytesSentAndError = function(result) {
319     var numBytes = result.bytes_sent;
320     var error = result.error;
321     if (!error) {
322       this.reportBytesSent();
323       return;
324     }
325     var result =
326         this.sendsAwaitingAck_[0].reportBytesSentAndError(numBytes, error);
327     this.sendsAwaitingAck_.shift();
328     if (this.sendsAwaitingAck_.length)
329       return;
330     this.callCancelCallback_();
331     this.sink_.clearError();
332   };
334   return {DataSender: DataSender};