Home | History | Annotate | Download | only in js
      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.
      4 
      5 define("mojo/public/js/router", [
      6   "console",
      7   "mojo/public/js/codec",
      8   "mojo/public/js/core",
      9   "mojo/public/js/connector",
     10   "mojo/public/js/lib/control_message_handler",
     11   "mojo/public/js/validator",
     12 ], function(console, codec, core, connector, controlMessageHandler, validator) {
     13 
     14   var Connector = connector.Connector;
     15   var MessageReader = codec.MessageReader;
     16   var Validator = validator.Validator;
     17   var ControlMessageHandler = controlMessageHandler.ControlMessageHandler;
     18 
     19   function Router(handle, interface_version, connectorFactory) {
     20     if (!core.isHandle(handle))
     21       throw new Error("Router constructor: Not a handle");
     22     if (connectorFactory === undefined)
     23       connectorFactory = Connector;
     24     this.connector_ = new connectorFactory(handle);
     25     this.incomingReceiver_ = null;
     26     this.errorHandler_ = null;
     27     this.nextRequestID_ = 0;
     28     this.completers_ = new Map();
     29     this.payloadValidators_ = [];
     30     this.testingController_ = null;
     31 
     32     if (interface_version !== undefined) {
     33       this.controlMessageHandler_ = new
     34           ControlMessageHandler(interface_version);
     35     }
     36 
     37     this.connector_.setIncomingReceiver({
     38         accept: this.handleIncomingMessage_.bind(this),
     39     });
     40     this.connector_.setErrorHandler({
     41         onError: this.handleConnectionError_.bind(this),
     42     });
     43   }
     44 
     45   Router.prototype.close = function() {
     46     this.completers_.clear();  // Drop any responders.
     47     this.connector_.close();
     48     this.testingController_ = null;
     49   };
     50 
     51   Router.prototype.accept = function(message) {
     52     this.connector_.accept(message);
     53   };
     54 
     55   Router.prototype.reject = function(message) {
     56     // TODO(mpcomplete): no way to trasmit errors over a Connection.
     57   };
     58 
     59   Router.prototype.acceptAndExpectResponse = function(message) {
     60     // Reserve 0 in case we want it to convey special meaning in the future.
     61     var requestID = this.nextRequestID_++;
     62     if (requestID == 0)
     63       requestID = this.nextRequestID_++;
     64 
     65     message.setRequestID(requestID);
     66     var result = this.connector_.accept(message);
     67     if (!result)
     68       return Promise.reject(Error("Connection error"));
     69 
     70     var completer = {};
     71     this.completers_.set(requestID, completer);
     72     return new Promise(function(resolve, reject) {
     73       completer.resolve = resolve;
     74       completer.reject = reject;
     75     });
     76   };
     77 
     78   Router.prototype.setIncomingReceiver = function(receiver) {
     79     this.incomingReceiver_ = receiver;
     80   };
     81 
     82   Router.prototype.setPayloadValidators = function(payloadValidators) {
     83     this.payloadValidators_ = payloadValidators;
     84   };
     85 
     86   Router.prototype.setErrorHandler = function(handler) {
     87     this.errorHandler_ = handler;
     88   };
     89 
     90   Router.prototype.encounteredError = function() {
     91     return this.connector_.encounteredError();
     92   };
     93 
     94   Router.prototype.enableTestingMode = function() {
     95     this.testingController_ = new RouterTestingController(this.connector_);
     96     return this.testingController_;
     97   };
     98 
     99   Router.prototype.handleIncomingMessage_ = function(message) {
    100     var noError = validator.validationError.NONE;
    101     var messageValidator = new Validator(message);
    102     var err = messageValidator.validateMessageHeader();
    103     for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
    104       err = this.payloadValidators_[i](messageValidator);
    105 
    106     if (err == noError)
    107       this.handleValidIncomingMessage_(message);
    108     else
    109       this.handleInvalidIncomingMessage_(message, err);
    110   };
    111 
    112   Router.prototype.handleValidIncomingMessage_ = function(message) {
    113     if (this.testingController_)
    114       return;
    115 
    116     if (message.expectsResponse()) {
    117       if (controlMessageHandler.isControlMessage(message)) {
    118         if (this.controlMessageHandler_) {
    119           this.controlMessageHandler_.acceptWithResponder(message, this);
    120         } else {
    121           this.close();
    122         }
    123       } else if (this.incomingReceiver_) {
    124         this.incomingReceiver_.acceptWithResponder(message, this);
    125       } else {
    126         // If we receive a request expecting a response when the client is not
    127         // listening, then we have no choice but to tear down the pipe.
    128         this.close();
    129       }
    130     } else if (message.isResponse()) {
    131       var reader = new MessageReader(message);
    132       var requestID = reader.requestID;
    133       var completer = this.completers_.get(requestID);
    134       if (completer) {
    135         this.completers_.delete(requestID);
    136         completer.resolve(message);
    137       } else {
    138         console.log("Unexpected response with request ID: " + requestID);
    139       }
    140     } else {
    141       if (controlMessageHandler.isControlMessage(message)) {
    142         if (this.controlMessageHandler_) {
    143           var ok = this.controlMessageHandler_.accept(message);
    144           if (ok) return;
    145         }
    146         this.close();
    147       } else if (this.incomingReceiver_) {
    148         this.incomingReceiver_.accept(message);
    149       }
    150     }
    151   };
    152 
    153   Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
    154     if (!this.testingController_) {
    155       // TODO(yzshen): Consider notifying the embedder.
    156       // TODO(yzshen): This should also trigger connection error handler.
    157       // Consider making accept() return a boolean and let the connector deal
    158       // with this, as the C++ code does.
    159       console.log("Invalid message: " + validator.validationError[error]);
    160 
    161       this.close();
    162       return;
    163     }
    164 
    165     this.testingController_.onInvalidIncomingMessage(error);
    166   };
    167 
    168   Router.prototype.handleConnectionError_ = function(result) {
    169     this.completers_.forEach(function(value) {
    170       value.reject(result);
    171     });
    172     if (this.errorHandler_)
    173       this.errorHandler_();
    174     this.close();
    175   };
    176 
    177   // The RouterTestingController is used in unit tests. It defeats valid message
    178   // handling and delgates invalid message handling.
    179 
    180   function RouterTestingController(connector) {
    181     this.connector_ = connector;
    182     this.invalidMessageHandler_ = null;
    183   }
    184 
    185   RouterTestingController.prototype.waitForNextMessage = function() {
    186     this.connector_.waitForNextMessageForTesting();
    187   };
    188 
    189   RouterTestingController.prototype.setInvalidIncomingMessageHandler =
    190       function(callback) {
    191     this.invalidMessageHandler_ = callback;
    192   };
    193 
    194   RouterTestingController.prototype.onInvalidIncomingMessage =
    195       function(error) {
    196     if (this.invalidMessageHandler_)
    197       this.invalidMessageHandler_(error);
    198   };
    199 
    200   var exports = {};
    201   exports.Router = Router;
    202   return exports;
    203 });
    204