text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  define('data_receiver', [
      'device/serial/data_stream.mojom',
      'device/serial/data_stream_serialization.mojom',
      'mojo/public/js/core',
      'mojo/public/js/router',
  ], function(dataStream, serialization, core, router) {
    /**
     * @module data_receiver
     */
  
    /**
     * A pending receive operation.
     * @constructor
     * @alias module:data_receiver~PendingReceive
     * @private
     */
    function PendingReceive() {
      /**
       * The promise that will be resolved or rejected when this receive completes
       * or fails, respectively.
       * @type {!Promise.<ArrayBuffer>}
       * @private
       */
      this.promise_ = new Promise(function(resolve, reject) {
        /**
         * The callback to call with the data received on success.
         * @type {Function}
         * @private
         */
        this.dataCallback_ = resolve;
        /**
         * The callback to call with the error on failure.
         * @type {Function}
         * @private
         */
        this.errorCallback_ = reject;
      }.bind(this));
    }
  
    /**
     * Returns the promise that will be resolved when this operation completes or
     * rejected if an error occurs.
     * @return {Promise.<ArrayBuffer>} A promise to the data received.
     */
    PendingReceive.prototype.getPromise = function() {
      return this.promise_;
    };
  
    /**
     * Dispatches received data to the promise returned by
     * [getPromise]{@link module:data_receiver.PendingReceive#getPromise}.
     * @param {!ArrayBuffer} data The data to dispatch.
     */
    PendingReceive.prototype.dispatchData = function(data) {
      this.dataCallback_(data);
    };
  
    /**
     * Dispatches an error if the offset of the error has been reached.
     * @param {!PendingReceiveError} error The error to dispatch.
     * @param {number} bytesReceived The number of bytes that have been received.
     */
    PendingReceive.prototype.dispatchError = function(error) {
      if (error.queuePosition > 0)
        return false;
  
      var e = new Error();
      e.error = error.error;
      this.errorCallback_(e);
      return true;
    };
  
    /**
     * Unconditionally dispatches an error.
     * @param {number} error The error to dispatch.
     */
    PendingReceive.prototype.dispatchFatalError = function(error) {
      var e = new Error();
      e.error = error;
      this.errorCallback_(e);
    };
  
    /**
     * A DataReceiver that receives data from a DataSource.
     * @param {!MojoHandle} handle The handle to the DataSource.
     * @param {number} bufferSize How large a buffer to use.
     * @param {number} fatalErrorValue The receive error value to report in the
     *     event of a fatal error.
     * @constructor
     * @alias module:data_receiver.DataReceiver
     */
    function DataReceiver(handle, bufferSize, fatalErrorValue) {
      this.init_(handle, fatalErrorValue, 0, null, [], false);
      this.source_.init(bufferSize);
    }
  
    DataReceiver.prototype =
        $Object.create(dataStream.DataSourceClient.stubClass.prototype);
  
    /**
     * Closes this DataReceiver.
     */
    DataReceiver.prototype.close = function() {
      if (this.shutDown_)
        return;
      this.shutDown_ = true;
      this.router_.close();
      if (this.receive_) {
        this.receive_.dispatchFatalError(this.fatalErrorValue_);
        this.receive_ = null;
      }
    };
  
    /**
     * Initialize this DataReceiver.
     * @param {!MojoHandle} source A handle to the DataSource
     * @param {number} fatalErrorValue The error to dispatch in the event of a
     *     fatal error.
     * @param {number} bytesReceived The number of bytes already received.
     * @param {PendingReceiveError} pendingError The pending error if there is
     *     one.
     * @param {!Array.<!ArrayBuffer>} pendingData Data received from the
     *     DataSource not yet requested by the client.
     * @param {boolean} paused Whether the DataSource is paused.
     * @private
     */
    DataReceiver.prototype.init_ = function(source,
                                            fatalErrorValue,
                                            bytesReceived,
                                            pendingError,
                                            pendingData,
                                            paused) {
      /**
       * The [Router]{@link module:mojo/public/js/router.Router} for the
       * connection to the DataSource.
       * @private
       */
      this.router_ = new router.Router(source);
      /**
       * The connection to the DataSource.
       * @private
       */
      this.source_ = new dataStream.DataSource.proxyClass(this.router_);
      this.router_.setIncomingReceiver(this);
      /**
       * The current receive operation.
       * @type {module:data_receiver~PendingReceive}
       * @private
       */
      this.receive_ = null;
      /**
       * The error to be dispatched in the event of a fatal error.
       * @const {number}
       * @private
       */
      this.fatalErrorValue_ = fatalErrorValue;
      /**
       * The pending error if there is one.
       * @type {PendingReceiveError}
       * @private
       */
      this.pendingError_ = pendingError;
      /**
       * Whether the DataSource is paused.
       * @type {boolean}
       * @private
       */
      this.paused_ = paused;
      /**
       * A queue of data that has been received from the DataSource, but not
       * consumed by the client.
       * @type {module:data_receiver~PendingData[]}
       * @private
       */
      this.pendingDataBuffers_ = pendingData;
      /**
       * Whether this DataReceiver has shut down.
       * @type {boolean}
       * @private
       */
      this.shutDown_ = false;
    };
  
    /**
     * Serializes this DataReceiver.
     * This will cancel a receive if one is in progress.
     * @return {!Promise.<SerializedDataReceiver>} A promise that will resolve to
     *     the serialization of this DataReceiver. If this DataReceiver has shut
     *     down, the promise will resolve to null.
     */
    DataReceiver.prototype.serialize = function() {
      if (this.shutDown_)
        return Promise.resolve(null);
  
      if (this.receive_) {
        this.receive_.dispatchFatalError(this.fatalErrorValue_);
        this.receive_ = null;
      }
      var serialized = new serialization.SerializedDataReceiver();
      serialized.source = this.router_.connector_.handle_;
      serialized.fatal_error_value = this.fatalErrorValue_;
      serialized.paused = this.paused_;
      serialized.pending_error = this.pendingError_;
      serialized.pending_data = [];
      $Array.forEach(this.pendingDataBuffers_, function(buffer) {
        serialized.pending_data.push(new Uint8Array(buffer));
      });
      this.router_.connector_.handle_ = null;
      this.router_.close();
      this.shutDown_ = true;
      return Promise.resolve(serialized);
    };
  
    /**
     * Deserializes a SerializedDataReceiver.
     * @param {SerializedDataReceiver} serialized The serialized DataReceiver.
     * @return {!DataReceiver} The deserialized DataReceiver.
     */
    DataReceiver.deserialize = function(serialized) {
      var receiver = $Object.create(DataReceiver.prototype);
      receiver.deserialize_(serialized);
      return receiver;
    };
  
    /**
     * Deserializes a SerializedDataReceiver into this DataReceiver.
     * @param {SerializedDataReceiver} serialized The serialized DataReceiver.
     * @private
     */
    DataReceiver.prototype.deserialize_ = function(serialized) {
      if (!serialized) {
        this.shutDown_ = true;
        return;
      }
      var pendingData = [];
      $Array.forEach(serialized.pending_data, function(data) {
        var buffer = new Uint8Array(data.length);
        buffer.set(data);
        pendingData.push(buffer.buffer);
      });
      this.init_(serialized.source,
                 serialized.fatal_error_value,
                 serialized.bytes_received,
                 serialized.pending_error,
                 pendingData,
                 serialized.paused);
    };
  
    /**
     * Receive data from the DataSource.
     * @return {Promise.<ArrayBuffer>} A promise to the received data. If an error
     *     occurs, the promise will reject with an Error object with a property
     *     error containing the error code.
     * @throws Will throw if this has encountered a fatal error or another receive
     *     is in progress.
     */
    DataReceiver.prototype.receive = function() {
      if (this.shutDown_)
        throw new Error('DataReceiver has been closed');
      if (this.receive_)
        throw new Error('Receive already in progress.');
      var receive = new PendingReceive();
      var promise = receive.getPromise();
      if (this.pendingError_ &&
          receive.dispatchError(this.pendingError_)) {
        this.pendingError_ = null;
        this.paused_ = true;
        return promise;
      }
      if (this.paused_) {
        this.source_.resume();
        this.paused_ = false;
      }
      this.receive_ = receive;
      this.dispatchData_();
      return promise;
    };
  
    DataReceiver.prototype.dispatchData_ = function() {
      if (!this.receive_) {
        this.close();
        return;
      }
      if (this.pendingDataBuffers_.length) {
        this.receive_.dispatchData(this.pendingDataBuffers_[0]);
        this.source_.reportBytesReceived(this.pendingDataBuffers_[0].byteLength);
        this.receive_ = null;
        this.pendingDataBuffers_.shift();
        if (this.pendingError_)
          this.pendingError_.queuePosition--;
      }
    };
  
    /**
     * Invoked by the DataSource when an error is encountered.
     * @param {number} offset The location at which the error occurred.
     * @param {number} error The error that occurred.
     * @private
     */
    DataReceiver.prototype.onError = function(error) {
      if (this.shutDown_)
        return;
  
      var pendingError = new serialization.PendingReceiveError();
      pendingError.error = error;
      pendingError.queuePosition = this.pendingDataBuffers_.length;
      if (this.receive_ && this.receive_.dispatchError(pendingError)) {
        this.receive_ = null;
        this.paused_ = true;
        return;
      }
      this.pendingError_ = pendingError;
    };
  
    DataReceiver.prototype.onData = function(data) {
      var buffer = new ArrayBuffer(data.length);
      var uintView = new Uint8Array(buffer);
      uintView.set(data);
      this.pendingDataBuffers_.push(buffer);
      if (this.receive_)
        this.dispatchData_();
    };
  
    return {DataReceiver: DataReceiver};
  });
  