Path: blob/trunk/third_party/closure/goog/net/xpc/iframerelaytransport.js
1865 views
// Copyright 2007 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 Contains the iframe relay tranport.16*/171819goog.provide('goog.net.xpc.IframeRelayTransport');2021goog.require('goog.dom');22goog.require('goog.dom.TagName');23goog.require('goog.dom.safe');24goog.require('goog.events');25goog.require('goog.html.SafeHtml');26goog.require('goog.log');27goog.require('goog.log.Level');28goog.require('goog.net.xpc');29goog.require('goog.net.xpc.CfgFields');30goog.require('goog.net.xpc.Transport');31goog.require('goog.net.xpc.TransportTypes');32goog.require('goog.string');33goog.require('goog.string.Const');34goog.require('goog.userAgent');35363738/**39* Iframe relay transport. Creates hidden iframes containing a document40* from the peer's origin. Data is transferred in the fragment identifier.41* Therefore the document loaded in the iframes can be served from the42* browser's cache.43*44* @param {goog.net.xpc.CrossPageChannel} channel The channel this45* transport belongs to.46* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding47* the correct window.48* @constructor49* @extends {goog.net.xpc.Transport}50* @final51*/52goog.net.xpc.IframeRelayTransport = function(channel, opt_domHelper) {53goog.net.xpc.IframeRelayTransport.base(this, 'constructor', opt_domHelper);5455/**56* The channel this transport belongs to.57* @type {goog.net.xpc.CrossPageChannel}58* @private59*/60this.channel_ = channel;6162/**63* The URI used to relay data to the peer.64* @type {string}65* @private66*/67this.peerRelayUri_ =68this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_RELAY_URI];6970/**71* The id of the iframe the peer page lives in.72* @type {string}73* @private74*/75this.peerIframeId_ =76this.channel_.getConfig()[goog.net.xpc.CfgFields.IFRAME_ID];7778if (goog.userAgent.WEBKIT) {79goog.net.xpc.IframeRelayTransport.startCleanupTimer_();80}81};82goog.inherits(goog.net.xpc.IframeRelayTransport, goog.net.xpc.Transport);838485if (goog.userAgent.WEBKIT) {86/**87* Array to keep references to the relay-iframes. Used only if88* there is no way to detect when the iframes are loaded. In that89* case the relay-iframes are removed after a timeout.90* @type {Array<Object>}91* @private92*/93goog.net.xpc.IframeRelayTransport.iframeRefs_ = [];949596/**97* Interval at which iframes are destroyed.98* @type {number}99* @private100*/101goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_ = 1000;102103104/**105* Time after which a relay-iframe is destroyed.106* @type {number}107* @private108*/109goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_ = 3000;110111112/**113* The cleanup timer id.114* @type {number}115* @private116*/117goog.net.xpc.IframeRelayTransport.cleanupTimer_ = 0;118119120/**121* Starts the cleanup timer.122* @private123*/124goog.net.xpc.IframeRelayTransport.startCleanupTimer_ = function() {125if (!goog.net.xpc.IframeRelayTransport.cleanupTimer_) {126goog.net.xpc.IframeRelayTransport.cleanupTimer_ =127window.setTimeout(function() {128goog.net.xpc.IframeRelayTransport.cleanup_();129}, goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);130}131};132133134/**135* Remove all relay-iframes which are older than the maximal age.136* @param {number=} opt_maxAge The maximal age in milliseconds.137* @private138*/139goog.net.xpc.IframeRelayTransport.cleanup_ = function(opt_maxAge) {140var now = goog.now();141var maxAge =142opt_maxAge || goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_;143144while (goog.net.xpc.IframeRelayTransport.iframeRefs_.length &&145now - goog.net.xpc.IframeRelayTransport.iframeRefs_[0].timestamp >=146maxAge) {147var ifr =148goog.net.xpc.IframeRelayTransport.iframeRefs_.shift().iframeElement;149goog.dom.removeNode(ifr);150goog.log.log(151goog.net.xpc.logger, goog.log.Level.FINEST, 'iframe removed');152}153154goog.net.xpc.IframeRelayTransport.cleanupTimer_ = window.setTimeout(155goog.net.xpc.IframeRelayTransport.cleanupCb_,156goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);157};158159160/**161* Function which wraps cleanup_().162* @private163*/164goog.net.xpc.IframeRelayTransport.cleanupCb_ = function() {165goog.net.xpc.IframeRelayTransport.cleanup_();166};167}168169170/**171* Maximum sendable size of a payload via a single iframe in IE.172* @type {number}173* @private174*/175goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_ = 1800;176177178/**179* @typedef {{fragments: !Array<string>, received: number, expected: number}}180*/181goog.net.xpc.IframeRelayTransport.FragmentInfo;182183184/**185* Used to track incoming payload fragments. The implementation can process186* incoming fragments from several channels at a time, even if data is187* out-of-order or interleaved.188*189* @type {!Object<string, !goog.net.xpc.IframeRelayTransport.FragmentInfo>}190* @private191*/192goog.net.xpc.IframeRelayTransport.fragmentMap_ = {};193194195/**196* The transport type.197* @type {number}198* @override199*/200goog.net.xpc.IframeRelayTransport.prototype.transportType =201goog.net.xpc.TransportTypes.IFRAME_RELAY;202203204/**205* Connects this transport.206* @override207*/208goog.net.xpc.IframeRelayTransport.prototype.connect = function() {209if (!this.getWindow()['xpcRelay']) {210this.getWindow()['xpcRelay'] =211goog.net.xpc.IframeRelayTransport.receiveMessage_;212}213214this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP);215};216217218/**219* Processes an incoming message.220*221* @param {string} channelName The name of the channel.222* @param {string} frame The raw frame content.223* @private224*/225goog.net.xpc.IframeRelayTransport.receiveMessage_ = function(226channelName, frame) {227var pos = frame.indexOf(':');228var header = frame.substr(0, pos);229var payload = frame.substr(pos + 1);230231if (!goog.userAgent.IE || (pos = header.indexOf('|')) == -1) {232// First, the easy case.233var service = header;234} else {235// There was a fragment id in the header, so this is a message236// fragment, not a whole message.237var service = header.substr(0, pos);238var fragmentIdStr = header.substr(pos + 1);239240// Separate the message id string and the fragment number. Note that241// there may be a single leading + in the argument to parseInt, but242// this is harmless.243pos = fragmentIdStr.indexOf('+');244var messageIdStr = fragmentIdStr.substr(0, pos);245var fragmentNum = parseInt(fragmentIdStr.substr(pos + 1), 10);246var fragmentInfo =247goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];248if (!fragmentInfo) {249fragmentInfo =250goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr] =251{fragments: [], received: 0, expected: 0};252}253254if (goog.string.contains(fragmentIdStr, '++')) {255fragmentInfo.expected = fragmentNum + 1;256}257fragmentInfo.fragments[fragmentNum] = payload;258fragmentInfo.received++;259260if (fragmentInfo.received != fragmentInfo.expected) {261return;262}263264// We've received all outstanding fragments; combine what we've received265// into payload and fall out to the call to xpcDeliver.266payload = fragmentInfo.fragments.join('');267delete goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];268}269270goog.net.xpc.channels[channelName].xpcDeliver(271service, decodeURIComponent(payload));272};273274275/**276* Handles transport service messages (internal signalling).277* @param {string} payload The message content.278* @override279*/280goog.net.xpc.IframeRelayTransport.prototype.transportServiceHandler = function(281payload) {282if (payload == goog.net.xpc.SETUP) {283// TODO(user) Safari swallows the SETUP_ACK from the iframe to the284// container after hitting reload.285this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);286this.channel_.notifyConnected();287} else if (payload == goog.net.xpc.SETUP_ACK_) {288this.channel_.notifyConnected();289}290};291292293/**294* Sends a message.295*296* @param {string} service Name of service this the message has to be delivered.297* @param {string} payload The message content.298* @override299*/300goog.net.xpc.IframeRelayTransport.prototype.send = function(service, payload) {301// If we're on IE and the post-encoding payload is large, split it302// into multiple payloads and send each one separately. Otherwise,303// just send the whole thing.304var encodedPayload = encodeURIComponent(payload);305var encodedLen = encodedPayload.length;306var maxSize = goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_;307308if (goog.userAgent.IE && encodedLen > maxSize) {309// A probabilistically-unique string used to link together all fragments310// in this message.311var messageIdStr = goog.string.getRandomString();312313for (var startIndex = 0, fragmentNum = 0; startIndex < encodedLen;314fragmentNum++) {315var payloadFragment = encodedPayload.substr(startIndex, maxSize);316startIndex += maxSize;317var fragmentIdStr =318messageIdStr + (startIndex >= encodedLen ? '++' : '+') + fragmentNum;319this.send_(service, payloadFragment, fragmentIdStr);320}321} else {322this.send_(service, encodedPayload);323}324};325326327/**328* Sends an encoded message or message fragment.329* @param {string} service Name of service this the message has to be delivered.330* @param {string} encodedPayload The message content, URI encoded.331* @param {string=} opt_fragmentIdStr If sending a fragment, a string that332* identifies the fragment.333* @private334*/335goog.net.xpc.IframeRelayTransport.prototype.send_ = function(336service, encodedPayload, opt_fragmentIdStr) {337// IE requires that we create the onload attribute inline, otherwise the338// handler is not triggered339if (goog.userAgent.IE) {340var div =341this.getWindow().document.createElement(String(goog.dom.TagName.DIV));342// TODO(mlourenco): It might be possible to set the sandbox attribute343// to restrict the privileges of the created iframe.344goog.dom.safe.setInnerHtml(345div, goog.html.SafeHtml.createIframe(null, null, {346'onload': goog.string.Const.from('this.xpcOnload()'),347'sandbox': null348}));349var ifr = div.childNodes[0];350div = null;351ifr['xpcOnload'] = goog.net.xpc.IframeRelayTransport.iframeLoadHandler_;352} else {353var ifr = this.getWindow().document.createElement(354String(goog.dom.TagName.IFRAME));355356if (goog.userAgent.WEBKIT) {357// safari doesn't fire load-events on iframes.358// keep a reference and remove after a timeout.359goog.net.xpc.IframeRelayTransport.iframeRefs_.push(360{timestamp: goog.now(), iframeElement: ifr});361} else {362goog.events.listen(363ifr, 'load', goog.net.xpc.IframeRelayTransport.iframeLoadHandler_);364}365}366367var style = ifr.style;368style.visibility = 'hidden';369style.width = ifr.style.height = '0px';370style.position = 'absolute';371372var url = this.peerRelayUri_;373url += '#' + this.channel_.name;374if (this.peerIframeId_) {375url += ',' + this.peerIframeId_;376}377url += '|' + service;378if (opt_fragmentIdStr) {379url += '|' + opt_fragmentIdStr;380}381url += ':' + encodedPayload;382383ifr.src = url;384385this.getWindow().document.body.appendChild(ifr);386387goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'msg sent: ' + url);388};389390391/**392* The iframe load handler. Gets called as method on the iframe element.393* @private394* @this {Element}395*/396goog.net.xpc.IframeRelayTransport.iframeLoadHandler_ = function() {397goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'iframe-load');398goog.dom.removeNode(this);399this.xpcOnload = null;400};401402403/** @override */404goog.net.xpc.IframeRelayTransport.prototype.disposeInternal = function() {405goog.net.xpc.IframeRelayTransport.base(this, 'disposeInternal');406if (goog.userAgent.WEBKIT) {407goog.net.xpc.IframeRelayTransport.cleanup_(0);408}409};410411412