Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/net/xpc/iframepollingtransport.js
2884 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 polling transport.
17
*/
18
19
20
goog.provide('goog.net.xpc.IframePollingTransport');
21
goog.provide('goog.net.xpc.IframePollingTransport.Receiver');
22
goog.provide('goog.net.xpc.IframePollingTransport.Sender');
23
24
goog.require('goog.array');
25
goog.require('goog.dom');
26
goog.require('goog.dom.TagName');
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.CrossPageChannelRole');
32
goog.require('goog.net.xpc.Transport');
33
goog.require('goog.net.xpc.TransportTypes');
34
goog.require('goog.userAgent');
35
36
37
38
/**
39
* Iframe polling transport. Uses hidden iframes to transfer data
40
* in the fragment identifier of the URL. The peer polls the iframe's location
41
* for changes.
42
* Unfortunately, in Safari this screws up the history, because Safari doesn't
43
* allow to call location.replace() on a window containing a document from a
44
* different domain (last version tested: 2.0.4).
45
*
46
* @param {goog.net.xpc.CrossPageChannel} channel The channel this
47
* transport belongs to.
48
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding
49
* the correct window.
50
* @constructor
51
* @extends {goog.net.xpc.Transport}
52
* @final
53
*/
54
goog.net.xpc.IframePollingTransport = function(channel, opt_domHelper) {
55
goog.net.xpc.IframePollingTransport.base(this, 'constructor', opt_domHelper);
56
57
/**
58
* The channel this transport belongs to.
59
* @type {goog.net.xpc.CrossPageChannel}
60
* @private
61
*/
62
this.channel_ = channel;
63
64
/**
65
* The URI used to send messages.
66
* @type {string}
67
* @private
68
*/
69
this.sendUri_ =
70
this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_POLL_URI];
71
72
/**
73
* The URI which is polled for incoming messages.
74
* @type {string}
75
* @private
76
*/
77
this.rcvUri_ =
78
this.channel_.getConfig()[goog.net.xpc.CfgFields.LOCAL_POLL_URI];
79
80
/**
81
* The queue to hold messages which can't be sent immediately.
82
* @type {Array<string>}
83
* @private
84
*/
85
this.sendQueue_ = [];
86
};
87
goog.inherits(goog.net.xpc.IframePollingTransport, goog.net.xpc.Transport);
88
89
90
/**
91
* The number of times the inner frame will check for evidence of the outer
92
* frame before it tries its reconnection sequence. These occur at 100ms
93
* intervals, making this an effective max waiting period of 500ms.
94
* @type {number}
95
* @private
96
*/
97
goog.net.xpc.IframePollingTransport.prototype.pollsBeforeReconnect_ = 5;
98
99
100
/**
101
* The transport type.
102
* @type {number}
103
* @protected
104
* @override
105
*/
106
goog.net.xpc.IframePollingTransport.prototype.transportType =
107
goog.net.xpc.TransportTypes.IFRAME_POLLING;
108
109
110
/**
111
* Sequence counter.
112
* @type {number}
113
* @private
114
*/
115
goog.net.xpc.IframePollingTransport.prototype.sequence_ = 0;
116
117
118
/**
119
* Flag indicating whether we are waiting for an acknoledgement.
120
* @type {boolean}
121
* @private
122
*/
123
goog.net.xpc.IframePollingTransport.prototype.waitForAck_ = false;
124
125
126
/**
127
* Flag indicating if channel has been initialized.
128
* @type {boolean}
129
* @private
130
*/
131
goog.net.xpc.IframePollingTransport.prototype.initialized_ = false;
132
133
134
/**
135
* Reconnection iframe created by inner peer.
136
* @type {Element}
137
* @private
138
*/
139
goog.net.xpc.IframePollingTransport.prototype.reconnectFrame_ = null;
140
141
142
/** @private {goog.net.xpc.IframePollingTransport.Receiver} */
143
goog.net.xpc.IframePollingTransport.prototype.ackReceiver_;
144
145
146
/** @private {goog.net.xpc.IframePollingTransport.Sender} */
147
goog.net.xpc.IframePollingTransport.prototype.ackSender_;
148
149
150
/** @private */
151
goog.net.xpc.IframePollingTransport.prototype.ackIframeElm_;
152
153
154
/** @private */
155
goog.net.xpc.IframePollingTransport.prototype.ackWinObj_;
156
157
158
/** @private {!Function|undefined} */
159
goog.net.xpc.IframePollingTransport.prototype.checkLocalFramesPresentCb_;
160
161
162
/** @private */
163
goog.net.xpc.IframePollingTransport.prototype.deliveryQueue_;
164
165
166
/** @private */
167
goog.net.xpc.IframePollingTransport.prototype.msgIframeElm_;
168
169
170
/** @private */
171
goog.net.xpc.IframePollingTransport.prototype.msgReceiver_;
172
173
174
/** @private */
175
goog.net.xpc.IframePollingTransport.prototype.msgSender_;
176
177
178
/** @private */
179
goog.net.xpc.IframePollingTransport.prototype.msgWinObj_;
180
181
182
/** @private */
183
goog.net.xpc.IframePollingTransport.prototype.rcvdConnectionSetupAck_;
184
185
186
/** @private */
187
goog.net.xpc.IframePollingTransport.prototype.sentConnectionSetupAck_;
188
189
190
/** @private */
191
goog.net.xpc.IframePollingTransport.prototype.parts_;
192
193
194
/**
195
* The string used to prefix all iframe names and IDs.
196
* @type {string}
197
*/
198
goog.net.xpc.IframePollingTransport.IFRAME_PREFIX = 'googlexpc';
199
200
201
/**
202
* Returns the name/ID of the message frame.
203
* @return {string} Name of message frame.
204
* @private
205
*/
206
goog.net.xpc.IframePollingTransport.prototype.getMsgFrameName_ = function() {
207
return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' +
208
this.channel_.name + '_msg';
209
};
210
211
212
/**
213
* Returns the name/ID of the ack frame.
214
* @return {string} Name of ack frame.
215
* @private
216
*/
217
goog.net.xpc.IframePollingTransport.prototype.getAckFrameName_ = function() {
218
return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' +
219
this.channel_.name + '_ack';
220
};
221
222
223
/**
224
* Determines whether the channel is still available. The channel is
225
* unavailable if the transport was disposed or the peer is no longer
226
* available.
227
* @return {boolean} Whether the channel is available.
228
*/
229
goog.net.xpc.IframePollingTransport.prototype.isChannelAvailable = function() {
230
return !this.isDisposed() && this.channel_.isPeerAvailable();
231
};
232
233
234
/**
235
* Safely retrieves the frames from the peer window. If an error is thrown
236
* (e.g. the window is closing) an empty frame object is returned.
237
* @return {!Object<string|number, !Window>} The frames from the peer window.
238
* @private
239
*/
240
goog.net.xpc.IframePollingTransport.prototype.getPeerFrames_ = function() {
241
try {
242
if (this.isChannelAvailable()) {
243
return this.channel_.getPeerWindowObject().frames || {};
244
}
245
} catch (e) {
246
// An error may be thrown if the window is closing.
247
goog.log.fine(goog.net.xpc.logger, 'error retrieving peer frames');
248
}
249
return {};
250
};
251
252
253
/**
254
* Safely retrieves the peer frame with the specified name.
255
* @param {string} frameName The name of the peer frame to retrieve.
256
* @return {!Window} The peer frame with the specified name.
257
* @private
258
*/
259
goog.net.xpc.IframePollingTransport.prototype.getPeerFrame_ = function(
260
frameName) {
261
return this.getPeerFrames_()[frameName];
262
};
263
264
265
/**
266
* Connects this transport.
267
* @override
268
*/
269
goog.net.xpc.IframePollingTransport.prototype.connect = function() {
270
if (!this.isChannelAvailable()) {
271
// When the channel is unavailable there is no peer to poll so stop trying
272
// to connect.
273
return;
274
}
275
276
goog.log.fine(goog.net.xpc.logger, 'transport connect called');
277
if (!this.initialized_) {
278
goog.log.fine(goog.net.xpc.logger, 'initializing...');
279
this.constructSenderFrames_();
280
this.initialized_ = true;
281
}
282
this.checkForeignFramesReady_();
283
};
284
285
286
/**
287
* Creates the iframes which are used to send messages (and acknowledgements)
288
* to the peer. Sender iframes contain a document from a different origin and
289
* therefore their content can't be accessed.
290
* @private
291
*/
292
goog.net.xpc.IframePollingTransport.prototype.constructSenderFrames_ =
293
function() {
294
var name = this.getMsgFrameName_();
295
this.msgIframeElm_ = this.constructSenderFrame_(name);
296
this.msgWinObj_ = this.getWindow().frames[name];
297
298
name = this.getAckFrameName_();
299
this.ackIframeElm_ = this.constructSenderFrame_(name);
300
this.ackWinObj_ = this.getWindow().frames[name];
301
};
302
303
304
/**
305
* Constructs a sending frame the the given id.
306
* @param {string} id The id.
307
* @return {!Element} The constructed frame.
308
* @private
309
*/
310
goog.net.xpc.IframePollingTransport.prototype.constructSenderFrame_ = function(
311
id) {
312
goog.log.log(
313
goog.net.xpc.logger, goog.log.Level.FINEST,
314
'constructing sender frame: ' + id);
315
var ifr = goog.dom.createElement(goog.dom.TagName.IFRAME);
316
var s = ifr.style;
317
s.position = 'absolute';
318
s.top = '-10px';
319
s.left = '10px';
320
s.width = '1px';
321
s.height = '1px';
322
ifr.id = ifr.name = id;
323
ifr.src = this.sendUri_ + '#INITIAL';
324
this.getWindow().document.body.appendChild(ifr);
325
return ifr;
326
};
327
328
329
/**
330
* The protocol for reconnecting is for the inner frame to change channel
331
* names, and then communicate the new channel name to the outer peer.
332
* The outer peer looks in a predefined location for the channel name
333
* upate. It is important to use a completely new channel name, as this
334
* will ensure that all messaging iframes are not in the bfcache.
335
* Otherwise, Safari may pollute the history when modifying the location
336
* of bfcached iframes.
337
* @private
338
*/
339
goog.net.xpc.IframePollingTransport.prototype.maybeInnerPeerReconnect_ =
340
function() {
341
// Reconnection has been found to not function on some browsers (eg IE7), so
342
// it's important that the mechanism only be triggered as a last resort. As
343
// such, we poll a number of times to find the outer iframe before triggering
344
// it.
345
if (this.reconnectFrame_ || this.pollsBeforeReconnect_-- > 0) {
346
return;
347
}
348
349
goog.log.log(
350
goog.net.xpc.logger, goog.log.Level.FINEST,
351
'Inner peer reconnect triggered.');
352
this.channel_.updateChannelNameAndCatalog(goog.net.xpc.getRandomString(10));
353
goog.log.log(
354
goog.net.xpc.logger, goog.log.Level.FINEST,
355
'switching channels: ' + this.channel_.name);
356
this.deconstructSenderFrames_();
357
this.initialized_ = false;
358
// Communicate new channel name to outer peer.
359
this.reconnectFrame_ = this.constructSenderFrame_(
360
goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_reconnect_' +
361
this.channel_.name);
362
};
363
364
365
/**
366
* Scans inner peer for a reconnect message, which will be used to update
367
* the outer peer's channel name. If a reconnect message is found, the
368
* sender frames will be cleaned up to make way for the new sender frames.
369
* Only called by the outer peer.
370
* @private
371
*/
372
goog.net.xpc.IframePollingTransport.prototype.outerPeerReconnect_ = function() {
373
goog.log.log(
374
goog.net.xpc.logger, goog.log.Level.FINEST, 'outerPeerReconnect called');
375
var frames = this.getPeerFrames_();
376
var length = frames.length;
377
for (var i = 0; i < length; i++) {
378
var frameName;
379
try {
380
if (frames[i] && frames[i].name) {
381
frameName = frames[i].name;
382
}
383
} catch (e) {
384
// Do nothing.
385
}
386
if (!frameName) {
387
continue;
388
}
389
var message = frameName.split('_');
390
if (message.length == 3 &&
391
message[0] == goog.net.xpc.IframePollingTransport.IFRAME_PREFIX &&
392
message[1] == 'reconnect') {
393
// This is a legitimate reconnect message from the peer. Start using
394
// the peer provided channel name, and start a connection over from
395
// scratch.
396
this.channel_.name = message[2];
397
this.deconstructSenderFrames_();
398
this.initialized_ = false;
399
break;
400
}
401
}
402
};
403
404
405
/**
406
* Cleans up the existing sender frames owned by this peer. Only called by
407
* the outer peer.
408
* @private
409
*/
410
goog.net.xpc.IframePollingTransport.prototype.deconstructSenderFrames_ =
411
function() {
412
goog.log.log(
413
goog.net.xpc.logger, goog.log.Level.FINEST,
414
'deconstructSenderFrames called');
415
if (this.msgIframeElm_) {
416
this.msgIframeElm_.parentNode.removeChild(this.msgIframeElm_);
417
this.msgIframeElm_ = null;
418
this.msgWinObj_ = null;
419
}
420
if (this.ackIframeElm_) {
421
this.ackIframeElm_.parentNode.removeChild(this.ackIframeElm_);
422
this.ackIframeElm_ = null;
423
this.ackWinObj_ = null;
424
}
425
};
426
427
428
/**
429
* Checks if the frames in the peer's page are ready. These contain a
430
* document from the own domain and are the ones messages are received through.
431
* @private
432
*/
433
goog.net.xpc.IframePollingTransport.prototype.checkForeignFramesReady_ =
434
function() {
435
// check if the connected iframe ready
436
if (!(this.isRcvFrameReady_(this.getMsgFrameName_()) &&
437
this.isRcvFrameReady_(this.getAckFrameName_()))) {
438
goog.log.log(
439
goog.net.xpc.logger, goog.log.Level.FINEST,
440
'foreign frames not (yet) present');
441
442
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.INNER) {
443
// The outer peer might need a short time to get its frames ready, as
444
// CrossPageChannel prevents them from getting created until the inner
445
// peer's frame has thrown its loaded event. This method is a noop for
446
// the first few times it's called, and then allows the reconnection
447
// sequence to begin.
448
this.maybeInnerPeerReconnect_();
449
} else if (
450
this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER) {
451
// The inner peer is either not loaded yet, or the receiving
452
// frames are simply missing. Since we cannot discern the two cases, we
453
// should scan for a reconnect message from the inner peer.
454
this.outerPeerReconnect_();
455
}
456
457
// start a timer to check again
458
this.getWindow().setTimeout(goog.bind(this.connect, this), 100);
459
} else {
460
goog.log.fine(goog.net.xpc.logger, 'foreign frames present');
461
462
// Create receivers.
463
this.msgReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver(
464
this, this.getPeerFrame_(this.getMsgFrameName_()),
465
goog.bind(this.processIncomingMsg, this));
466
this.ackReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver(
467
this, this.getPeerFrame_(this.getAckFrameName_()),
468
goog.bind(this.processIncomingAck, this));
469
470
this.checkLocalFramesPresent_();
471
}
472
};
473
474
475
/**
476
* Checks if the receiving frame is ready.
477
* @param {string} frameName Which receiving frame to check.
478
* @return {boolean} Whether the receiving frame is ready.
479
* @private
480
*/
481
goog.net.xpc.IframePollingTransport.prototype.isRcvFrameReady_ = function(
482
frameName) {
483
goog.log.log(
484
goog.net.xpc.logger, goog.log.Level.FINEST,
485
'checking for receive frame: ' + frameName);
486
487
try {
488
var winObj = this.getPeerFrame_(frameName);
489
if (!winObj || winObj.location.href.indexOf(this.rcvUri_) != 0) {
490
return false;
491
}
492
} catch (e) {
493
return false;
494
}
495
return true;
496
};
497
498
499
/**
500
* Checks if the iframes created in the own document are ready.
501
* @private
502
*/
503
goog.net.xpc.IframePollingTransport.prototype.checkLocalFramesPresent_ =
504
function() {
505
506
// Are the sender frames ready?
507
// These contain a document from the peer's domain, therefore we can only
508
// check if the frame itself is present.
509
var frames = this.getPeerFrames_();
510
if (!(frames[this.getAckFrameName_()] && frames[this.getMsgFrameName_()])) {
511
// start a timer to check again
512
if (!this.checkLocalFramesPresentCb_) {
513
this.checkLocalFramesPresentCb_ =
514
goog.bind(this.checkLocalFramesPresent_, this);
515
}
516
this.getWindow().setTimeout(this.checkLocalFramesPresentCb_, 100);
517
goog.log.fine(goog.net.xpc.logger, 'local frames not (yet) present');
518
} else {
519
// Create senders.
520
this.msgSender_ = new goog.net.xpc.IframePollingTransport.Sender(
521
this.sendUri_, this.msgWinObj_);
522
this.ackSender_ = new goog.net.xpc.IframePollingTransport.Sender(
523
this.sendUri_, this.ackWinObj_);
524
525
goog.log.fine(goog.net.xpc.logger, 'local frames ready');
526
527
this.getWindow().setTimeout(goog.bind(function() {
528
this.msgSender_.send(goog.net.xpc.SETUP);
529
this.waitForAck_ = true;
530
goog.log.fine(goog.net.xpc.logger, 'SETUP sent');
531
}, this), 100);
532
}
533
};
534
535
536
/**
537
* Check if connection is ready.
538
* @private
539
*/
540
goog.net.xpc.IframePollingTransport.prototype.checkIfConnected_ = function() {
541
if (this.sentConnectionSetupAck_ && this.rcvdConnectionSetupAck_) {
542
this.channel_.notifyConnected();
543
544
if (this.deliveryQueue_) {
545
goog.log.fine(
546
goog.net.xpc.logger, 'delivering queued messages ' +
547
'(' + this.deliveryQueue_.length + ')');
548
549
for (var i = 0, m; i < this.deliveryQueue_.length; i++) {
550
m = this.deliveryQueue_[i];
551
this.channel_.xpcDeliver(m.service, m.payload);
552
}
553
delete this.deliveryQueue_;
554
}
555
} else {
556
goog.log.log(
557
goog.net.xpc.logger, goog.log.Level.FINEST, 'checking if connected: ' +
558
'ack sent:' + this.sentConnectionSetupAck_ + ', ack rcvd: ' +
559
this.rcvdConnectionSetupAck_);
560
}
561
};
562
563
564
/**
565
* Processes an incoming message.
566
* @param {string} raw The complete received string.
567
*/
568
goog.net.xpc.IframePollingTransport.prototype.processIncomingMsg = function(
569
raw) {
570
goog.log.log(
571
goog.net.xpc.logger, goog.log.Level.FINEST, 'msg received: ' + raw);
572
573
if (raw == goog.net.xpc.SETUP) {
574
if (!this.ackSender_) {
575
// Got SETUP msg, but we can't send an ack.
576
return;
577
}
578
579
this.ackSender_.send(goog.net.xpc.SETUP_ACK_);
580
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'SETUP_ACK sent');
581
582
this.sentConnectionSetupAck_ = true;
583
this.checkIfConnected_();
584
585
} else if (this.channel_.isConnected() || this.sentConnectionSetupAck_) {
586
var pos = raw.indexOf('|');
587
var head = raw.substring(0, pos);
588
var frame = raw.substring(pos + 1);
589
590
// check if it is a framed message
591
pos = head.indexOf(',');
592
if (pos == -1) {
593
var seq = head;
594
// send acknowledgement
595
this.ackSender_.send('ACK:' + seq);
596
this.deliverPayload_(frame);
597
} else {
598
var seq = head.substring(0, pos);
599
// send acknowledgement
600
this.ackSender_.send('ACK:' + seq);
601
602
var partInfo = head.substring(pos + 1).split('/');
603
var part0 = parseInt(partInfo[0], 10);
604
var part1 = parseInt(partInfo[1], 10);
605
// create an array to accumulate the parts if this is the
606
// first frame of a message
607
if (part0 == 1) {
608
this.parts_ = [];
609
}
610
this.parts_.push(frame);
611
// deliver the message if this was the last frame of a message
612
if (part0 == part1) {
613
this.deliverPayload_(this.parts_.join(''));
614
delete this.parts_;
615
}
616
}
617
} else {
618
goog.log.warning(
619
goog.net.xpc.logger, 'received msg, but channel is not connected');
620
}
621
};
622
623
624
/**
625
* Process an incoming acknowdedgement.
626
* @param {string} msgStr The incoming ack string to process.
627
*/
628
goog.net.xpc.IframePollingTransport.prototype.processIncomingAck = function(
629
msgStr) {
630
goog.log.log(
631
goog.net.xpc.logger, goog.log.Level.FINEST, 'ack received: ' + msgStr);
632
633
if (msgStr == goog.net.xpc.SETUP_ACK_) {
634
this.waitForAck_ = false;
635
this.rcvdConnectionSetupAck_ = true;
636
// send the next frame
637
this.checkIfConnected_();
638
639
} else if (this.channel_.isConnected()) {
640
if (!this.waitForAck_) {
641
goog.log.warning(goog.net.xpc.logger, 'got unexpected ack');
642
return;
643
}
644
645
var seq = parseInt(msgStr.split(':')[1], 10);
646
if (seq == this.sequence_) {
647
this.waitForAck_ = false;
648
this.sendNextFrame_();
649
} else {
650
goog.log.warning(goog.net.xpc.logger, 'got ack with wrong sequence');
651
}
652
} else {
653
goog.log.warning(
654
goog.net.xpc.logger, 'received ack, but channel not connected');
655
}
656
};
657
658
659
/**
660
* Sends a frame (message part).
661
* @private
662
*/
663
goog.net.xpc.IframePollingTransport.prototype.sendNextFrame_ = function() {
664
// do nothing if we are waiting for an acknowledgement or the
665
// queue is emtpy
666
if (this.waitForAck_ || !this.sendQueue_.length) {
667
return;
668
}
669
670
var s = this.sendQueue_.shift();
671
++this.sequence_;
672
this.msgSender_.send(this.sequence_ + s);
673
goog.log.log(
674
goog.net.xpc.logger, goog.log.Level.FINEST,
675
'msg sent: ' + this.sequence_ + s);
676
677
678
this.waitForAck_ = true;
679
};
680
681
682
/**
683
* Delivers a message.
684
* @param {string} s The complete message string ("<service_name>:<payload>").
685
* @private
686
*/
687
goog.net.xpc.IframePollingTransport.prototype.deliverPayload_ = function(s) {
688
// determine the service name and the payload
689
var pos = s.indexOf(':');
690
var service = s.substr(0, pos);
691
var payload = s.substring(pos + 1);
692
693
// deliver the message
694
if (!this.channel_.isConnected()) {
695
// as valid messages can come in before a SETUP_ACK has
696
// been received (because subchannels for msgs and acks are independent),
697
// delay delivery of early messages until after 'connect'-event
698
(this.deliveryQueue_ || (this.deliveryQueue_ = [
699
])).push({service: service, payload: payload});
700
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'queued delivery');
701
} else {
702
this.channel_.xpcDeliver(service, payload);
703
}
704
};
705
706
707
// ---- send message ----
708
709
710
/**
711
* Maximal frame length.
712
* @type {number}
713
* @private
714
*/
715
goog.net.xpc.IframePollingTransport.prototype.MAX_FRAME_LENGTH_ = 3800;
716
717
718
/**
719
* Sends a message. Splits it in multiple frames if too long (exceeds IE's
720
* URL-length maximum.
721
* Wireformat: `<seq>[,<frame_no>/<#frames>]|<frame_content>`
722
*
723
* @param {string} service Name of service this the message has to be delivered.
724
* @param {string} payload The message content.
725
* @override
726
*/
727
goog.net.xpc.IframePollingTransport.prototype.send = function(
728
service, payload) {
729
var frame = service + ':' + payload;
730
// put in queue
731
if (!goog.userAgent.IE || payload.length <= this.MAX_FRAME_LENGTH_) {
732
this.sendQueue_.push('|' + frame);
733
} else {
734
var l = payload.length;
735
var num = Math.ceil(l / this.MAX_FRAME_LENGTH_); // number of frames
736
var pos = 0;
737
var i = 1;
738
while (pos < l) {
739
this.sendQueue_.push(
740
',' + i + '/' + num + '|' +
741
frame.substr(pos, this.MAX_FRAME_LENGTH_));
742
i++;
743
pos += this.MAX_FRAME_LENGTH_;
744
}
745
}
746
this.sendNextFrame_();
747
};
748
749
750
/** @override */
751
goog.net.xpc.IframePollingTransport.prototype.disposeInternal = function() {
752
goog.net.xpc.IframePollingTransport.base(this, 'disposeInternal');
753
754
var receivers = goog.net.xpc.IframePollingTransport.receivers_;
755
goog.array.remove(receivers, this.msgReceiver_);
756
goog.array.remove(receivers, this.ackReceiver_);
757
this.msgReceiver_ = this.ackReceiver_ = null;
758
759
goog.dom.removeNode(this.msgIframeElm_);
760
goog.dom.removeNode(this.ackIframeElm_);
761
this.msgIframeElm_ = this.ackIframeElm_ = null;
762
this.msgWinObj_ = this.ackWinObj_ = null;
763
};
764
765
766
/**
767
* Array holding all Receiver-instances.
768
* @type {Array<goog.net.xpc.IframePollingTransport.Receiver>}
769
* @private
770
*/
771
goog.net.xpc.IframePollingTransport.receivers_ = [];
772
773
774
/**
775
* Short polling interval.
776
* @type {number}
777
* @private
778
*/
779
goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_ = 10;
780
781
782
/**
783
* Long polling interval.
784
* @type {number}
785
* @private
786
*/
787
goog.net.xpc.IframePollingTransport.TIME_POLL_LONG_ = 100;
788
789
790
/**
791
* Period how long to use TIME_POLL_SHORT_ before raising polling-interval
792
* to TIME_POLL_LONG_ after an activity.
793
* @type {number}
794
* @private
795
*/
796
goog.net.xpc.IframePollingTransport.TIME_SHORT_POLL_AFTER_ACTIVITY_ = 1000;
797
798
799
/**
800
* Polls all receivers.
801
* @private
802
*/
803
goog.net.xpc.IframePollingTransport.receive_ = function() {
804
var receivers = goog.net.xpc.IframePollingTransport.receivers_;
805
var receiver;
806
var rcvd = false;
807
808
809
try {
810
for (var i = 0; receiver = receivers[i]; i++) {
811
rcvd = rcvd || receiver.receive();
812
}
813
} catch (e) {
814
goog.log.info(goog.net.xpc.logger, 'receive_() failed: ' + e);
815
816
// Notify the channel that the transport had an error.
817
receiver.transport_.channel_.notifyTransportError();
818
819
// notifyTransportError() closes the channel and disposes the transport.
820
// If there are no other channels present, this.receivers_ will now be empty
821
// and there is no need to keep polling.
822
if (!receivers.length) {
823
return;
824
}
825
}
826
827
var now = goog.now();
828
if (rcvd) {
829
goog.net.xpc.IframePollingTransport.lastActivity_ = now;
830
}
831
832
// Schedule next check.
833
var t = now - goog.net.xpc.IframePollingTransport.lastActivity_ <
834
goog.net.xpc.IframePollingTransport.TIME_SHORT_POLL_AFTER_ACTIVITY_ ?
835
goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_ :
836
goog.net.xpc.IframePollingTransport.TIME_POLL_LONG_;
837
goog.net.xpc.IframePollingTransport.rcvTimer_ =
838
window.setTimeout(goog.net.xpc.IframePollingTransport.receiveCb_, t);
839
};
840
841
842
/**
843
* Callback that wraps receive_ to be used in timers.
844
* @type {Function}
845
* @private
846
*/
847
goog.net.xpc.IframePollingTransport.receiveCb_ = goog.bind(
848
goog.net.xpc.IframePollingTransport.receive_,
849
goog.net.xpc.IframePollingTransport);
850
851
852
/**
853
* Starts the polling loop.
854
* @private
855
*/
856
goog.net.xpc.IframePollingTransport.startRcvTimer_ = function() {
857
goog.log.fine(goog.net.xpc.logger, 'starting receive-timer');
858
goog.net.xpc.IframePollingTransport.lastActivity_ = goog.now();
859
if (goog.net.xpc.IframePollingTransport.rcvTimer_) {
860
window.clearTimeout(goog.net.xpc.IframePollingTransport.rcvTimer_);
861
}
862
goog.net.xpc.IframePollingTransport.rcvTimer_ = window.setTimeout(
863
goog.net.xpc.IframePollingTransport.receiveCb_,
864
goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_);
865
};
866
867
868
869
/**
870
* goog.net.xpc.IframePollingTransport.Sender
871
*
872
* Utility class to send message-parts to a document from a different origin.
873
*
874
* @constructor
875
* @param {string} url The url the other document will use for polling. Must
876
* be an http:// or https:// URL.
877
* @param {Object} windowObj The frame used for sending information to.
878
* @final
879
*/
880
goog.net.xpc.IframePollingTransport.Sender = function(url, windowObj) {
881
// This class is instantiated from goog.net.xpc.IframePollingTransport, which
882
// takes its URLs from a goog.net.xpc.CrossPageChannel, which in turns
883
// sanitizes them. However, since this class can be instantiated from
884
// elsewhere than IframePollingTransport the url needs to be sanitized
885
// here too.
886
if (!/^https?:\/\//.test(url)) {
887
throw Error('URL ' + url + ' is invalid');
888
}
889
890
/**
891
* The URI used to sending messages.
892
* @type {string}
893
* @private
894
*/
895
this.sanitizedSendUri_ = url;
896
897
/**
898
* The window object of the iframe used to send messages.
899
* The script instantiating the Sender won't have access to
900
* the content of sendFrame_.
901
* @type {Window}
902
* @private
903
*/
904
this.sendFrame_ = /** @type {Window} */ (windowObj);
905
906
/**
907
* Cycle counter (used to make sure that sending two identical messages sent
908
* in direct succession can be recognized as such by the receiver).
909
* @type {number}
910
* @private
911
*/
912
this.cycle_ = 0;
913
};
914
915
916
/**
917
* Sends a message-part (frame) to the peer.
918
* The message-part is encoded and put in the fragment identifier
919
* of the URL used for sending (and belongs to the origin/domain of the peer).
920
* @param {string} payload The message to send.
921
*/
922
goog.net.xpc.IframePollingTransport.Sender.prototype.send = function(payload) {
923
this.cycle_ = ++this.cycle_ % 2;
924
925
var url =
926
this.sanitizedSendUri_ + '#' + this.cycle_ + encodeURIComponent(payload);
927
928
// TODO(user) Find out if try/catch is still needed
929
930
try {
931
// safari doesn't allow to call location.replace()
932
if (goog.userAgent.WEBKIT) {
933
this.sendFrame_.location.href = url;
934
} else {
935
this.sendFrame_.location.replace(url);
936
}
937
} catch (e) {
938
goog.log.error(goog.net.xpc.logger, 'sending failed', e);
939
}
940
941
// Restart receiver timer on short polling interval, to support use-cases
942
// where we need to capture responses quickly.
943
goog.net.xpc.IframePollingTransport.startRcvTimer_();
944
};
945
946
947
948
/**
949
* goog.net.xpc.IframePollingTransport.Receiver
950
*
951
* @constructor
952
* @param {goog.net.xpc.IframePollingTransport} transport The transport to
953
* receive from.
954
* @param {Object} windowObj The window-object to poll for location-changes.
955
* @param {Function} callback The callback-function to be called when
956
* location has changed.
957
* @final
958
*/
959
goog.net.xpc.IframePollingTransport.Receiver = function(
960
transport, windowObj, callback) {
961
/**
962
* The transport to receive from.
963
* @type {goog.net.xpc.IframePollingTransport}
964
* @private
965
*/
966
this.transport_ = transport;
967
this.rcvFrame_ = windowObj;
968
969
this.cb_ = callback;
970
this.currentLoc_ = this.rcvFrame_.location.href.split('#')[0] + '#INITIAL';
971
972
goog.net.xpc.IframePollingTransport.receivers_.push(this);
973
goog.net.xpc.IframePollingTransport.startRcvTimer_();
974
};
975
976
977
/**
978
* Polls the location of the receiver-frame for changes.
979
* @return {boolean} Whether a change has been detected.
980
*/
981
goog.net.xpc.IframePollingTransport.Receiver.prototype.receive = function() {
982
var loc = this.rcvFrame_.location.href;
983
984
if (loc != this.currentLoc_) {
985
this.currentLoc_ = loc;
986
var payload = loc.split('#')[1];
987
if (payload) {
988
payload = payload.substr(1); // discard first character (cycle)
989
this.cb_(decodeURIComponent(payload));
990
}
991
return true;
992
} else {
993
return false;
994
}
995
};
996
997