// Copyright 2011 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview The leaf node of a {@link goog.messaging.PortNetwork}. Callers * connect to the operator, and request connections with other contexts from it. * */ goog.provide('goog.messaging.PortCaller'); goog.require('goog.Disposable'); goog.require('goog.async.Deferred'); goog.require('goog.messaging.DeferredChannel'); goog.require('goog.messaging.PortChannel'); goog.require('goog.messaging.PortNetwork'); // interface goog.require('goog.object'); /** * The leaf node of a network. * * @param {!goog.messaging.MessageChannel} operatorPort The channel for * communicating with the operator. The other side of this channel should be * passed to {@link goog.messaging.PortOperator#addPort}. Must be either a * {@link goog.messaging.PortChannel} or a decorator wrapping a PortChannel; * in particular, it must be able to send and receive {@link MessagePort}s. * @constructor * @extends {goog.Disposable} * @implements {goog.messaging.PortNetwork} */ goog.messaging.PortCaller = function(operatorPort) { goog.base(this); /** * The channel to the {@link goog.messaging.PortOperator} for this network. * * @type {!goog.messaging.MessageChannel} * @private */ this.operatorPort_ = operatorPort; /** * The collection of channels for communicating with other contexts in the * network. Each value can contain a {@link goog.aync.Deferred} and/or a * {@link goog.messaging.MessageChannel}. * * If the value contains a Deferred, then the channel is a * {@link goog.messaging.DeferredChannel} wrapping that Deferred. The Deferred * will be resolved with a {@link goog.messaging.PortChannel} once we receive * the appropriate port from the operator. This is the situation when this * caller requests a connection to another context; the DeferredChannel is * used to queue up messages until we receive the port from the operator. * * If the value does not contain a Deferred, then the channel is simply a * {@link goog.messaging.PortChannel} communicating with the given context. * This is the situation when this context received a port for the other * context before it was requested. * * If a value exists for a given key, it must contain a channel, but it * doesn't necessarily contain a Deferred. * * @type {!Object.<{deferred: goog.async.Deferred, * channel: !goog.messaging.MessageChannel}>} * @private */ this.connections_ = {}; this.operatorPort_.registerService( goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, goog.bind(this.connectionGranted_, this), true /* opt_json */); }; goog.inherits(goog.messaging.PortCaller, goog.Disposable); /** @override */ goog.messaging.PortCaller.prototype.dial = function(name) { if (name in this.connections_) { return this.connections_[name].channel; } this.operatorPort_.send( goog.messaging.PortNetwork.REQUEST_CONNECTION_SERVICE, name); var deferred = new goog.async.Deferred(); var channel = new goog.messaging.DeferredChannel(deferred); this.connections_[name] = {deferred: deferred, channel: channel}; return channel; }; /** * Registers a connection to another context in the network. This is called when * the operator sends us one end of a {@link MessageChannel}, either because * this caller requested a connection with another context, or because that * context requested a connection with this caller. * * It's possible that the remote context and this one request each other roughly * concurrently. The operator doesn't keep track of which contexts have been * connected, so it will create two separate {@link MessageChannel}s in this * case. However, the first channel created will reach both contexts first, so * we simply ignore all connections with a given context after the first. * * @param {{name: string, port: MessagePort}} args The name of the context * being connected and the port connecting the context. * @private */ goog.messaging.PortCaller.prototype.connectionGranted_ = function(args) { var port = args['port']; var entry = this.connections_[args['name']]; if (entry && (!entry.deferred || entry.deferred.hasFired())) { // If two PortCallers request one another at the same time, the operator may // send out a channel for connecting them multiple times. Since both callers // will receive the first channel's ports first, we can safely ignore and // close any future ports. port.close(); } else if (!args['success']) { throw Error(args['message']); } else { port.start(); var channel = new goog.messaging.PortChannel(port); if (entry) { entry.deferred.callback(channel); } else { this.connections_[args['name']] = {channel: channel, deferred: null}; } } }; /** @override */ goog.messaging.PortCaller.prototype.disposeInternal = function() { goog.dispose(this.operatorPort_); goog.object.forEach(this.connections_, goog.dispose); delete this.operatorPort_; delete this.connections_; goog.base(this, 'disposeInternal'); };