Update wmts-hidpi, add nicer-api-docs

This commit is contained in:
Andreas Hocevar
2014-05-06 13:02:46 -05:00
parent b3ac1afd00
commit 1e25fc5585
2239 changed files with 3726515 additions and 37010 deletions

View File

@@ -0,0 +1,210 @@
// Copyright 2010 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 An abstract superclass for message channels that handles the
* repetitive details of registering and dispatching to services. This is more
* useful for full-fledged channels than for decorators, since decorators
* generally delegate service registering anyway.
*
*/
goog.provide('goog.messaging.AbstractChannel');
goog.require('goog.Disposable');
goog.require('goog.debug');
goog.require('goog.json');
goog.require('goog.log');
goog.require('goog.messaging.MessageChannel'); // interface
/**
* Creates an abstract message channel.
*
* @constructor
* @extends {goog.Disposable}
* @implements {goog.messaging.MessageChannel}
*/
goog.messaging.AbstractChannel = function() {
goog.base(this);
/**
* The services registered for this channel.
* @type {Object.<string, {callback: function((string|!Object)),
objectPayload: boolean}>}
* @private
*/
this.services_ = {};
};
goog.inherits(goog.messaging.AbstractChannel, goog.Disposable);
/**
* The default service to be run when no other services match.
*
* @type {?function(string, (string|!Object))}
* @private
*/
goog.messaging.AbstractChannel.prototype.defaultService_;
/**
* Logger for this class.
* @type {goog.log.Logger}
* @protected
*/
goog.messaging.AbstractChannel.prototype.logger =
goog.log.getLogger('goog.messaging.AbstractChannel');
/**
* Immediately calls opt_connectCb if given, and is otherwise a no-op. If
* subclasses have configuration that needs to happen before the channel is
* connected, they should override this and {@link #isConnected}.
* @override
*/
goog.messaging.AbstractChannel.prototype.connect = function(opt_connectCb) {
if (opt_connectCb) {
opt_connectCb();
}
};
/**
* Always returns true. If subclasses have configuration that needs to happen
* before the channel is connected, they should override this and
* {@link #connect}.
* @override
*/
goog.messaging.AbstractChannel.prototype.isConnected = function() {
return true;
};
/** @override */
goog.messaging.AbstractChannel.prototype.registerService =
function(serviceName, callback, opt_objectPayload) {
this.services_[serviceName] = {
callback: callback,
objectPayload: !!opt_objectPayload
};
};
/** @override */
goog.messaging.AbstractChannel.prototype.registerDefaultService =
function(callback) {
this.defaultService_ = callback;
};
/** @override */
goog.messaging.AbstractChannel.prototype.send = goog.abstractMethod;
/**
* Delivers a message to the appropriate service. This is meant to be called by
* subclasses when they receive messages.
*
* This method takes into account both explicitly-registered and default
* services, as well as making sure that JSON payloads are decoded when
* necessary. If the subclass is capable of passing objects as payloads, those
* objects can be passed in to this method directly. Otherwise, the (potentially
* JSON-encoded) strings should be passed in.
*
* @param {string} serviceName The name of the service receiving the message.
* @param {string|!Object} payload The contents of the message.
* @protected
*/
goog.messaging.AbstractChannel.prototype.deliver = function(
serviceName, payload) {
var service = this.getService(serviceName, payload);
if (!service) {
return;
}
var decodedPayload =
this.decodePayload(serviceName, payload, service.objectPayload);
if (goog.isDefAndNotNull(decodedPayload)) {
service.callback(decodedPayload);
}
};
/**
* Find the service object for a given service name. If there's no service
* explicitly registered, but there is a default service, a service object is
* constructed for it.
*
* @param {string} serviceName The name of the service receiving the message.
* @param {string|!Object} payload The contents of the message.
* @return {?{callback: function((string|!Object)), objectPayload: boolean}} The
* service object for the given service, or null if none was found.
* @protected
*/
goog.messaging.AbstractChannel.prototype.getService = function(
serviceName, payload) {
var service = this.services_[serviceName];
if (service) {
return service;
} else if (this.defaultService_) {
var callback = goog.partial(this.defaultService_, serviceName);
var objectPayload = goog.isObject(payload);
return {callback: callback, objectPayload: objectPayload};
}
goog.log.warning(this.logger, 'Unknown service name "' + serviceName + '"');
return null;
};
/**
* Converts the message payload into the format expected by the registered
* service (either JSON or string).
*
* @param {string} serviceName The name of the service receiving the message.
* @param {string|!Object} payload The contents of the message.
* @param {boolean} objectPayload Whether the service expects an object or a
* plain string.
* @return {string|Object} The payload in the format expected by the service, or
* null if something went wrong.
* @protected
*/
goog.messaging.AbstractChannel.prototype.decodePayload = function(
serviceName, payload, objectPayload) {
if (objectPayload && goog.isString(payload)) {
try {
return goog.json.parse(payload);
} catch (err) {
goog.log.warning(this.logger,
'Expected JSON payload for ' + serviceName +
', was "' + payload + '"');
return null;
}
} else if (!objectPayload && !goog.isString(payload)) {
return goog.json.serialize(payload);
}
return payload;
};
/** @override */
goog.messaging.AbstractChannel.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
delete this.logger;
delete this.services_;
delete this.defaultService_;
};

View File

