123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617 |
- // Copyright 2013 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 Provides an implementation of a transport that can call methods
- * directly on a frame. Useful if you want to use XPC for crossdomain messaging
- * (using another transport), or same domain messaging (using this transport).
- */
- goog.provide('goog.net.xpc.DirectTransport');
- goog.require('goog.Timer');
- goog.require('goog.async.Deferred');
- goog.require('goog.events.EventHandler');
- goog.require('goog.log');
- goog.require('goog.net.xpc');
- goog.require('goog.net.xpc.CfgFields');
- goog.require('goog.net.xpc.CrossPageChannelRole');
- goog.require('goog.net.xpc.Transport');
- goog.require('goog.net.xpc.TransportTypes');
- goog.require('goog.object');
- goog.scope(function() {
- var CfgFields = goog.net.xpc.CfgFields;
- var CrossPageChannelRole = goog.net.xpc.CrossPageChannelRole;
- var Deferred = goog.async.Deferred;
- var EventHandler = goog.events.EventHandler;
- var Timer = goog.Timer;
- var Transport = goog.net.xpc.Transport;
- /**
- * A direct window to window method transport.
- *
- * If the windows are in the same security context, this transport calls
- * directly into the other window without using any additional mechanism. This
- * is mainly used in scenarios where you want to optionally use a cross domain
- * transport in cross security context situations, or optionally use a direct
- * transport in same security context situations.
- *
- * Note: Global properties are exported by using this transport. One to
- * communicate with the other window by, currently crosswindowmessaging.channel,
- * and by using goog.getUid on window, currently closure_uid_[0-9]+.
- *
- * @param {!goog.net.xpc.CrossPageChannel} channel The channel this
- * transport belongs to.
- * @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for
- * finding the correct window/document. If omitted, uses the current
- * document.
- * @constructor
- * @extends {Transport}
- */
- goog.net.xpc.DirectTransport = function(channel, opt_domHelper) {
- goog.net.xpc.DirectTransport.base(this, 'constructor', opt_domHelper);
- /**
- * The channel this transport belongs to.
- * @private {!goog.net.xpc.CrossPageChannel}
- */
- this.channel_ = channel;
- /** @private {!EventHandler<!goog.net.xpc.DirectTransport>} */
- this.eventHandler_ = new EventHandler(this);
- this.registerDisposable(this.eventHandler_);
- /**
- * Timer for connection reattempts.
- * @private {!Timer}
- */
- this.maybeAttemptToConnectTimer_ = new Timer(
- DirectTransport.CONNECTION_ATTEMPT_INTERVAL_MS_, this.getWindow());
- this.registerDisposable(this.maybeAttemptToConnectTimer_);
- /**
- * Fires once we've received our SETUP_ACK message.
- * @private {!Deferred}
- */
- this.setupAckReceived_ = new Deferred();
- /**
- * Fires once we've sent our SETUP_ACK message.
- * @private {!Deferred}
- */
- this.setupAckSent_ = new Deferred();
- /**
- * Fires once we're marked connected.
- * @private {!Deferred}
- */
- this.connected_ = new Deferred();
- /**
- * The unique ID of this side of the connection. Used to determine when a peer
- * is reloaded.
- * @private {string}
- */
- this.endpointId_ = goog.net.xpc.getRandomString(10);
- /**
- * The unique ID of the peer. If we get a message from a peer with an ID we
- * don't expect, we reset the connection.
- * @private {?string}
- */
- this.peerEndpointId_ = null;
- /**
- * The map of sending messages.
- * @private {Object}
- */
- this.asyncSendsMap_ = {};
- /**
- * The original channel name.
- * @private {string}
- */
- this.originalChannelName_ = this.channel_.name;
- // We reconfigure the channel name to include the role so that we can
- // communicate in the same window between the different roles on the
- // same channel.
- this.channel_.updateChannelNameAndCatalog(
- DirectTransport.getRoledChannelName_(
- this.channel_.name, this.channel_.getRole()));
- /**
- * Flag indicating if this instance of the transport has been initialized.
- * @private {boolean}
- */
- this.initialized_ = false;
- // We don't want to mark ourselves connected until we have sent whatever
- // message will cause our counterpart in the other frame to also declare
- // itself connected, if there is such a message. Otherwise we risk a user
- // message being sent in advance of that message, and it being discarded.
- // Two sided handshake:
- // SETUP_ACK has to have been received, and sent.
- this.connected_.awaitDeferred(this.setupAckReceived_);
- this.connected_.awaitDeferred(this.setupAckSent_);
- this.connected_.addCallback(this.notifyConnected_, this);
- this.connected_.callback(true);
- this.eventHandler_.listen(
- this.maybeAttemptToConnectTimer_, Timer.TICK,
- this.maybeAttemptToConnect_);
- goog.log.info(
- goog.net.xpc.logger,
- 'DirectTransport created. role=' + this.channel_.getRole());
- };
- goog.inherits(goog.net.xpc.DirectTransport, Transport);
- var DirectTransport = goog.net.xpc.DirectTransport;
- /**
- * @private {number}
- * @const
- */
- DirectTransport.CONNECTION_ATTEMPT_INTERVAL_MS_ = 100;
- /**
- * The delay to notify the xpc of a successful connection. This is used
- * to allow both parties to be connected if one party's connection callback
- * invokes an immediate send.
- * @private {number}
- * @const
- */
- DirectTransport.CONNECTION_DELAY_INTERVAL_MS_ = 0;
- /**
- * @param {!Window} peerWindow The peer window to check if DirectTranport is
- * supported on.
- * @return {boolean} Whether this transport is supported.
- */
- DirectTransport.isSupported = function(peerWindow) {
- try {
- return window.document.domain == peerWindow.document.domain;
- } catch (e) {
- return false;
- }
- };
- /**
- * Tracks the number of DirectTransport channels that have been
- * initialized but not disposed yet in a map keyed by the UID of the window
- * object. This allows for multiple windows to be initiallized and listening
- * for messages.
- * @private {!Object<number>}
- */
- DirectTransport.activeCount_ = {};
- /**
- * Path of global message proxy.
- * @private {string}
- * @const
- */
- // TODO(user): Make this configurable using the CfgFields.
- DirectTransport.GLOBAL_TRANPORT_PATH_ = 'crosswindowmessaging.channel';
- /**
- * The delimiter used for transport service messages.
- * @private {string}
- * @const
- */
- DirectTransport.MESSAGE_DELIMITER_ = ',';
- /**
- * Initializes this transport. Registers a method for 'message'-events in the
- * global scope.
- * @param {!Window} listenWindow The window to listen to events on.
- * @private
- */
- DirectTransport.initialize_ = function(listenWindow) {
- var uid = goog.getUid(listenWindow);
- var value = DirectTransport.activeCount_[uid] || 0;
- if (value == 0) {
- // Set up a handler on the window to proxy messages to class.
- var globalProxy = goog.getObjectByName(
- DirectTransport.GLOBAL_TRANPORT_PATH_, listenWindow);
- if (globalProxy == null) {
- goog.exportSymbol(
- DirectTransport.GLOBAL_TRANPORT_PATH_,
- DirectTransport.messageReceivedHandler_, listenWindow);
- }
- }
- DirectTransport.activeCount_[uid]++;
- };
- /**
- * @param {string} channelName The channel name.
- * @param {string|number} role The role.
- * @return {string} The formatted channel name including role.
- * @private
- */
- DirectTransport.getRoledChannelName_ = function(channelName, role) {
- return channelName + '_' + role;
- };
- /**
- * @param {!Object} literal The literal unrenamed message.
- * @return {boolean} Whether the message was successfully delivered to a
- * channel.
- * @private
- */
- DirectTransport.messageReceivedHandler_ = function(literal) {
- var msg = DirectTransport.Message_.fromLiteral(literal);
- var channelName = msg.channelName;
- var service = msg.service;
- var payload = msg.payload;
- goog.log.fine(
- goog.net.xpc.logger, 'messageReceived: channel=' + channelName +
- ', service=' + service + ', payload=' + payload);
- // Attempt to deliver message to the channel. Keep in mind that it may not
- // exist for several reasons, including but not limited to:
- // - a malformed message
- // - the channel simply has not been created
- // - channel was created in a different namespace
- // - message was sent to the wrong window
- // - channel has become stale (e.g. caching iframes and back clicks)
- var channel = goog.net.xpc.channels[channelName];
- if (channel) {
- channel.xpcDeliver(service, payload);
- return true;
- }
- var transportMessageType = DirectTransport.parseTransportPayload_(payload)[0];
- // Check if there are any stale channel names that can be updated.
- for (var staleChannelName in goog.net.xpc.channels) {
- var staleChannel = goog.net.xpc.channels[staleChannelName];
- if (staleChannel.getRole() == CrossPageChannelRole.INNER &&
- !staleChannel.isConnected() &&
- service == goog.net.xpc.TRANSPORT_SERVICE_ &&
- transportMessageType == goog.net.xpc.SETUP) {
- // Inner peer received SETUP message but channel names did not match.
- // Start using the channel name sent from outer peer. The channel name
- // of the inner peer can easily become out of date, as iframe's and their
- // JS state get cached in many browsers upon page reload or history
- // navigation (particularly Firefox 1.5+).
- staleChannel.updateChannelNameAndCatalog(channelName);
- staleChannel.xpcDeliver(service, payload);
- return true;
- }
- }
- // Failed to find a channel to deliver this message to, so simply ignore it.
- goog.log.info(goog.net.xpc.logger, 'channel name mismatch; message ignored.');
- return false;
- };
- /**
- * The transport type.
- * @type {number}
- * @override
- */
- DirectTransport.prototype.transportType = goog.net.xpc.TransportTypes.DIRECT;
- /**
- * Handles transport service messages.
- * @param {string} payload The message content.
- * @override
- */
- DirectTransport.prototype.transportServiceHandler = function(payload) {
- var transportParts = DirectTransport.parseTransportPayload_(payload);
- var transportMessageType = transportParts[0];
- var peerEndpointId = transportParts[1];
- switch (transportMessageType) {
- case goog.net.xpc.SETUP_ACK_:
- if (!this.setupAckReceived_.hasFired()) {
- this.setupAckReceived_.callback(true);
- }
- break;
- case goog.net.xpc.SETUP:
- this.sendSetupAckMessage_();
- if ((this.peerEndpointId_ != null) &&
- (this.peerEndpointId_ != peerEndpointId)) {
- // Send a new SETUP message since the peer has been replaced.
- goog.log.info(
- goog.net.xpc.logger,
- 'Sending SETUP and changing peer ID to: ' + peerEndpointId);
- this.sendSetupMessage_();
- }
- this.peerEndpointId_ = peerEndpointId;
- break;
- }
- };
- /**
- * Sends a SETUP transport service message.
- * @private
- */
- DirectTransport.prototype.sendSetupMessage_ = function() {
- // Although we could send real objects, since some other transports are
- // limited to strings we also keep this requirement.
- var payload = goog.net.xpc.SETUP;
- payload += DirectTransport.MESSAGE_DELIMITER_;
- payload += this.endpointId_;
- this.send(goog.net.xpc.TRANSPORT_SERVICE_, payload);
- };
- /**
- * Sends a SETUP_ACK transport service message.
- * @private
- */
- DirectTransport.prototype.sendSetupAckMessage_ = function() {
- this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
- if (!this.setupAckSent_.hasFired()) {
- this.setupAckSent_.callback(true);
- }
- };
- /** @override */
- DirectTransport.prototype.connect = function() {
- var win = this.getWindow();
- if (win) {
- DirectTransport.initialize_(win);
- this.initialized_ = true;
- this.maybeAttemptToConnect_();
- } else {
- goog.log.fine(goog.net.xpc.logger, 'connect(): no window to initialize.');
- }
- };
- /**
- * Connects to other peer. In the case of the outer peer, the setup messages are
- * likely sent before the inner peer is ready to receive them. Therefore, this
- * function will continue trying to send the SETUP message until the inner peer
- * responds. In the case of the inner peer, it will occasionally have its
- * channel name fall out of sync with the outer peer, particularly during
- * soft-reloads and history navigations.
- * @private
- */
- DirectTransport.prototype.maybeAttemptToConnect_ = function() {
- if (this.channel_.isConnected()) {
- this.maybeAttemptToConnectTimer_.stop();
- return;
- }
- this.maybeAttemptToConnectTimer_.start();
- this.sendSetupMessage_();
- };
- /**
- * Prepares to send a message.
- * @param {string} service The name of the service the message is to be
- * delivered to.
- * @param {string} payload The message content.
- * @override
- */
- DirectTransport.prototype.send = function(service, payload) {
- if (!this.channel_.getPeerWindowObject()) {
- goog.log.fine(goog.net.xpc.logger, 'send(): window not ready');
- return;
- }
- var channelName = DirectTransport.getRoledChannelName_(
- this.originalChannelName_, this.getPeerRole_());
- var message = new DirectTransport.Message_(channelName, service, payload);
- if (this.channel_.getConfig()[CfgFields.DIRECT_TRANSPORT_SYNC_MODE]) {
- this.executeScheduledSend_(message);
- } else {
- // Note: goog.async.nextTick doesn't support cancelling or disposal so
- // leaving as 0ms timer, though this may have performance implications.
- this.asyncSendsMap_[goog.getUid(message)] =
- Timer.callOnce(goog.bind(this.executeScheduledSend_, this, message), 0);
- }
- };
- /**
- * Sends the message.
- * @param {!DirectTransport.Message_} message The message to send.
- * @private
- */
- DirectTransport.prototype.executeScheduledSend_ = function(message) {
- var messageId = goog.getUid(message);
- if (this.asyncSendsMap_[messageId]) {
- delete this.asyncSendsMap_[messageId];
- }
- try {
- var peerProxy = goog.getObjectByName(
- DirectTransport.GLOBAL_TRANPORT_PATH_,
- this.channel_.getPeerWindowObject());
- } catch (error) {
- goog.log.warning(
- goog.net.xpc.logger, 'Can\'t access other window, ignoring.', error);
- return;
- }
- if (goog.isNull(peerProxy)) {
- goog.log.warning(
- goog.net.xpc.logger, 'Peer window had no global function.');
- return;
- }
- try {
- peerProxy(message.toLiteral());
- goog.log.info(
- goog.net.xpc.logger, 'send(): channelName=' + message.channelName +
- ' service=' + message.service + ' payload=' + message.payload);
- } catch (error) {
- goog.log.warning(
- goog.net.xpc.logger, 'Error performing call, ignoring.', error);
- }
- };
- /**
- * @return {goog.net.xpc.CrossPageChannelRole} The role of peer channel (either
- * inner or outer).
- * @private
- */
- DirectTransport.prototype.getPeerRole_ = function() {
- var role = this.channel_.getRole();
- return role == goog.net.xpc.CrossPageChannelRole.OUTER ?
- goog.net.xpc.CrossPageChannelRole.INNER :
- goog.net.xpc.CrossPageChannelRole.OUTER;
- };
- /**
- * Notifies the channel that this transport is connected.
- * @private
- */
- DirectTransport.prototype.notifyConnected_ = function() {
- // Add a delay as the connection callback will break if this transport is
- // synchronous and the callback invokes send() immediately.
- this.channel_.notifyConnected(
- this.channel_.getConfig()[CfgFields.DIRECT_TRANSPORT_SYNC_MODE] ?
- DirectTransport.CONNECTION_DELAY_INTERVAL_MS_ :
- 0);
- };
- /** @override */
- DirectTransport.prototype.disposeInternal = function() {
- if (this.initialized_) {
- var listenWindow = this.getWindow();
- var uid = goog.getUid(listenWindow);
- var value = --DirectTransport.activeCount_[uid];
- if (value == 1) {
- goog.exportSymbol(
- DirectTransport.GLOBAL_TRANPORT_PATH_, null, listenWindow);
- }
- }
- if (this.asyncSendsMap_) {
- goog.object.forEach(
- this.asyncSendsMap_, function(timerId) { Timer.clear(timerId); });
- this.asyncSendsMap_ = null;
- }
- // Deferred's aren't disposables.
- if (this.setupAckReceived_) {
- this.setupAckReceived_.cancel();
- delete this.setupAckReceived_;
- }
- if (this.setupAckSent_) {
- this.setupAckSent_.cancel();
- delete this.setupAckSent_;
- }
- if (this.connected_) {
- this.connected_.cancel();
- delete this.connected_;
- }
- DirectTransport.base(this, 'disposeInternal');
- };
- /**
- * Parses a transport service payload message.
- * @param {string} payload The payload.
- * @return {!Array<?string>} An array with the message type as the first member
- * and the endpoint id as the second, if one was sent, or null otherwise.
- * @private
- */
- DirectTransport.parseTransportPayload_ = function(payload) {
- var transportParts = /** @type {!Array<?string>} */ (
- payload.split(DirectTransport.MESSAGE_DELIMITER_));
- transportParts[1] = transportParts[1] || null; // Usually endpointId.
- return transportParts;
- };
- /**
- * Message container that gets passed back and forth between windows.
- * @param {string} channelName The channel name to tranport messages on.
- * @param {string} service The service to send the payload to.
- * @param {string} payload The payload to send.
- * @constructor
- * @struct
- * @private
- */
- DirectTransport.Message_ = function(channelName, service, payload) {
- /**
- * The name of the channel.
- * @type {string}
- */
- this.channelName = channelName;
- /**
- * The service on the channel.
- * @type {string}
- */
- this.service = service;
- /**
- * The payload.
- * @type {string}
- */
- this.payload = payload;
- };
- /**
- * Converts a message to a literal object.
- * @return {!Object} The message as a literal object.
- */
- DirectTransport.Message_.prototype.toLiteral = function() {
- return {
- 'channelName': this.channelName,
- 'service': this.service,
- 'payload': this.payload
- };
- };
- /**
- * Creates a Message_ from a literal object.
- * @param {!Object} literal The literal to convert to Message.
- * @return {!DirectTransport.Message_} The Message.
- */
- DirectTransport.Message_.fromLiteral = function(literal) {
- return new DirectTransport.Message_(
- literal['channelName'], literal['service'], literal['payload']);
- };
- }); // goog.scope
|