Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/net/xpc/iframerelaytransport.js
1865 views
1
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview Contains the iframe relay tranport.
17
*/
18
19
20
goog.provide('goog.net.xpc.IframeRelayTransport');
21
22
goog.require('goog.dom');
23
goog.require('goog.dom.TagName');
24
goog.require('goog.dom.safe');
25
goog.require('goog.events');
26
goog.require('goog.html.SafeHtml');
27
goog.require('goog.log');
28
goog.require('goog.log.Level');
29
goog.require('goog.net.xpc');
30
goog.require('goog.net.xpc.CfgFields');
31
goog.require('goog.net.xpc.Transport');
32
goog.require('goog.net.xpc.TransportTypes');
33
goog.require('goog.string');
34
goog.require('goog.string.Const');
35
goog.require('goog.userAgent');
36
37
38
39
/**
40
* Iframe relay transport. Creates hidden iframes containing a document
41
* from the peer's origin. Data is transferred in the fragment identifier.
42
* Therefore the document loaded in the iframes can be served from the
43
* browser's cache.
44
*
45
* @param {goog.net.xpc.CrossPageChannel} channel The channel this
46
* transport belongs to.
47
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding
48
* the correct window.
49
* @constructor
50
* @extends {goog.net.xpc.Transport}
51
* @final
52
*/
53
goog.net.xpc.IframeRelayTransport = function(channel, opt_domHelper) {
54
goog.net.xpc.IframeRelayTransport.base(this, 'constructor', opt_domHelper);
55
56
/**
57
* The channel this transport belongs to.
58
* @type {goog.net.xpc.CrossPageChannel}
59
* @private
60
*/
61
this.channel_ = channel;
62
63
/**
64
* The URI used to relay data to the peer.
65
* @type {string}
66
* @private
67
*/
68
this.peerRelayUri_ =
69
this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_RELAY_URI];
70
71
/**
72
* The id of the iframe the peer page lives in.
73
* @type {string}
74
* @private
75
*/
76
this.peerIframeId_ =
77
this.channel_.getConfig()[goog.net.xpc.CfgFields.IFRAME_ID];
78
79
if (goog.userAgent.WEBKIT) {
80
goog.net.xpc.IframeRelayTransport.startCleanupTimer_();
81
}
82
};
83
goog.inherits(goog.net.xpc.IframeRelayTransport, goog.net.xpc.Transport);
84
85
86
if (goog.userAgent.WEBKIT) {
87
/**
88
* Array to keep references to the relay-iframes. Used only if
89
* there is no way to detect when the iframes are loaded. In that
90
* case the relay-iframes are removed after a timeout.
91
* @type {Array<Object>}
92
* @private
93
*/
94
goog.net.xpc.IframeRelayTransport.iframeRefs_ = [];
95
96
97
/**
98
* Interval at which iframes are destroyed.
99
* @type {number}
100
* @private
101
*/
102
goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_ = 1000;
103
104
105
/**
106
* Time after which a relay-iframe is destroyed.
107
* @type {number}
108
* @private
109
*/
110
goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_ = 3000;
111
112
113
/**
114
* The cleanup timer id.
115
* @type {number}
116
* @private
117
*/
118
goog.net.xpc.IframeRelayTransport.cleanupTimer_ = 0;
119
120
121
/**
122
* Starts the cleanup timer.
123
* @private
124
*/
125
goog.net.xpc.IframeRelayTransport.startCleanupTimer_ = function() {
126
if (!goog.net.xpc.IframeRelayTransport.cleanupTimer_) {
127
goog.net.xpc.IframeRelayTransport.cleanupTimer_ =
128
window.setTimeout(function() {
129
goog.net.xpc.IframeRelayTransport.cleanup_();
130
}, goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);
131
}
132
};
133
134
135
/**
136
* Remove all relay-iframes which are older than the maximal age.
137
* @param {number=} opt_maxAge The maximal age in milliseconds.
138
* @private
139
*/
140
goog.net.xpc.IframeRelayTransport.cleanup_ = function(opt_maxAge) {
141
var now = goog.now();
142
var maxAge =
143
opt_maxAge || goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_;
144
145
while (goog.net.xpc.IframeRelayTransport.iframeRefs_.length &&
146
now - goog.net.xpc.IframeRelayTransport.iframeRefs_[0].timestamp >=
147
maxAge) {
148
var ifr =
149
goog.net.xpc.IframeRelayTransport.iframeRefs_.shift().iframeElement;
150
goog.dom.removeNode(ifr);
151
goog.log.log(
152
goog.net.xpc.logger, goog.log.Level.FINEST, 'iframe removed');
153
}
154
155
goog.net.xpc.IframeRelayTransport.cleanupTimer_ = window.setTimeout(
156
goog.net.xpc.IframeRelayTransport.cleanupCb_,
157
goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);
158
};
159
160
161
/**
162
* Function which wraps cleanup_().
163
* @private
164
*/
165
goog.net.xpc.IframeRelayTransport.cleanupCb_ = function() {
166
goog.net.xpc.IframeRelayTransport.cleanup_();
167
};
168
}
169
170
171
/**
172
* Maximum sendable size of a payload via a single iframe in IE.
173
* @type {number}
174
* @private
175
*/
176
goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_ = 1800;
177
178
179
/**
180
* @typedef {{fragments: !Array<string>, received: number, expected: number}}
181
*/
182
goog.net.xpc.IframeRelayTransport.FragmentInfo;
183
184
185
/**
186
* Used to track incoming payload fragments. The implementation can process
187
* incoming fragments from several channels at a time, even if data is
188
* out-of-order or interleaved.
189
*
190
* @type {!Object<string, !goog.net.xpc.IframeRelayTransport.FragmentInfo>}
191
* @private
192
*/
193
goog.net.xpc.IframeRelayTransport.fragmentMap_ = {};
194
195
196
/**
197
* The transport type.
198
* @type {number}
199
* @override
200
*/
201
goog.net.xpc.IframeRelayTransport.prototype.transportType =
202
goog.net.xpc.TransportTypes.IFRAME_RELAY;
203
204
205
/**
206
* Connects this transport.
207
* @override
208
*/
209
goog.net.xpc.IframeRelayTransport.prototype.connect = function() {
210
if (!this.getWindow()['xpcRelay']) {
211
this.getWindow()['xpcRelay'] =
212
goog.net.xpc.IframeRelayTransport.receiveMessage_;
213
}
214
215
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP);
216
};
217
218
219
/**
220
* Processes an incoming message.
221
*
222
* @param {string} channelName The name of the channel.
223
* @param {string} frame The raw frame content.
224
* @private
225
*/
226
goog.net.xpc.IframeRelayTransport.receiveMessage_ = function(
227
channelName, frame) {
228
var pos = frame.indexOf(':');
229
var header = frame.substr(0, pos);
230
var payload = frame.substr(pos + 1);
231
232
if (!goog.userAgent.IE || (pos = header.indexOf('|')) == -1) {
233
// First, the easy case.
234
var service = header;
235
} else {
236
// There was a fragment id in the header, so this is a message
237
// fragment, not a whole message.
238
var service = header.substr(0, pos);
239
var fragmentIdStr = header.substr(pos + 1);
240
241
// Separate the message id string and the fragment number. Note that
242
// there may be a single leading + in the argument to parseInt, but
243
// this is harmless.
244
pos = fragmentIdStr.indexOf('+');
245
var messageIdStr = fragmentIdStr.substr(0, pos);
246
var fragmentNum = parseInt(fragmentIdStr.substr(pos + 1), 10);
247
var fragmentInfo =
248
goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];
249
if (!fragmentInfo) {
250
fragmentInfo =
251
goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr] =
252
{fragments: [], received: 0, expected: 0};
253
}
254
255
if (goog.string.contains(fragmentIdStr, '++')) {
256
fragmentInfo.expected = fragmentNum + 1;
257
}
258
fragmentInfo.fragments[fragmentNum] = payload;
259
fragmentInfo.received++;
260
261
if (fragmentInfo.received != fragmentInfo.expected) {
262
return;
263
}
264
265
// We've received all outstanding fragments; combine what we've received
266
// into payload and fall out to the call to xpcDeliver.
267
payload = fragmentInfo.fragments.join('');
268
delete goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];
269
}
270
271
goog.net.xpc.channels[channelName].xpcDeliver(
272
service, decodeURIComponent(payload));
273
};
274
275
276
/**
277
* Handles transport service messages (internal signalling).
278
* @param {string} payload The message content.
279
* @override
280
*/
281
goog.net.xpc.IframeRelayTransport.prototype.transportServiceHandler = function(
282
payload) {
283
if (payload == goog.net.xpc.SETUP) {
284
// TODO(user) Safari swallows the SETUP_ACK from the iframe to the
285
// container after hitting reload.
286
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
287
this.channel_.notifyConnected();
288
} else if (payload == goog.net.xpc.SETUP_ACK_) {
289
this.channel_.notifyConnected();
290
}
291
};
292
293
294
/**
295
* Sends a message.
296
*
297
* @param {string} service Name of service this the message has to be delivered.
298
* @param {string} payload The message content.
299
* @override
300
*/
301
goog.net.xpc.IframeRelayTransport.prototype.send = function(service, payload) {
302
// If we're on IE and the post-encoding payload is large, split it
303
// into multiple payloads and send each one separately. Otherwise,
304
// just send the whole thing.
305
var encodedPayload = encodeURIComponent(payload);
306
var encodedLen = encodedPayload.length;
307
var maxSize = goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_;
308
309
if (goog.userAgent.IE && encodedLen > maxSize) {
310
// A probabilistically-unique string used to link together all fragments
311
// in this message.
312
var messageIdStr = goog.string.getRandomString();
313
314
for (var startIndex = 0, fragmentNum = 0; startIndex < encodedLen;
315
fragmentNum++) {
316
var payloadFragment = encodedPayload.substr(startIndex, maxSize);
317
startIndex += maxSize;
318
var fragmentIdStr =
319
messageIdStr + (startIndex >= encodedLen ? '++' : '+') + fragmentNum;
320
this.send_(service, payloadFragment, fragmentIdStr);
321
}
322
} else {
323
this.send_(service, encodedPayload);
324
}
325
};
326
327
328
/**
329
* Sends an encoded message or message fragment.
330
* @param {string} service Name of service this the message has to be delivered.
331
* @param {string} encodedPayload The message content, URI encoded.
332
* @param {string=} opt_fragmentIdStr If sending a fragment, a string that
333
* identifies the fragment.
334
* @private
335
*/
336
goog.net.xpc.IframeRelayTransport.prototype.send_ = function(
337
service, encodedPayload, opt_fragmentIdStr) {
338
// IE requires that we create the onload attribute inline, otherwise the
339
// handler is not triggered
340
if (goog.userAgent.IE) {
341
var div =
342
this.getWindow().document.createElement(String(goog.dom.TagName.DIV));
343
// TODO(mlourenco): It might be possible to set the sandbox attribute
344
// to restrict the privileges of the created iframe.
345
goog.dom.safe.setInnerHtml(
346
div, goog.html.SafeHtml.createIframe(null, null, {
347
'onload': goog.string.Const.from('this.xpcOnload()'),
348
'sandbox': null
349
}));
350
var ifr = div.childNodes[0];
351
div = null;
352
ifr['xpcOnload'] = goog.net.xpc.IframeRelayTransport.iframeLoadHandler_;
353
} else {
354
var ifr = this.getWindow().document.createElement(
355
String(goog.dom.TagName.IFRAME));
356
357
if (goog.userAgent.WEBKIT) {
358
// safari doesn't fire load-events on iframes.
359
// keep a reference and remove after a timeout.
360
goog.net.xpc.IframeRelayTransport.iframeRefs_.push(
361
{timestamp: goog.now(), iframeElement: ifr});
362
} else {
363
goog.events.listen(
364
ifr, 'load', goog.net.xpc.IframeRelayTransport.iframeLoadHandler_);
365
}
366
}
367
368
var style = ifr.style;
369
style.visibility = 'hidden';
370
style.width = ifr.style.height = '0px';
371
style.position = 'absolute';
372
373
var url = this.peerRelayUri_;
374
url += '#' + this.channel_.name;
375
if (this.peerIframeId_) {
376
url += ',' + this.peerIframeId_;
377
}
378
url += '|' + service;
379
if (opt_fragmentIdStr) {
380
url += '|' + opt_fragmentIdStr;
381
}
382
url += ':' + encodedPayload;
383
384
ifr.src = url;
385
386
this.getWindow().document.body.appendChild(ifr);
387
388
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'msg sent: ' + url);
389
};
390
391
392
/**
393
* The iframe load handler. Gets called as method on the iframe element.
394
* @private
395
* @this {Element}
396
*/
397
goog.net.xpc.IframeRelayTransport.iframeLoadHandler_ = function() {
398
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'iframe-load');
399
goog.dom.removeNode(this);
400
this.xpcOnload = null;
401
};
402
403
404
/** @override */
405
goog.net.xpc.IframeRelayTransport.prototype.disposeInternal = function() {
406
goog.net.xpc.IframeRelayTransport.base(this, 'disposeInternal');
407
if (goog.userAgent.WEBKIT) {
408
goog.net.xpc.IframeRelayTransport.cleanup_(0);
409
}
410
};
411
412