@@ -0,0 +1,287 @@
// Copyright 2010 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 A wrapper for asynchronous message-passing channels that buffer
* their output until both ends of the channel are connected.
*
*/
goog.provide('goog.messaging.BufferedChannel');
goog.require('goog.Timer');
goog.require('goog.Uri');
goog.require('goog.debug.Error');
goog.require('goog.events');
goog.require('goog.log');
goog.require('goog.messaging.MessageChannel');
goog.require('goog.messaging.MultiChannel');
/**
* Creates a new BufferedChannel, which operates like its underlying channel
* except that it buffers calls to send until it receives a message from its
* peer claiming that the peer is ready to receive. The peer is also expected
* to be a BufferedChannel, though this is not enforced.
*
* @param {!goog.messaging.MessageChannel} messageChannel The MessageChannel
* we're wrapping.
* @param {number=} opt_interval Polling interval for sending ready
* notifications to peer, in ms. Default is 50.
* @constructor
* @extends {goog.Disposable}
* @implements {goog.messaging.MessageChannel};
*/
goog.messaging.BufferedChannel = function(messageChannel, opt_interval) {
goog.Disposable.call(this);
/**
* Buffer of messages to be sent when the channel's peer is ready.
*
* @type {Array.<Object>}
* @private
*/
this.buffer_ = [];
/**
* Channel dispatcher wrapping the underlying delegate channel.
*
* @type {!goog.messaging.MultiChannel}
* @private
*/
this.multiChannel_ = new goog.messaging.MultiChannel(messageChannel);
/**
* Virtual channel for carrying the user's messages.
*
* @type {!goog.messaging.MessageChannel}
* @private
*/
this.userChannel_ = this.multiChannel_.createVirtualChannel(
goog.messaging.BufferedChannel.USER_CHANNEL_NAME_);
/**
* Virtual channel for carrying control messages for BufferedChannel.
*
* @type {!goog.messaging.MessageChannel}
* @private
*/
this.controlChannel_ = this.multiChannel_.createVirtualChannel(
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_);
/**
* Timer for the peer ready ping loop.
*
* @type {goog.Timer}
* @private
*/
this.timer_ = new goog.Timer(
opt_interval || goog.messaging.BufferedChannel.DEFAULT_INTERVAL_MILLIS_);
this.timer_.start();
goog.events.listen(
this.timer_, goog.Timer.TICK, this.sendReadyPing_, false, this);
this.controlChannel_.registerService(
goog.messaging.BufferedChannel.PEER_READY_SERVICE_NAME_,
goog.bind(this.setPeerReady_, this));
};
goog.inherits(goog.messaging.BufferedChannel, goog.Disposable);
/**
* Default polling interval (in ms) for setPeerReady_ notifications.
*
* @type {number}
* @const
* @private
*/
goog.messaging.BufferedChannel.DEFAULT_INTERVAL_MILLIS_ = 50;
/**
* The name of the private service which handles peer ready pings. The
* service registered with this name is bound to this.setPeerReady_, an internal
* part of BufferedChannel's implementation that clients should not send to
* directly.
*
* @type {string}
* @const
* @private
*/
goog.messaging.BufferedChannel.PEER_READY_SERVICE_NAME_ = 'setPeerReady_';
/**
* The name of the virtual channel along which user messages are sent.
*
* @type {string}
* @const
* @private
*/
goog.messaging.BufferedChannel.USER_CHANNEL_NAME_ = 'user';
/**
* The name of the virtual channel along which internal control messages are
* sent.
*
* @type {string}
* @const
* @private
*/
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ = 'control';
/** @override */
goog.messaging.BufferedChannel.prototype.connect = function(opt_connectCb) {
if (opt_connectCb) {
opt_connectCb();
}
};
/** @override */
goog.messaging.BufferedChannel.prototype.isConnected = function() {
return true;
};
/**
* @return {boolean} Whether the channel's peer is ready.
*/
goog.messaging.BufferedChannel.prototype.isPeerReady = function() {
return this.peerReady_;
};
/**
* Logger.
*
* @type {goog.log.Logger}
* @const
* @private
*/
goog.messaging.BufferedChannel.prototype.logger_ = goog.log.getLogger(
'goog.messaging.bufferedchannel');
/**
* Handles one tick of our peer ready notification loop. This entails sending a
* ready ping to the peer and shutting down the loop if we've received a ping
* ourselves.
*
* @private
*/
goog.messaging.BufferedChannel.prototype.sendReadyPing_ = function() {
try {
this.controlChannel_.send(
goog.messaging.BufferedChannel.PEER_READY_SERVICE_NAME_,
/* payload */ this.isPeerReady() ? '1' : '');
} catch (e) {
this.timer_.stop(); // So we don't keep calling send and re-throwing.
throw e;
}
};
/**
* Whether or not the peer channel is ready to receive messages.
*
* @type {boolean}
* @private
*/
goog.messaging.BufferedChannel.prototype.peerReady_;
/** @override */
goog.messaging.BufferedChannel.prototype.registerService = function(
serviceName, callback, opt_objectPayload) {
this.userChannel_.registerService(serviceName, callback, opt_objectPayload);
};
/** @override */
goog.messaging.BufferedChannel.prototype.registerDefaultService = function(
callback) {
this.userChannel_.registerDefaultService(callback);
};
/**
* Send a message over the channel. If the peer is not ready, the message will
* be buffered and sent once we've received a ready message from our peer.
*
* @param {string} serviceName The name of the service this message should be
* delivered to.
* @param {string|!Object} payload The value of the message. If this is an
* Object, it is serialized to JSON before sending. It's the responsibility
* of implementors of this class to perform the serialization.
* @see goog.net.xpc.BufferedChannel.send
* @override
*/
goog.messaging.BufferedChannel.prototype.send = function(serviceName, payload) {
if (this.isPeerReady()) {
this.userChannel_.send(serviceName, payload);
} else {
goog.log.fine(goog.messaging.BufferedChannel.prototype.logger_,
'buffering message ' + serviceName);
this.buffer_.push({serviceName: serviceName, payload: payload});
}
};
/**
* Marks the channel's peer as ready, then sends buffered messages and nulls the
* buffer. Subsequent calls to setPeerReady_ have no effect.
*
* @param {(!Object|string)} peerKnowsWeKnowItsReady Passed by the peer to
* indicate whether it knows that we've received its ping and that it's
* ready. Non-empty if true, empty if false.
* @private
*/
goog.messaging.BufferedChannel.prototype.setPeerReady_ = function(
peerKnowsWeKnowItsReady) {
if (peerKnowsWeKnowItsReady) {
this.timer_.stop();
} else {
// Our peer doesn't know we're ready, so restart (or continue) pinging.
// Restarting may be needed if the peer iframe was reloaded after the
// connection was first established.
this.timer_.start();
}
if (this.peerReady_) {
return;
}
this.peerReady_ = true;
// Send one last ping so that the peer knows we know it's ready.
this.sendReadyPing_();
for (var i = 0; i < this.buffer_.length; i++) {
var message = this.buffer_[i];
goog.log.fine(goog.messaging.BufferedChannel.prototype.logger_,
'sending buffered message ' + message.serviceName);
this.userChannel_.send(message.serviceName, message.payload);
}
this.buffer_ = null;
};
/** @override */
goog.messaging.BufferedChannel.prototype.disposeInternal = function() {
goog.dispose(this.multiChannel_);
goog.dispose(this.timer_);
goog.base(this, 'disposeInternal');
};

View File

@@ -0,0 +1,98 @@
// Copyright 2010 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 A MessageChannel decorator that wraps a deferred MessageChannel
* and enqueues messages and service registrations until that channel exists.
*
*/
goog.provide('goog.messaging.DeferredChannel');
goog.require('goog.Disposable');
goog.require('goog.async.Deferred');
goog.require('goog.messaging.MessageChannel'); // interface
/**
* Creates a new DeferredChannel, which wraps a deferred MessageChannel and
* enqueues messages to be sent once the wrapped channel is resolved.
*
* @param {!goog.async.Deferred} deferredChannel The underlying deferred
* MessageChannel.
* @constructor
* @extends {goog.Disposable}
* @implements {goog.messaging.MessageChannel}
*/
goog.messaging.DeferredChannel = function(deferredChannel) {
goog.base(this);
this.deferred_ = deferredChannel;
};
goog.inherits(goog.messaging.DeferredChannel, goog.Disposable);
/**
* Cancels the wrapped Deferred.
*/
goog.messaging.DeferredChannel.prototype.cancel = function() {
this.deferred_.cancel();
};
/** @override */
goog.messaging.DeferredChannel.prototype.connect = function(opt_connectCb) {
if (opt_connectCb) {
opt_connectCb();
}
};
/** @override */
goog.messaging.DeferredChannel.prototype.isConnected = function() {
return true;
};
/** @override */
goog.messaging.DeferredChannel.prototype.registerService = function(
serviceName, callback, opt_objectPayload) {
this.deferred_.addCallback(function(resolved) {
resolved.registerService(serviceName, callback, opt_objectPayload);
});
};
/** @override */
goog.messaging.DeferredChannel.prototype.registerDefaultService =
function(callback) {
this.deferred_.addCallback(function(resolved) {
resolved.registerDefaultService(callback);
});
};
/** @override */
goog.messaging.DeferredChannel.prototype.send = function(serviceName, payload) {
this.deferred_.addCallback(function(resolved) {
resolved.send(serviceName, payload);
});
};
/** @override */
goog.messaging.DeferredChannel.prototype.disposeInternal = function() {
this.cancel();
goog.base(this, 'disposeInternal');
};

View File

