Adding mapbox-gl branch
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
// 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.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.messaging.AbstractChannel.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* 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.messaging.AbstractChannel.base(this, 'disposeInternal');
|
||||
delete this.logger;
|
||||
delete this.services_;
|
||||
delete this.defaultService_;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.AbstractChannel
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.messaging.AbstractChannelTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.messaging.AbstractChannelTest');
|
||||
goog.setTestOnly('goog.messaging.AbstractChannelTest');
|
||||
|
||||
goog.require('goog.messaging.AbstractChannel');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.async.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var mockControl;
|
||||
var mockWorker;
|
||||
var asyncMockControl;
|
||||
var channel;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
asyncMockControl = new goog.testing.async.MockControl(mockControl);
|
||||
channel = new goog.messaging.AbstractChannel();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
channel.dispose();
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testConnect() {
|
||||
channel.connect(
|
||||
asyncMockControl.createCallbackMock('connectCallback', function() {}));
|
||||
}
|
||||
|
||||
function testIsConnected() {
|
||||
assertTrue('Channel should be connected by default', channel.isConnected());
|
||||
}
|
||||
|
||||
function testDeliverString() {
|
||||
channel.registerService(
|
||||
'foo',
|
||||
asyncMockControl.asyncAssertEquals(
|
||||
'should pass string to service', 'bar'),
|
||||
false /* opt_json */);
|
||||
channel.deliver('foo', 'bar');
|
||||
}
|
||||
|
||||
function testDeliverDeserializedString() {
|
||||
channel.registerService(
|
||||
'foo',
|
||||
asyncMockControl.asyncAssertEquals(
|
||||
'should pass string to service', '{"bar":"baz"}'),
|
||||
false /* opt_json */);
|
||||
channel.deliver('foo', {bar: 'baz'});
|
||||
}
|
||||
|
||||
function testDeliverObject() {
|
||||
channel.registerService(
|
||||
'foo',
|
||||
asyncMockControl.asyncAssertEquals(
|
||||
'should pass string to service', {bar: 'baz'}),
|
||||
true /* opt_json */);
|
||||
channel.deliver('foo', {bar: 'baz'});
|
||||
}
|
||||
|
||||
function testDeliverSerializedObject() {
|
||||
channel.registerService(
|
||||
'foo',
|
||||
asyncMockControl.asyncAssertEquals(
|
||||
'should pass string to service', {bar: 'baz'}),
|
||||
true /* opt_json */);
|
||||
channel.deliver('foo', '{"bar":"baz"}');
|
||||
}
|
||||
@@ -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.Disposable');
|
||||
goog.require('goog.Timer');
|
||||
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};
|
||||
* @final
|
||||
*/
|
||||
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.messaging.BufferedChannel.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.BufferedChannel
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.messaging.BufferedChannelTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="debug-container"
|
||||
style="border: 1px solid black; font-size: small; font-family: courier;">
|
||||
Debug Window
|
||||
[
|
||||
<a href="#" onclick="document.getElementById('debug-div').innerHTML = '';">
|
||||
clear
|
||||
</a>
|
||||
]
|
||||
<div id="debug-div">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,268 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.messaging.BufferedChannelTest');
|
||||
goog.setTestOnly('goog.messaging.BufferedChannelTest');
|
||||
|
||||
goog.require('goog.debug.Console');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.log.Level');
|
||||
goog.require('goog.messaging.BufferedChannel');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.async.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
|
||||
var clock;
|
||||
var messages = [
|
||||
{serviceName: 'firstService', payload: 'firstPayload'},
|
||||
{serviceName: 'secondService', payload: 'secondPayload'}
|
||||
];
|
||||
var mockControl;
|
||||
var asyncMockControl;
|
||||
|
||||
function setUpPage() {
|
||||
if (goog.global.console) {
|
||||
new goog.debug.Console().setCapturing(true);
|
||||
}
|
||||
var logger = goog.log.getLogger('goog.messaging');
|
||||
logger.setLevel(goog.log.Level.ALL);
|
||||
goog.log.addHandler(logger, function(logRecord) {
|
||||
var msg = goog.dom.createDom('div');
|
||||
msg.innerHTML = logRecord.getMessage();
|
||||
goog.dom.appendChild(goog.dom.getElement('debug-div'), msg);
|
||||
});
|
||||
clock = new goog.testing.MockClock();
|
||||
mockControl = new goog.testing.MockControl();
|
||||
asyncMockControl = new goog.testing.async.MockControl(mockControl);
|
||||
}
|
||||
|
||||
|
||||
function setUp() {
|
||||
clock.install();
|
||||
}
|
||||
|
||||
|
||||
function tearDown() {
|
||||
clock.uninstall();
|
||||
mockControl.$tearDown();
|
||||
}
|
||||
|
||||
|
||||
function assertMessageArraysEqual(ma1, ma2) {
|
||||
assertEquals('message array lengths differ', ma1.length, ma2.length);
|
||||
for (var i = 0; i < ma1.length; i++) {
|
||||
assertEquals(
|
||||
'message array serviceNames differ',
|
||||
ma1[i].serviceName, ma2[i].serviceName);
|
||||
assertEquals(
|
||||
'message array payloads differ',
|
||||
ma1[i].payload, ma2[i].payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function testDelegationToWrappedChannel() {
|
||||
var mockChannel = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var channel = new goog.messaging.BufferedChannel(mockChannel);
|
||||
|
||||
channel.registerDefaultService(
|
||||
asyncMockControl.asyncAssertEquals(
|
||||
'default service should be delegated',
|
||||
'defaultServiceName', 'default service payload'));
|
||||
channel.registerService(
|
||||
'normalServiceName',
|
||||
asyncMockControl.asyncAssertEquals(
|
||||
'normal service should be delegated',
|
||||
'normal service payload'));
|
||||
mockChannel.send(
|
||||
goog.messaging.BufferedChannel.USER_CHANNEL_NAME_ + ':message',
|
||||
'payload');
|
||||
|
||||
mockControl.$replayAll();
|
||||
channel.peerReady_ = true; // Prevent buffering so we delegate send calls.
|
||||
mockChannel.receive(
|
||||
goog.messaging.BufferedChannel.USER_CHANNEL_NAME_ + ':defaultServiceName',
|
||||
'default service payload');
|
||||
mockChannel.receive(
|
||||
goog.messaging.BufferedChannel.USER_CHANNEL_NAME_ + ':normalServiceName',
|
||||
'normal service payload');
|
||||
channel.send('message', 'payload');
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
function testOptionalConnectCallbackExecutes() {
|
||||
var mockChannel = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var channel = new goog.messaging.BufferedChannel(mockChannel);
|
||||
var mockConnectCb = mockControl.createFunctionMock('mockConnectCb');
|
||||
mockConnectCb();
|
||||
|
||||
mockControl.$replayAll();
|
||||
channel.connect(mockConnectCb);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
function testSendExceptionsInSendReadyPingStopsTimerAndReraises() {
|
||||
var mockChannel = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var channel = new goog.messaging.BufferedChannel(mockChannel);
|
||||
|
||||
var errorMessage = 'errorMessage';
|
||||
mockChannel.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':' +
|
||||
goog.messaging.BufferedChannel.PEER_READY_SERVICE_NAME_,
|
||||
/* payload */ '').$throws(Error(errorMessage));
|
||||
channel.timer_.enabled = true;
|
||||
|
||||
mockControl.$replayAll();
|
||||
var exception = assertThrows(function() {
|
||||
channel.sendReadyPing_();
|
||||
});
|
||||
assertContains(errorMessage, exception.message);
|
||||
assertFalse(channel.timer_.enabled);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
function testPollingIntervalDefaultAndOverride() {
|
||||
var mockChannel = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var channel = new goog.messaging.BufferedChannel(mockChannel);
|
||||
|
||||
assertEquals(
|
||||
goog.messaging.BufferedChannel.DEFAULT_INTERVAL_MILLIS_,
|
||||
channel.timer_.getInterval());
|
||||
var interval = 100;
|
||||
var longIntervalChannel = new goog.messaging.BufferedChannel(
|
||||
new goog.testing.messaging.MockMessageChannel(mockControl), interval);
|
||||
assertEquals(interval, longIntervalChannel.timer_.getInterval());
|
||||
}
|
||||
|
||||
|
||||
function testBidirectionalCommunicationBuffersUntilReadyPingsSucceed() {
|
||||
var mockChannel1 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var mockChannel2 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var bufferedChannel1 = new goog.messaging.BufferedChannel(mockChannel1);
|
||||
var bufferedChannel2 = new goog.messaging.BufferedChannel(mockChannel2);
|
||||
mockChannel1.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
'').$does(function() {
|
||||
bufferedChannel2.setPeerReady_('');
|
||||
});
|
||||
mockChannel2.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
'1').$does(function() {
|
||||
bufferedChannel1.setPeerReady_('1');
|
||||
});
|
||||
mockChannel1.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
'1').$does(function() {
|
||||
bufferedChannel2.setPeerReady_('1');
|
||||
});
|
||||
mockChannel1.send(goog.messaging.BufferedChannel.USER_CHANNEL_NAME_ + ':' +
|
||||
messages[0].serviceName,
|
||||
messages[0].payload);
|
||||
mockChannel2.send(goog.messaging.BufferedChannel.USER_CHANNEL_NAME_ + ':' +
|
||||
messages[1].serviceName,
|
||||
messages[1].payload);
|
||||
|
||||
mockControl.$replayAll();
|
||||
bufferedChannel1.send(messages[0].serviceName, messages[0].payload);
|
||||
bufferedChannel2.send(messages[1].serviceName, messages[1].payload);
|
||||
assertMessageArraysEqual([messages[0]], bufferedChannel1.buffer_);
|
||||
assertMessageArraysEqual([messages[1]], bufferedChannel2.buffer_);
|
||||
// First tick causes setPeerReady_ to fire, which in turn flushes the buffers.
|
||||
clock.tick(goog.messaging.BufferedChannel.DEFAULT_INTERVAL_MILLIS_);
|
||||
assertEquals(bufferedChannel1.buffer_, null);
|
||||
assertEquals(bufferedChannel2.buffer_, null);
|
||||
// Now that peers are ready, a second tick causes no more sends.
|
||||
clock.tick(goog.messaging.BufferedChannel.DEFAULT_INTERVAL_MILLIS_);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
|
||||
function testBidirectionalCommunicationReconnectsAfterOneSideRestarts() {
|
||||
var mockChannel1 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var mockChannel2 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var mockChannel3 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var bufferedChannel1 = new goog.messaging.BufferedChannel(mockChannel1);
|
||||
var bufferedChannel2 = new goog.messaging.BufferedChannel(mockChannel2);
|
||||
var bufferedChannel3 = new goog.messaging.BufferedChannel(mockChannel3);
|
||||
|
||||
// First tick
|
||||
mockChannel1.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
'').$does(function() {
|
||||
bufferedChannel2.setPeerReady_('');
|
||||
});
|
||||
mockChannel2.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
'1').$does(function() {
|
||||
bufferedChannel1.setPeerReady_('1');
|
||||
});
|
||||
mockChannel1.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
'1').$does(function() {
|
||||
bufferedChannel2.setPeerReady_('1');
|
||||
});
|
||||
mockChannel3.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
''); // pretend it's not ready to connect yet
|
||||
|
||||
// Second tick
|
||||
mockChannel3.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
'').$does(function() {
|
||||
bufferedChannel1.setPeerReady_('');
|
||||
});
|
||||
|
||||
// Third tick
|
||||
mockChannel1.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
'1').$does(function() {
|
||||
bufferedChannel3.setPeerReady_('1');
|
||||
});
|
||||
mockChannel3.send(
|
||||
goog.messaging.BufferedChannel.CONTROL_CHANNEL_NAME_ + ':setPeerReady_',
|
||||
'1').$does(function() {
|
||||
bufferedChannel1.setPeerReady_('1');
|
||||
});
|
||||
|
||||
mockChannel1.send(goog.messaging.BufferedChannel.USER_CHANNEL_NAME_ + ':' +
|
||||
messages[0].serviceName,
|
||||
messages[0].payload);
|
||||
mockChannel3.send(goog.messaging.BufferedChannel.USER_CHANNEL_NAME_ + ':' +
|
||||
messages[1].serviceName,
|
||||
messages[1].payload);
|
||||
|
||||
mockControl.$replayAll();
|
||||
// First tick causes setPeerReady_ to fire, which sets up the connection
|
||||
// between channels 1 and 2.
|
||||
clock.tick(goog.messaging.BufferedChannel.DEFAULT_INTERVAL_MILLIS_);
|
||||
assertTrue(bufferedChannel1.peerReady_);
|
||||
assertTrue(bufferedChannel2.peerReady_);
|
||||
// Now pretend that channel 2 went down and was replaced by channel 3, which
|
||||
// is trying to connect with channel 1.
|
||||
clock.tick(goog.messaging.BufferedChannel.DEFAULT_INTERVAL_MILLIS_);
|
||||
assertTrue(bufferedChannel1.peerReady_);
|
||||
clock.tick(goog.messaging.BufferedChannel.DEFAULT_INTERVAL_MILLIS_);
|
||||
assertTrue(bufferedChannel3.peerReady_);
|
||||
bufferedChannel1.send(messages[0].serviceName, messages[0].payload);
|
||||
bufferedChannel3.send(messages[1].serviceName, messages[1].payload);
|
||||
// All timers stopped, nothing happens on the fourth tick.
|
||||
clock.tick(goog.messaging.BufferedChannel.DEFAULT_INTERVAL_MILLIS_);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
@@ -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.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}
|
||||
* @final
|
||||
*/
|
||||
goog.messaging.DeferredChannel = function(deferredChannel) {
|
||||
goog.messaging.DeferredChannel.base(this, 'constructor');
|
||||
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.messaging.DeferredChannel.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.DeferredChannel
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.messaging.DeferredChannelTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.messaging.DeferredChannelTest');
|
||||
goog.setTestOnly('goog.messaging.DeferredChannelTest');
|
||||
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.messaging.DeferredChannel');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.async.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
|
||||
var mockControl, asyncMockControl;
|
||||
var mockChannel, deferredChannel;
|
||||
var cancelled;
|
||||
var deferred;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
asyncMockControl = new goog.testing.async.MockControl(mockControl);
|
||||
mockChannel = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
cancelled = false;
|
||||
deferred = new goog.async.Deferred(function() { cancelled = true; });
|
||||
deferredChannel = new goog.messaging.DeferredChannel(deferred);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testDeferredResolvedBeforeSend() {
|
||||
mockChannel.send('test', 'val');
|
||||
mockControl.$replayAll();
|
||||
deferred.callback(mockChannel);
|
||||
deferredChannel.send('test', 'val');
|
||||
}
|
||||
|
||||
function testDeferredResolvedBeforeRegister() {
|
||||
deferred.callback(mockChannel);
|
||||
deferredChannel.registerService(
|
||||
'test', asyncMockControl.asyncAssertEquals('passes on register', 'val'));
|
||||
mockChannel.receive('test', 'val');
|
||||
}
|
||||
|
||||
function testDeferredResolvedBeforeRegisterObject() {
|
||||
deferred.callback(mockChannel);
|
||||
deferredChannel.registerService(
|
||||
'test',
|
||||
asyncMockControl.asyncAssertEquals('passes on register', {'key': 'val'}),
|
||||
true);
|
||||
mockChannel.receive('test', {'key': 'val'});
|
||||
}
|
||||
|
||||
function testDeferredResolvedBeforeRegisterDefault() {
|
||||
deferred.callback(mockChannel);
|
||||
deferredChannel.registerDefaultService(
|
||||
asyncMockControl.asyncAssertEquals('passes on register', 'test', 'val'));
|
||||
mockChannel.receive('test', 'val');
|
||||
}
|
||||
|
||||
function testDeferredResolvedAfterSend() {
|
||||
mockChannel.send('test', 'val');
|
||||
mockControl.$replayAll();
|
||||
deferredChannel.send('test', 'val');
|
||||
deferred.callback(mockChannel);
|
||||
}
|
||||
|
||||
function testDeferredResolvedAfterRegister() {
|
||||
deferredChannel.registerService(
|
||||
'test', asyncMockControl.asyncAssertEquals('passes on register', 'val'));
|
||||
deferred.callback(mockChannel);
|
||||
mockChannel.receive('test', 'val');
|
||||
}
|
||||
|
||||
function testDeferredResolvedAfterRegisterObject() {
|
||||
deferredChannel.registerService(
|
||||
'test',
|
||||
asyncMockControl.asyncAssertEquals('passes on register', {'key': 'val'}),
|
||||
true);
|
||||
deferred.callback(mockChannel);
|
||||
mockChannel.receive('test', {'key': 'val'});
|
||||
}
|
||||
|
||||
function testDeferredResolvedAfterRegisterDefault() {
|
||||
deferredChannel.registerDefaultService(
|
||||
asyncMockControl.asyncAssertEquals('passes on register', 'test', 'val'));
|
||||
deferred.callback(mockChannel);
|
||||
mockChannel.receive('test', 'val');
|
||||
}
|
||||
|
||||
function testCancel() {
|
||||
deferredChannel.cancel();
|
||||
assertTrue(cancelled);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// 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}
|
||||
* @final
|
||||
*/
|
||||
goog.messaging.LoggerClient = function(channel, serviceName) {
|
||||
if (goog.messaging.LoggerClient.instance_) {
|
||||
return goog.messaging.LoggerClient.instance_;
|
||||
}
|
||||
|
||||
goog.messaging.LoggerClient.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* 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.messaging.LoggerClient.base(this, 'disposeInternal');
|
||||
goog.debug.LogManager.getRoot().removeHandler(this.publishHandler_);
|
||||
delete this.channel_;
|
||||
goog.messaging.LoggerClient.instance_ = null;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.LoggerClient
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.messaging.LoggerClientTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,97 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.messaging.LoggerClientTest');
|
||||
goog.setTestOnly('goog.messaging.LoggerClientTest');
|
||||
|
||||
goog.require('goog.debug');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.messaging.LoggerClient');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
|
||||
var mockControl;
|
||||
var channel;
|
||||
var client;
|
||||
var logger;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
channel = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
client = new goog.messaging.LoggerClient(channel, 'log');
|
||||
logger = goog.debug.Logger.getLogger('test.logging.Object');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
channel.dispose();
|
||||
client.dispose();
|
||||
}
|
||||
|
||||
function testCommand() {
|
||||
channel.send('log', {
|
||||
name: 'test.logging.Object',
|
||||
level: goog.debug.Logger.Level.WARNING.value,
|
||||
message: 'foo bar',
|
||||
exception: undefined
|
||||
});
|
||||
mockControl.$replayAll();
|
||||
logger.warning('foo bar');
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testCommandWithException() {
|
||||
var ex = Error('oh no');
|
||||
ex.stack = ['one', 'two'];
|
||||
ex.message0 = 'message 0';
|
||||
ex.message1 = 'message 1';
|
||||
ex.ignoredProperty = 'ignored';
|
||||
|
||||
channel.send('log', {
|
||||
name: 'test.logging.Object',
|
||||
level: goog.debug.Logger.Level.WARNING.value,
|
||||
message: 'foo bar',
|
||||
exception: {
|
||||
name: 'Error',
|
||||
message: ex.message,
|
||||
stack: ex.stack,
|
||||
lineNumber: ex.lineNumber || 'Not available',
|
||||
fileName: ex.fileName || window.location.href,
|
||||
message0: ex.message0,
|
||||
message1: ex.message1
|
||||
}
|
||||
});
|
||||
mockControl.$replayAll();
|
||||
logger.warning('foo bar', ex);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testCommandWithStringException() {
|
||||
channel.send('log', {
|
||||
name: 'test.logging.Object',
|
||||
level: goog.debug.Logger.Level.WARNING.value,
|
||||
message: 'foo bar',
|
||||
exception: {
|
||||
name: 'Unknown error',
|
||||
message: 'oh no',
|
||||
stack: '[Anonymous](object, foo bar, oh no)\n' +
|
||||
'[Anonymous](foo bar, oh no)\n' + goog.debug.getStacktrace(),
|
||||
lineNumber: 'Not available',
|
||||
fileName: window.location.href
|
||||
}
|
||||
});
|
||||
mockControl.$replayAll();
|
||||
logger.warning('foo bar', 'oh no');
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// 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');
|
||||
goog.require('goog.log.Level');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @final
|
||||
*/
|
||||
goog.messaging.LoggerServer = function(channel, serviceName, opt_channelName) {
|
||||
goog.messaging.LoggerServer.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* 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.messaging.LoggerServer.base(this, 'disposeInternal');
|
||||
this.channel_.registerService(this.serviceName_, goog.nullFunction, true);
|
||||
delete this.channel_;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.LoggerServer
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.messaging.LoggerServerTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,101 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.messaging.LoggerServerTest');
|
||||
goog.setTestOnly('goog.messaging.LoggerServerTest');
|
||||
|
||||
goog.require('goog.debug.LogManager');
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.log.Level');
|
||||
goog.require('goog.messaging.LoggerServer');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
|
||||
var mockControl;
|
||||
var channel;
|
||||
var stubs;
|
||||
|
||||
function setUpPage() {
|
||||
stubs = new goog.testing.PropertyReplacer();
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
channel = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
stubs.set(goog.debug.LogManager, 'getLogger',
|
||||
mockControl.createFunctionMock('goog.log.getLogger'));
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
channel.dispose();
|
||||
stubs.reset();
|
||||
}
|
||||
|
||||
function testCommandWithoutChannelName() {
|
||||
var mockLogger = mockControl.createStrictMock(goog.debug.Logger);
|
||||
goog.log.getLogger('test.object.Name').$returns(mockLogger);
|
||||
goog.log.log(mockLogger,
|
||||
goog.log.Level.SEVERE, '[remote logger] foo bar', null);
|
||||
mockControl.$replayAll();
|
||||
|
||||
var server = new goog.messaging.LoggerServer(channel, 'log');
|
||||
channel.receive('log', {
|
||||
name: 'test.object.Name',
|
||||
level: goog.log.Level.SEVERE.value,
|
||||
message: 'foo bar',
|
||||
exception: null
|
||||
});
|
||||
mockControl.$verifyAll();
|
||||
server.dispose();
|
||||
}
|
||||
|
||||
function testCommandWithChannelName() {
|
||||
var mockLogger = mockControl.createStrictMock(goog.debug.Logger);
|
||||
goog.log.getLogger('test.object.Name').$returns(mockLogger);
|
||||
goog.log.log(mockLogger,
|
||||
goog.log.Level.SEVERE, '[some channel] foo bar', null);
|
||||
mockControl.$replayAll();
|
||||
|
||||
var server = new goog.messaging.LoggerServer(channel, 'log', 'some channel');
|
||||
channel.receive('log', {
|
||||
name: 'test.object.Name',
|
||||
level: goog.log.Level.SEVERE.value,
|
||||
message: 'foo bar',
|
||||
exception: null
|
||||
});
|
||||
mockControl.$verifyAll();
|
||||
server.dispose();
|
||||
}
|
||||
|
||||
function testCommandWithException() {
|
||||
var mockLogger = mockControl.createStrictMock(goog.debug.Logger);
|
||||
goog.log.getLogger('test.object.Name').$returns(mockLogger);
|
||||
goog.log.log(mockLogger,
|
||||
goog.log.Level.SEVERE, '[some channel] foo bar',
|
||||
{message: 'Bad things', stack: ['foo', 'bar']});
|
||||
mockControl.$replayAll();
|
||||
|
||||
var server = new goog.messaging.LoggerServer(channel, 'log', 'some channel');
|
||||
channel.receive('log', {
|
||||
name: 'test.object.Name',
|
||||
level: goog.log.Level.SEVERE.value,
|
||||
message: 'foo bar',
|
||||
exception: {message: 'Bad things', stack: ['foo', 'bar']}
|
||||
});
|
||||
mockControl.$verifyAll();
|
||||
server.dispose();
|
||||
}
|
||||
@@ -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) {};
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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');
|
||||
|
||||
|
||||
/**
|
||||
* 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));
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.testing.messaging.MockMessageChannelTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.testing.messaging.MockMessageChannelTest');
|
||||
goog.setTestOnly('goog.testing.messaging.MockMessageChannelTest');
|
||||
|
||||
goog.require('goog.messaging');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
|
||||
function testPipe() {
|
||||
var mockControl = new goog.testing.MockControl();
|
||||
var ch1 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
var ch2 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
ch1.send('ping', 'HELLO');
|
||||
ch2.send('pong', {key: 'value'});
|
||||
goog.messaging.pipe(ch1, ch2);
|
||||
|
||||
mockControl.$replayAll();
|
||||
ch2.receive('ping', 'HELLO');
|
||||
ch1.receive('pong', {key: 'value'});
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
// 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.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}
|
||||
* @final
|
||||
*/
|
||||
goog.messaging.MultiChannel = function(underlyingChannel) {
|
||||
goog.messaging.MultiChannel.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @final
|
||||
*/
|
||||
goog.messaging.MultiChannel.VirtualChannel = function(parent, name) {
|
||||
goog.messaging.MultiChannel.VirtualChannel.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.MultiChannel
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.messaging.MultiChannelTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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.
|
||||
|
||||
goog.provide('goog.messaging.MultiChannelTest');
|
||||
goog.setTestOnly('goog.messaging.MultiChannelTest');
|
||||
|
||||
goog.require('goog.messaging.MultiChannel');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
goog.require('goog.testing.mockmatchers.IgnoreArgument');
|
||||
|
||||
var mockControl;
|
||||
var mockChannel;
|
||||
var multiChannel;
|
||||
var channel1;
|
||||
var channel2;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
mockChannel = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
multiChannel = new goog.messaging.MultiChannel(mockChannel);
|
||||
channel0 = multiChannel.createVirtualChannel('foo');
|
||||
channel1 = multiChannel.createVirtualChannel('bar');
|
||||
}
|
||||
|
||||
function expectedFn(name, callback) {
|
||||
var ignored = new goog.testing.mockmatchers.IgnoreArgument();
|
||||
var fn = mockControl.createFunctionMock(name);
|
||||
fn(ignored).$does(function(args) {
|
||||
callback.apply(this, args);
|
||||
});
|
||||
return function() { fn(arguments); };
|
||||
}
|
||||
|
||||
function notExpectedFn() {
|
||||
return mockControl.createFunctionMock('notExpectedFn');
|
||||
}
|
||||
|
||||
function assertEqualsFn() {
|
||||
var expectedArgs = Array.prototype.slice.call(arguments);
|
||||
return expectedFn('assertEqualsFn', function() {
|
||||
assertObjectEquals(expectedArgs, Array.prototype.slice.call(arguments));
|
||||
});
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
multiChannel.dispose();
|
||||
mockControl.$verifyAll();
|
||||
assertTrue(mockChannel.disposed);
|
||||
}
|
||||
|
||||
function testSend0() {
|
||||
mockChannel.send('foo:fooBar', {foo: 'bar'});
|
||||
mockControl.$replayAll();
|
||||
channel0.send('fooBar', {foo: 'bar'});
|
||||
}
|
||||
|
||||
function testSend1() {
|
||||
mockChannel.send('bar:fooBar', {foo: 'bar'});
|
||||
mockControl.$replayAll();
|
||||
channel1.send('fooBar', {foo: 'bar'});
|
||||
}
|
||||
|
||||
function testReceive0() {
|
||||
channel0.registerService('fooBar', assertEqualsFn('Baz bang'));
|
||||
channel1.registerService('fooBar', notExpectedFn());
|
||||
mockControl.$replayAll();
|
||||
mockChannel.receive('foo:fooBar', 'Baz bang');
|
||||
}
|
||||
|
||||
function testReceive1() {
|
||||
channel1.registerService('fooBar', assertEqualsFn('Baz bang'));
|
||||
channel0.registerService('fooBar', notExpectedFn());
|
||||
mockControl.$replayAll();
|
||||
mockChannel.receive('bar:fooBar', 'Baz bang');
|
||||
}
|
||||
|
||||
function testDefaultReceive0() {
|
||||
channel0.registerDefaultService(assertEqualsFn('fooBar', 'Baz bang'));
|
||||
channel1.registerDefaultService(notExpectedFn());
|
||||
mockControl.$replayAll();
|
||||
mockChannel.receive('foo:fooBar', 'Baz bang');
|
||||
}
|
||||
|
||||
function testDefaultReceive1() {
|
||||
channel1.registerDefaultService(assertEqualsFn('fooBar', 'Baz bang'));
|
||||
channel0.registerDefaultService(notExpectedFn());
|
||||
mockControl.$replayAll();
|
||||
mockChannel.receive('bar:fooBar', 'Baz bang');
|
||||
}
|
||||
|
||||
function testReceiveAfterDisposed() {
|
||||
channel0.registerService('fooBar', notExpectedFn());
|
||||
mockControl.$replayAll();
|
||||
channel0.dispose();
|
||||
mockChannel.receive('foo:fooBar', 'Baz bang');
|
||||
}
|
||||
|
||||
function testReceiveAfterParentDisposed() {
|
||||
channel0.registerService('fooBar', notExpectedFn());
|
||||
mockControl.$replayAll();
|
||||
multiChannel.dispose();
|
||||
mockChannel.receive('foo:fooBar', 'Baz bang');
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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}
|
||||
* @final
|
||||
*/
|
||||
goog.messaging.PortCaller = function(operatorPort) {
|
||||
goog.messaging.PortCaller.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* 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.messaging.PortCaller.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.PortCaller
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.messaging.PortCallerTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.messaging.PortCallerTest');
|
||||
goog.setTestOnly('goog.messaging.PortCallerTest');
|
||||
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.messaging.PortCaller');
|
||||
goog.require('goog.messaging.PortNetwork');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
|
||||
var mockControl;
|
||||
var mockChannel;
|
||||
var caller;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
mockChannel = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
caller = new goog.messaging.PortCaller(mockChannel);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
goog.dispose(caller);
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function MockMessagePort(index, port) {
|
||||
goog.base(this);
|
||||
this.index = index;
|
||||
this.port = port;
|
||||
this.started = false;
|
||||
}
|
||||
goog.inherits(MockMessagePort, goog.events.EventTarget);
|
||||
|
||||
|
||||
MockMessagePort.prototype.start = function() {
|
||||
this.started = true;
|
||||
};
|
||||
|
||||
function testGetPort() {
|
||||
mockChannel.send(
|
||||
goog.messaging.PortNetwork.REQUEST_CONNECTION_SERVICE, 'foo');
|
||||
mockControl.$replayAll();
|
||||
caller.dial('foo');
|
||||
}
|
||||
@@ -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.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');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @final
|
||||
*/
|
||||
goog.messaging.PortChannel = function(underlyingPort) {
|
||||
goog.messaging.PortChannel.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* 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, peerOrigin, [channel.port2]);
|
||||
});
|
||||
|
||||
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.messaging.PortChannel.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,392 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.PortChannel
|
||||
</title>
|
||||
<script src="../base.js"></script>
|
||||
<script>
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.messaging.PortChannel');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.async.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageEvent');
|
||||
goog.require('goog.userAgent.product');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="frame"></div>
|
||||
<script>
|
||||
|
||||
var mockControl;
|
||||
var asyncMockControl;
|
||||
var mockPort;
|
||||
var portChannel;
|
||||
|
||||
var workerChannel;
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
var timer;
|
||||
|
||||
// Use a relatively long timeout because workers can take a while to start up.
|
||||
asyncTestCase.stepTimeout = 3 * 1000;
|
||||
|
||||
function setUpPage() {
|
||||
if (!('Worker' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
workerChannel = new goog.messaging.PortChannel(
|
||||
new Worker('testdata/portchannel_worker.js'));
|
||||
}
|
||||
|
||||
function tearDownPage() {
|
||||
goog.dispose(workerChannel);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
timer = new goog.Timer(50);
|
||||
mockControl = new goog.testing.MockControl();
|
||||
asyncMockControl = new goog.testing.async.MockControl(mockControl);
|
||||
mockPort = new goog.events.EventTarget();
|
||||
mockPort.postMessage = mockControl.createFunctionMock('postMessage');
|
||||
portChannel = new goog.messaging.PortChannel(mockPort);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
goog.dispose(timer);
|
||||
portChannel.dispose();
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function makeMessage(serviceName, payload) {
|
||||
var msg = {'serviceName': serviceName, 'payload': payload};
|
||||
msg[goog.messaging.PortChannel.FLAG] = true;
|
||||
if (goog.messaging.PortChannel.REQUIRES_SERIALIZATION_) {
|
||||
msg = goog.json.serialize(msg);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
function expectNoMessage() {
|
||||
portChannel.registerDefaultService(
|
||||
mockControl.createFunctionMock('expectNoMessage'));
|
||||
}
|
||||
|
||||
function receiveMessage(serviceName, payload, opt_origin, opt_ports) {
|
||||
mockPort.dispatchEvent(
|
||||
goog.testing.messaging.MockMessageEvent.wrap(
|
||||
makeMessage(serviceName, payload),
|
||||
opt_origin || 'http://google.com',
|
||||
undefined, undefined, opt_ports));
|
||||
}
|
||||
|
||||
function receiveNonChannelMessage(data) {
|
||||
if (goog.messaging.PortChannel.REQUIRES_SERIALIZATION_ &&
|
||||
!goog.isString(data)) {
|
||||
data = goog.json.serialize(data);
|
||||
}
|
||||
mockPort.dispatchEvent(
|
||||
goog.testing.messaging.MockMessageEvent.wrap(
|
||||
data, 'http://google.com'));
|
||||
}
|
||||
|
||||
function testPostMessage() {
|
||||
mockPort.postMessage(makeMessage('foobar', 'This is a value'), []);
|
||||
mockControl.$replayAll();
|
||||
portChannel.send('foobar', 'This is a value');
|
||||
}
|
||||
|
||||
function testPostMessageWithPorts() {
|
||||
if (!('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
var channel = new MessageChannel();
|
||||
var port1 = channel.port1;
|
||||
var port2 = channel.port2;
|
||||
mockPort.postMessage(makeMessage('foobar', {'val': [
|
||||
{'_port': {'type': 'real', 'index': 0}},
|
||||
{'_port': {'type': 'real', 'index': 1}}
|
||||
]}), [port1, port2]);
|
||||
mockControl.$replayAll();
|
||||
portChannel.send('foobar', {'val': [port1, port2]});
|
||||
}
|
||||
|
||||
function testReceiveMessage() {
|
||||
portChannel.registerService(
|
||||
'foobar', asyncMockControl.asyncAssertEquals(
|
||||
'testReceiveMessage', 'This is a string'));
|
||||
mockControl.$replayAll();
|
||||
receiveMessage('foobar', 'This is a string');
|
||||
}
|
||||
|
||||
function testReceiveMessageWithPorts() {
|
||||
if (!('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
var channel = new MessageChannel();
|
||||
var port1 = channel.port1;
|
||||
var port2 = channel.port2;
|
||||
portChannel.registerService(
|
||||
'foobar', asyncMockControl.asyncAssertEquals(
|
||||
'testReceiveMessage', {'val': [port1, port2]}),
|
||||
true);
|
||||
mockControl.$replayAll();
|
||||
receiveMessage('foobar', {'val': [
|
||||
{'_port': {'type': 'real', 'index': 0}},
|
||||
{'_port': {'type': 'real', 'index': 1}}
|
||||
]}, null, [port1, port2]);
|
||||
}
|
||||
|
||||
function testReceiveNonChannelMessageWithStringBody() {
|
||||
expectNoMessage();
|
||||
mockControl.$replayAll();
|
||||
receiveNonChannelMessage('Foo bar');
|
||||
}
|
||||
|
||||
function testReceiveNonChannelMessageWithArrayBody() {
|
||||
expectNoMessage();
|
||||
mockControl.$replayAll();
|
||||
receiveNonChannelMessage([5, 'Foo bar']);
|
||||
}
|
||||
|
||||
function testReceiveNonChannelMessageWithNoFlag() {
|
||||
expectNoMessage();
|
||||
mockControl.$replayAll();
|
||||
receiveNonChannelMessage({
|
||||
serviceName: 'foobar',
|
||||
payload: 'this is a payload'
|
||||
});
|
||||
}
|
||||
|
||||
function testReceiveNonChannelMessageWithFalseFlag() {
|
||||
expectNoMessage();
|
||||
mockControl.$replayAll();
|
||||
var body = {
|
||||
serviceName: 'foobar',
|
||||
payload: 'this is a payload'
|
||||
};
|
||||
body[goog.messaging.PortChannel.FLAG] = false;
|
||||
receiveNonChannelMessage(body);
|
||||
}
|
||||
|
||||
// Integration tests
|
||||
|
||||
function testWorker() {
|
||||
if (!('Worker' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
workerChannel.registerService('pong', function(msg) {
|
||||
assertObjectEquals({'val': 'fizzbang'}, msg);
|
||||
asyncTestCase.continueTesting();
|
||||
}, true);
|
||||
workerChannel.send('ping', {'val': 'fizzbang'});
|
||||
asyncTestCase.waitForAsync('worker response');
|
||||
}
|
||||
|
||||
function testWorkerWithPorts() {
|
||||
if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
var messageChannel = new MessageChannel();
|
||||
workerChannel.registerService('pong', function(msg) {
|
||||
assertPortsEntangled(msg['port'], messageChannel.port2, function() {
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
}, true);
|
||||
workerChannel.send('ping', {'port': messageChannel.port1});
|
||||
asyncTestCase.waitForAsync('worker response');
|
||||
}
|
||||
|
||||
function testPort() {
|
||||
if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
var messageChannel = new MessageChannel();
|
||||
workerChannel.send('addPort', messageChannel.port1);
|
||||
messageChannel.port2.start();
|
||||
var realPortChannel = new goog.messaging.PortChannel(messageChannel.port2);
|
||||
realPortChannel.registerService('pong', function(msg) {
|
||||
assertObjectEquals({'val': 'fizzbang'}, msg);
|
||||
|
||||
messageChannel.port2.close();
|
||||
realPortChannel.dispose();
|
||||
asyncTestCase.continueTesting();
|
||||
}, true);
|
||||
realPortChannel.send('ping', {'val': 'fizzbang'});
|
||||
asyncTestCase.waitForAsync('port response');
|
||||
}
|
||||
|
||||
function testPortIgnoresOrigin() {
|
||||
if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
var messageChannel = new MessageChannel();
|
||||
workerChannel.send('addPort', messageChannel.port1);
|
||||
messageChannel.port2.start();
|
||||
var realPortChannel = new goog.messaging.PortChannel(
|
||||
messageChannel.port2, 'http://somewhere-else.com');
|
||||
realPortChannel.registerService('pong', function(msg) {
|
||||
assertObjectEquals({'val': 'fizzbang'}, msg);
|
||||
|
||||
messageChannel.port2.close();
|
||||
realPortChannel.dispose();
|
||||
asyncTestCase.continueTesting();
|
||||
}, true);
|
||||
realPortChannel.send('ping', {'val': 'fizzbang'});
|
||||
asyncTestCase.waitForAsync('port response');
|
||||
}
|
||||
|
||||
function testWindow() {
|
||||
if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE(nicksantos): This test is having problems in Safari4 on the
|
||||
// test farm, but no one can reproduce them locally. Just turn it off.
|
||||
if (goog.userAgent.product.SAFARI) {
|
||||
return;
|
||||
}
|
||||
|
||||
withIframe(function() {
|
||||
var iframeChannel = goog.messaging.PortChannel.forEmbeddedWindow(
|
||||
window.frames['inner'], '*', timer);
|
||||
iframeChannel.registerService('pong', function(msg) {
|
||||
assertEquals('fizzbang', msg);
|
||||
|
||||
goog.dispose(iframeChannel);
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
iframeChannel.send('ping', 'fizzbang');
|
||||
asyncTestCase.waitForAsync('window response');
|
||||
});
|
||||
}
|
||||
|
||||
function testWindowCancelled() {
|
||||
if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
withIframe(function() {
|
||||
var iframeChannel = goog.messaging.PortChannel.forEmbeddedWindow(
|
||||
window.frames['inner'], '*', timer);
|
||||
iframeChannel.cancel();
|
||||
|
||||
iframeChannel.registerService('pong', function(msg) {
|
||||
fail('no messages should be received due to cancellation');
|
||||
goog.dispose(iframeChannel);
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
|
||||
iframeChannel.send('ping', 'fizzbang');
|
||||
asyncTestCase.waitForAsync('window response');
|
||||
|
||||
// Leave plenty of time for the connection to be made if the test fails, but
|
||||
// stop the test before the asyncTestCase timeout is hit.
|
||||
setTimeout(goog.bind(asyncTestCase.continueTesting, asyncTestCase),
|
||||
asyncTestCase.stepTimeout / 3);
|
||||
});
|
||||
}
|
||||
|
||||
function testWindowWontSendToWrongOrigin() {
|
||||
if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
withIframe(function() {
|
||||
var iframeChannel = goog.messaging.PortChannel.forEmbeddedWindow(
|
||||
window.frames['inner'], 'http://somewhere-else.com', timer);
|
||||
iframeChannel.registerService('pong', function(msg) {
|
||||
fail('Should not receive pong from unexpected origin');
|
||||
iframeChannel.dispose();
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
iframeChannel.send('ping', 'fizzbang');
|
||||
|
||||
setTimeout(function() {
|
||||
iframeChannel.dispose();
|
||||
asyncTestCase.continueTesting();
|
||||
}, asyncTestCase.stepTimeout - 500);
|
||||
asyncTestCase.waitForAsync('window response');
|
||||
});
|
||||
}
|
||||
|
||||
function testWindowWontReceiveFromWrongOrigin() {
|
||||
if (!('Worker' in goog.global) || !('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
withIframe(function() {
|
||||
var iframeChannel = goog.messaging.PortChannel.forEmbeddedWindow(
|
||||
window.frames['inner'], '*', timer);
|
||||
iframeChannel.registerService('pong', function(msg) {
|
||||
fail('Should not receive pong from unexpected origin');
|
||||
iframeChannel.dispose();
|
||||
asyncTestCase.continueTesting();
|
||||
});
|
||||
iframeChannel.send('ping', 'fizzbang');
|
||||
|
||||
setTimeout(function() {
|
||||
iframeChannel.dispose();
|
||||
asyncTestCase.continueTesting();
|
||||
}, asyncTestCase.stepTimeout - 500);
|
||||
asyncTestCase.waitForAsync('window response');
|
||||
}, 'testdata/portchannel_wrong_origin_inner.html');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that two HTML5 MessagePorts are entangled by posting messages from
|
||||
* each to the other.
|
||||
*
|
||||
* @param {!MessagePort} port1
|
||||
* @param {!MessagePort} port2
|
||||
* @param {function()} callback Called when the assertion is finished.
|
||||
*/
|
||||
function assertPortsEntangled(port1, port2, callback) {
|
||||
port1.onmessage = function(e) {
|
||||
assertEquals('port 2 should send messages to port 1',
|
||||
'port2 to port1', e.data);
|
||||
callback();
|
||||
};
|
||||
|
||||
port2.onmessage = function(e) {
|
||||
assertEquals('port 1 should send messages to port 2',
|
||||
'port1 to port2', e.data);
|
||||
port2.postMessage('port2 to port1');
|
||||
asyncTestCase.waitForAsync('port 1 receiving message');
|
||||
};
|
||||
|
||||
port1.postMessage('port1 to port2');
|
||||
asyncTestCase.waitForAsync('port 2 receiving message');
|
||||
}
|
||||
|
||||
function withIframe(callback, opt_url) {
|
||||
var frameDiv = goog.dom.getElement('frame');
|
||||
goog.dom.removeChildren(frameDiv);
|
||||
goog.dom.appendChild(frameDiv, goog.dom.createDom('iframe', {
|
||||
style: 'display: none',
|
||||
name: 'inner',
|
||||
id: 'inner',
|
||||
src: opt_url || 'testdata/portchannel_inner.html'
|
||||
}));
|
||||
|
||||
asyncTestCase.waitForAsync('creating iframe');
|
||||
// We need to pass control back to the event loop to give the iframe a chance
|
||||
// to load.
|
||||
setTimeout(function() {
|
||||
asyncTestCase.continueTesting();
|
||||
callback();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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';
|
||||
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.PortNetwork
|
||||
</title>
|
||||
<script src="../base.js"></script>
|
||||
<script>
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.messaging.PortChannel');
|
||||
goog.require('goog.messaging.PortOperator');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.async.MockControl');
|
||||
goog.require('goog.testing.messaging.MockMessageEvent');
|
||||
goog.require('goog.testing.jsunit');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe style="display: none;" name="inner" id="inner"
|
||||
src="testdata/portnetwork_inner.html"></iframe>
|
||||
<script>
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
|
||||
|
||||
// Use a relatively long timeout because workers can take a while to start up.
|
||||
asyncTestCase.stepTimeout = 5 * 1000;
|
||||
|
||||
var timer;
|
||||
|
||||
function setUp() {
|
||||
timer = new goog.Timer(50);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
goog.dispose(timer);
|
||||
}
|
||||
|
||||
function testRouteMessageThroughWorkers() {
|
||||
if (!('MessageChannel' in goog.global)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var master = new goog.messaging.PortOperator('main');
|
||||
master.addPort('worker1', new goog.messaging.PortChannel(
|
||||
new Worker('testdata/portnetwork_worker1.js')));
|
||||
master.addPort('worker2', new goog.messaging.PortChannel(
|
||||
new Worker('testdata/portnetwork_worker2.js')));
|
||||
master.addPort(
|
||||
'frame', goog.messaging.PortChannel.forEmbeddedWindow(
|
||||
window.frames['inner'], '*', timer));
|
||||
|
||||
master.dial('worker1').registerService('result', function(msg) {
|
||||
assertArrayEquals(['main', 'worker2', 'frame', 'worker1'], msg);
|
||||
master.dispose();
|
||||
asyncTestCase.continueTesting();
|
||||
}, true);
|
||||
|
||||
master.dial('worker2').send('sendToFrame', ['main']);
|
||||
|
||||
asyncTestCase.waitForAsync('routing messages');
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,198 @@
|
||||
// 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}
|
||||
* @final
|
||||
*/
|
||||
goog.messaging.PortOperator = function(name) {
|
||||
goog.messaging.PortOperator.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* 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.messaging.PortOperator.base(this, 'disposeInternal');
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.PortOperator
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.messaging.PortOperatorTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.messaging.PortOperatorTest');
|
||||
goog.setTestOnly('goog.messaging.PortOperatorTest');
|
||||
|
||||
goog.require('goog.messaging.PortNetwork');
|
||||
goog.require('goog.messaging.PortOperator');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.PropertyReplacer');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
goog.require('goog.testing.messaging.MockMessagePort');
|
||||
|
||||
var stubs;
|
||||
|
||||
var mockControl;
|
||||
var mockChannel1;
|
||||
var mockChannel2;
|
||||
var operator;
|
||||
|
||||
function setUpPage() {
|
||||
stubs = new goog.testing.PropertyReplacer();
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
var index = 0;
|
||||
stubs.set(goog.global, 'MessageChannel', function() {
|
||||
this.port1 = makeMockPort(index, 1);
|
||||
this.port2 = makeMockPort(index, 2);
|
||||
index += 1;
|
||||
});
|
||||
|
||||
mockChannel1 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
mockChannel2 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
operator = new goog.messaging.PortOperator('operator');
|
||||
operator.addPort('1', mockChannel1);
|
||||
operator.addPort('2', mockChannel2);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
goog.dispose(operator);
|
||||
mockControl.$verifyAll();
|
||||
stubs.reset();
|
||||
}
|
||||
|
||||
function makeMockPort(index, port) {
|
||||
return new goog.testing.messaging.MockMessagePort(
|
||||
{index: index, port: port}, mockControl);
|
||||
}
|
||||
|
||||
function testConnectSelfToPortViaRequestConnection() {
|
||||
mockChannel1.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, {
|
||||
success: true, name: 'operator', port: makeMockPort(0, 1)
|
||||
});
|
||||
mockControl.$replayAll();
|
||||
mockChannel1.receive(
|
||||
goog.messaging.PortNetwork.REQUEST_CONNECTION_SERVICE, 'operator');
|
||||
var port = operator.dial('1').port_;
|
||||
assertObjectEquals({index: 0, port: 2}, port.id);
|
||||
assertEquals(true, port.started);
|
||||
}
|
||||
|
||||
function testConnectSelfToPortViaGetPort() {
|
||||
mockChannel1.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, {
|
||||
success: true, name: 'operator', port: makeMockPort(0, 1)
|
||||
});
|
||||
mockControl.$replayAll();
|
||||
var port = operator.dial('1').port_;
|
||||
assertObjectEquals({index: 0, port: 2}, port.id);
|
||||
assertEquals(true, port.started);
|
||||
}
|
||||
|
||||
function testConnectTwoCallers() {
|
||||
mockChannel1.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, {
|
||||
success: true, name: '2', port: makeMockPort(0, 1)
|
||||
});
|
||||
mockChannel2.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, {
|
||||
success: true, name: '1', port: makeMockPort(0, 2)
|
||||
});
|
||||
mockControl.$replayAll();
|
||||
mockChannel1.receive(
|
||||
goog.messaging.PortNetwork.REQUEST_CONNECTION_SERVICE, '2');
|
||||
}
|
||||
|
||||
function testConnectCallerToNonexistentCaller() {
|
||||
mockChannel1.send(goog.messaging.PortNetwork.GRANT_CONNECTION_SERVICE, {
|
||||
success: false,
|
||||
message: 'Port "1" requested a connection to port "no", which doesn\'t ' +
|
||||
'exist'
|
||||
});
|
||||
mockControl.$replayAll();
|
||||
mockChannel1.receive(
|
||||
goog.messaging.PortNetwork.REQUEST_CONNECTION_SERVICE, 'no');
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
// 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.MultiChannel');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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.messaging.RespondingChannel.base(this, 'constructor');
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2012 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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging
|
||||
</title>
|
||||
<script type="text/javascript" src="../base.js">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.messaging.RespondingChannelTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.messaging.RespondingChannelTest');
|
||||
goog.setTestOnly('goog.messaging.RespondingChannelTest');
|
||||
|
||||
goog.require('goog.messaging.RespondingChannel');
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.messaging.MockMessageChannel');
|
||||
|
||||
var CH1_REQUEST = {'request': 'quux1'};
|
||||
var CH2_REQUEST = {'request': 'quux2'};
|
||||
var CH1_RESPONSE = {'response': 'baz1'};
|
||||
var CH2_RESPONSE = {'response': 'baz2'};
|
||||
var SERVICE_NAME = 'serviceName';
|
||||
|
||||
var mockControl;
|
||||
var ch1;
|
||||
var ch2;
|
||||
var respondingCh1;
|
||||
var respondingCh2;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
|
||||
ch1 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
ch2 = new goog.testing.messaging.MockMessageChannel(mockControl);
|
||||
|
||||
respondingCh1 = new goog.messaging.RespondingChannel(ch1);
|
||||
respondingCh2 = new goog.messaging.RespondingChannel(ch2);
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
respondingCh1.dispose();
|
||||
respondingCh2.dispose();
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testSendWithSignature() {
|
||||
// 1 to 2 and back.
|
||||
var message1Ch1Request = {'data': CH1_REQUEST,
|
||||
'signature': 0};
|
||||
var message1Ch2Response = {'data': CH2_RESPONSE,
|
||||
'signature': 0};
|
||||
var message2Ch1Request = {'data': CH1_REQUEST,
|
||||
'signature': 1};
|
||||
var message2Ch2Response = {'data': CH2_RESPONSE,
|
||||
'signature': 1};
|
||||
// 2 to 1 and back.
|
||||
var message3Ch2Request = {'data': CH2_REQUEST,
|
||||
'signature': 0};
|
||||
var message3Ch1Response = {'data': CH1_RESPONSE,
|
||||
'signature': 0};
|
||||
var message4Ch2Request = {'data': CH2_REQUEST,
|
||||
'signature': 1};
|
||||
var message4Ch1Response = {'data': CH1_RESPONSE,
|
||||
'signature': 1};
|
||||
|
||||
// 1 to 2 and back.
|
||||
ch1.send(
|
||||
'public:' + SERVICE_NAME,
|
||||
message1Ch1Request);
|
||||
ch2.send(
|
||||
'private:mics',
|
||||
message1Ch2Response);
|
||||
ch1.send(
|
||||
'public:' + SERVICE_NAME,
|
||||
message2Ch1Request);
|
||||
ch2.send(
|
||||
'private:mics',
|
||||
message2Ch2Response);
|
||||
|
||||
// 2 to 1 and back.
|
||||
ch2.send(
|
||||
'public:' + SERVICE_NAME,
|
||||
message3Ch2Request);
|
||||
ch1.send(
|
||||
'private:mics',
|
||||
message3Ch1Response);
|
||||
ch2.send(
|
||||
'public:' + SERVICE_NAME,
|
||||
message4Ch2Request);
|
||||
ch1.send(
|
||||
'private:mics',
|
||||
message4Ch1Response);
|
||||
|
||||
mockControl.$replayAll();
|
||||
|
||||
var hasInvokedCh1 = false;
|
||||
var hasInvokedCh2 = false;
|
||||
var hasReturnedFromCh1 = false;
|
||||
var hasReturnedFromCh2 = false;
|
||||
|
||||
var serviceCallback1 = function(message) {
|
||||
hasInvokedCh1 = true;
|
||||
assertObjectEquals(CH2_REQUEST, message);
|
||||
return CH1_RESPONSE;
|
||||
};
|
||||
|
||||
var serviceCallback2 = function(message) {
|
||||
hasInvokedCh2 = true;
|
||||
assertObjectEquals(CH1_REQUEST, message);
|
||||
return CH2_RESPONSE;
|
||||
};
|
||||
|
||||
var invocationCallback1 = function(message) {
|
||||
hasReturnedFromCh2 = true;
|
||||
assertObjectEquals(CH2_RESPONSE, message);
|
||||
};
|
||||
|
||||
var invocationCallback2 = function(message) {
|
||||
hasReturnedFromCh1 = true;
|
||||
assertObjectEquals(CH1_RESPONSE, message);
|
||||
};
|
||||
|
||||
respondingCh1.registerService(SERVICE_NAME, serviceCallback1);
|
||||
respondingCh2.registerService(SERVICE_NAME, serviceCallback2);
|
||||
|
||||
respondingCh1.send(SERVICE_NAME, CH1_REQUEST, invocationCallback1);
|
||||
ch2.receive('public:' + SERVICE_NAME, message1Ch1Request);
|
||||
ch1.receive('private:mics', message1Ch2Response);
|
||||
|
||||
respondingCh1.send(SERVICE_NAME, CH1_REQUEST, invocationCallback1);
|
||||
ch2.receive('public:' + SERVICE_NAME, message2Ch1Request);
|
||||
ch1.receive('private:mics', message2Ch2Response);
|
||||
|
||||
respondingCh2.send(SERVICE_NAME, CH2_REQUEST, invocationCallback2);
|
||||
ch1.receive('public:' + SERVICE_NAME, message3Ch2Request);
|
||||
ch2.receive('private:mics', message3Ch1Response);
|
||||
|
||||
respondingCh2.send(SERVICE_NAME, CH2_REQUEST, invocationCallback2);
|
||||
ch1.receive('public:' + SERVICE_NAME, message4Ch2Request);
|
||||
ch2.receive('private:mics', message4Ch1Response);
|
||||
|
||||
assertTrue(
|
||||
hasInvokedCh1 &&
|
||||
hasInvokedCh2 &&
|
||||
hasReturnedFromCh1 &&
|
||||
hasReturnedFromCh2);
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>PortChannel test inner document</title>
|
||||
<script src="../../base.js"></script>
|
||||
<script>
|
||||
goog.require('goog.messaging.PortChannel');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
var channel = goog.messaging.PortChannel.forGlobalWindow('*');
|
||||
channel.registerService('ping', function(msg) {
|
||||
channel.send('pong', msg);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+37
@@ -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();
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>PortChannel test inner document</title>
|
||||
<script src="../../base.js"></script>
|
||||
<script>
|
||||
goog.require('goog.messaging.PortChannel');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
var channel = goog.messaging.PortChannel.forGlobalWindow(
|
||||
'http://somewhere-else.com');
|
||||
channel.registerService('ping', function(msg) {
|
||||
channel.send('pong', msg);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<title>
|
||||
Closure Unit Tests - goog.messaging.PortNetwork iframe page
|
||||
</title>
|
||||
<script src="../../base.js"></script>
|
||||
<script>
|
||||
goog.require('goog.messaging.PortCaller');
|
||||
goog.require('goog.messaging.PortChannel');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var caller = new goog.messaging.PortCaller(
|
||||
goog.messaging.PortChannel.forGlobalWindow('*'));
|
||||
|
||||
caller.dial('worker2').registerService('sendToWorker1', function(msg) {
|
||||
msg.push('frame');
|
||||
caller.dial('worker1').send('sendToMain', msg);
|
||||
}, true);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+32
@@ -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();
|
||||
+32
@@ -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();
|
||||
Reference in New Issue
Block a user