Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/net/xpc/crosspagechannel.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 Provides the class CrossPageChannel, the main class in
17
* goog.net.xpc.
18
*
19
* @see ../../demos/xpc/index.html
20
*/
21
22
goog.provide('goog.net.xpc.CrossPageChannel');
23
24
goog.require('goog.Uri');
25
goog.require('goog.async.Deferred');
26
goog.require('goog.async.Delay');
27
goog.require('goog.dispose');
28
goog.require('goog.dom');
29
goog.require('goog.dom.TagName');
30
goog.require('goog.events');
31
goog.require('goog.events.EventHandler');
32
goog.require('goog.events.EventType');
33
goog.require('goog.json');
34
goog.require('goog.log');
35
goog.require('goog.messaging.AbstractChannel');
36
goog.require('goog.net.xpc');
37
goog.require('goog.net.xpc.CfgFields');
38
goog.require('goog.net.xpc.ChannelStates');
39
goog.require('goog.net.xpc.CrossPageChannelRole');
40
goog.require('goog.net.xpc.DirectTransport');
41
goog.require('goog.net.xpc.FrameElementMethodTransport');
42
goog.require('goog.net.xpc.IframePollingTransport');
43
goog.require('goog.net.xpc.IframeRelayTransport');
44
goog.require('goog.net.xpc.NativeMessagingTransport');
45
goog.require('goog.net.xpc.NixTransport');
46
goog.require('goog.net.xpc.TransportTypes');
47
goog.require('goog.net.xpc.UriCfgFields');
48
goog.require('goog.string');
49
goog.require('goog.uri.utils');
50
goog.require('goog.userAgent');
51
52
53
54
/**
55
* A communication channel between two documents from different domains.
56
* Provides asynchronous messaging.
57
*
58
* @param {Object} cfg Channel configuration object.
59
* @param {goog.dom.DomHelper=} opt_domHelper The optional dom helper to
60
* use for looking up elements in the dom.
61
* @constructor
62
* @extends {goog.messaging.AbstractChannel}
63
*/
64
goog.net.xpc.CrossPageChannel = function(cfg, opt_domHelper) {
65
goog.net.xpc.CrossPageChannel.base(this, 'constructor');
66
67
for (var i = 0, uriField; uriField = goog.net.xpc.UriCfgFields[i]; i++) {
68
if (uriField in cfg && !/^https?:\/\//.test(cfg[uriField])) {
69
throw Error('URI ' + cfg[uriField] + ' is invalid for field ' + uriField);
70
}
71
}
72
73
/**
74
* The configuration for this channel.
75
* @type {Object}
76
* @private
77
*/
78
this.cfg_ = cfg;
79
80
/**
81
* The name of the channel. Please use
82
* <code>updateChannelNameAndCatalog</code> to change this from the transports
83
* vs changing the property directly.
84
* @type {string}
85
*/
86
this.name = this.cfg_[goog.net.xpc.CfgFields.CHANNEL_NAME] ||
87
goog.net.xpc.getRandomString(10);
88
89
/**
90
* The dom helper to use for accessing the dom.
91
* @type {goog.dom.DomHelper}
92
* @private
93
*/
94
this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();
95
96
/**
97
* Collects deferred function calls which will be made once the connection
98
* has been fully set up.
99
* @type {!Array<function()>}
100
* @private
101
*/
102
this.deferredDeliveries_ = [];
103
104
/**
105
* An event handler used to listen for load events on peer iframes.
106
* @type {!goog.events.EventHandler<!goog.net.xpc.CrossPageChannel>}
107
* @private
108
*/
109
this.peerLoadHandler_ = new goog.events.EventHandler(this);
110
111
// If LOCAL_POLL_URI or PEER_POLL_URI is not available, try using
112
// robots.txt from that host.
113
cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] =
114
cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] ||
115
goog.uri.utils.getHost(this.domHelper_.getWindow().location.href) +
116
'/robots.txt';
117
// PEER_URI is sometimes undefined in tests.
118
cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =
119
cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] ||
120
goog.uri.utils.getHost(cfg[goog.net.xpc.CfgFields.PEER_URI] || '') +
121
'/robots.txt';
122
123
goog.net.xpc.channels[this.name] = this;
124
125
if (!goog.events.getListener(
126
window, goog.events.EventType.UNLOAD,
127
goog.net.xpc.CrossPageChannel.disposeAll_)) {
128
// Set listener to dispose all registered channels on page unload.
129
goog.events.listenOnce(
130
window, goog.events.EventType.UNLOAD,
131
goog.net.xpc.CrossPageChannel.disposeAll_);
132
}
133
134
goog.log.info(goog.net.xpc.logger, 'CrossPageChannel created: ' + this.name);
135
};
136
goog.inherits(goog.net.xpc.CrossPageChannel, goog.messaging.AbstractChannel);
137
138
139
/**
140
* Regexp for escaping service names.
141
* @type {RegExp}
142
* @private
143
*/
144
goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_ESCAPE_RE_ =
145
new RegExp('^%*' + goog.net.xpc.TRANSPORT_SERVICE_ + '$');
146
147
148
/**
149
* Regexp for unescaping service names.
150
* @type {RegExp}
151
* @private
152
*/
153
goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_UNESCAPE_RE_ =
154
new RegExp('^%+' + goog.net.xpc.TRANSPORT_SERVICE_ + '$');
155
156
157
/**
158
* A delay between the transport reporting as connected and the calling of the
159
* connection callback. Sometimes used to paper over timing vulnerabilities.
160
* @type {goog.async.Delay}
161
* @private
162
*/
163
goog.net.xpc.CrossPageChannel.prototype.connectionDelay_ = null;
164
165
166
/**
167
* A deferred which is set to non-null while a peer iframe is being created
168
* but has not yet thrown its load event, and which fires when that load event
169
* arrives.
170
* @type {goog.async.Deferred}
171
* @private
172
*/
173
goog.net.xpc.CrossPageChannel.prototype.peerWindowDeferred_ = null;
174
175
176
/**
177
* The transport.
178
* @type {goog.net.xpc.Transport?}
179
* @private
180
*/
181
goog.net.xpc.CrossPageChannel.prototype.transport_ = null;
182
183
184
/**
185
* The channel state.
186
* @type {number}
187
* @private
188
*/
189
goog.net.xpc.CrossPageChannel.prototype.state_ =
190
goog.net.xpc.ChannelStates.NOT_CONNECTED;
191
192
193
/**
194
* @override
195
* @return {boolean} Whether the channel is connected.
196
*/
197
goog.net.xpc.CrossPageChannel.prototype.isConnected = function() {
198
return this.state_ == goog.net.xpc.ChannelStates.CONNECTED;
199
};
200
201
202
/**
203
* Reference to the window-object of the peer page.
204
* @type {Object}
205
* @private
206
*/
207
goog.net.xpc.CrossPageChannel.prototype.peerWindowObject_ = null;
208
209
210
/**
211
* Reference to the iframe-element.
212
* @type {?HTMLIFrameElement}
213
* @private
214
*/
215
goog.net.xpc.CrossPageChannel.prototype.iframeElement_ = null;
216
217
218
/**
219
* Returns the configuration object for this channel.
220
* Package private. Do not call from outside goog.net.xpc.
221
*
222
* @return {Object} The configuration object for this channel.
223
*/
224
goog.net.xpc.CrossPageChannel.prototype.getConfig = function() {
225
return this.cfg_;
226
};
227
228
229
/**
230
* Returns a reference to the iframe-element.
231
* Package private. Do not call from outside goog.net.xpc.
232
*
233
* @return {?HTMLIFrameElement} A reference to the iframe-element.
234
*/
235
goog.net.xpc.CrossPageChannel.prototype.getIframeElement = function() {
236
return this.iframeElement_;
237
};
238
239
240
/**
241
* Sets the window object the foreign document resides in.
242
*
243
* @param {Object} peerWindowObject The window object of the peer.
244
*/
245
goog.net.xpc.CrossPageChannel.prototype.setPeerWindowObject = function(
246
peerWindowObject) {
247
this.peerWindowObject_ = peerWindowObject;
248
};
249
250
251
/**
252
* Returns the window object the foreign document resides in.
253
*
254
* @return {Object} The window object of the peer.
255
* @package
256
*/
257
goog.net.xpc.CrossPageChannel.prototype.getPeerWindowObject = function() {
258
return this.peerWindowObject_;
259
};
260
261
262
/**
263
* Determines whether the peer window is available (e.g. not closed).
264
*
265
* @return {boolean} Whether the peer window is available.
266
* @package
267
*/
268
goog.net.xpc.CrossPageChannel.prototype.isPeerAvailable = function() {
269
// NOTE(user): This check is not reliable in IE, where a document in an
270
// iframe does not get unloaded when removing the iframe element from the DOM.
271
// TODO(user): Find something that works in IE as well.
272
// NOTE(user): "!this.peerWindowObject_.closed" evaluates to 'false' in IE9
273
// sometimes even though typeof(this.peerWindowObject_.closed) is boolean and
274
// this.peerWindowObject_.closed evaluates to 'false'. Casting it to a Boolean
275
// results in sane evaluation. When this happens, it's in the inner iframe
276
// when querying its parent's 'closed' status. Note that this is a different
277
// case than mibuerge@'s note above.
278
try {
279
return !!this.peerWindowObject_ && !this.peerWindowObject_.closed;
280
} catch (e) {
281
// If the window is closing, an error may be thrown.
282
return false;
283
}
284
};
285
286
287
/**
288
* Determine which transport type to use for this channel / useragent.
289
* @return {goog.net.xpc.TransportTypes|undefined} The best transport type.
290
* @private
291
*/
292
goog.net.xpc.CrossPageChannel.prototype.determineTransportType_ = function() {
293
var transportType;
294
if (goog.isFunction(document.postMessage) ||
295
goog.isFunction(window.postMessage) ||
296
// IE8 supports window.postMessage, but
297
// typeof window.postMessage returns "object"
298
(goog.userAgent.IE && window.postMessage)) {
299
transportType = goog.net.xpc.TransportTypes.NATIVE_MESSAGING;
300
} else if (goog.userAgent.GECKO) {
301
transportType = goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD;
302
} else if (
303
goog.userAgent.IE && this.cfg_[goog.net.xpc.CfgFields.PEER_RELAY_URI]) {
304
transportType = goog.net.xpc.TransportTypes.IFRAME_RELAY;
305
} else if (goog.userAgent.IE && goog.net.xpc.NixTransport.isNixSupported()) {
306
transportType = goog.net.xpc.TransportTypes.NIX;
307
} else {
308
transportType = goog.net.xpc.TransportTypes.IFRAME_POLLING;
309
}
310
return transportType;
311
};
312
313
314
/**
315
* Creates the transport for this channel. Chooses from the available
316
* transport based on the user agent and the configuration.
317
* @private
318
*/
319
goog.net.xpc.CrossPageChannel.prototype.createTransport_ = function() {
320
// return, if the transport has already been created
321
if (this.transport_) {
322
return;
323
}
324
325
// TODO(user): Use goog.scope.
326
var CfgFields = goog.net.xpc.CfgFields;
327
328
if (!this.cfg_[CfgFields.TRANSPORT]) {
329
this.cfg_[CfgFields.TRANSPORT] = this.determineTransportType_();
330
}
331
332
switch (this.cfg_[CfgFields.TRANSPORT]) {
333
case goog.net.xpc.TransportTypes.NATIVE_MESSAGING:
334
var protocolVersion =
335
this.cfg_[CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION] || 2;
336
this.transport_ = new goog.net.xpc.NativeMessagingTransport(
337
this, this.cfg_[CfgFields.PEER_HOSTNAME], this.domHelper_,
338
!!this.cfg_[CfgFields.ONE_SIDED_HANDSHAKE], protocolVersion);
339
break;
340
case goog.net.xpc.TransportTypes.NIX:
341
this.transport_ = new goog.net.xpc.NixTransport(this, this.domHelper_);
342
break;
343
case goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD:
344
this.transport_ =
345
new goog.net.xpc.FrameElementMethodTransport(this, this.domHelper_);
346
break;
347
case goog.net.xpc.TransportTypes.IFRAME_RELAY:
348
this.transport_ =
349
new goog.net.xpc.IframeRelayTransport(this, this.domHelper_);
350
break;
351
case goog.net.xpc.TransportTypes.IFRAME_POLLING:
352
this.transport_ =
353
new goog.net.xpc.IframePollingTransport(this, this.domHelper_);
354
break;
355
case goog.net.xpc.TransportTypes.DIRECT:
356
if (this.peerWindowObject_ &&
357
goog.net.xpc.DirectTransport.isSupported(
358
/** @type {!Window} */ (this.peerWindowObject_))) {
359
this.transport_ =
360
new goog.net.xpc.DirectTransport(this, this.domHelper_);
361
} else {
362
goog.log.info(
363
goog.net.xpc.logger,
364
'DirectTransport not supported for this window, peer window in' +
365
' different security context or not set yet.');
366
}
367
break;
368
}
369
370
if (this.transport_) {
371
goog.log.info(
372
goog.net.xpc.logger, 'Transport created: ' + this.transport_.getName());
373
} else {
374
throw Error('CrossPageChannel: No suitable transport found!');
375
}
376
};
377
378
379
/**
380
* Returns the transport type in use for this channel.
381
* @return {number} Transport-type identifier.
382
*/
383
goog.net.xpc.CrossPageChannel.prototype.getTransportType = function() {
384
return this.transport_.getType();
385
};
386
387
388
/**
389
* Returns the tranport name in use for this channel.
390
* @return {string} The transport name.
391
*/
392
goog.net.xpc.CrossPageChannel.prototype.getTransportName = function() {
393
return this.transport_.getName();
394
};
395
396
397
/**
398
* @return {!Object} Configuration-object to be used by the peer to
399
* initialize the channel.
400
*/
401
goog.net.xpc.CrossPageChannel.prototype.getPeerConfiguration = function() {
402
var peerCfg = {};
403
peerCfg[goog.net.xpc.CfgFields.CHANNEL_NAME] = this.name;
404
peerCfg[goog.net.xpc.CfgFields.TRANSPORT] =
405
this.cfg_[goog.net.xpc.CfgFields.TRANSPORT];
406
peerCfg[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE] =
407
this.cfg_[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE];
408
409
if (this.cfg_[goog.net.xpc.CfgFields.LOCAL_RELAY_URI]) {
410
peerCfg[goog.net.xpc.CfgFields.PEER_RELAY_URI] =
411
this.cfg_[goog.net.xpc.CfgFields.LOCAL_RELAY_URI];
412
}
413
if (this.cfg_[goog.net.xpc.CfgFields.LOCAL_POLL_URI]) {
414
peerCfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =
415
this.cfg_[goog.net.xpc.CfgFields.LOCAL_POLL_URI];
416
}
417
if (this.cfg_[goog.net.xpc.CfgFields.PEER_POLL_URI]) {
418
peerCfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] =
419
this.cfg_[goog.net.xpc.CfgFields.PEER_POLL_URI];
420
}
421
var role = this.cfg_[goog.net.xpc.CfgFields.ROLE];
422
if (role) {
423
peerCfg[goog.net.xpc.CfgFields.ROLE] =
424
role == goog.net.xpc.CrossPageChannelRole.INNER ?
425
goog.net.xpc.CrossPageChannelRole.OUTER :
426
goog.net.xpc.CrossPageChannelRole.INNER;
427
}
428
429
return peerCfg;
430
};
431
432
433
/**
434
* Creates the iframe containing the peer page in a specified parent element.
435
* This method does not connect the channel, connect() still has to be called
436
* separately.
437
*
438
* @param {!Element} parentElm The container element the iframe is appended to.
439
* @param {Function=} opt_configureIframeCb If present, this function gets
440
* called with the iframe element as parameter to allow setting properties
441
* on it before it gets added to the DOM. If absent, the iframe's width and
442
* height are set to '100%'.
443
* @param {boolean=} opt_addCfgParam Whether to add the peer configuration as
444
* URL parameter (default: true).
445
* @return {!HTMLIFrameElement} The iframe element.
446
*/
447
goog.net.xpc.CrossPageChannel.prototype.createPeerIframe = function(
448
parentElm, opt_configureIframeCb, opt_addCfgParam) {
449
goog.log.info(goog.net.xpc.logger, 'createPeerIframe()');
450
451
var iframeId = this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID];
452
if (!iframeId) {
453
// Create a randomized ID for the iframe element to avoid
454
// bfcache-related issues.
455
iframeId = this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID] =
456
'xpcpeer' + goog.net.xpc.getRandomString(4);
457
}
458
459
// TODO(user) Opera creates a history-entry when creating an iframe
460
// programmatically as follows. Find a way which avoids this.
461
462
var iframeElm =
463
goog.dom.getDomHelper(parentElm).createElement(goog.dom.TagName.IFRAME);
464
iframeElm.id = iframeElm.name = iframeId;
465
if (opt_configureIframeCb) {
466
opt_configureIframeCb(iframeElm);
467
} else {
468
iframeElm.style.width = iframeElm.style.height = '100%';
469
}
470
471
this.cleanUpIncompleteConnection_();
472
this.peerWindowDeferred_ = new goog.async.Deferred(undefined, this);
473
var peerUri = this.getPeerUri(opt_addCfgParam);
474
this.peerLoadHandler_.listenOnceWithScope(
475
iframeElm, 'load', this.peerWindowDeferred_.callback, false,
476
this.peerWindowDeferred_);
477
478
if (goog.userAgent.GECKO || goog.userAgent.WEBKIT) {
479
// Appending the iframe in a timeout to avoid a weird fastback issue, which
480
// is present in Safari and Gecko.
481
window.setTimeout(goog.bind(function() {
482
parentElm.appendChild(iframeElm);
483
iframeElm.src = peerUri.toString();
484
goog.log.info(
485
goog.net.xpc.logger, 'peer iframe created (' + iframeId + ')');
486
}, this), 1);
487
} else {
488
iframeElm.src = peerUri.toString();
489
parentElm.appendChild(iframeElm);
490
goog.log.info(
491
goog.net.xpc.logger, 'peer iframe created (' + iframeId + ')');
492
}
493
494
return /** @type {!HTMLIFrameElement} */ (iframeElm);
495
};
496
497
498
/**
499
* Clean up after any incomplete attempt to establish and connect to a peer
500
* iframe.
501
* @private
502
*/
503
goog.net.xpc.CrossPageChannel.prototype.cleanUpIncompleteConnection_ =
504
function() {
505
if (this.peerWindowDeferred_) {
506
this.peerWindowDeferred_.cancel();
507
this.peerWindowDeferred_ = null;
508
}
509
this.deferredDeliveries_.length = 0;
510
this.peerLoadHandler_.removeAll();
511
};
512
513
514
/**
515
* Returns the peer URI, with an optional URL parameter for configuring the peer
516
* window.
517
*
518
* @param {boolean=} opt_addCfgParam Whether to add the peer configuration as
519
* URL parameter (default: true).
520
* @return {!goog.Uri} The peer URI.
521
*/
522
goog.net.xpc.CrossPageChannel.prototype.getPeerUri = function(opt_addCfgParam) {
523
var peerUri = this.cfg_[goog.net.xpc.CfgFields.PEER_URI];
524
if (goog.isString(peerUri)) {
525
peerUri = this.cfg_[goog.net.xpc.CfgFields.PEER_URI] =
526
new goog.Uri(peerUri);
527
}
528
529
// Add the channel configuration used by the peer as URL parameter.
530
if (opt_addCfgParam !== false) {
531
peerUri.setParameterValue(
532
'xpc', goog.json.serialize(this.getPeerConfiguration()));
533
}
534
535
return peerUri;
536
};
537
538
539
/**
540
* Initiates connecting the channel. When this method is called, all the
541
* information needed to connect the channel has to be available.
542
*
543
* @override
544
* @param {Function=} opt_connectCb The function to be called when the
545
* channel has been connected and is ready to be used.
546
*/
547
goog.net.xpc.CrossPageChannel.prototype.connect = function(opt_connectCb) {
548
this.connectCb_ = opt_connectCb || goog.nullFunction;
549
550
// If this channel was previously closed, transition back to the NOT_CONNECTED
551
// state to ensure that the connection can proceed (xpcDeliver blocks
552
// transport messages while the connection state is CLOSED).
553
if (this.state_ == goog.net.xpc.ChannelStates.CLOSED) {
554
this.state_ = goog.net.xpc.ChannelStates.NOT_CONNECTED;
555
}
556
557
// If we know of a peer window whose creation has been requested but is not
558
// complete, peerWindowDeferred_ will be non-null, and we should block on it.
559
if (this.peerWindowDeferred_) {
560
this.peerWindowDeferred_.addCallback(this.continueConnection_);
561
} else {
562
this.continueConnection_();
563
}
564
};
565
566
567
/**
568
* Continues the connection process once we're as sure as we can be that the
569
* peer iframe has been created.
570
* @private
571
*/
572
goog.net.xpc.CrossPageChannel.prototype.continueConnection_ = function() {
573
goog.log.info(goog.net.xpc.logger, 'continueConnection_()');
574
this.peerWindowDeferred_ = null;
575
if (this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]) {
576
this.iframeElement_ = /** @type {?HTMLIFrameElement} */ (
577
this.domHelper_.getElement(
578
this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]));
579
}
580
if (this.iframeElement_) {
581
var winObj = this.iframeElement_.contentWindow;
582
// accessing the window using contentWindow doesn't work in safari
583
if (!winObj) {
584
winObj = window.frames[this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]];
585
}
586
this.setPeerWindowObject(winObj);
587
}
588
589
// if the peer window object has not been set at this point, we assume
590
// being in an iframe and the channel is meant to be to the containing page
591
if (!this.peerWindowObject_) {
592
// throw an error if we are in the top window (== not in an iframe)
593
if (window == window.top) {
594
throw Error(
595
"CrossPageChannel: Can't connect, peer window-object not set.");
596
} else {
597
this.setPeerWindowObject(window.parent);
598
}
599
}
600
601
this.createTransport_();
602
603
this.transport_.connect();
604
605
// Now we run any deferred deliveries collected while connection was deferred.
606
while (this.deferredDeliveries_.length > 0) {
607
this.deferredDeliveries_.shift()();
608
}
609
};
610
611
612
/**
613
* Closes the channel.
614
*/
615
goog.net.xpc.CrossPageChannel.prototype.close = function() {
616
this.cleanUpIncompleteConnection_();
617
this.state_ = goog.net.xpc.ChannelStates.CLOSED;
618
goog.dispose(this.transport_);
619
this.transport_ = null;
620
this.connectCb_ = null;
621
goog.dispose(this.connectionDelay_);
622
this.connectionDelay_ = null;
623
goog.log.info(goog.net.xpc.logger, 'Channel "' + this.name + '" closed');
624
};
625
626
627
/**
628
* Package-private.
629
* Called by the transport when the channel is connected.
630
* @param {number=} opt_delay Delay this number of milliseconds before calling
631
* the connection callback. Usage is discouraged, but can be used to paper
632
* over timing vulnerabilities when there is no alternative.
633
*/
634
goog.net.xpc.CrossPageChannel.prototype.notifyConnected = function(opt_delay) {
635
if (this.isConnected() ||
636
(this.connectionDelay_ && this.connectionDelay_.isActive())) {
637
return;
638
}
639
this.state_ = goog.net.xpc.ChannelStates.CONNECTED;
640
goog.log.info(goog.net.xpc.logger, 'Channel "' + this.name + '" connected');
641
goog.dispose(this.connectionDelay_);
642
if (goog.isDef(opt_delay)) {
643
this.connectionDelay_ = new goog.async.Delay(this.connectCb_, opt_delay);
644
this.connectionDelay_.start();
645
} else {
646
this.connectionDelay_ = null;
647
this.connectCb_();
648
}
649
};
650
651
652
/**
653
* Called by the transport in case of an unrecoverable failure.
654
* Package private. Do not call from outside goog.net.xpc.
655
*/
656
goog.net.xpc.CrossPageChannel.prototype.notifyTransportError = function() {
657
goog.log.info(goog.net.xpc.logger, 'Transport Error');
658
this.close();
659
};
660
661
662
/** @override */
663
goog.net.xpc.CrossPageChannel.prototype.send = function(serviceName, payload) {
664
if (!this.isConnected()) {
665
goog.log.error(goog.net.xpc.logger, 'Can\'t send. Channel not connected.');
666
return;
667
}
668
// Check if the peer is still around.
669
if (!this.isPeerAvailable()) {
670
goog.log.error(goog.net.xpc.logger, 'Peer has disappeared.');
671
this.close();
672
return;
673
}
674
if (goog.isObject(payload)) {
675
payload = goog.json.serialize(payload);
676
}
677
678
// Partially URL-encode the service name because some characters (: and |) are
679
// used as delimiters for some transports, and we want to allow those
680
// characters in service names.
681
this.transport_.send(this.escapeServiceName_(serviceName), payload);
682
};
683
684
685
/**
686
* Delivers messages to the appropriate service-handler. Named xpcDeliver to
687
* avoid name conflict with {@code deliver} function in superclass
688
* goog.messaging.AbstractChannel.
689
*
690
* @param {string} serviceName The name of the port.
691
* @param {string} payload The payload.
692
* @param {string=} opt_origin An optional origin for the message, where the
693
* underlying transport makes that available. If this is specified, and
694
* the PEER_HOSTNAME parameter was provided, they must match or the message
695
* will be rejected.
696
* @package
697
*/
698
goog.net.xpc.CrossPageChannel.prototype.xpcDeliver = function(
699
serviceName, payload, opt_origin) {
700
701
// This check covers the very rare (but producable) case where the inner frame
702
// becomes ready and sends its setup message while the outer frame is
703
// deferring its connect method waiting for the inner frame to be ready. The
704
// resulting deferral ensures the message will not be processed until the
705
// channel is fully configured.
706
if (this.peerWindowDeferred_) {
707
this.deferredDeliveries_.push(
708
goog.bind(this.xpcDeliver, this, serviceName, payload, opt_origin));
709
return;
710
}
711
712
// Check whether the origin of the message is as expected.
713
if (!this.isMessageOriginAcceptable(opt_origin)) {
714
goog.log.warning(
715
goog.net.xpc.logger, 'Message received from unapproved origin "' +
716
opt_origin + '" - rejected.');
717
return;
718
}
719
720
// If there is another channel still open, the native transport's global
721
// postMessage listener will still be active. This will mean that messages
722
// being sent to the now-closed channel will still be received and delivered,
723
// such as transport service traffic from its previous correspondent in the
724
// other frame. Ensure these messages don't cause exceptions.
725
// Example: http://b/12419303
726
if (this.isDisposed() || this.state_ == goog.net.xpc.ChannelStates.CLOSED) {
727
goog.log.warning(
728
goog.net.xpc.logger, 'CrossPageChannel::xpcDeliver(): Channel closed.');
729
} else if (!serviceName || serviceName == goog.net.xpc.TRANSPORT_SERVICE_) {
730
this.transport_.transportServiceHandler(payload);
731
} else {
732
// only deliver messages if connected
733
if (this.isConnected()) {
734
this.deliver(this.unescapeServiceName_(serviceName), payload);
735
} else {
736
goog.log.info(
737
goog.net.xpc.logger,
738
'CrossPageChannel::xpcDeliver(): Not connected.');
739
}
740
}
741
};
742
743
744
/**
745
* Escape the user-provided service name for sending across the channel. This
746
* URL-encodes certain special characters so they don't conflict with delimiters
747
* used by some of the transports, and adds a special prefix if the name
748
* conflicts with the reserved transport service name.
749
*
750
* This is the opposite of {@link #unescapeServiceName_}.
751
*
752
* @param {string} name The name of the service to escape.
753
* @return {string} The escaped service name.
754
* @private
755
*/
756
goog.net.xpc.CrossPageChannel.prototype.escapeServiceName_ = function(name) {
757
if (goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_ESCAPE_RE_.test(name)) {
758
name = '%' + name;
759
}
760
return name.replace(/[%:|]/g, encodeURIComponent);
761
};
762
763
764
/**
765
* Unescape the escaped service name that was sent across the channel. This is
766
* the opposite of {@link #escapeServiceName_}.
767
*
768
* @param {string} name The name of the service to unescape.
769
* @return {string} The unescaped service name.
770
* @private
771
*/
772
goog.net.xpc.CrossPageChannel.prototype.unescapeServiceName_ = function(name) {
773
name = name.replace(/%[0-9a-f]{2}/gi, decodeURIComponent);
774
if (goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_UNESCAPE_RE_.test(name)) {
775
return name.substring(1);
776
} else {
777
return name;
778
}
779
};
780
781
782
/**
783
* Returns the role of this channel (either inner or outer).
784
* @return {number} The role of this channel.
785
*/
786
goog.net.xpc.CrossPageChannel.prototype.getRole = function() {
787
var role = this.cfg_[goog.net.xpc.CfgFields.ROLE];
788
if (goog.isNumber(role)) {
789
return role;
790
} else {
791
return window.parent == this.peerWindowObject_ ?
792
goog.net.xpc.CrossPageChannelRole.INNER :
793
goog.net.xpc.CrossPageChannelRole.OUTER;
794
}
795
};
796
797
798
/**
799
* Sets the channel name. Note, this doesn't establish a unique channel to
800
* communicate on.
801
* @param {string} name The new channel name.
802
*/
803
goog.net.xpc.CrossPageChannel.prototype.updateChannelNameAndCatalog = function(
804
name) {
805
goog.log.fine(goog.net.xpc.logger, 'changing channel name to ' + name);
806
delete goog.net.xpc.channels[this.name];
807
this.name = name;
808
goog.net.xpc.channels[name] = this;
809
};
810
811
812
/**
813
* Returns whether an incoming message with the given origin is acceptable.
814
* If an incoming request comes with a specified (non-empty) origin, and the
815
* PEER_HOSTNAME config parameter has also been provided, the two must match,
816
* or the message is unacceptable.
817
* @param {string=} opt_origin The origin associated with the incoming message.
818
* @return {boolean} Whether the message is acceptable.
819
* @package
820
*/
821
goog.net.xpc.CrossPageChannel.prototype.isMessageOriginAcceptable = function(
822
opt_origin) {
823
var peerHostname = this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME];
824
return goog.string.isEmptyOrWhitespace(goog.string.makeSafe(opt_origin)) ||
825
goog.string.isEmptyOrWhitespace(goog.string.makeSafe(peerHostname)) ||
826
opt_origin == this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME];
827
};
828
829
830
/** @override */
831
goog.net.xpc.CrossPageChannel.prototype.disposeInternal = function() {
832
this.close();
833
834
this.peerWindowObject_ = null;
835
this.iframeElement_ = null;
836
delete goog.net.xpc.channels[this.name];
837
goog.dispose(this.peerLoadHandler_);
838
delete this.peerLoadHandler_;
839
goog.net.xpc.CrossPageChannel.base(this, 'disposeInternal');
840
};
841
842
843
/**
844
* Disposes all channels.
845
* @private
846
*/
847
goog.net.xpc.CrossPageChannel.disposeAll_ = function() {
848
for (var name in goog.net.xpc.channels) {
849
goog.dispose(goog.net.xpc.channels[name]);
850
}
851
};
852
853