@@ -0,0 +1,131 @@
// Copyright 2010 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 This class sends logging messages over a message channel to a
* server on the main page that prints them using standard logging mechanisms.
*
*/
goog.provide('goog.messaging.LoggerClient');
goog.require('goog.Disposable');
goog.require('goog.debug');
goog.require('goog.debug.LogManager');
goog.require('goog.debug.Logger');
/**
* Creates a logger client that sends messages along a message channel for the
* remote end to log. The remote end of the channel should use a
* {goog.messaging.LoggerServer} with the same service name.
*
* @param {!goog.messaging.MessageChannel} channel The channel that on which to
* send the log messages.
* @param {string} serviceName The name of the logging service to use.
* @constructor
* @extends {goog.Disposable}
*/
goog.messaging.LoggerClient = function(channel, serviceName) {
if (goog.messaging.LoggerClient.instance_) {
return goog.messaging.LoggerClient.instance_;
}
goog.base(this);
/**
* The channel on which to send the log messages.
* @type {!goog.messaging.MessageChannel}
* @private
*/
this.channel_ = channel;
/**
* The name of the logging service to use.
* @type {string}
* @private
*/
this.serviceName_ = serviceName;
/**
* The bound handler function for handling log messages. This is kept in a
* variable so that it can be deregistered when the logger client is disposed.
* @type {Function}
* @private
*/
this.publishHandler_ = goog.bind(this.sendLog_, this);
goog.debug.LogManager.getRoot().addHandler(this.publishHandler_);
goog.messaging.LoggerClient.instance_ = this;
};
goog.inherits(goog.messaging.LoggerClient, goog.Disposable);
/**
* The singleton instance, if any.
* @type {goog.messaging.LoggerClient}
* @private
*/
goog.messaging.LoggerClient.instance_ = null;
/**
* Sends a log message through the channel.
* @param {!goog.debug.LogRecord} logRecord The log message.
* @private
*/
goog.messaging.LoggerClient.prototype.sendLog_ = function(logRecord) {
var name = logRecord.getLoggerName();
var level = logRecord.getLevel();
var msg = logRecord.getMessage();
var originalException = logRecord.getException();
var exception;
if (originalException) {
var normalizedException =
goog.debug.normalizeErrorObject(originalException);
exception = {
'name': normalizedException.name,
'message': normalizedException.message,
'lineNumber': normalizedException.lineNumber,
'fileName': normalizedException.fileName,
// Normalized exceptions without a stack have 'stack' set to 'Not
// available', so we check for the existance of 'stack' on the original
// exception instead.
'stack': originalException.stack ||
goog.debug.getStacktrace(goog.debug.Logger.prototype.log)
};
if (goog.isObject(originalException)) {
// Add messageN to the exception in case it was added using
// goog.debug.enhanceError.
for (var i = 0; 'message' + i in originalException; i++) {
exception['message' + i] = String(originalException['message' + i]);
}
}
}
this.channel_.send(this.serviceName_, {
'name': name, 'level': level.value, 'message': msg, 'exception': exception
});
};
/** @override */
goog.messaging.LoggerClient.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
goog.debug.LogManager.getRoot().removeHandler(this.publishHandler_);
delete this.channel_;
goog.messaging.LoggerClient.instance_ = null;
};

View File

@@ -0,0 +1,98 @@
// Copyright 2010 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 This class listens on a message channel for logger commands and
* logs them on the local page. This is useful when dealing with message
* channels to contexts that don't have access to their own logging facilities.
*
*/
goog.provide('goog.messaging.LoggerServer');
goog.require('goog.Disposable');
goog.require('goog.log');
/**
* Creates a logger server that logs messages on behalf of the remote end of a
* message channel. The remote end of the channel should use a
* {goog.messaging.LoggerClient} with the same service name.
*
* @param {!goog.messaging.MessageChannel} channel The channel that is sending
* the log messages.
* @param {string} serviceName The name of the logging service to listen for.
* @param {string=} opt_channelName The name of this channel. Used to help
* distinguish this client's messages.
* @constructor
* @extends {goog.Disposable}
*/
goog.messaging.LoggerServer = function(channel, serviceName, opt_channelName) {
goog.base(this);
/**
* The channel that is sending the log messages.
* @type {!goog.messaging.MessageChannel}
* @private
*/
this.channel_ = channel;
/**
* The name of the logging service to listen for.
* @type {string}
* @private
*/
this.serviceName_ = serviceName;
/**
* The name of the channel.
* @type {string}
* @private
*/
this.channelName_ = opt_channelName || 'remote logger';
this.channel_.registerService(
this.serviceName_, goog.bind(this.log_, this), true /* opt_json */);
};
goog.inherits(goog.messaging.LoggerServer, goog.Disposable);
/**
* Handles logging messages from the client.
* @param {!Object|string} message
* The logging information from the client.
* @private
*/
goog.messaging.LoggerServer.prototype.log_ = function(message) {
var args =
/**
* @type {!{level: number, message: string,
* name: string, exception: Object}}
*/ (message);
var level = goog.log.Level.getPredefinedLevelByValue(args['level']);
if (level) {
var msg = '[' + this.channelName_ + '] ' + args['message'];
goog.log.getLogger(args['name'])
.log(level, msg, args['exception']);
}
};
/** @override */
goog.messaging.LoggerServer.prototype.disposeInternal = function() {
goog.base(this, 'disposeInternal');
this.channel_.registerService(this.serviceName_, goog.nullFunction, true);
delete this.channel_;
};

View File

@@ -0,0 +1,116 @@
// Copyright 2010 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 An interface for asynchronous message-passing channels.
*
* This interface is useful for writing code in a message-passing style that's
* independent of the underlying communication medium. It's also useful for
* adding decorators that wrap message channels and add extra functionality on
* top. For example, {@link goog.messaging.BufferedChannel} enqueues messages
* until communication is established, while {@link goog.messaging.MultiChannel}
* splits a single underlying channel into multiple virtual ones.
*
* Decorators should be passed their underlying channel(s) in the constructor,
* and should assume that those channels are already connected. Decorators are
* responsible for disposing of the channels they wrap when the decorators
* themselves are disposed. Decorators should also follow the APIs of the
* individual methods listed below.
*
*/
goog.provide('goog.messaging.MessageChannel');
/**
* @interface
*/
goog.messaging.MessageChannel = function() {};
/**
* Initiates the channel connection. When this method is called, all the
* information needed to connect the channel has to be available.
*
* Implementers should only require this method to be called if the channel
* needs to be configured in some way between when it's created and when it
* becomes active. Otherwise, the channel should be immediately active and this
* method should do nothing but immediately call opt_connectCb.
*
* @param {Function=} opt_connectCb Called when the channel has been connected
* and is ready to use.
*/
goog.messaging.MessageChannel.prototype.connect = function(opt_connectCb) {};
/**
* Gets whether the channel is connected.
*
* If {@link #connect} is not required for this class, this should always return
* true. Otherwise, this should return true by the time the callback passed to
* {@link #connect} has been called and always after that.
*
* @return {boolean} Whether the channel is connected.
*/
goog.messaging.MessageChannel.prototype.isConnected = function() {};
/**
* Registers a service to be called when a message is received.
*
* Implementers shouldn't impose any restrictions on the service names that may
* be registered. If some services are needed as control codes,
* {@link goog.messaging.MultiMessageChannel} can be used to safely split the
* channel into "public" and "control" virtual channels.
*
* @param {string} serviceName The name of the service.
* @param {function((string|!Object))} callback The callback to process the
* incoming messages. Passed the payload. If opt_objectPayload is set, the
* payload is decoded and passed as an object.
* @param {boolean=} opt_objectPayload If true, incoming messages for this
* service are expected to contain an object, and will be deserialized from
* a string automatically if necessary. It's the responsibility of
* implementors of this class to perform the deserialization.
*/
goog.messaging.MessageChannel.prototype.registerService =
function(serviceName, callback, opt_objectPayload) {};
/**
* Registers a service to be called when a message is received that doesn't
* match any other services.
*
* @param {function(string, (string|!Object))} callback The callback to process
* the incoming messages. Passed the service name and the payload. Since
* some channels can pass objects natively, the payload may be either an
* object or a string.
*/
goog.messaging.MessageChannel.prototype.registerDefaultService =
function(callback) {};
/**
* Sends a message over the channel.
*
* @param {string} serviceName The name of the service this message should be
* delivered to.
* @param {string|!Object} payload The value of the message. If this is an
* Object, it is serialized to a string before sending if necessary. It's
* the responsibility of implementors of this class to perform the
* serialization.
*/
goog.messaging.MessageChannel.prototype.send =
function(serviceName, payload) {};

