OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 (function() { | 5 (function() { |
6 var internal = mojo.internal; | 6 var internal = mojo.internal; |
7 | 7 |
8 function Router(handle, interface_version, connectorFactory) { | 8 /** |
9 if (!(handle instanceof MojoHandle)) | 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)) { |
10 throw new Error("Router constructor: Not a handle"); | 43 throw new Error("Router constructor: Not a handle"); |
11 if (connectorFactory === undefined) | 44 } |
12 connectorFactory = internal.Connector; | 45 if (setInterfaceIdNamespaceBit === undefined) { |
13 this.connector_ = new connectorFactory(handle); | 46 setInterfaceIdNamespaceBit = false; |
14 this.incomingReceiver_ = null; | 47 } |
15 this.errorHandler_ = null; | 48 |
16 this.nextRequestID_ = 0; | 49 this.connector_ = new internal.Connector(handle); |
17 this.completers_ = new Map(); | |
18 this.payloadValidators_ = []; | |
19 this.testingController_ = null; | |
20 | |
21 if (interface_version !== undefined) { | |
22 this.controlMessageHandler_ = new | |
23 internal.ControlMessageHandler(interface_version); | |
24 } | |
25 | 50 |
26 this.connector_.setIncomingReceiver({ | 51 this.connector_.setIncomingReceiver({ |
27 accept: this.handleIncomingMessage_.bind(this), | 52 accept: this.accept.bind(this), |
28 }); | 53 }); |
29 this.connector_.setErrorHandler({ | 54 this.connector_.setErrorHandler({ |
30 onError: this.handleConnectionError_.bind(this), | 55 onError: this.onPipeConnectionError.bind(this), |
31 }); | 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(); |
32 } | 68 } |
33 | 69 |
34 Router.prototype.close = function() { | 70 Router.prototype.associateInterface = function(handleToSend) { |
35 this.completers_.clear(); // Drop any responders. | 71 if (!handleToSend.pendingAssociation()) { |
36 this.connector_.close(); | 72 return internal.kInvalidInterfaceId; |
37 this.testingController_ = null; | 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 var endpoint = this.endpoints_.get(interfaceId); |
| 169 |
| 170 if (!endpoint) { |
| 171 endpoint = new InterfaceEndpoint(this, interfaceId); |
| 172 this.endpoints_.set(interfaceId, endpoint); |
| 173 |
| 174 check(!endpoint.handleCreated); |
| 175 |
| 176 if (this.encounteredError_) { |
| 177 this.updateEndpointStateMayRemove(endpoint, |
| 178 EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); |
| 179 } |
| 180 } else { |
| 181 // If the endpoint already exist, it is because we have received a |
| 182 // notification that the peer endpoint has closed. |
| 183 check(!endpoint.closed); |
| 184 check(endpoint.peerClosed); |
| 185 |
| 186 if (endpoint.handleCreated) { |
| 187 return new internal.InterfaceEndpointHandle(); |
| 188 } |
| 189 } |
| 190 |
| 191 endpoint.handleCreated = true; |
| 192 return new internal.InterfaceEndpointHandle(interfaceId, this); |
38 }; | 193 }; |
39 | 194 |
40 Router.prototype.accept = function(message) { | 195 Router.prototype.accept = function(message) { |
41 this.connector_.accept(message); | |
42 }; | |
43 | |
44 Router.prototype.reject = function(message) { | |
45 // TODO(mpcomplete): no way to trasmit errors over a Connection. | |
46 }; | |
47 | |
48 Router.prototype.acceptAndExpectResponse = function(message) { | |
49 // Reserve 0 in case we want it to convey special meaning in the future. | |
50 var requestID = this.nextRequestID_++; | |
51 if (requestID == 0) | |
52 requestID = this.nextRequestID_++; | |
53 | |
54 message.setRequestID(requestID); | |
55 var result = this.connector_.accept(message); | |
56 if (!result) | |
57 return Promise.reject(Error("Connection error")); | |
58 | |
59 var completer = {}; | |
60 this.completers_.set(requestID, completer); | |
61 return new Promise(function(resolve, reject) { | |
62 completer.resolve = resolve; | |
63 completer.reject = reject; | |
64 }); | |
65 }; | |
66 | |
67 Router.prototype.setIncomingReceiver = function(receiver) { | |
68 this.incomingReceiver_ = receiver; | |
69 }; | |
70 | |
71 Router.prototype.setPayloadValidators = function(payloadValidators) { | |
72 this.payloadValidators_ = payloadValidators; | |
73 }; | |
74 | |
75 Router.prototype.setErrorHandler = function(handler) { | |
76 this.errorHandler_ = handler; | |
77 }; | |
78 | |
79 Router.prototype.encounteredError = function() { | |
80 return this.connector_.encounteredError(); | |
81 }; | |
82 | |
83 Router.prototype.enableTestingMode = function() { | |
84 this.testingController_ = new RouterTestingController(this.connector_); | |
85 return this.testingController_; | |
86 }; | |
87 | |
88 Router.prototype.handleIncomingMessage_ = function(message) { | |
89 var noError = internal.validationError.NONE; | |
90 var messageValidator = new internal.Validator(message); | 196 var messageValidator = new internal.Validator(message); |
91 var err = messageValidator.validateMessageHeader(); | 197 var err = messageValidator.validateMessageHeader(); |
92 for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i) | 198 |
93 err = this.payloadValidators_[i](messageValidator); | 199 var ok = false; |
94 | 200 if (err !== internal.validationError.NONE) { |
95 if (err == noError) | 201 internal.reportValidationError(err); |
96 this.handleValidIncomingMessage_(message); | 202 } else if (message.deserializeAssociatedEndpointHandles(this)) { |
97 else | 203 if (internal.isPipeControlMessage(message)) { |
98 this.handleInvalidIncomingMessage_(message, err); | 204 ok = this.controlMessageHandler_.accept(message); |
99 }; | 205 } else { |
100 | 206 var interfaceId = message.getInterfaceId(); |
101 Router.prototype.handleValidIncomingMessage_ = function(message) { | 207 var endpoint = this.endpoints_.get(interfaceId); |
102 if (this.testingController_) | 208 if (!endpoint || endpoint.closed) { |
| 209 return true; |
| 210 } |
| 211 |
| 212 if (!endpoint.client) { |
| 213 // We need to wait until a client is attached in order to dispatch |
| 214 // further messages. |
| 215 this.cachedMessageData = {message: message, |
| 216 messageValidator: messageValidator}; |
| 217 this.connector_.pauseIncomingMethodCallProcessing(); |
| 218 return true; |
| 219 } |
| 220 ok = endpoint.client.handleIncomingMessage(message, messageValidator); |
| 221 } |
| 222 } |
| 223 return ok; |
| 224 }; |
| 225 |
| 226 Router.prototype.close = function() { |
| 227 this.connector_.close(); |
| 228 // Closing the message pipe won't trigger connection error handler. |
| 229 // Explicitly call onPipeConnectionError() so that associated endpoints |
| 230 // will get notified. |
| 231 this.onPipeConnectionError(); |
| 232 }; |
| 233 |
| 234 Router.prototype.waitForNextMessageForTesting = function() { |
| 235 this.connector_.waitForNextMessageForTesting(); |
| 236 }; |
| 237 |
| 238 Router.prototype.onPeerAssociatedEndpointClosed = function(interfaceId, |
| 239 reason) { |
| 240 check(!internal.isMasterInterfaceId(interfaceId) || reason); |
| 241 |
| 242 var endpoint = this.endpoints_.get(interfaceId); |
| 243 if (!endpoint) { |
| 244 endpoint = new InterfaceEndpoint(this, interfaceId); |
| 245 this.endpoints_.set(interfaceId, endpoint); |
| 246 } |
| 247 |
| 248 if (reason) { |
| 249 endpoint.disconnectReason = reason; |
| 250 } |
| 251 |
| 252 if (!endpoint.peerClosed) { |
| 253 if (endpoint.client) { |
| 254 setTimeout(endpoint.client.notifyError.bind(endpoint.client, reason), |
| 255 0); |
| 256 } |
| 257 this.updateEndpointStateMayRemove(endpoint, |
| 258 EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); |
| 259 } |
| 260 return true; |
| 261 }; |
| 262 |
| 263 Router.prototype.onPipeConnectionError = function() { |
| 264 this.encounteredError_ = true; |
| 265 |
| 266 for (var endpoint of this.endpoints_.values()) { |
| 267 if (endpoint.client) { |
| 268 setTimeout( |
| 269 endpoint.client.notifyError.bind( |
| 270 endpoint.client, endpoint.disconnectReason), |
| 271 0); |
| 272 } |
| 273 this.updateEndpointStateMayRemove(endpoint, |
| 274 EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); |
| 275 } |
| 276 }; |
| 277 |
| 278 Router.prototype.closeEndpointHandle = function(interfaceId, reason) { |
| 279 if (!internal.isValidInterfaceId(interfaceId)) { |
103 return; | 280 return; |
104 | 281 } |
105 if (message.expectsResponse()) { | 282 var endpoint = this.endpoints_.get(interfaceId); |
106 if (internal.isInterfaceControlMessage(message)) { | 283 check(endpoint); |
107 if (this.controlMessageHandler_) { | 284 check(!endpoint.client); |
108 this.controlMessageHandler_.acceptWithResponder(message, this); | 285 check(!endpoint.closed); |
109 } else { | 286 |
110 this.close(); | 287 this.updateEndpointStateMayRemove(endpoint, |
111 } | 288 EndpointStateUpdateType.ENDPOINT_CLOSED); |
112 } else if (this.incomingReceiver_) { | 289 |
113 this.incomingReceiver_.acceptWithResponder(message, this); | 290 if (!internal.isMasterInterfaceId(interfaceId) || reason) { |
114 } else { | 291 this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason); |
115 // If we receive a request expecting a response when the client is not | 292 } |
116 // listening, then we have no choice but to tear down the pipe. | 293 |
117 this.close(); | 294 if (this.cachedMessageData && interfaceId === |
118 } | 295 this.cachedMessageData.message.getInterfaceId()) { |
119 } else if (message.isResponse()) { | 296 this.cachedMessageData = null; |
120 var reader = new internal.MessageReader(message); | 297 this.connector_.resumeIncomingMethodCallProcessing(); |
121 var requestID = reader.requestID; | 298 } |
122 var completer = this.completers_.get(requestID); | 299 }; |
123 if (completer) { | 300 |
124 this.completers_.delete(requestID); | 301 Router.prototype.updateEndpointStateMayRemove = function(endpoint, |
125 completer.resolve(message); | 302 endpointStateUpdateType) { |
126 } else { | 303 if (endpointStateUpdateType === EndpointStateUpdateType.ENDPOINT_CLOSED) { |
127 console.log("Unexpected response with request ID: " + requestID); | 304 endpoint.closed = true; |
128 } | |
129 } else { | 305 } else { |
130 if (internal.isInterfaceControlMessage(message)) { | 306 endpoint.peerClosed = true; |
131 if (this.controlMessageHandler_) { | 307 } |
132 var ok = this.controlMessageHandler_.accept(message); | 308 if (endpoint.closed && endpoint.peerClosed) { |
133 if (ok) return; | 309 this.endpoints_.delete(endpoint.id); |
134 } | 310 } |
135 this.close(); | |
136 } else if (this.incomingReceiver_) { | |
137 this.incomingReceiver_.accept(message); | |
138 } | |
139 } | |
140 }; | |
141 | |
142 Router.prototype.handleInvalidIncomingMessage_ = function(message, error) { | |
143 if (!this.testingController_) { | |
144 // TODO(yzshen): Consider notifying the embedder. | |
145 // TODO(yzshen): This should also trigger connection error handler. | |
146 // Consider making accept() return a boolean and let the connector deal | |
147 // with this, as the C++ code does. | |
148 console.log("Invalid message: " + internal.validationError[error]); | |
149 | |
150 this.close(); | |
151 return; | |
152 } | |
153 | |
154 this.testingController_.onInvalidIncomingMessage(error); | |
155 }; | |
156 | |
157 Router.prototype.handleConnectionError_ = function(result) { | |
158 this.completers_.forEach(function(value) { | |
159 value.reject(result); | |
160 }); | |
161 if (this.errorHandler_) | |
162 this.errorHandler_(); | |
163 this.close(); | |
164 }; | |
165 | |
166 // The RouterTestingController is used in unit tests. It defeats valid message | |
167 // handling and delgates invalid message handling. | |
168 | |
169 function RouterTestingController(connector) { | |
170 this.connector_ = connector; | |
171 this.invalidMessageHandler_ = null; | |
172 } | |
173 | |
174 RouterTestingController.prototype.waitForNextMessage = function() { | |
175 this.connector_.waitForNextMessageForTesting(); | |
176 }; | |
177 | |
178 RouterTestingController.prototype.setInvalidIncomingMessageHandler = | |
179 function(callback) { | |
180 this.invalidMessageHandler_ = callback; | |
181 }; | |
182 | |
183 RouterTestingController.prototype.onInvalidIncomingMessage = | |
184 function(error) { | |
185 if (this.invalidMessageHandler_) | |
186 this.invalidMessageHandler_(error); | |
187 }; | 311 }; |
188 | 312 |
189 internal.Router = Router; | 313 internal.Router = Router; |
190 })(); | 314 })(); |
OLD | NEW |