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(function() { 6 var internal = mojo.internal; 7 8 /** 9 * The state of |endpoint|. If both the endpoint and its peer have been 10 * closed, removes it from |endpoints_|. 11 * @enum {string} 12 */ 13 var EndpointStateUpdateType = { 14 ENDPOINT_CLOSED: 'endpoint_closed', 15 PEER_ENDPOINT_CLOSED: 'peer_endpoint_closed' 16 }; 17 18 function check(condition, output) { 19 if (!condition) { 20 // testharness.js does not rethrow errors so the error stack needs to be 21 // included as a string in the error we throw for debugging layout tests. 22 throw new Error((new Error()).stack); 23 } 24 } 25 26 function InterfaceEndpoint(router, interfaceId) { 27 this.router_ = router; 28 this.id = interfaceId; 29 this.closed = false; 30 this.peerClosed = false; 31 this.handleCreated = false; 32 this.disconnectReason = null; 33 this.client = null; 34 } 35 36 InterfaceEndpoint.prototype.sendMessage = function(message) { 37 message.setInterfaceId(this.id); 38 return this.router_.connector_.accept(message); 39 }; 40 41 function Router(handle, setInterfaceIdNamespaceBit) { 42 if (!(handle instanceof MojoHandle)) { 43 throw new Error("Router constructor: Not a handle"); 44 } 45 if (setInterfaceIdNamespaceBit === undefined) { 46 setInterfaceIdNamespaceBit = false; 47 } 48 49 this.connector_ = new internal.Connector(handle); 50 51 this.connector_.setIncomingReceiver({ 52 accept: this.accept.bind(this), 53 }); 54 this.connector_.setErrorHandler({ 55 onError: this.onPipeConnectionError.bind(this), 56 }); 57 58 this.setInterfaceIdNamespaceBit_ = setInterfaceIdNamespaceBit; 59 // |cachedMessageData| caches infomation about a message, so it can be 60 // processed later if a client is not yet attached to the target endpoint. 61 this.cachedMessageData = null; 62 this.controlMessageHandler_ = new internal.PipeControlMessageHandler(this); 63 this.controlMessageProxy_ = 64 new internal.PipeControlMessageProxy(this.connector_); 65 this.nextInterfaceIdValue_ = 1; 66 this.encounteredError_ = false; 67 this.endpoints_ = new Map(); 68 } 69 70 Router.prototype.associateInterface = function(handleToSend) { 71 if (!handleToSend.pendingAssociation()) { 72 return internal.kInvalidInterfaceId; 73 } 74 75 var id = 0; 76 do { 77 if (this.nextInterfaceIdValue_ >= internal.kInterfaceIdNamespaceMask) { 78 this.nextInterfaceIdValue_ = 1; 79 } 80 id = this.nextInterfaceIdValue_++; 81 if (this.setInterfaceIdNamespaceBit_) { 82 id += internal.kInterfaceIdNamespaceMask; 83 } 84 } while (this.endpoints_.has(id)); 85 86 var endpoint = new InterfaceEndpoint(this, id); 87 this.endpoints_.set(id, endpoint); 88 if (this.encounteredError_) { 89 this.updateEndpointStateMayRemove(endpoint, 90 EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); 91 } 92 endpoint.handleCreated = true; 93 94 if (!handleToSend.notifyAssociation(id, this)) { 95 // The peer handle of |handleToSend|, which is supposed to join this 96 // associated group, has been closed. 97 this.updateEndpointStateMayRemove(endpoint, 98 EndpointStateUpdateType.ENDPOINT_CLOSED); 99 100 pipeControlMessageproxy.notifyPeerEndpointClosed(id, 101 handleToSend.disconnectReason()); 102 } 103 104 return id; 105 }; 106 107 Router.prototype.attachEndpointClient = function( 108 interfaceEndpointHandle, interfaceEndpointClient) { 109 check(internal.isValidInterfaceId(interfaceEndpointHandle.id())); 110 check(interfaceEndpointClient); 111 112 var endpoint = this.endpoints_.get(interfaceEndpointHandle.id()); 113 check(endpoint); 114 check(!endpoint.client); 115 check(!endpoint.closed); 116 endpoint.client = interfaceEndpointClient; 117 118 if (endpoint.peerClosed) { 119 setTimeout(endpoint.client.notifyError.bind(endpoint.client), 0); 120 } 121 122 if (this.cachedMessageData && interfaceEndpointHandle.id() === 123 this.cachedMessageData.message.getInterfaceId()) { 124 setTimeout((function() { 125 if (!this.cachedMessageData) { 126 return; 127 } 128 129 var targetEndpoint = this.endpoints_.get( 130 this.cachedMessageData.message.getInterfaceId()); 131 // Check that the target endpoint's client still exists. 132 if (targetEndpoint && targetEndpoint.client) { 133 var message = this.cachedMessageData.message; 134 var messageValidator = this.cachedMessageData.messageValidator; 135 this.cachedMessageData = null; 136 this.connector_.resumeIncomingMethodCallProcessing(); 137 var ok = endpoint.client.handleIncomingMessage(message, 138 messageValidator); 139 140 // Handle invalid cached incoming message. 141 if (!internal.isTestingMode() && !ok) { 142 this.connector_.handleError(true, true); 143 } 144 } 145 }).bind(this), 0); 146 } 147 148 return endpoint; 149 }; 150 151 Router.prototype.detachEndpointClient = function( 152 interfaceEndpointHandle) { 153 check(internal.isValidInterfaceId(interfaceEndpointHandle.id())); 154 var endpoint = this.endpoints_.get(interfaceEndpointHandle.id()); 155 check(endpoint); 156 check(endpoint.client); 157 check(!endpoint.closed); 158 159 endpoint.client = null; 160 }; 161 162 Router.prototype.createLocalEndpointHandle = function( 163 interfaceId) { 164 if (!internal.isValidInterfaceId(interfaceId)) { 165 return new internal.InterfaceEndpointHandle(); 166 } 167 168 // Unless it is the master ID, |interfaceId| is from the remote side and 169 // therefore its namespace bit is supposed to be different than the value 170 // that this router would use. 171 if (!internal.isMasterInterfaceId(interfaceId) && 172 this.setInterfaceIdNamespaceBit_ === 173 internal.hasInterfaceIdNamespaceBitSet(interfaceId)) { 174 return new internal.InterfaceEndpointHandle(); 175 } 176 177 var endpoint = this.endpoints_.get(interfaceId); 178 179 if (!endpoint) { 180 endpoint = new InterfaceEndpoint(this, interfaceId); 181 this.endpoints_.set(interfaceId, endpoint); 182 183 check(!endpoint.handleCreated); 184 185 if (this.encounteredError_) { 186 this.updateEndpointStateMayRemove(endpoint, 187 EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); 188 } 189 } else { 190 // If the endpoint already exist, it is because we have received a 191 // notification that the peer endpoint has closed. 192 check(!endpoint.closed); 193 check(endpoint.peerClosed); 194 195 if (endpoint.handleCreated) { 196 return new internal.InterfaceEndpointHandle(); 197 } 198 } 199 200 endpoint.handleCreated = true; 201 return new internal.InterfaceEndpointHandle(interfaceId, this); 202 }; 203 204 Router.prototype.accept = function(message) { 205 var messageValidator = new internal.Validator(message); 206 var err = messageValidator.validateMessageHeader(); 207 208 var ok = false; 209 if (err !== internal.validationError.NONE) { 210 internal.reportValidationError(err); 211 } else if (message.deserializeAssociatedEndpointHandles(this)) { 212 if (internal.isPipeControlMessage(message)) { 213 ok = this.controlMessageHandler_.accept(message); 214 } else { 215 var interfaceId = message.getInterfaceId(); 216 var endpoint = this.endpoints_.get(interfaceId); 217 if (!endpoint || endpoint.closed) { 218 return true; 219 } 220 221 if (!endpoint.client) { 222 // We need to wait until a client is attached in order to dispatch 223 // further messages. 224 this.cachedMessageData = {message: message, 225 messageValidator: messageValidator}; 226 this.connector_.pauseIncomingMethodCallProcessing(); 227 return true; 228 } 229 ok = endpoint.client.handleIncomingMessage(message, messageValidator); 230 } 231 } 232 return ok; 233 }; 234 235 Router.prototype.close = function() { 236 this.connector_.close(); 237 // Closing the message pipe won't trigger connection error handler. 238 // Explicitly call onPipeConnectionError() so that associated endpoints 239 // will get notified. 240 this.onPipeConnectionError(); 241 }; 242 243 Router.prototype.onPeerAssociatedEndpointClosed = function(interfaceId, 244 reason) { 245 var endpoint = this.endpoints_.get(interfaceId); 246 if (!endpoint) { 247 endpoint = new InterfaceEndpoint(this, interfaceId); 248 this.endpoints_.set(interfaceId, endpoint); 249 } 250 251 if (reason) { 252 endpoint.disconnectReason = reason; 253 } 254 255 if (!endpoint.peerClosed) { 256 if (endpoint.client) { 257 setTimeout(endpoint.client.notifyError.bind(endpoint.client, reason), 258 0); 259 } 260 this.updateEndpointStateMayRemove(endpoint, 261 EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); 262 } 263 return true; 264 }; 265 266 Router.prototype.onPipeConnectionError = function() { 267 this.encounteredError_ = true; 268 269 for (var endpoint of this.endpoints_.values()) { 270 if (endpoint.client) { 271 setTimeout( 272 endpoint.client.notifyError.bind( 273 endpoint.client, endpoint.disconnectReason), 274 0); 275 } 276 this.updateEndpointStateMayRemove(endpoint, 277 EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); 278 } 279 }; 280 281 Router.prototype.closeEndpointHandle = function(interfaceId, reason) { 282 if (!internal.isValidInterfaceId(interfaceId)) { 283 return; 284 } 285 var endpoint = this.endpoints_.get(interfaceId); 286 check(endpoint); 287 check(!endpoint.client); 288 check(!endpoint.closed); 289 290 this.updateEndpointStateMayRemove(endpoint, 291 EndpointStateUpdateType.ENDPOINT_CLOSED); 292 293 if (!internal.isMasterInterfaceId(interfaceId) || reason) { 294 this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason); 295 } 296 297 if (this.cachedMessageData && interfaceId === 298 this.cachedMessageData.message.getInterfaceId()) { 299 this.cachedMessageData = null; 300 this.connector_.resumeIncomingMethodCallProcessing(); 301 } 302 }; 303 304 Router.prototype.updateEndpointStateMayRemove = function(endpoint, 305 endpointStateUpdateType) { 306 if (endpointStateUpdateType === EndpointStateUpdateType.ENDPOINT_CLOSED) { 307 endpoint.closed = true; 308 } else { 309 endpoint.peerClosed = true; 310 } 311 if (endpoint.closed && endpoint.peerClosed) { 312 this.endpoints_.delete(endpoint.id); 313 } 314 }; 315 316 internal.Router = Router; 317})(); 318