View File

@@ -0,0 +1,34 @@
// Copyright 2010 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 Functions for manipulating message channels.
*
*/
goog.provide('goog.messaging');
goog.require('goog.messaging.MessageChannel');
/**
* Creates a bidirectional pipe between two message channels.
*
* @param {goog.messaging.MessageChannel} channel1 The first channel.
* @param {goog.messaging.MessageChannel} channel2 The second channel.
*/
goog.messaging.pipe = function(channel1, channel2) {
channel1.registerDefaultService(goog.bind(channel2.send, channel2));
channel2.registerDefaultService(goog.bind(channel1.send, channel1));
};

View File

@@ -0,0 +1,302 @@
// Copyright 2010 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 Definition of goog.messaging.MultiChannel, which uses a
* single underlying MessageChannel to carry several independent virtual message
* channels.
*
*/
goog.provide('goog.messaging.MultiChannel');
goog.provide('goog.messaging.MultiChannel.VirtualChannel');
goog.require('goog.Disposable');
goog.require('goog.events.EventHandler');
goog.require('goog.log');
goog.require('goog.messaging.MessageChannel'); // interface
goog.require('goog.object');
/**
* Creates a new MultiChannel wrapping a single MessageChannel. The
* underlying channel shouldn't have any other listeners registered, but it
* should be connected.
*
* Note that the other side of the channel should also be connected to a
* MultiChannel with the same number of virtual channels.
*
* @param {goog.messaging.MessageChannel} underlyingChannel The underlying
* channel to use as transport for the virtual channels.
* @constructor
* @extends {goog.Disposable}
*/
goog.messaging.MultiChannel = function(underlyingChannel) {
goog.base(this);
/**
* The underlying channel across which all requests are sent.
* @type {goog.messaging.MessageChannel}
* @private
*/
this.underlyingChannel_ = underlyingChannel;
/**
* All the virtual channels that are registered for this MultiChannel.
* These are null if they've been disposed.
* @type {Object.<?goog.messaging.MultiChannel.VirtualChannel>}
* @private
*/
this.virtualChannels_ = {};
this.underlyingChannel_.registerDefaultService(
goog.bind(this.handleDefault_, this));
};
goog.inherits(goog.messaging.MultiChannel, goog.Disposable);
/**
* Logger object for goog.messaging.MultiChannel.
* @type {goog.log.Logger}
* @private
*/
goog.messaging.MultiChannel.prototype.logger_ =
goog.log.getLogger('goog.messaging.MultiChannel');
/**
* Creates a new virtual channel that will communicate across the underlying
* channel.
* @param {string} name The name of the virtual channel. Must be unique for this
* MultiChannel. Cannot contain colons.
* @return {!goog.messaging.MultiChannel.VirtualChannel} The new virtual
* channel.
*/
goog.messaging.MultiChannel.prototype.createVirtualChannel = function(name) {
if (name.indexOf(':') != -1) {
throw Error(
'Virtual channel name "' + name + '" should not contain colons');
}
if (name in this.virtualChannels_) {
throw Error('Virtual channel "' + name + '" was already created for ' +
'this multichannel.');
}
var channel =
new goog.messaging.MultiChannel.VirtualChannel(this, name);
this.virtualChannels_[name] = channel;
return channel;
};
/**
* Handles the default service for the underlying channel. This dispatches any
* unrecognized services to the appropriate virtual channel.
*
* @param {string} serviceName The name of the service being called.
* @param {string|!Object} payload The message payload.
* @private
*/
goog.messaging.MultiChannel.prototype.handleDefault_ = function(
serviceName, payload) {
var match = serviceName.match(/^([^:]*):(.*)/);
if (!match) {
goog.log.warning(this.logger_,
'Invalid service name "' + serviceName + '": no ' +
'virtual channel specified');
return;
}
var channelName = match[1];
serviceName = match[2];
if (!(channelName in this.virtualChannels_)) {
goog.log.warning(this.logger_,
'Virtual channel "' + channelName + ' does not ' +
'exist, but a message was received for it: "' + serviceName + '"');
return;
}
var virtualChannel = this.virtualChannels_[channelName];
if (!virtualChannel) {
goog.log.warning(this.logger_,
'Virtual channel "' + channelName + ' has been ' +
'disposed, but a message was received for it: "' + serviceName + '"');
return;
}
if (!virtualChannel.defaultService_) {
goog.log.warning(this.logger_,
'Service "' + serviceName + '" is not registered ' +
'on virtual channel "' + channelName + '"');
return;
}
virtualChannel.defaultService_(serviceName, payload);
};
/** @override */
goog.messaging.MultiChannel.prototype.disposeInternal = function() {
goog.object.forEach(this.virtualChannels_, function(channel) {
goog.dispose(channel);
});
goog.dispose(this.underlyingChannel_);
delete this.virtualChannels_;
delete this.underlyingChannel_;
};
/**
* A message channel that proxies its messages over another underlying channel.
*
* @param {goog.messaging.MultiChannel} parent The MultiChannel
* which created this channel, and which contains the underlying
* MessageChannel that's used as the transport.
* @param {string} name The name of this virtual channel. Unique among the
* virtual channels in parent.
* @constructor
* @implements {goog.messaging.MessageChannel}
* @extends {goog.Disposable}
*/
goog.messaging.MultiChannel.VirtualChannel = function(parent, name) {
goog.base(this);
/**
* The MultiChannel containing the underlying transport channel.
* @type {goog.messaging.MultiChannel}
* @private
*/
this.parent_ = parent;
/**
* The name of this virtual channel.
* @type {string}
* @private
*/
this.name_ = name;
};
goog.inherits(goog.messaging.MultiChannel.VirtualChannel,
goog.Disposable);
/**
* The default service to run if no other services match.
* @type {?function(string, (string|!Object))}
* @private
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.defaultService_;
/**
* Logger object for goog.messaging.MultiChannel.VirtualChannel.
* @type {goog.log.Logger}
* @private
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.logger_ =
goog.log.getLogger(
'goog.messaging.MultiChannel.VirtualChannel');
/**
* This is a no-op, since the underlying channel is expected to already be
* initialized when it's passed in.
*
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.connect =
function(opt_connectCb) {
if (opt_connectCb) {
opt_connectCb();
}
};
/**
* This always returns true, since the underlying channel is expected to already
* be initialized when it's passed in.
*
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.isConnected =
function() {
return true;
};
/**
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.registerService =
function(serviceName, callback, opt_objectPayload) {
this.parent_.underlyingChannel_.registerService(
this.name_ + ':' + serviceName,
goog.bind(this.doCallback_, this, callback),
opt_objectPayload);
};
/**
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.
registerDefaultService = function(callback) {
this.defaultService_ = goog.bind(this.doCallback_, this, callback);
};
/**
* @override
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.send =
function(serviceName, payload) {
if (this.isDisposed()) {
throw Error('#send called for disposed VirtualChannel.');
}
this.parent_.underlyingChannel_.send(this.name_ + ':' + serviceName,
payload);
};
/**
* Wraps a callback with a function that will log a warning and abort if it's
* called when this channel is disposed.
*
* @param {function()} callback The callback to wrap.
* @param {...*} var_args Other arguments, passed to the callback.
* @private
*/
goog.messaging.MultiChannel.VirtualChannel.prototype.doCallback_ =
function(callback, var_args) {
if (this.isDisposed()) {
goog.log.warning(this.logger_,
'Virtual channel "' + this.name_ + '" received ' +
' a message after being disposed.');
return;
}
callback.apply({}, Array.prototype.slice.call(arguments, 1));
};
/** @override */
goog.messaging.MultiChannel.VirtualChannel.prototype.disposeInternal =
function() {
this.parent_.virtualChannels_[this.name_] = null;
this.parent_ = null;
};

