Home | History | Annotate | Download | only in bindings
      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/bindings/router", [
      6   "mojo/public/js/bindings/codec",
      7   "mojo/public/js/bindings/connector",
      8   "mojo/public/js/bindings/validator",
      9 ], function(codec, connector, validator) {
     10 
     11   function Router(handle, connectorFactory) {
     12     if (connectorFactory === undefined)
     13       connectorFactory = connector.Connector;
     14     this.connector_ = new connectorFactory(handle);
     15     this.incomingReceiver_ = null;
     16     this.nextRequestID_ = 0;
     17     this.responders_ = {};
     18     this.payloadValidators_ = [];
     19 
     20     this.connector_.setIncomingReceiver({
     21         accept: this.handleIncomingMessage_.bind(this),
     22     });
     23     this.connector_.setErrorHandler({
     24         onError: this.handleConnectionError_.bind(this),
     25     });
     26   }
     27 
     28   Router.prototype.close = function() {
     29     this.responders_ = {};  // Drop any responders.
     30     this.connector_.close();
     31   };
     32 
     33   Router.prototype.accept = function(message) {
     34     this.connector_.accept(message);
     35   };
     36 
     37   Router.prototype.reject = function(message) {
     38     // TODO(mpcomplete): no way to trasmit errors over a Connection.
     39   };
     40 
     41   Router.prototype.acceptWithResponder = function(message, responder) {
     42     // Reserve 0 in case we want it to convey special meaning in the future.
     43     var requestID = this.nextRequestID_++;
     44     if (requestID == 0)
     45       requestID = this.nextRequestID_++;
     46 
     47     message.setRequestID(requestID);
     48     var result = this.connector_.accept(message);
     49 
     50     this.responders_[requestID] = responder;
     51 
     52     // TODO(mpcomplete): accept should return a Promise too, maybe?
     53     if (result)
     54       return Promise.resolve();
     55     return Promise.reject(Error("Connection error"));
     56   };
     57 
     58   Router.prototype.setIncomingReceiver = function(receiver) {
     59     this.incomingReceiver_ = receiver;
     60   };
     61 
     62   Router.prototype.setPayloadValidators = function(payloadValidators) {
     63     this.payloadValidators_ = payloadValidators;
     64   };
     65 
     66   Router.prototype.encounteredError = function() {
     67     return this.connector_.encounteredError();
     68   };
     69 
     70   Router.prototype.handleIncomingMessage_ = function(message) {
     71     var noError = validator.validationError.NONE;
     72     var messageValidator = new validator.Validator(message);
     73     var err = messageValidator.validateMessageHeader();
     74     for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
     75       err = this.payloadValidators_[i](messageValidator);
     76 
     77     if (err == noError)
     78       this.handleValidIncomingMessage_(message);
     79     else
     80       this.handleInvalidIncomingMessage_(message, err);
     81   };
     82 
     83   Router.prototype.handleValidIncomingMessage_ = function(message) {
     84     if (message.expectsResponse()) {
     85       if (this.incomingReceiver_) {
     86         this.incomingReceiver_.acceptWithResponder(message, this);
     87       } else {
     88         // If we receive a request expecting a response when the client is not
     89         // listening, then we have no choice but to tear down the pipe.
     90         this.close();
     91       }
     92     } else if (message.isResponse()) {
     93       var reader = new codec.MessageReader(message);
     94       var requestID = reader.requestID;
     95       var responder = this.responders_[requestID];
     96       delete this.responders_[requestID];
     97       responder.accept(message);
     98     } else {
     99       if (this.incomingReceiver_)
    100         this.incomingReceiver_.accept(message);
    101     }
    102   }
    103 
    104   Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
    105     this.close();
    106   }
    107 
    108   Router.prototype.handleConnectionError_ = function(result) {
    109     for (var each in this.responders_)
    110       this.responders_[each].reject(result);
    111     this.close();
    112   };
    113 
    114   // The TestRouter subclass is only intended to be used in unit tests.
    115   // It defeats valid message handling and delgates invalid message handling.
    116 
    117   function TestRouter(handle, connectorFactory) {
    118     Router.call(this, handle, connectorFactory);
    119   }
    120 
    121   TestRouter.prototype = Object.create(Router.prototype);
    122 
    123   TestRouter.prototype.handleValidIncomingMessage_ = function() {
    124   };
    125 
    126   TestRouter.prototype.handleInvalidIncomingMessage_ =
    127       function(message, error) {
    128         this.validationErrorHandler(error);
    129       };
    130 
    131   var exports = {};
    132   exports.Router = Router;
    133   exports.TestRouter = TestRouter;
    134   return exports;
    135 });
    136