Path: blob/trunk/third_party/closure/goog/net/xpc/directtransport.js
1865 views
// Copyright 2013 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Provides an implementation of a transport that can call methods16* directly on a frame. Useful if you want to use XPC for crossdomain messaging17* (using another transport), or same domain messaging (using this transport).18*/192021goog.provide('goog.net.xpc.DirectTransport');2223goog.require('goog.Timer');24goog.require('goog.async.Deferred');25goog.require('goog.events.EventHandler');26goog.require('goog.log');27goog.require('goog.net.xpc');28goog.require('goog.net.xpc.CfgFields');29goog.require('goog.net.xpc.CrossPageChannelRole');30goog.require('goog.net.xpc.Transport');31goog.require('goog.net.xpc.TransportTypes');32goog.require('goog.object');333435goog.scope(function() {36var CfgFields = goog.net.xpc.CfgFields;37var CrossPageChannelRole = goog.net.xpc.CrossPageChannelRole;38var Deferred = goog.async.Deferred;39var EventHandler = goog.events.EventHandler;40var Timer = goog.Timer;41var Transport = goog.net.xpc.Transport;42434445/**46* A direct window to window method transport.47*48* If the windows are in the same security context, this transport calls49* directly into the other window without using any additional mechanism. This50* is mainly used in scenarios where you want to optionally use a cross domain51* transport in cross security context situations, or optionally use a direct52* transport in same security context situations.53*54* Note: Global properties are exported by using this transport. One to55* communicate with the other window by, currently crosswindowmessaging.channel,56* and by using goog.getUid on window, currently closure_uid_[0-9]+.57*58* @param {!goog.net.xpc.CrossPageChannel} channel The channel this59* transport belongs to.60* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for61* finding the correct window/document. If omitted, uses the current62* document.63* @constructor64* @extends {Transport}65*/66goog.net.xpc.DirectTransport = function(channel, opt_domHelper) {67goog.net.xpc.DirectTransport.base(this, 'constructor', opt_domHelper);6869/**70* The channel this transport belongs to.71* @private {!goog.net.xpc.CrossPageChannel}72*/73this.channel_ = channel;7475/** @private {!EventHandler<!goog.net.xpc.DirectTransport>} */76this.eventHandler_ = new EventHandler(this);77this.registerDisposable(this.eventHandler_);7879/**80* Timer for connection reattempts.81* @private {!Timer}82*/83this.maybeAttemptToConnectTimer_ = new Timer(84DirectTransport.CONNECTION_ATTEMPT_INTERVAL_MS_, this.getWindow());85this.registerDisposable(this.maybeAttemptToConnectTimer_);8687/**88* Fires once we've received our SETUP_ACK message.89* @private {!Deferred}90*/91this.setupAckReceived_ = new Deferred();9293/**94* Fires once we've sent our SETUP_ACK message.95* @private {!Deferred}96*/97this.setupAckSent_ = new Deferred();9899/**100* Fires once we're marked connected.101* @private {!Deferred}102*/103this.connected_ = new Deferred();104105/**106* The unique ID of this side of the connection. Used to determine when a peer107* is reloaded.108* @private {string}109*/110this.endpointId_ = goog.net.xpc.getRandomString(10);111112/**113* The unique ID of the peer. If we get a message from a peer with an ID we114* don't expect, we reset the connection.115* @private {?string}116*/117this.peerEndpointId_ = null;118119/**120* The map of sending messages.121* @private {Object}122*/123this.asyncSendsMap_ = {};124125/**126* The original channel name.127* @private {string}128*/129this.originalChannelName_ = this.channel_.name;130131// We reconfigure the channel name to include the role so that we can132// communicate in the same window between the different roles on the133// same channel.134this.channel_.updateChannelNameAndCatalog(135DirectTransport.getRoledChannelName_(136this.channel_.name, this.channel_.getRole()));137138/**139* Flag indicating if this instance of the transport has been initialized.140* @private {boolean}141*/142this.initialized_ = false;143144// We don't want to mark ourselves connected until we have sent whatever145// message will cause our counterpart in the other frame to also declare146// itself connected, if there is such a message. Otherwise we risk a user147// message being sent in advance of that message, and it being discarded.148149// Two sided handshake:150// SETUP_ACK has to have been received, and sent.151this.connected_.awaitDeferred(this.setupAckReceived_);152this.connected_.awaitDeferred(this.setupAckSent_);153154this.connected_.addCallback(this.notifyConnected_, this);155this.connected_.callback(true);156157this.eventHandler_.listen(158this.maybeAttemptToConnectTimer_, Timer.TICK,159this.maybeAttemptToConnect_);160161goog.log.info(162goog.net.xpc.logger,163'DirectTransport created. role=' + this.channel_.getRole());164};165goog.inherits(goog.net.xpc.DirectTransport, Transport);166var DirectTransport = goog.net.xpc.DirectTransport;167168169/**170* @private {number}171* @const172*/173DirectTransport.CONNECTION_ATTEMPT_INTERVAL_MS_ = 100;174175176/**177* The delay to notify the xpc of a successful connection. This is used178* to allow both parties to be connected if one party's connection callback179* invokes an immediate send.180* @private {number}181* @const182*/183DirectTransport.CONNECTION_DELAY_INTERVAL_MS_ = 0;184185186/**187* @param {!Window} peerWindow The peer window to check if DirectTranport is188* supported on.189* @return {boolean} Whether this transport is supported.190*/191DirectTransport.isSupported = function(peerWindow) {192193try {194return window.document.domain == peerWindow.document.domain;195} catch (e) {196return false;197}198};199200201/**202* Tracks the number of DirectTransport channels that have been203* initialized but not disposed yet in a map keyed by the UID of the window204* object. This allows for multiple windows to be initiallized and listening205* for messages.206* @private {!Object<number>}207*/208DirectTransport.activeCount_ = {};209210211/**212* Path of global message proxy.213* @private {string}214* @const215*/216// TODO(user): Make this configurable using the CfgFields.217DirectTransport.GLOBAL_TRANPORT_PATH_ = 'crosswindowmessaging.channel';218219220/**221* The delimiter used for transport service messages.222* @private {string}223* @const224*/225DirectTransport.MESSAGE_DELIMITER_ = ',';226227228/**229* Initializes this transport. Registers a method for 'message'-events in the230* global scope.231* @param {!Window} listenWindow The window to listen to events on.232* @private233*/234DirectTransport.initialize_ = function(listenWindow) {235var uid = goog.getUid(listenWindow);236var value = DirectTransport.activeCount_[uid] || 0;237if (value == 0) {238// Set up a handler on the window to proxy messages to class.239var globalProxy = goog.getObjectByName(240DirectTransport.GLOBAL_TRANPORT_PATH_, listenWindow);241if (globalProxy == null) {242goog.exportSymbol(243DirectTransport.GLOBAL_TRANPORT_PATH_,244DirectTransport.messageReceivedHandler_, listenWindow);245}246}247DirectTransport.activeCount_[uid]++;248};249250251/**252* @param {string} channelName The channel name.253* @param {string|number} role The role.254* @return {string} The formatted channel name including role.255* @private256*/257DirectTransport.getRoledChannelName_ = function(channelName, role) {258return channelName + '_' + role;259};260261262/**263* @param {!Object} literal The literal unrenamed message.264* @return {boolean} Whether the message was successfully delivered to a265* channel.266* @private267*/268DirectTransport.messageReceivedHandler_ = function(literal) {269var msg = DirectTransport.Message_.fromLiteral(literal);270271var channelName = msg.channelName;272var service = msg.service;273var payload = msg.payload;274275goog.log.fine(276goog.net.xpc.logger, 'messageReceived: channel=' + channelName +277', service=' + service + ', payload=' + payload);278279// Attempt to deliver message to the channel. Keep in mind that it may not280// exist for several reasons, including but not limited to:281// - a malformed message282// - the channel simply has not been created283// - channel was created in a different namespace284// - message was sent to the wrong window285// - channel has become stale (e.g. caching iframes and back clicks)286var channel = goog.net.xpc.channels[channelName];287if (channel) {288channel.xpcDeliver(service, payload);289return true;290}291292var transportMessageType = DirectTransport.parseTransportPayload_(payload)[0];293294// Check if there are any stale channel names that can be updated.295for (var staleChannelName in goog.net.xpc.channels) {296var staleChannel = goog.net.xpc.channels[staleChannelName];297if (staleChannel.getRole() == CrossPageChannelRole.INNER &&298!staleChannel.isConnected() &&299service == goog.net.xpc.TRANSPORT_SERVICE_ &&300transportMessageType == goog.net.xpc.SETUP) {301// Inner peer received SETUP message but channel names did not match.302// Start using the channel name sent from outer peer. The channel name303// of the inner peer can easily become out of date, as iframe's and their304// JS state get cached in many browsers upon page reload or history305// navigation (particularly Firefox 1.5+).306staleChannel.updateChannelNameAndCatalog(channelName);307staleChannel.xpcDeliver(service, payload);308return true;309}310}311312// Failed to find a channel to deliver this message to, so simply ignore it.313goog.log.info(goog.net.xpc.logger, 'channel name mismatch; message ignored.');314return false;315};316317318/**319* The transport type.320* @type {number}321* @override322*/323DirectTransport.prototype.transportType = goog.net.xpc.TransportTypes.DIRECT;324325326/**327* Handles transport service messages.328* @param {string} payload The message content.329* @override330*/331DirectTransport.prototype.transportServiceHandler = function(payload) {332var transportParts = DirectTransport.parseTransportPayload_(payload);333var transportMessageType = transportParts[0];334var peerEndpointId = transportParts[1];335switch (transportMessageType) {336case goog.net.xpc.SETUP_ACK_:337if (!this.setupAckReceived_.hasFired()) {338this.setupAckReceived_.callback(true);339}340break;341case goog.net.xpc.SETUP:342this.sendSetupAckMessage_();343if ((this.peerEndpointId_ != null) &&344(this.peerEndpointId_ != peerEndpointId)) {345// Send a new SETUP message since the peer has been replaced.346goog.log.info(347goog.net.xpc.logger,348'Sending SETUP and changing peer ID to: ' + peerEndpointId);349this.sendSetupMessage_();350}351this.peerEndpointId_ = peerEndpointId;352break;353}354};355356357/**358* Sends a SETUP transport service message.359* @private360*/361DirectTransport.prototype.sendSetupMessage_ = function() {362// Although we could send real objects, since some other transports are363// limited to strings we also keep this requirement.364var payload = goog.net.xpc.SETUP;365payload += DirectTransport.MESSAGE_DELIMITER_;366payload += this.endpointId_;367this.send(goog.net.xpc.TRANSPORT_SERVICE_, payload);368};369370371/**372* Sends a SETUP_ACK transport service message.373* @private374*/375DirectTransport.prototype.sendSetupAckMessage_ = function() {376this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);377if (!this.setupAckSent_.hasFired()) {378this.setupAckSent_.callback(true);379}380};381382383/** @override */384DirectTransport.prototype.connect = function() {385var win = this.getWindow();386if (win) {387DirectTransport.initialize_(win);388this.initialized_ = true;389this.maybeAttemptToConnect_();390} else {391goog.log.fine(goog.net.xpc.logger, 'connect(): no window to initialize.');392}393};394395396/**397* Connects to other peer. In the case of the outer peer, the setup messages are398* likely sent before the inner peer is ready to receive them. Therefore, this399* function will continue trying to send the SETUP message until the inner peer400* responds. In the case of the inner peer, it will occasionally have its401* channel name fall out of sync with the outer peer, particularly during402* soft-reloads and history navigations.403* @private404*/405DirectTransport.prototype.maybeAttemptToConnect_ = function() {406if (this.channel_.isConnected()) {407this.maybeAttemptToConnectTimer_.stop();408return;409}410this.maybeAttemptToConnectTimer_.start();411this.sendSetupMessage_();412};413414415/**416* Prepares to send a message.417* @param {string} service The name of the service the message is to be418* delivered to.419* @param {string} payload The message content.420* @override421*/422DirectTransport.prototype.send = function(service, payload) {423if (!this.channel_.getPeerWindowObject()) {424goog.log.fine(goog.net.xpc.logger, 'send(): window not ready');425return;426}427var channelName = DirectTransport.getRoledChannelName_(428this.originalChannelName_, this.getPeerRole_());429430var message = new DirectTransport.Message_(channelName, service, payload);431432if (this.channel_.getConfig()[CfgFields.DIRECT_TRANSPORT_SYNC_MODE]) {433this.executeScheduledSend_(message);434} else {435// Note: goog.async.nextTick doesn't support cancelling or disposal so436// leaving as 0ms timer, though this may have performance implications.437this.asyncSendsMap_[goog.getUid(message)] =438Timer.callOnce(goog.bind(this.executeScheduledSend_, this, message), 0);439}440};441442443/**444* Sends the message.445* @param {!DirectTransport.Message_} message The message to send.446* @private447*/448DirectTransport.prototype.executeScheduledSend_ = function(message) {449var messageId = goog.getUid(message);450if (this.asyncSendsMap_[messageId]) {451delete this.asyncSendsMap_[messageId];452}453454455try {456var peerProxy = goog.getObjectByName(457DirectTransport.GLOBAL_TRANPORT_PATH_,458this.channel_.getPeerWindowObject());459} catch (error) {460goog.log.warning(461goog.net.xpc.logger, 'Can\'t access other window, ignoring.', error);462return;463}464465if (goog.isNull(peerProxy)) {466goog.log.warning(467goog.net.xpc.logger, 'Peer window had no global function.');468return;469}470471472try {473peerProxy(message.toLiteral());474goog.log.info(475goog.net.xpc.logger, 'send(): channelName=' + message.channelName +476' service=' + message.service + ' payload=' + message.payload);477} catch (error) {478goog.log.warning(479goog.net.xpc.logger, 'Error performing call, ignoring.', error);480}481};482483484/**485* @return {goog.net.xpc.CrossPageChannelRole} The role of peer channel (either486* inner or outer).487* @private488*/489DirectTransport.prototype.getPeerRole_ = function() {490var role = this.channel_.getRole();491return role == goog.net.xpc.CrossPageChannelRole.OUTER ?492goog.net.xpc.CrossPageChannelRole.INNER :493goog.net.xpc.CrossPageChannelRole.OUTER;494};495496497/**498* Notifies the channel that this transport is connected.499* @private500*/501DirectTransport.prototype.notifyConnected_ = function() {502// Add a delay as the connection callback will break if this transport is503// synchronous and the callback invokes send() immediately.504this.channel_.notifyConnected(505this.channel_.getConfig()[CfgFields.DIRECT_TRANSPORT_SYNC_MODE] ?506DirectTransport.CONNECTION_DELAY_INTERVAL_MS_ :5070);508};509510511/** @override */512DirectTransport.prototype.disposeInternal = function() {513if (this.initialized_) {514var listenWindow = this.getWindow();515var uid = goog.getUid(listenWindow);516var value = --DirectTransport.activeCount_[uid];517if (value == 1) {518goog.exportSymbol(519DirectTransport.GLOBAL_TRANPORT_PATH_, null, listenWindow);520}521}522523if (this.asyncSendsMap_) {524goog.object.forEach(525this.asyncSendsMap_, function(timerId) { Timer.clear(timerId); });526this.asyncSendsMap_ = null;527}528529// Deferred's aren't disposables.530if (this.setupAckReceived_) {531this.setupAckReceived_.cancel();532delete this.setupAckReceived_;533}534if (this.setupAckSent_) {535this.setupAckSent_.cancel();536delete this.setupAckSent_;537}538if (this.connected_) {539this.connected_.cancel();540delete this.connected_;541}542543DirectTransport.base(this, 'disposeInternal');544};545546547/**548* Parses a transport service payload message.549* @param {string} payload The payload.550* @return {!Array<?string>} An array with the message type as the first member551* and the endpoint id as the second, if one was sent, or null otherwise.552* @private553*/554DirectTransport.parseTransportPayload_ = function(payload) {555var transportParts = /** @type {!Array<?string>} */ (556payload.split(DirectTransport.MESSAGE_DELIMITER_));557transportParts[1] = transportParts[1] || null; // Usually endpointId.558return transportParts;559};560561562563/**564* Message container that gets passed back and forth between windows.565* @param {string} channelName The channel name to tranport messages on.566* @param {string} service The service to send the payload to.567* @param {string} payload The payload to send.568* @constructor569* @struct570* @private571*/572DirectTransport.Message_ = function(channelName, service, payload) {573/**574* The name of the channel.575* @type {string}576*/577this.channelName = channelName;578579/**580* The service on the channel.581* @type {string}582*/583this.service = service;584585/**586* The payload.587* @type {string}588*/589this.payload = payload;590};591592593/**594* Converts a message to a literal object.595* @return {!Object} The message as a literal object.596*/597DirectTransport.Message_.prototype.toLiteral = function() {598return {599'channelName': this.channelName,600'service': this.service,601'payload': this.payload602};603};604605606/**607* Creates a Message_ from a literal object.608* @param {!Object} literal The literal to convert to Message.609* @return {!DirectTransport.Message_} The Message.610*/611DirectTransport.Message_.fromLiteral = function(literal) {612return new DirectTransport.Message_(613literal['channelName'], literal['service'], literal['payload']);614};615616}); // goog.scope617618619