View File

@@ -0,0 +1,151 @@
// 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 {!Object|string} message The name of the context
* being connected and the port connecting the context.
* @private
*/
goog.messaging.PortCaller.prototype.connectionGranted_ = function(message) {
var args = /** @type {{name: string, port: MessagePort}} */ (message);
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');
};

View File

@@ -0,0 +1,401 @@
// Copyright 2010 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 A class that wraps several types of HTML5 message-passing
* entities ({@link MessagePort}s, {@link WebWorker}s, and {@link Window}s),
* providing a unified interface.
*
* This is tested under Chrome, Safari, and Firefox. Since Firefox 3.6 has an
* incomplete implementation of web workers, it doesn't support sending ports
* over Window connections. IE has no web worker support at all, and so is
* unsupported by this class.
*
*/
goog.provide('goog.messaging.PortChannel');
goog.require('goog.Timer');
goog.require('goog.array');
goog.require('goog.async.Deferred');
goog.require('goog.debug');
goog.require('goog.dom');
goog.require('goog.dom.DomHelper');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.json');
goog.require('goog.log');
goog.require('goog.messaging.AbstractChannel');
goog.require('goog.messaging.DeferredChannel');
goog.require('goog.object');
goog.require('goog.string');
/**
* A wrapper for several types of HTML5 message-passing entities
* ({@link MessagePort}s and {@link WebWorker}s). This class implements the
* {@link goog.messaging.MessageChannel} interface.
*
* This class can be used in conjunction with other communication on the port.
* It sets {@link goog.messaging.PortChannel.FLAG} to true on all messages it
* sends.
*
* @param {!MessagePort|!WebWorker} underlyingPort The message-passing
* entity to wrap. If this is a {@link MessagePort}, it should be started.
* The remote end should also be wrapped in a PortChannel. This will be
* disposed along with the PortChannel; this means terminating it if it's a
* worker or removing it from the DOM if it's an iframe.
* @constructor
* @extends {goog.messaging.AbstractChannel}
*/
goog.messaging.PortChannel = function(underlyingPort) {
goog.base(this);
/**
* The wrapped message-passing entity.
* @type {!MessagePort|!WebWorker}
* @private
*/
this.port_ = underlyingPort;
/**
* The key for the event listener.
* @type {goog.events.Key}
* @private
*/
this.listenerKey_ = goog.events.listen(
this.port_, goog.events.EventType.MESSAGE, this.deliver_, false, this);
};
goog.inherits(goog.messaging.PortChannel, goog.messaging.AbstractChannel);
/**
* Create a PortChannel that communicates with a window embedded in the current
* page (e.g. an iframe contentWindow). The code within the window should call
* {@link forGlobalWindow} to establish the connection.
*
* It's possible to use this channel in conjunction with other messages to the
* embedded window. However, only one PortChannel should be used for a given
* window at a time.
*
* @param {!Window} window The window object to communicate with.
* @param {string} peerOrigin The expected origin of the window. See
* http://dev.w3.org/html5/postmsg/#dom-window-postmessage.
* @param {goog.Timer=} opt_timer The timer that regulates how often the initial
* connection message is attempted. This will be automatically disposed once
* the connection is established, or when the connection is cancelled.
* @return {!goog.messaging.DeferredChannel} The PortChannel. Although this is
* not actually an instance of the PortChannel class, it will behave like
* one in that MessagePorts may be sent across it. The DeferredChannel may
* be cancelled before a connection is established in order to abort the
* attempt to make a connection.
*/
goog.messaging.PortChannel.forEmbeddedWindow = function(
window, peerOrigin, opt_timer) {
var timer = opt_timer || new goog.Timer(50);
var disposeTimer = goog.partial(goog.dispose, timer);
var deferred = new goog.async.Deferred(disposeTimer);
deferred.addBoth(disposeTimer);
timer.start();
// Every tick, attempt to set up a connection by sending in one end of an
// HTML5 MessageChannel. If the inner window posts a response along a channel,
// then we'll use that channel to create the PortChannel.
//
// As per http://dev.w3.org/html5/postmsg/#ports-and-garbage-collection, any
// ports that are not ultimately used to set up the channel will be garbage
// collected (since there are no references in this context, and the remote
// context hasn't seen them).
goog.events.listen(timer, goog.Timer.TICK, function() {
var channel = new MessageChannel();
var gotMessage = function(e) {
channel.port1.removeEventListener(
goog.events.EventType.MESSAGE, gotMessage, true);
// If the connection has been cancelled, don't create the channel.
if (!timer.isDisposed()) {
deferred.callback(new goog.messaging.PortChannel(channel.port1));
}
};
channel.port1.start();
// Don't use goog.events because we don't want any lingering references to
// the ports to prevent them from getting GCed. Only modern browsers support
// these APIs anyway, so we don't need to worry about event API
// compatibility.
channel.port1.addEventListener(
goog.events.EventType.MESSAGE, gotMessage, true);
var msg = {};
msg[goog.messaging.PortChannel.FLAG] = true;
window.postMessage(msg, [channel.port2], peerOrigin);
});
return new goog.messaging.DeferredChannel(deferred);
};
/**
* Create a PortChannel that communicates with the document in which this window
* is embedded (e.g. within an iframe). The enclosing document should call
* {@link forEmbeddedWindow} to establish the connection.
*
* It's possible to use this channel in conjunction with other messages posted
* to the global window. However, only one PortChannel should be used for the
* global window at a time.
*
* @param {string} peerOrigin The expected origin of the enclosing document. See
* http://dev.w3.org/html5/postmsg/#dom-window-postmessage.
* @return {!goog.messaging.MessageChannel} The PortChannel. Although this may
* not actually be an instance of the PortChannel class, it will behave like
* one in that MessagePorts may be sent across it.
*/
goog.messaging.PortChannel.forGlobalWindow = function(peerOrigin) {
var deferred = new goog.async.Deferred();
// Wait for the external page to post a message containing the message port
// which we'll use to set up the PortChannel. Ignore all other messages. Once
// we receive the port, notify the other end and then set up the PortChannel.
var key = goog.events.listen(
window, goog.events.EventType.MESSAGE, function(e) {
var browserEvent = e.getBrowserEvent();
var data = browserEvent.data;
if (!goog.isObject(data) || !data[goog.messaging.PortChannel.FLAG]) {
return;
}
if (peerOrigin != '*' && peerOrigin != browserEvent.origin) {
return;
}
var port = browserEvent.ports[0];
// Notify the other end of the channel that we've received our port
port.postMessage({});
port.start();
deferred.callback(new goog.messaging.PortChannel(port));
goog.events.unlistenByKey(key);
});
return new goog.messaging.DeferredChannel(deferred);
};
/**
* The flag added to messages that are sent by a PortChannel, and are meant to
* be handled by one on the other side.
* @type {string}
*/
goog.messaging.PortChannel.FLAG = '--goog.messaging.PortChannel';
/**
* Whether the messages sent across the channel must be JSON-serialized. This is
* required for older versions of Webkit, which can only send string messages.
*
* Although Safari and Chrome have separate implementations of message passing,
* both of them support passing objects by Webkit 533.
*
* @type {boolean}
* @private
*/
goog.messaging.PortChannel.REQUIRES_SERIALIZATION_ = goog.userAgent.WEBKIT &&
goog.string.compareVersions(goog.userAgent.VERSION, '533') < 0;
/**
* Logger for this class.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.messaging.PortChannel.prototype.logger =
goog.log.getLogger('goog.messaging.PortChannel');
/**
* Sends a message over the channel.
*
* As an addition to the basic MessageChannel send API, PortChannels can send
* objects that contain MessagePorts. Note that only plain Objects and Arrays,
* not their subclasses, can contain MessagePorts.
*
* As per {@link http://www.w3.org/TR/html5/comms.html#clone-a-port}, once a
* port is copied to be sent across a channel, the original port will cease
* being able to send or receive messages.
*
* @override
* @param {string} serviceName The name of the service this message should be
* delivered to.
* @param {string|!Object|!MessagePort} payload The value of the message. May
* contain MessagePorts or be a MessagePort.
*/
goog.messaging.PortChannel.prototype.send = function(serviceName, payload) {
var ports = [];
payload = this.extractPorts_(ports, payload);
var message = {'serviceName': serviceName, 'payload': payload};
message[goog.messaging.PortChannel.FLAG] = true;
if (goog.messaging.PortChannel.REQUIRES_SERIALIZATION_) {
message = goog.json.serialize(message);
}
this.port_.postMessage(message, ports);
};
/**
* Delivers a message to the appropriate service handler. If this message isn't
* a GearsWorkerChannel message, it's ignored and passed on to other handlers.
*
* @param {goog.events.Event} e The event.
* @private
*/
goog.messaging.PortChannel.prototype.deliver_ = function(e) {
var browserEvent = e.getBrowserEvent();
var data = browserEvent.data;
if (goog.messaging.PortChannel.REQUIRES_SERIALIZATION_) {
try {
data = goog.json.parse(data);
} catch (error) {
// Ignore any non-JSON messages.
return;
}
}
if (!goog.isObject(data) || !data[goog.messaging.PortChannel.FLAG]) {
return;
}
if (this.validateMessage_(data)) {
var serviceName = data['serviceName'];
var payload = data['payload'];
var service = this.getService(serviceName, payload);
if (!service) {
return;
}
payload = this.decodePayload(
serviceName,
this.injectPorts_(browserEvent.ports || [], payload),
service.objectPayload);
if (goog.isDefAndNotNull(payload)) {
service.callback(payload);
}
}
};
/**
* Checks whether the message is invalid in some way.
*
* @param {Object} data The contents of the message.
* @return {boolean} True if the message is valid, false otherwise.
* @private
*/
goog.messaging.PortChannel.prototype.validateMessage_ = function(data) {
if (!('serviceName' in data)) {
goog.log.warning(this.logger,
'Message object doesn\'t contain service name: ' +
goog.debug.deepExpose(data));
return false;
}
if (!('payload' in data)) {
goog.log.warning(this.logger,
'Message object doesn\'t contain payload: ' +
goog.debug.deepExpose(data));
return false;
}
return true;
};
/**
* Extracts all MessagePort objects from a message to be sent into an array.
*
* The message ports are replaced by placeholder objects that will be replaced
* with the ports again on the other side of the channel.
*
* @param {Array.<MessagePort>} ports The array that will contain ports
* extracted from the message. Will be destructively modified. Should be
* empty initially.
* @param {string|!Object} message The message from which ports will be
* extracted.
* @return {string|!Object} The message with ports extracted.
* @private
*/
goog.messaging.PortChannel.prototype.extractPorts_ = function(ports, message) {
// Can't use instanceof here because MessagePort is undefined in workers
if (message &&
Object.prototype.toString.call(/** @type {!Object} */ (message)) ==
'[object MessagePort]') {
ports.push(message);
return {'_port': {'type': 'real', 'index': ports.length - 1}};
} else if (goog.isArray(message)) {
return goog.array.map(message, goog.bind(this.extractPorts_, this, ports));
// We want to compare the exact constructor here because we only want to
// recurse into object literals, not native objects like Date.
} else if (message && message.constructor == Object) {
return goog.object.map(/** @type {Object} */ (message), function(val, key) {
val = this.extractPorts_(ports, val);
return key == '_port' ? {'type': 'escaped', 'val': val} : val;
}, this);
} else {
return message;
}
};
/**
* Injects MessagePorts back into a message received from across the channel.
*
* @param {Array.<MessagePort>} ports The array of ports to be injected into the
* message.
* @param {string|!Object} message The message into which the ports will be
* injected.
* @return {string|!Object} The message with ports injected.
* @private
*/
goog.messaging.PortChannel.prototype.injectPorts_ = function(ports, message) {
if (goog.isArray(message)) {
return goog.array.map(message, goog.bind(this.injectPorts_, this, ports));
} else if (message && message.constructor == Object) {
message = /** @type {!Object} */ (message);
if (message['_port'] && message['_port']['type'] == 'real') {
return /** @type {!MessagePort} */ (ports[message['_port']['index']]);
}
return goog.object.map(message, function(val, key) {
return this.injectPorts_(ports, key == '_port' ? val['val'] : val);
}, this);
} else {
return message;
}
};
/** @override */
goog.messaging.PortChannel.prototype.disposeInternal = function() {
goog.events.unlistenByKey(this.listenerKey_);
// Can't use instanceof here because MessagePort is undefined in workers and
// in Firefox
if (Object.prototype.toString.call(this.port_) == '[object MessagePort]') {
this.port_.close();
// Worker is undefined in workers as well as of Chrome 9
} else if (Object.prototype.toString.call(this.port_) == '[object Worker]') {
this.port_.terminate();
}
delete this.port_;
goog.base(this, 'disposeInternal');
};

