Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/net/xpc/directtransport.js
1865 views
1
// Copyright 2013 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 an implementation of a transport that can call methods
17
* directly on a frame. Useful if you want to use XPC for crossdomain messaging
18
* (using another transport), or same domain messaging (using this transport).
19
*/
20
21
22
goog.provide('goog.net.xpc.DirectTransport');
23
24
goog.require('goog.Timer');
25
goog.require('goog.async.Deferred');
26
goog.require('goog.events.EventHandler');
27
goog.require('goog.log');
28
goog.require('goog.net.xpc');
29
goog.require('goog.net.xpc.CfgFields');
30
goog.require('goog.net.xpc.CrossPageChannelRole');
31
goog.require('goog.net.xpc.Transport');
32
goog.require('goog.net.xpc.TransportTypes');
33
goog.require('goog.object');
34
35
36
goog.scope(function() {
37
var CfgFields = goog.net.xpc.CfgFields;
38
var CrossPageChannelRole = goog.net.xpc.CrossPageChannelRole;
39
var Deferred = goog.async.Deferred;
40
var EventHandler = goog.events.EventHandler;
41
var Timer = goog.Timer;
42
var Transport = goog.net.xpc.Transport;
43
44
45
46
/**
47
* A direct window to window method transport.
48
*
49
* If the windows are in the same security context, this transport calls
50
* directly into the other window without using any additional mechanism. This
51
* is mainly used in scenarios where you want to optionally use a cross domain
52
* transport in cross security context situations, or optionally use a direct
53
* transport in same security context situations.
54
*
55
* Note: Global properties are exported by using this transport. One to
56
* communicate with the other window by, currently crosswindowmessaging.channel,
57
* and by using goog.getUid on window, currently closure_uid_[0-9]+.
58
*
59
* @param {!goog.net.xpc.CrossPageChannel} channel The channel this
60
* transport belongs to.
61
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for
62
* finding the correct window/document. If omitted, uses the current
63
* document.
64
* @constructor
65
* @extends {Transport}
66
*/
67
goog.net.xpc.DirectTransport = function(channel, opt_domHelper) {
68
goog.net.xpc.DirectTransport.base(this, 'constructor', opt_domHelper);
69
70
/**
71
* The channel this transport belongs to.
72
* @private {!goog.net.xpc.CrossPageChannel}
73
*/
74
this.channel_ = channel;
75
76
/** @private {!EventHandler<!goog.net.xpc.DirectTransport>} */
77
this.eventHandler_ = new EventHandler(this);
78
this.registerDisposable(this.eventHandler_);
79
80
/**
81
* Timer for connection reattempts.
82
* @private {!Timer}
83
*/
84
this.maybeAttemptToConnectTimer_ = new Timer(
85
DirectTransport.CONNECTION_ATTEMPT_INTERVAL_MS_, this.getWindow());
86
this.registerDisposable(this.maybeAttemptToConnectTimer_);
87
88
/**
89
* Fires once we've received our SETUP_ACK message.
90
* @private {!Deferred}
91
*/
92
this.setupAckReceived_ = new Deferred();
93
94
/**
95
* Fires once we've sent our SETUP_ACK message.
96
* @private {!Deferred}
97
*/
98
this.setupAckSent_ = new Deferred();
99
100
/**
101
* Fires once we're marked connected.
102
* @private {!Deferred}
103
*/
104
this.connected_ = new Deferred();
105
106
/**
107
* The unique ID of this side of the connection. Used to determine when a peer
108
* is reloaded.
109
* @private {string}
110
*/
111
this.endpointId_ = goog.net.xpc.getRandomString(10);
112
113
/**
114
* The unique ID of the peer. If we get a message from a peer with an ID we
115
* don't expect, we reset the connection.
116
* @private {?string}
117
*/
118
this.peerEndpointId_ = null;
119
120
/**
121
* The map of sending messages.
122
* @private {Object}
123
*/
124
this.asyncSendsMap_ = {};
125
126
/**
127
* The original channel name.
128
* @private {string}
129
*/
130
this.originalChannelName_ = this.channel_.name;
131
132
// We reconfigure the channel name to include the role so that we can
133
// communicate in the same window between the different roles on the
134
// same channel.
135
this.channel_.updateChannelNameAndCatalog(
136
DirectTransport.getRoledChannelName_(
137
this.channel_.name, this.channel_.getRole()));
138
139
/**
140
* Flag indicating if this instance of the transport has been initialized.
141
* @private {boolean}
142
*/
143
this.initialized_ = false;
144
145
// We don't want to mark ourselves connected until we have sent whatever
146
// message will cause our counterpart in the other frame to also declare
147
// itself connected, if there is such a message. Otherwise we risk a user
148
// message being sent in advance of that message, and it being discarded.
149
150
// Two sided handshake:
151
// SETUP_ACK has to have been received, and sent.
152
this.connected_.awaitDeferred(this.setupAckReceived_);
153
this.connected_.awaitDeferred(this.setupAckSent_);
154
155
this.connected_.addCallback(this.notifyConnected_, this);
156
this.connected_.callback(true);
157
158
this.eventHandler_.listen(
159
this.maybeAttemptToConnectTimer_, Timer.TICK,
160
this.maybeAttemptToConnect_);
161
162
goog.log.info(
163
goog.net.xpc.logger,
164
'DirectTransport created. role=' + this.channel_.getRole());
165
};
166
goog.inherits(goog.net.xpc.DirectTransport, Transport);
167
var DirectTransport = goog.net.xpc.DirectTransport;
168
169
170
/**
171
* @private {number}
172
* @const
173
*/
174
DirectTransport.CONNECTION_ATTEMPT_INTERVAL_MS_ = 100;
175
176
177
/**
178
* The delay to notify the xpc of a successful connection. This is used
179
* to allow both parties to be connected if one party's connection callback
180
* invokes an immediate send.
181
* @private {number}
182
* @const
183
*/
184
DirectTransport.CONNECTION_DELAY_INTERVAL_MS_ = 0;
185
186
187
/**
188
* @param {!Window} peerWindow The peer window to check if DirectTranport is
189
* supported on.
190
* @return {boolean} Whether this transport is supported.
191
*/
192
DirectTransport.isSupported = function(peerWindow) {
193
194
try {
195
return window.document.domain == peerWindow.document.domain;
196
} catch (e) {
197
return false;
198
}
199
};
200
201
202
/**
203
* Tracks the number of DirectTransport channels that have been
204
* initialized but not disposed yet in a map keyed by the UID of the window
205
* object. This allows for multiple windows to be initiallized and listening
206
* for messages.
207
* @private {!Object<number>}
208
*/
209
DirectTransport.activeCount_ = {};
210
211
212
/**
213
* Path of global message proxy.
214
* @private {string}
215
* @const
216
*/
217
// TODO(user): Make this configurable using the CfgFields.
218
DirectTransport.GLOBAL_TRANPORT_PATH_ = 'crosswindowmessaging.channel';
219
220
221
/**
222
* The delimiter used for transport service messages.
223
* @private {string}
224
* @const
225
*/
226
DirectTransport.MESSAGE_DELIMITER_ = ',';
227
228
229
/**
230
* Initializes this transport. Registers a method for 'message'-events in the
231
* global scope.
232
* @param {!Window} listenWindow The window to listen to events on.
233
* @private
234
*/
235
DirectTransport.initialize_ = function(listenWindow) {
236
var uid = goog.getUid(listenWindow);
237
var value = DirectTransport.activeCount_[uid] || 0;
238
if (value == 0) {
239
// Set up a handler on the window to proxy messages to class.
240
var globalProxy = goog.getObjectByName(
241
DirectTransport.GLOBAL_TRANPORT_PATH_, listenWindow);
242
if (globalProxy == null) {
243
goog.exportSymbol(
244
DirectTransport.GLOBAL_TRANPORT_PATH_,
245
DirectTransport.messageReceivedHandler_, listenWindow);
246
}
247
}
248
DirectTransport.activeCount_[uid]++;
249
};
250
251
252
/**
253
* @param {string} channelName The channel name.
254
* @param {string|number} role The role.
255
* @return {string} The formatted channel name including role.
256
* @private
257
*/
258
DirectTransport.getRoledChannelName_ = function(channelName, role) {
259
return channelName + '_' + role;
260
};
261
262
263
/**
264
* @param {!Object} literal The literal unrenamed message.
265
* @return {boolean} Whether the message was successfully delivered to a
266
* channel.
267
* @private
268
*/
269
DirectTransport.messageReceivedHandler_ = function(literal) {
270
var msg = DirectTransport.Message_.fromLiteral(literal);
271
272
var channelName = msg.channelName;
273
var service = msg.service;
274
var payload = msg.payload;
275
276
goog.log.fine(
277
goog.net.xpc.logger, 'messageReceived: channel=' + channelName +
278
', service=' + service + ', payload=' + payload);
279
280
// Attempt to deliver message to the channel. Keep in mind that it may not
281
// exist for several reasons, including but not limited to:
282
// - a malformed message
283
// - the channel simply has not been created
284
// - channel was created in a different namespace
285
// - message was sent to the wrong window
286
// - channel has become stale (e.g. caching iframes and back clicks)
287
var channel = goog.net.xpc.channels[channelName];
288
if (channel) {
289
channel.xpcDeliver(service, payload);
290
return true;
291
}
292
293
var transportMessageType = DirectTransport.parseTransportPayload_(payload)[0];
294
295
// Check if there are any stale channel names that can be updated.
296
for (var staleChannelName in goog.net.xpc.channels) {
297
var staleChannel = goog.net.xpc.channels[staleChannelName];
298
if (staleChannel.getRole() == CrossPageChannelRole.INNER &&
299
!staleChannel.isConnected() &&
300
service == goog.net.xpc.TRANSPORT_SERVICE_ &&
301
transportMessageType == goog.net.xpc.SETUP) {
302
// Inner peer received SETUP message but channel names did not match.
303
// Start using the channel name sent from outer peer. The channel name
304
// of the inner peer can easily become out of date, as iframe's and their
305
// JS state get cached in many browsers upon page reload or history
306
// navigation (particularly Firefox 1.5+).
307
staleChannel.updateChannelNameAndCatalog(channelName);
308
staleChannel.xpcDeliver(service, payload);
309
return true;
310
}
311
}
312
313
// Failed to find a channel to deliver this message to, so simply ignore it.
314
goog.log.info(goog.net.xpc.logger, 'channel name mismatch; message ignored.');
315
return false;
316
};
317
318
319
/**
320
* The transport type.
321
* @type {number}
322
* @override
323
*/
324
DirectTransport.prototype.transportType = goog.net.xpc.TransportTypes.DIRECT;
325
326
327
/**
328
* Handles transport service messages.
329
* @param {string} payload The message content.
330
* @override
331
*/
332
DirectTransport.prototype.transportServiceHandler = function(payload) {
333
var transportParts = DirectTransport.parseTransportPayload_(payload);
334
var transportMessageType = transportParts[0];
335
var peerEndpointId = transportParts[1];
336
switch (transportMessageType) {
337
case goog.net.xpc.SETUP_ACK_:
338
if (!this.setupAckReceived_.hasFired()) {
339
this.setupAckReceived_.callback(true);
340
}
341
break;
342
case goog.net.xpc.SETUP:
343
this.sendSetupAckMessage_();
344
if ((this.peerEndpointId_ != null) &&
345
(this.peerEndpointId_ != peerEndpointId)) {
346
// Send a new SETUP message since the peer has been replaced.
347
goog.log.info(
348
goog.net.xpc.logger,
349
'Sending SETUP and changing peer ID to: ' + peerEndpointId);
350
this.sendSetupMessage_();
351
}
352
this.peerEndpointId_ = peerEndpointId;
353
break;
354
}
355
};
356
357
358
/**
359
* Sends a SETUP transport service message.
360
* @private
361
*/
362
DirectTransport.prototype.sendSetupMessage_ = function() {
363
// Although we could send real objects, since some other transports are
364
// limited to strings we also keep this requirement.
365
var payload = goog.net.xpc.SETUP;
366
payload += DirectTransport.MESSAGE_DELIMITER_;
367
payload += this.endpointId_;
368
this.send(goog.net.xpc.TRANSPORT_SERVICE_, payload);
369
};
370
371
372
/**
373
* Sends a SETUP_ACK transport service message.
374
* @private
375
*/
376
DirectTransport.prototype.sendSetupAckMessage_ = function() {
377
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
378
if (!this.setupAckSent_.hasFired()) {
379
this.setupAckSent_.callback(true);
380
}
381
};
382
383
384
/** @override */
385
DirectTransport.prototype.connect = function() {
386
var win = this.getWindow();
387
if (win) {
388
DirectTransport.initialize_(win);
389
this.initialized_ = true;
390
this.maybeAttemptToConnect_();
391
} else {
392
goog.log.fine(goog.net.xpc.logger, 'connect(): no window to initialize.');
393
}
394
};
395
396
397
/**
398
* Connects to other peer. In the case of the outer peer, the setup messages are
399
* likely sent before the inner peer is ready to receive them. Therefore, this
400
* function will continue trying to send the SETUP message until the inner peer
401
* responds. In the case of the inner peer, it will occasionally have its
402
* channel name fall out of sync with the outer peer, particularly during
403
* soft-reloads and history navigations.
404
* @private
405
*/
406
DirectTransport.prototype.maybeAttemptToConnect_ = function() {
407
if (this.channel_.isConnected()) {
408
this.maybeAttemptToConnectTimer_.stop();
409
return;
410
}
411
this.maybeAttemptToConnectTimer_.start();
412
this.sendSetupMessage_();
413
};
414
415
416
/**
417
* Prepares to send a message.
418
* @param {string} service The name of the service the message is to be
419
* delivered to.
420
* @param {string} payload The message content.
421
* @override
422
*/
423
DirectTransport.prototype.send = function(service, payload) {
424
if (!this.channel_.getPeerWindowObject()) {
425
goog.log.fine(goog.net.xpc.logger, 'send(): window not ready');
426
return;
427
}
428
var channelName = DirectTransport.getRoledChannelName_(
429
this.originalChannelName_, this.getPeerRole_());
430
431
var message = new DirectTransport.Message_(channelName, service, payload);
432
433
if (this.channel_.getConfig()[CfgFields.DIRECT_TRANSPORT_SYNC_MODE]) {
434
this.executeScheduledSend_(message);
435
} else {
436
// Note: goog.async.nextTick doesn't support cancelling or disposal so
437
// leaving as 0ms timer, though this may have performance implications.
438
this.asyncSendsMap_[goog.getUid(message)] =
439
Timer.callOnce(goog.bind(this.executeScheduledSend_, this, message), 0);
440
}
441
};
442
443
444
/**
445
* Sends the message.
446
* @param {!DirectTransport.Message_} message The message to send.
447
* @private
448
*/
449
DirectTransport.prototype.executeScheduledSend_ = function(message) {
450
var messageId = goog.getUid(message);
451
if (this.asyncSendsMap_[messageId]) {
452
delete this.asyncSendsMap_[messageId];
453
}
454
455
456
try {
457
var peerProxy = goog.getObjectByName(
458
DirectTransport.GLOBAL_TRANPORT_PATH_,
459
this.channel_.getPeerWindowObject());
460
} catch (error) {
461
goog.log.warning(
462
goog.net.xpc.logger, 'Can\'t access other window, ignoring.', error);
463
return;
464
}
465
466
if (goog.isNull(peerProxy)) {
467
goog.log.warning(
468
goog.net.xpc.logger, 'Peer window had no global function.');
469
return;
470
}
471
472
473
try {
474
peerProxy(message.toLiteral());
475
goog.log.info(
476
goog.net.xpc.logger, 'send(): channelName=' + message.channelName +
477
' service=' + message.service + ' payload=' + message.payload);
478
} catch (error) {
479
goog.log.warning(
480
goog.net.xpc.logger, 'Error performing call, ignoring.', error);
481
}
482
};
483
484
485
/**
486
* @return {goog.net.xpc.CrossPageChannelRole} The role of peer channel (either
487
* inner or outer).
488
* @private
489
*/
490
DirectTransport.prototype.getPeerRole_ = function() {
491
var role = this.channel_.getRole();
492
return role == goog.net.xpc.CrossPageChannelRole.OUTER ?
493
goog.net.xpc.CrossPageChannelRole.INNER :
494
goog.net.xpc.CrossPageChannelRole.OUTER;
495
};
496
497
498
/**
499
* Notifies the channel that this transport is connected.
500
* @private
501
*/
502
DirectTransport.prototype.notifyConnected_ = function() {
503
// Add a delay as the connection callback will break if this transport is
504
// synchronous and the callback invokes send() immediately.
505
this.channel_.notifyConnected(
506
this.channel_.getConfig()[CfgFields.DIRECT_TRANSPORT_SYNC_MODE] ?
507
DirectTransport.CONNECTION_DELAY_INTERVAL_MS_ :
508
0);
509
};
510
511
512
/** @override */
513
DirectTransport.prototype.disposeInternal = function() {
514
if (this.initialized_) {
515
var listenWindow = this.getWindow();
516
var uid = goog.getUid(listenWindow);
517
var value = --DirectTransport.activeCount_[uid];
518
if (value == 1) {
519
goog.exportSymbol(
520
DirectTransport.GLOBAL_TRANPORT_PATH_, null, listenWindow);
521
}
522
}
523
524
if (this.asyncSendsMap_) {
525
goog.object.forEach(
526
this.asyncSendsMap_, function(timerId) { Timer.clear(timerId); });
527
this.asyncSendsMap_ = null;
528
}
529
530
// Deferred's aren't disposables.
531
if (this.setupAckReceived_) {
532
this.setupAckReceived_.cancel();
533
delete this.setupAckReceived_;
534
}
535
if (this.setupAckSent_) {
536
this.setupAckSent_.cancel();
537
delete this.setupAckSent_;
538
}
539
if (this.connected_) {
540
this.connected_.cancel();
541
delete this.connected_;
542
}
543
544
DirectTransport.base(this, 'disposeInternal');
545
};
546
547
548
/**
549
* Parses a transport service payload message.
550
* @param {string} payload The payload.
551
* @return {!Array<?string>} An array with the message type as the first member
552
* and the endpoint id as the second, if one was sent, or null otherwise.
553
* @private
554
*/
555
DirectTransport.parseTransportPayload_ = function(payload) {
556
var transportParts = /** @type {!Array<?string>} */ (
557
payload.split(DirectTransport.MESSAGE_DELIMITER_));
558
transportParts[1] = transportParts[1] || null; // Usually endpointId.
559
return transportParts;
560
};
561
562
563
564
/**
565
* Message container that gets passed back and forth between windows.
566
* @param {string} channelName The channel name to tranport messages on.
567
* @param {string} service The service to send the payload to.
568
* @param {string} payload The payload to send.
569
* @constructor
570
* @struct
571
* @private
572
*/
573
DirectTransport.Message_ = function(channelName, service, payload) {
574
/**
575
* The name of the channel.
576
* @type {string}
577
*/
578
this.channelName = channelName;
579
580
/**
581
* The service on the channel.
582
* @type {string}
583
*/
584
this.service = service;
585
586
/**
587
* The payload.
588
* @type {string}
589
*/
590
this.payload = payload;
591
};
592
593
594
/**
595
* Converts a message to a literal object.
596
* @return {!Object} The message as a literal object.
597
*/
598
DirectTransport.Message_.prototype.toLiteral = function() {
599
return {
600
'channelName': this.channelName,
601
'service': this.service,
602
'payload': this.payload
603
};
604
};
605
606
607
/**
608
* Creates a Message_ from a literal object.
609
* @param {!Object} literal The literal to convert to Message.
610
* @return {!DirectTransport.Message_} The Message.
611
*/
612
DirectTransport.Message_.fromLiteral = function(literal) {
613
return new DirectTransport.Message_(
614
literal['channelName'], literal['service'], literal['payload']);
615
};
616
617
}); // goog.scope
618
619