• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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