View File

@@ -0,0 +1,78 @@
// 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 An interface for classes that connect a collection of HTML5
* message-passing entities ({@link MessagePort}s, {@link Worker}s, and
* {@link Window}s) and allow them to seamlessly communicate with one another.
*
* Conceptually, a PortNetwork is a collection of JS contexts, such as pages (in
* or outside of iframes) or web workers. Each context has a unique name, and
* each one can communicate with any of the others in the same network. This
* communication takes place through a {@link goog.messaging.PortChannel} that
* is retrieved via {#link goog.messaging.PortNetwork#dial}.
*
* One context (usually the main page) has a
* {@link goog.messaging.PortOperator}, which is in charge of connecting each
* context to each other context. All other contexts have
* {@link goog.messaging.PortCaller}s which connect to the operator.
*
*/
goog.provide('goog.messaging.PortNetwork');
/**
* @interface
*/
goog.messaging.PortNetwork = function() {};
/**
* Returns a message channel that communicates with the named context. If no
* such port exists, an error will either be thrown immediately or after a round
* trip with the operator, depending on whether this pool is the operator or a
* caller.
*
* If context A calls dial('B') and context B calls dial('A'), the two
* ports returned will be connected to one another.
*
* @param {string} name The name of the context to get.
* @return {goog.messaging.MessageChannel} The channel communicating with the
* given context. This is either a {@link goog.messaging.PortChannel} or a
* decorator around a PortChannel, so it's safe to send {@link MessagePorts}
* across it. This will be disposed along with the PortNetwork.
*/
goog.messaging.PortNetwork.prototype.dial = function(name) {};
/**
* The name of the service exported by the operator for creating a connection
* between two callers.
*
* @type {string}
* @const
*/
goog.messaging.PortNetwork.REQUEST_CONNECTION_SERVICE = 'requestConnection';
/**
* The name of the service exported by the callers for adding a connection to
* another context.
*
* @type {string}
* @const
*/
goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE = 'grantConnection';

View File

@@ -0,0 +1,197 @@
// 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 central node of a {@link goog.messaging.PortNetwork}. The
* operator is responsible for providing the two-way communication channels (via
* {@link MessageChannel}s) between each pair of nodes in the network that need
* to communicate with one another. Each network should have one and only one
* operator.
*
*/
goog.provide('goog.messaging.PortOperator');
goog.require('goog.Disposable');
goog.require('goog.asserts');
goog.require('goog.log');
goog.require('goog.messaging.PortChannel');
goog.require('goog.messaging.PortNetwork'); // interface
goog.require('goog.object');
/**
* The central node of a PortNetwork.
*
* @param {string} name The name of this node.
* @constructor
* @extends {goog.Disposable}
* @implements {goog.messaging.PortNetwork}
*/
goog.messaging.PortOperator = function(name) {
goog.base(this);
/**
* The collection of channels for communicating with other contexts in the
* network. These are the channels that are returned to the user, as opposed
* to the channels used for internal network communication. This is lazily
* populated as the user requests communication with other contexts, or other
* contexts request communication with the operator.
*
* @type {!Object.<!goog.messaging.PortChannel>}
* @private
*/
this.connections_ = {};
/**
* The collection of channels for internal network communication with other
* contexts. This is not lazily populated, and always contains entries for
* each member of the network.
*
* @type {!Object.<!goog.messaging.MessageChannel>}
* @private
*/
this.switchboard_ = {};
/**
* The name of the operator context.
*
* @type {string}
* @private
*/
this.name_ = name;
};
goog.inherits(goog.messaging.PortOperator, goog.Disposable);
/**
* The logger for PortOperator.
* @type {goog.log.Logger}
* @private
*/
goog.messaging.PortOperator.prototype.logger_ =
goog.log.getLogger('goog.messaging.PortOperator');
/** @override */
goog.messaging.PortOperator.prototype.dial = function(name) {
this.connectSelfToPort_(name);
return this.connections_[name];
};
/**
* Adds a caller to the network with the given name. This port should have no
* services registered on it. It will be disposed along with the PortOperator.
*
* @param {string} name The name of the port to add.
* @param {!goog.messaging.MessageChannel} port The port to add. 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.
*/
goog.messaging.PortOperator.prototype.addPort = function(name, port) {
this.switchboard_[name] = port;
port.registerService(goog.messaging.PortNetwork.REQUEST_CONNECTION_SERVICE,
goog.bind(this.requestConnection_, this, name));
};
/**
* Connects two contexts by creating a {@link MessageChannel} and sending one
* end to one context and the other end to the other. Called when we receive a
* request from a caller to connect it to another context (including potentially
* the operator).
*
* @param {string} sourceName The name of the context requesting the connection.
* @param {!Object|string} message The name of the context to which
* the connection is requested.
* @private
*/
goog.messaging.PortOperator.prototype.requestConnection_ = function(
sourceName, message) {
var requestedName = /** @type {string} */ (message);
if (requestedName == this.name_) {
this.connectSelfToPort_(sourceName);
return;
}
var sourceChannel = this.switchboard_[sourceName];
var requestedChannel = this.switchboard_[requestedName];
goog.asserts.assert(goog.isDefAndNotNull(sourceChannel));
if (!requestedChannel) {
var err = 'Port "' + sourceName + '" requested a connection to port "' +
requestedName + '", which doesn\'t exist';
goog.log.warning(this.logger_, err);
sourceChannel.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE,
{'success': false, 'message': err});
return;
}
var messageChannel = new MessageChannel();
sourceChannel.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, {
'success': true,
'name': requestedName,
'port': messageChannel.port1
});
requestedChannel.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, {
'success': true,
'name': sourceName,
'port': messageChannel.port2
});
};
/**
* Connects together the operator and a caller by creating a
* {@link MessageChannel} and sending one end to the remote context.
*
* @param {string} contextName The name of the context to which to connect the
* operator.
* @private
*/
goog.messaging.PortOperator.prototype.connectSelfToPort_ = function(
contextName) {
if (contextName in this.connections_) {
// We've already established a connection with this port.
return;
}
var contextChannel = this.switchboard_[contextName];
if (!contextChannel) {
throw Error('Port "' + contextName + '" doesn\'t exist');
}
var messageChannel = new MessageChannel();
contextChannel.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, {
'success': true,
'name': this.name_,
'port': messageChannel.port1
});
messageChannel.port2.start();
this.connections_[contextName] =
new goog.messaging.PortChannel(messageChannel.port2);
};
/** @override */
goog.messaging.PortOperator.prototype.disposeInternal = function() {
goog.object.forEach(this.switchboard_, goog.dispose);
goog.object.forEach(this.connections_, goog.dispose);
delete this.switchboard_;
delete this.connections_;
goog.base(this, 'disposeInternal');
};

