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