318 lines
10 KiB
JavaScript
318 lines
10 KiB
JavaScript
// Copyright 2014 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
(function() {
|
|
var internal = mojo.internal;
|
|
|
|
/**
|
|
* The state of |endpoint|. If both the endpoint and its peer have been
|
|
* closed, removes it from |endpoints_|.
|
|
* @enum {string}
|
|
*/
|
|
var EndpointStateUpdateType = {
|
|
ENDPOINT_CLOSED: 'endpoint_closed',
|
|
PEER_ENDPOINT_CLOSED: 'peer_endpoint_closed'
|
|
};
|
|
|
|
function check(condition, output) {
|
|
if (!condition) {
|
|
// testharness.js does not rethrow errors so the error stack needs to be
|
|
// included as a string in the error we throw for debugging layout tests.
|
|
throw new Error((new Error()).stack);
|
|
}
|
|
}
|
|
|
|
function InterfaceEndpoint(router, interfaceId) {
|
|
this.router_ = router;
|
|
this.id = interfaceId;
|
|
this.closed = false;
|
|
this.peerClosed = false;
|
|
this.handleCreated = false;
|
|
this.disconnectReason = null;
|
|
this.client = null;
|
|
}
|
|
|
|
InterfaceEndpoint.prototype.sendMessage = function(message) {
|
|
message.setInterfaceId(this.id);
|
|
return this.router_.connector_.accept(message);
|
|
};
|
|
|
|
function Router(handle, setInterfaceIdNamespaceBit) {
|
|
if (!(handle instanceof MojoHandle)) {
|
|
throw new Error("Router constructor: Not a handle");
|
|
}
|
|
if (setInterfaceIdNamespaceBit === undefined) {
|
|
setInterfaceIdNamespaceBit = false;
|
|
}
|
|
|
|
this.connector_ = new internal.Connector(handle);
|
|
|
|
this.connector_.setIncomingReceiver({
|
|
accept: this.accept.bind(this),
|
|
});
|
|
this.connector_.setErrorHandler({
|
|
onError: this.onPipeConnectionError.bind(this),
|
|
});
|
|
|
|
this.setInterfaceIdNamespaceBit_ = setInterfaceIdNamespaceBit;
|
|
// |cachedMessageData| caches infomation about a message, so it can be
|
|
// processed later if a client is not yet attached to the target endpoint.
|
|
this.cachedMessageData = null;
|
|
this.controlMessageHandler_ = new internal.PipeControlMessageHandler(this);
|
|
this.controlMessageProxy_ =
|
|
new internal.PipeControlMessageProxy(this.connector_);
|
|
this.nextInterfaceIdValue_ = 1;
|
|
this.encounteredError_ = false;
|
|
this.endpoints_ = new Map();
|
|
}
|
|
|
|
Router.prototype.associateInterface = function(handleToSend) {
|
|
if (!handleToSend.pendingAssociation()) {
|
|
return internal.kInvalidInterfaceId;
|
|
}
|
|
|
|
var id = 0;
|
|
do {
|
|
if (this.nextInterfaceIdValue_ >= internal.kInterfaceIdNamespaceMask) {
|
|
this.nextInterfaceIdValue_ = 1;
|
|
}
|
|
id = this.nextInterfaceIdValue_++;
|
|
if (this.setInterfaceIdNamespaceBit_) {
|
|
id += internal.kInterfaceIdNamespaceMask;
|
|
}
|
|
} while (this.endpoints_.has(id));
|
|
|
|
var endpoint = new InterfaceEndpoint(this, id);
|
|
this.endpoints_.set(id, endpoint);
|
|
if (this.encounteredError_) {
|
|
this.updateEndpointStateMayRemove(endpoint,
|
|
EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
|
|
}
|
|
endpoint.handleCreated = true;
|
|
|
|
if (!handleToSend.notifyAssociation(id, this)) {
|
|
// The peer handle of |handleToSend|, which is supposed to join this
|
|
// associated group, has been closed.
|
|
this.updateEndpointStateMayRemove(endpoint,
|
|
EndpointStateUpdateType.ENDPOINT_CLOSED);
|
|
|
|
pipeControlMessageproxy.notifyPeerEndpointClosed(id,
|
|
handleToSend.disconnectReason());
|
|
}
|
|
|
|
return id;
|
|
};
|
|
|
|
Router.prototype.attachEndpointClient = function(
|
|
interfaceEndpointHandle, interfaceEndpointClient) {
|
|
check(internal.isValidInterfaceId(interfaceEndpointHandle.id()));
|
|
check(interfaceEndpointClient);
|
|
|
|
var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
|
|
check(endpoint);
|
|
check(!endpoint.client);
|
|
check(!endpoint.closed);
|
|
endpoint.client = interfaceEndpointClient;
|
|
|
|
if (endpoint.peerClosed) {
|
|
setTimeout(endpoint.client.notifyError.bind(endpoint.client), 0);
|
|
}
|
|
|
|
if (this.cachedMessageData && interfaceEndpointHandle.id() ===
|
|
this.cachedMessageData.message.getInterfaceId()) {
|
|
setTimeout((function() {
|
|
if (!this.cachedMessageData) {
|
|
return;
|
|
}
|
|
|
|
var targetEndpoint = this.endpoints_.get(
|
|
this.cachedMessageData.message.getInterfaceId());
|
|
// Check that the target endpoint's client still exists.
|
|
if (targetEndpoint && targetEndpoint.client) {
|
|
var message = this.cachedMessageData.message;
|
|
var messageValidator = this.cachedMessageData.messageValidator;
|
|
this.cachedMessageData = null;
|
|
this.connector_.resumeIncomingMethodCallProcessing();
|
|
var ok = endpoint.client.handleIncomingMessage(message,
|
|
messageValidator);
|
|
|
|
// Handle invalid cached incoming message.
|
|
if (!internal.isTestingMode() && !ok) {
|
|
this.connector_.handleError(true, true);
|
|
}
|
|
}
|
|
}).bind(this), 0);
|
|
}
|
|
|
|
return endpoint;
|
|
};
|
|
|
|
Router.prototype.detachEndpointClient = function(
|
|
interfaceEndpointHandle) {
|
|
check(internal.isValidInterfaceId(interfaceEndpointHandle.id()));
|
|
var endpoint = this.endpoints_.get(interfaceEndpointHandle.id());
|
|
check(endpoint);
|
|
check(endpoint.client);
|
|
check(!endpoint.closed);
|
|
|
|
endpoint.client = null;
|
|
};
|
|
|
|
Router.prototype.createLocalEndpointHandle = function(
|
|
interfaceId) {
|
|
if (!internal.isValidInterfaceId(interfaceId)) {
|
|
return new internal.InterfaceEndpointHandle();
|
|
}
|
|
|
|
// Unless it is the master ID, |interfaceId| is from the remote side and
|
|
// therefore its namespace bit is supposed to be different than the value
|
|
// that this router would use.
|
|
if (!internal.isMasterInterfaceId(interfaceId) &&
|
|
this.setInterfaceIdNamespaceBit_ ===
|
|
internal.hasInterfaceIdNamespaceBitSet(interfaceId)) {
|
|
return new internal.InterfaceEndpointHandle();
|
|
}
|
|
|
|
var endpoint = this.endpoints_.get(interfaceId);
|
|
|
|
if (!endpoint) {
|
|
endpoint = new InterfaceEndpoint(this, interfaceId);
|
|
this.endpoints_.set(interfaceId, endpoint);
|
|
|
|
check(!endpoint.handleCreated);
|
|
|
|
if (this.encounteredError_) {
|
|
this.updateEndpointStateMayRemove(endpoint,
|
|
EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
|
|
}
|
|
} else {
|
|
// If the endpoint already exist, it is because we have received a
|
|
// notification that the peer endpoint has closed.
|
|
check(!endpoint.closed);
|
|
check(endpoint.peerClosed);
|
|
|
|
if (endpoint.handleCreated) {
|
|
return new internal.InterfaceEndpointHandle();
|
|
}
|
|
}
|
|
|
|
endpoint.handleCreated = true;
|
|
return new internal.InterfaceEndpointHandle(interfaceId, this);
|
|
};
|
|
|
|
Router.prototype.accept = function(message) {
|
|
var messageValidator = new internal.Validator(message);
|
|
var err = messageValidator.validateMessageHeader();
|
|
|
|
var ok = false;
|
|
if (err !== internal.validationError.NONE) {
|
|
internal.reportValidationError(err);
|
|
} else if (message.deserializeAssociatedEndpointHandles(this)) {
|
|
if (internal.isPipeControlMessage(message)) {
|
|
ok = this.controlMessageHandler_.accept(message);
|
|
} else {
|
|
var interfaceId = message.getInterfaceId();
|
|
var endpoint = this.endpoints_.get(interfaceId);
|
|
if (!endpoint || endpoint.closed) {
|
|
return true;
|
|
}
|
|
|
|
if (!endpoint.client) {
|
|
// We need to wait until a client is attached in order to dispatch
|
|
// further messages.
|
|
this.cachedMessageData = {message: message,
|
|
messageValidator: messageValidator};
|
|
this.connector_.pauseIncomingMethodCallProcessing();
|
|
return true;
|
|
}
|
|
ok = endpoint.client.handleIncomingMessage(message, messageValidator);
|
|
}
|
|
}
|
|
return ok;
|
|
};
|
|
|
|
Router.prototype.close = function() {
|
|
this.connector_.close();
|
|
// Closing the message pipe won't trigger connection error handler.
|
|
// Explicitly call onPipeConnectionError() so that associated endpoints
|
|
// will get notified.
|
|
this.onPipeConnectionError();
|
|
};
|
|
|
|
Router.prototype.onPeerAssociatedEndpointClosed = function(interfaceId,
|
|
reason) {
|
|
var endpoint = this.endpoints_.get(interfaceId);
|
|
if (!endpoint) {
|
|
endpoint = new InterfaceEndpoint(this, interfaceId);
|
|
this.endpoints_.set(interfaceId, endpoint);
|
|
}
|
|
|
|
if (reason) {
|
|
endpoint.disconnectReason = reason;
|
|
}
|
|
|
|
if (!endpoint.peerClosed) {
|
|
if (endpoint.client) {
|
|
setTimeout(endpoint.client.notifyError.bind(endpoint.client, reason),
|
|
0);
|
|
}
|
|
this.updateEndpointStateMayRemove(endpoint,
|
|
EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
Router.prototype.onPipeConnectionError = function() {
|
|
this.encounteredError_ = true;
|
|
|
|
for (var endpoint of this.endpoints_.values()) {
|
|
if (endpoint.client) {
|
|
setTimeout(
|
|
endpoint.client.notifyError.bind(
|
|
endpoint.client, endpoint.disconnectReason),
|
|
0);
|
|
}
|
|
this.updateEndpointStateMayRemove(endpoint,
|
|
EndpointStateUpdateType.PEER_ENDPOINT_CLOSED);
|
|
}
|
|
};
|
|
|
|
Router.prototype.closeEndpointHandle = function(interfaceId, reason) {
|
|
if (!internal.isValidInterfaceId(interfaceId)) {
|
|
return;
|
|
}
|
|
var endpoint = this.endpoints_.get(interfaceId);
|
|
check(endpoint);
|
|
check(!endpoint.client);
|
|
check(!endpoint.closed);
|
|
|
|
this.updateEndpointStateMayRemove(endpoint,
|
|
EndpointStateUpdateType.ENDPOINT_CLOSED);
|
|
|
|
if (!internal.isMasterInterfaceId(interfaceId) || reason) {
|
|
this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason);
|
|
}
|
|
|
|
if (this.cachedMessageData && interfaceId ===
|
|
this.cachedMessageData.message.getInterfaceId()) {
|
|
this.cachedMessageData = null;
|
|
this.connector_.resumeIncomingMethodCallProcessing();
|
|
}
|
|
};
|
|
|
|
Router.prototype.updateEndpointStateMayRemove = function(endpoint,
|
|
endpointStateUpdateType) {
|
|
if (endpointStateUpdateType === EndpointStateUpdateType.ENDPOINT_CLOSED) {
|
|
endpoint.closed = true;
|
|
} else {
|
|
endpoint.peerClosed = true;
|
|
}
|
|
if (endpoint.closed && endpoint.peerClosed) {
|
|
this.endpoints_.delete(endpoint.id);
|
|
}
|
|
};
|
|
|
|
internal.Router = Router;
|
|
})();
|