View File

@@ -0,0 +1,236 @@
// Copyright 2012 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 Definition of goog.messaging.RespondingChannel, which wraps a
* MessageChannel and allows the user to get the response from the services.
*
*/
goog.provide('goog.messaging.RespondingChannel');
goog.require('goog.Disposable');
goog.require('goog.log');
goog.require('goog.messaging.MessageChannel'); // interface
goog.require('goog.messaging.MultiChannel');
goog.require('goog.messaging.MultiChannel.VirtualChannel');
/**
* Creates a new RespondingChannel wrapping a single MessageChannel.
* @param {goog.messaging.MessageChannel} messageChannel The messageChannel to
* to wrap and allow for responses. This channel must not have any existing
* services registered. All service registration must be done through the
* {@link RespondingChannel#registerService} api instead. The other end of
* channel must also be a RespondingChannel.
* @constructor
* @extends {goog.Disposable}
*/
goog.messaging.RespondingChannel = function(messageChannel) {
goog.base(this);
/**
* The message channel wrapped in a MultiChannel so we can send private and
* public messages on it.
* @type {goog.messaging.MultiChannel}
* @private
*/
this.messageChannel_ = new goog.messaging.MultiChannel(messageChannel);
/**
* Map of invocation signatures to function callbacks. These are used to keep
* track of the asyncronous service invocations so the result of a service
* call can be passed back to a callback in the calling frame.
* @type {Object.<number, function(Object)>}
* @private
*/
this.sigCallbackMap_ = {};
/**
* The virtual channel to send private messages on.
* @type {goog.messaging.MultiChannel.VirtualChannel}
* @private
*/
this.privateChannel_ = this.messageChannel_.createVirtualChannel(
goog.messaging.RespondingChannel.PRIVATE_CHANNEL_);
/**
* The virtual channel to send public messages on.
* @type {goog.messaging.MultiChannel.VirtualChannel}
* @private
*/
this.publicChannel_ = this.messageChannel_.createVirtualChannel(
goog.messaging.RespondingChannel.PUBLIC_CHANNEL_);
this.privateChannel_.registerService(
goog.messaging.RespondingChannel.CALLBACK_SERVICE_,
goog.bind(this.callbackServiceHandler_, this),
true);
};
goog.inherits(goog.messaging.RespondingChannel, goog.Disposable);
/**
* The name of the method invocation callback service (used internally).
* @type {string}
* @const
* @private
*/
goog.messaging.RespondingChannel.CALLBACK_SERVICE_ = 'mics';
/**
* The name of the channel to send private control messages on.
* @type {string}
* @const
* @private
*/
goog.messaging.RespondingChannel.PRIVATE_CHANNEL_ = 'private';
/**
* The name of the channel to send public messages on.
* @type {string}
* @const
* @private
*/
goog.messaging.RespondingChannel.PUBLIC_CHANNEL_ = 'public';
/**
* The next signature index to save the callback against.
* @type {number}
* @private
*/
goog.messaging.RespondingChannel.prototype.nextSignatureIndex_ = 0;
/**
* Logger object for goog.messaging.RespondingChannel.
* @type {goog.log.Logger}
* @private
*/
goog.messaging.RespondingChannel.prototype.logger_ =
goog.log.getLogger('goog.messaging.RespondingChannel');
/**
* Gets a random number to use for method invocation results.
* @return {number} A unique random signature.
* @private
*/
goog.messaging.RespondingChannel.prototype.getNextSignature_ = function() {
return this.nextSignatureIndex_++;
};
/** @override */
goog.messaging.RespondingChannel.prototype.disposeInternal = function() {
goog.dispose(this.messageChannel_);
delete this.messageChannel_;
// Note: this.publicChannel_ and this.privateChannel_ get disposed by
// this.messageChannel_
delete this.publicChannel_;
delete this.privateChannel_;
};
/**
* Sends a message over the channel.
* @param {string} serviceName The name of the service this message should be
* delivered to.
* @param {string|!Object} payload The value of the message. If this is an
* Object, it is serialized to a string before sending if necessary.
* @param {function(?Object)} callback The callback invoked with
* the result of the service call.
*/
goog.messaging.RespondingChannel.prototype.send = function(
serviceName,
payload,
callback) {
var signature = this.getNextSignature_();
this.sigCallbackMap_[signature] = callback;
var message = {};
message['signature'] = signature;
message['data'] = payload;
this.publicChannel_.send(serviceName, message);
};
/**
* Receives the results of the peer's service results.
* @param {!Object|string} message The results from the remote service
* invocation.
* @private
*/
goog.messaging.RespondingChannel.prototype.callbackServiceHandler_ = function(
message) {
var signature = message['signature'];
var result = message['data'];
if (signature in this.sigCallbackMap_) {
var callback = /** @type {function(Object)} */ (this.sigCallbackMap_[
signature]);
callback(result);
delete this.sigCallbackMap_[signature];
} else {
goog.log.warning(this.logger_, 'Received signature is invalid');
}
};
/**
* Registers a service to be called when a message is received.
* @param {string} serviceName The name of the service.
* @param {function(!Object)} callback The callback to process the
* incoming messages. Passed the payload.
*/
goog.messaging.RespondingChannel.prototype.registerService = function(
serviceName, callback) {
this.publicChannel_.registerService(
serviceName,
goog.bind(this.callbackProxy_, this, callback),
true);
};
/**
* A intermediary proxy for service callbacks to be invoked and return their
* their results to the remote caller's callback.
* @param {function((string|!Object))} callback The callback to process the
* incoming messages. Passed the payload.
* @param {!Object|string} message The message containing the signature and
* the data to invoke the service callback with.
* @private
*/
goog.messaging.RespondingChannel.prototype.callbackProxy_ = function(
callback, message) {
var resultMessage = {};
resultMessage['data'] = callback(message['data']);
resultMessage['signature'] = message['signature'];
// The callback invoked above may have disposed the channel so check if it
// exists.
if (this.privateChannel_) {
this.privateChannel_.send(
goog.messaging.RespondingChannel.CALLBACK_SERVICE_,
resultMessage);
}
};

View File

@@ -0,0 +1,37 @@
// Copyright 2010 The Closure Library Authors. All Rights Reserved.
// Use of this source code is governed by the Apache License, Version 2.0.
// See the COPYING file for details.
/**
* @fileoverview A web worker for integration testing the PortChannel class.
*
* @nocompile
*/
self.CLOSURE_BASE_PATH = '../../';
importScripts('../../bootstrap/webworkers.js');
importScripts('../../base.js');
// The provide is necessary to stop the jscompiler from thinking this is an
// entry point and adding it into the manifest incorrectly.
goog.provide('goog.messaging.testdata.portchannel_worker');
goog.require('goog.messaging.PortChannel');
function registerPing(channel) {
channel.registerService('ping', function(msg) {
channel.send('pong', msg);
}, true);
}
function startListening() {
var channel = new goog.messaging.PortChannel(self);
registerPing(channel);
channel.registerService('addPort', function(port) {
port.start();
registerPing(new goog.messaging.PortChannel(port));
}, true);
}
startListening();

View File

@@ -0,0 +1,32 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
// Use of this source code is governed by the Apache License, Version 2.0.
// See the COPYING file for details.
/**
* @fileoverview A web worker for integration testing the PortPool class.
*
* @nocompile
*/
self.CLOSURE_BASE_PATH = '../../';
importScripts('../../bootstrap/webworkers.js');
importScripts('../../base.js');
// The provide is necessary to stop the jscompiler from thinking this is an
// entry point and adding it into the manifest incorrectly.
goog.provide('goog.messaging.testdata.portnetwork_worker1');
goog.require('goog.messaging.PortCaller');
goog.require('goog.messaging.PortChannel');
function startListening() {
var caller = new goog.messaging.PortCaller(
new goog.messaging.PortChannel(self));
caller.dial('frame').registerService('sendToMain', function(msg) {
msg.push('worker1');
caller.dial('main').send('result', msg);
}, true);
}
startListening();

View File

@@ -0,0 +1,32 @@
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
// Use of this source code is governed by the Apache License, Version 2.0.
// See the COPYING file for details.
/**
* @fileoverview A web worker for integration testing the PortPool class.
*
* @nocompile
*/
self.CLOSURE_BASE_PATH = '../../';
importScripts('../../bootstrap/webworkers.js');
importScripts('../../base.js');
// The provide is necessary to stop the jscompiler from thinking this is an
// entry point and adding it into the manifest incorrectly.
goog.provide('goog.messaging.testdata.portnetwork_worker2');
goog.require('goog.messaging.PortCaller');
goog.require('goog.messaging.PortChannel');
function startListening() {
var caller = new goog.messaging.PortCaller(
new goog.messaging.PortChannel(self));
caller.dial('main').registerService('sendToFrame', function(msg) {
msg.push('worker2');
caller.dial('frame').send('sendToWorker1', msg);
}, true);
}
startListening();