Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/net/xpc/nativemessagingtransport.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 class which uses native messaging
17
* facilities for cross domain communication.
18
*
19
*/
20
21
22
goog.provide('goog.net.xpc.NativeMessagingTransport');
23
24
goog.require('goog.Timer');
25
goog.require('goog.asserts');
26
goog.require('goog.async.Deferred');
27
goog.require('goog.events');
28
goog.require('goog.events.EventHandler');
29
goog.require('goog.log');
30
goog.require('goog.net.xpc');
31
goog.require('goog.net.xpc.CrossPageChannelRole');
32
goog.require('goog.net.xpc.Transport');
33
goog.require('goog.net.xpc.TransportTypes');
34
35
36
37
/**
38
* The native messaging transport
39
*
40
* Uses document.postMessage() to send messages to other documents.
41
* Receiving is done by listening on 'message'-events on the document.
42
*
43
* @param {goog.net.xpc.CrossPageChannel} channel The channel this
44
* transport belongs to.
45
* @param {string} peerHostname The hostname (protocol, domain, and port) of the
46
* peer.
47
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for
48
* finding the correct window/document.
49
* @param {boolean=} opt_oneSidedHandshake If this is true, only the outer
50
* transport sends a SETUP message and expects a SETUP_ACK. The inner
51
* transport goes connected when it receives the SETUP.
52
* @param {number=} opt_protocolVersion Which version of its setup protocol the
53
* transport should use. The default is '2'.
54
* @constructor
55
* @extends {goog.net.xpc.Transport}
56
* @final
57
*/
58
goog.net.xpc.NativeMessagingTransport = function(
59
channel, peerHostname, opt_domHelper, opt_oneSidedHandshake,
60
opt_protocolVersion) {
61
goog.net.xpc.NativeMessagingTransport.base(
62
this, 'constructor', opt_domHelper);
63
64
/**
65
* The channel this transport belongs to.
66
* @type {goog.net.xpc.CrossPageChannel}
67
* @private
68
*/
69
this.channel_ = channel;
70
71
/**
72
* Which version of the transport's protocol should be used.
73
* @type {number}
74
* @private
75
*/
76
this.protocolVersion_ = opt_protocolVersion || 2;
77
goog.asserts.assert(this.protocolVersion_ >= 1);
78
goog.asserts.assert(this.protocolVersion_ <= 2);
79
80
/**
81
* The hostname of the peer. This parameterizes all calls to postMessage, and
82
* should contain the precise protocol, domain, and port of the peer window.
83
* @type {string}
84
* @private
85
*/
86
this.peerHostname_ = peerHostname || '*';
87
88
/**
89
* The event handler.
90
* @type {!goog.events.EventHandler<!goog.net.xpc.NativeMessagingTransport>}
91
* @private
92
*/
93
this.eventHandler_ = new goog.events.EventHandler(this);
94
95
/**
96
* Timer for connection reattempts.
97
* @type {!goog.Timer}
98
* @private
99
*/
100
this.maybeAttemptToConnectTimer_ = new goog.Timer(100, this.getWindow());
101
102
/**
103
* Whether one-sided handshakes are enabled.
104
* @type {boolean}
105
* @private
106
*/
107
this.oneSidedHandshake_ = !!opt_oneSidedHandshake;
108
109
/**
110
* Fires once we've received our SETUP_ACK message.
111
* @type {!goog.async.Deferred}
112
* @private
113
*/
114
this.setupAckReceived_ = new goog.async.Deferred();
115
116
/**
117
* Fires once we've sent our SETUP_ACK message.
118
* @type {!goog.async.Deferred}
119
* @private
120
*/
121
this.setupAckSent_ = new goog.async.Deferred();
122
123
/**
124
* Fires once we're marked connected.
125
* @type {!goog.async.Deferred}
126
* @private
127
*/
128
this.connected_ = new goog.async.Deferred();
129
130
/**
131
* The unique ID of this side of the connection. Used to determine when a peer
132
* is reloaded.
133
* @type {string}
134
* @private
135
*/
136
this.endpointId_ = goog.net.xpc.getRandomString(10);
137
138
/**
139
* The unique ID of the peer. If we get a message from a peer with an ID we
140
* don't expect, we reset the connection.
141
* @type {?string}
142
* @private
143
*/
144
this.peerEndpointId_ = null;
145
146
// We don't want to mark ourselves connected until we have sent whatever
147
// message will cause our counterpart in the other frame to also declare
148
// itself connected, if there is such a message. Otherwise we risk a user
149
// message being sent in advance of that message, and it being discarded.
150
if (this.oneSidedHandshake_) {
151
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.INNER) {
152
// One sided handshake, inner frame:
153
// SETUP_ACK must be received.
154
this.connected_.awaitDeferred(this.setupAckReceived_);
155
} else {
156
// One sided handshake, outer frame:
157
// SETUP_ACK must be sent.
158
this.connected_.awaitDeferred(this.setupAckSent_);
159
}
160
} else {
161
// Two sided handshake:
162
// SETUP_ACK has to have been received, and sent.
163
this.connected_.awaitDeferred(this.setupAckReceived_);
164
if (this.protocolVersion_ == 2) {
165
this.connected_.awaitDeferred(this.setupAckSent_);
166
}
167
}
168
this.connected_.addCallback(this.notifyConnected_, this);
169
this.connected_.callback(true);
170
171
this.eventHandler_.listen(
172
this.maybeAttemptToConnectTimer_, goog.Timer.TICK,
173
this.maybeAttemptToConnect_);
174
175
goog.log.info(
176
goog.net.xpc.logger, 'NativeMessagingTransport created. ' +
177
'protocolVersion=' + this.protocolVersion_ + ', oneSidedHandshake=' +
178
this.oneSidedHandshake_ + ', role=' + this.channel_.getRole());
179
};
180
goog.inherits(goog.net.xpc.NativeMessagingTransport, goog.net.xpc.Transport);
181
182
183
/**
184
* Length of the delay in milliseconds between the channel being connected and
185
* the connection callback being called, in cases where coverage of timing flaws
186
* is required.
187
* @type {number}
188
* @private
189
*/
190
goog.net.xpc.NativeMessagingTransport.CONNECTION_DELAY_MS_ = 200;
191
192
193
/**
194
* Current determination of peer's protocol version, or null for unknown.
195
* @type {?number}
196
* @private
197
*/
198
goog.net.xpc.NativeMessagingTransport.prototype.peerProtocolVersion_ = null;
199
200
201
/**
202
* Flag indicating if this instance of the transport has been initialized.
203
* @type {boolean}
204
* @private
205
*/
206
goog.net.xpc.NativeMessagingTransport.prototype.initialized_ = false;
207
208
209
/**
210
* The transport type.
211
* @type {number}
212
* @override
213
*/
214
goog.net.xpc.NativeMessagingTransport.prototype.transportType =
215
goog.net.xpc.TransportTypes.NATIVE_MESSAGING;
216
217
218
/**
219
* The delimiter used for transport service messages.
220
* @type {string}
221
* @private
222
*/
223
goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_ = ',';
224
225
226
/**
227
* Tracks the number of NativeMessagingTransport channels that have been
228
* initialized but not disposed yet in a map keyed by the UID of the window
229
* object. This allows for multiple windows to be initiallized and listening
230
* for messages.
231
* @type {Object<number>}
232
* @private
233
*/
234
goog.net.xpc.NativeMessagingTransport.activeCount_ = {};
235
236
237
/**
238
* Id of a timer user during postMessage sends.
239
* @type {number}
240
* @private
241
*/
242
goog.net.xpc.NativeMessagingTransport.prototype.sendTimerId_ = 0;
243
244
245
/**
246
* Checks whether the peer transport protocol version could be as indicated.
247
* @param {number} version The version to check for.
248
* @return {boolean} Whether the peer transport protocol version is as
249
* indicated, or null.
250
* @private
251
*/
252
goog.net.xpc.NativeMessagingTransport.prototype.couldPeerVersionBe_ = function(
253
version) {
254
return this.peerProtocolVersion_ == null ||
255
this.peerProtocolVersion_ == version;
256
};
257
258
259
/**
260
* Initializes this transport. Registers a listener for 'message'-events
261
* on the document.
262
* @param {Window} listenWindow The window to listen to events on.
263
* @private
264
*/
265
goog.net.xpc.NativeMessagingTransport.initialize_ = function(listenWindow) {
266
var uid = goog.getUid(listenWindow);
267
var value = goog.net.xpc.NativeMessagingTransport.activeCount_[uid];
268
if (!goog.isNumber(value)) {
269
value = 0;
270
}
271
if (value == 0) {
272
// Listen for message-events. These are fired on window in FF3 and on
273
// document in Opera.
274
goog.events.listen(
275
listenWindow.postMessage ? listenWindow : listenWindow.document,
276
'message', goog.net.xpc.NativeMessagingTransport.messageReceived_,
277
false, goog.net.xpc.NativeMessagingTransport);
278
}
279
goog.net.xpc.NativeMessagingTransport.activeCount_[uid] = value + 1;
280
};
281
282
283
/**
284
* Processes an incoming message-event.
285
* @param {goog.events.BrowserEvent} msgEvt The message event.
286
* @return {boolean} True if message was successfully delivered to a channel.
287
* @private
288
*/
289
goog.net.xpc.NativeMessagingTransport.messageReceived_ = function(msgEvt) {
290
var data = msgEvt.getBrowserEvent().data;
291
292
if (!goog.isString(data)) {
293
return false;
294
}
295
296
var headDelim = data.indexOf('|');
297
var serviceDelim = data.indexOf(':');
298
299
// make sure we got something reasonable
300
if (headDelim == -1 || serviceDelim == -1) {
301
return false;
302
}
303
304
var channelName = data.substring(0, headDelim);
305
var service = data.substring(headDelim + 1, serviceDelim);
306
var payload = data.substring(serviceDelim + 1);
307
308
goog.log.fine(
309
goog.net.xpc.logger, 'messageReceived: channel=' + channelName +
310
', service=' + service + ', payload=' + payload);
311
312
// Attempt to deliver message to the channel. Keep in mind that it may not
313
// exist for several reasons, including but not limited to:
314
// - a malformed message
315
// - the channel simply has not been created
316
// - channel was created in a different namespace
317
// - message was sent to the wrong window
318
// - channel has become stale (e.g. caching iframes and back clicks)
319
var channel = goog.net.xpc.channels[channelName];
320
if (channel) {
321
channel.xpcDeliver(
322
service, payload,
323
/** @type {!MessageEvent} */ (msgEvt.getBrowserEvent()).origin);
324
return true;
325
}
326
327
var transportMessageType =
328
goog.net.xpc.NativeMessagingTransport.parseTransportPayload_(payload)[0];
329
330
// Check if there are any stale channel names that can be updated.
331
for (var staleChannelName in goog.net.xpc.channels) {
332
var staleChannel = goog.net.xpc.channels[staleChannelName];
333
if (staleChannel.getRole() == goog.net.xpc.CrossPageChannelRole.INNER &&
334
!staleChannel.isConnected() &&
335
service == goog.net.xpc.TRANSPORT_SERVICE_ &&
336
(transportMessageType == goog.net.xpc.SETUP ||
337
transportMessageType == goog.net.xpc.SETUP_NTPV2) &&
338
staleChannel.isMessageOriginAcceptable(
339
msgEvt.getBrowserEvent().origin)) {
340
// Inner peer received SETUP message but channel names did not match.
341
// Start using the channel name sent from outer peer. The channel name
342
// of the inner peer can easily become out of date, as iframe's and their
343
// JS state get cached in many browsers upon page reload or history
344
// navigation (particularly Firefox 1.5+). We can trust the outer peer,
345
// since we only accept postMessage messages from the same hostname that
346
// originally setup the channel.
347
staleChannel.updateChannelNameAndCatalog(channelName);
348
staleChannel.xpcDeliver(service, payload);
349
return true;
350
}
351
}
352
353
// Failed to find a channel to deliver this message to, so simply ignore it.
354
goog.log.info(goog.net.xpc.logger, 'channel name mismatch; message ignored"');
355
return false;
356
};
357
358
359
/**
360
* Handles transport service messages.
361
* @param {string} payload The message content.
362
* @override
363
*/
364
goog.net.xpc.NativeMessagingTransport.prototype.transportServiceHandler =
365
function(payload) {
366
var transportParts =
367
goog.net.xpc.NativeMessagingTransport.parseTransportPayload_(payload);
368
var transportMessageType = transportParts[0];
369
var peerEndpointId = transportParts[1];
370
switch (transportMessageType) {
371
case goog.net.xpc.SETUP_ACK_:
372
this.setPeerProtocolVersion_(1);
373
if (!this.setupAckReceived_.hasFired()) {
374
this.setupAckReceived_.callback(true);
375
}
376
break;
377
case goog.net.xpc.SETUP_ACK_NTPV2:
378
if (this.protocolVersion_ == 2) {
379
this.setPeerProtocolVersion_(2);
380
if (!this.setupAckReceived_.hasFired()) {
381
this.setupAckReceived_.callback(true);
382
}
383
}
384
break;
385
case goog.net.xpc.SETUP:
386
this.setPeerProtocolVersion_(1);
387
this.sendSetupAckMessage_(1);
388
break;
389
case goog.net.xpc.SETUP_NTPV2:
390
if (this.protocolVersion_ == 2) {
391
var prevPeerProtocolVersion = this.peerProtocolVersion_;
392
this.setPeerProtocolVersion_(2);
393
this.sendSetupAckMessage_(2);
394
if ((prevPeerProtocolVersion == 1 || this.peerEndpointId_ != null) &&
395
this.peerEndpointId_ != peerEndpointId) {
396
// Send a new SETUP message since the peer has been replaced.
397
goog.log.info(
398
goog.net.xpc.logger,
399
'Sending SETUP and changing peer ID to: ' + peerEndpointId);
400
this.sendSetupMessage_();
401
}
402
this.peerEndpointId_ = peerEndpointId;
403
}
404
break;
405
}
406
};
407
408
409
/**
410
* Sends a SETUP transport service message of the correct protocol number for
411
* our current situation.
412
* @private
413
*/
414
goog.net.xpc.NativeMessagingTransport.prototype.sendSetupMessage_ = function() {
415
// 'real' (legacy) v1 transports don't know about there being v2 ones out
416
// there, and we shouldn't either.
417
goog.asserts.assert(
418
!(this.protocolVersion_ == 1 && this.peerProtocolVersion_ == 2));
419
420
if (this.protocolVersion_ == 2 && this.couldPeerVersionBe_(2)) {
421
var payload = goog.net.xpc.SETUP_NTPV2;
422
payload += goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_;
423
payload += this.endpointId_;
424
this.send(goog.net.xpc.TRANSPORT_SERVICE_, payload);
425
}
426
427
// For backward compatibility reasons, the V1 SETUP message can be sent by
428
// both V1 and V2 transports. Once a V2 transport has 'heard' another V2
429
// transport it starts ignoring V1 messages, so the V2 message must be sent
430
// first.
431
if (this.couldPeerVersionBe_(1)) {
432
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP);
433
}
434
};
435
436
437
/**
438
* Sends a SETUP_ACK transport service message of the correct protocol number
439
* for our current situation.
440
* @param {number} protocolVersion The protocol version of the SETUP message
441
* which gave rise to this ack message.
442
* @private
443
*/
444
goog.net.xpc.NativeMessagingTransport.prototype.sendSetupAckMessage_ = function(
445
protocolVersion) {
446
goog.asserts.assert(
447
this.protocolVersion_ != 1 || protocolVersion != 2,
448
'Shouldn\'t try to send a v2 setup ack in v1 mode.');
449
if (this.protocolVersion_ == 2 && this.couldPeerVersionBe_(2) &&
450
protocolVersion == 2) {
451
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_NTPV2);
452
} else if (this.couldPeerVersionBe_(1) && protocolVersion == 1) {
453
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
454
} else {
455
return;
456
}
457
458
if (!this.setupAckSent_.hasFired()) {
459
this.setupAckSent_.callback(true);
460
}
461
};
462
463
464
/**
465
* Attempts to set the peer protocol number. Downgrades from 2 to 1 are not
466
* permitted.
467
* @param {number} version The new protocol number.
468
* @private
469
*/
470
goog.net.xpc.NativeMessagingTransport.prototype.setPeerProtocolVersion_ =
471
function(version) {
472
if (version > this.peerProtocolVersion_) {
473
this.peerProtocolVersion_ = version;
474
}
475
if (this.peerProtocolVersion_ == 1) {
476
if (!this.setupAckSent_.hasFired() && !this.oneSidedHandshake_) {
477
this.setupAckSent_.callback(true);
478
}
479
this.peerEndpointId_ = null;
480
}
481
};
482
483
484
/**
485
* Connects this transport.
486
* @override
487
*/
488
goog.net.xpc.NativeMessagingTransport.prototype.connect = function() {
489
goog.net.xpc.NativeMessagingTransport.initialize_(this.getWindow());
490
this.initialized_ = true;
491
this.maybeAttemptToConnect_();
492
};
493
494
495
/**
496
* Connects to other peer. In the case of the outer peer, the setup messages are
497
* likely sent before the inner peer is ready to receive them. Therefore, this
498
* function will continue trying to send the SETUP message until the inner peer
499
* responds. In the case of the inner peer, it will occasionally have its
500
* channel name fall out of sync with the outer peer, particularly during
501
* soft-reloads and history navigations.
502
* @private
503
*/
504
goog.net.xpc.NativeMessagingTransport.prototype.maybeAttemptToConnect_ =
505
function() {
506
// In a one-sided handshake, the outer frame does not send a SETUP message,
507
// but the inner frame does.
508
var outerFrame =
509
this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER;
510
if ((this.oneSidedHandshake_ && outerFrame) || this.channel_.isConnected() ||
511
this.isDisposed()) {
512
this.maybeAttemptToConnectTimer_.stop();
513
return;
514
}
515
this.maybeAttemptToConnectTimer_.start();
516
this.sendSetupMessage_();
517
};
518
519
520
/**
521
* Sends a message.
522
* @param {string} service The name off the service the message is to be
523
* delivered to.
524
* @param {string} payload The message content.
525
* @override
526
*/
527
goog.net.xpc.NativeMessagingTransport.prototype.send = function(
528
service, payload) {
529
var win = this.channel_.getPeerWindowObject();
530
if (!win) {
531
goog.log.fine(goog.net.xpc.logger, 'send(): window not ready');
532
return;
533
}
534
535
this.send = function(service, payload) {
536
// In IE8 (and perhaps elsewhere), it seems like postMessage is sometimes
537
// implemented as a synchronous call. That is, calling it synchronously
538
// calls whatever listeners it has, and control is not returned to the
539
// calling thread until those listeners are run. This produces different
540
// ordering to all other browsers, and breaks this protocol. This timer
541
// callback is introduced to produce standard behavior across all browsers.
542
var transport = this;
543
var channelName = this.channel_.name;
544
var sendFunctor = function() {
545
transport.sendTimerId_ = 0;
546
547
try {
548
// postMessage is a method of the window object, except in some
549
// versions of Opera, where it is a method of the document object. It
550
// also seems that the appearance of postMessage on the peer window
551
// object can sometimes be delayed.
552
var obj = win.postMessage ? win : win.document;
553
if (!obj.postMessage) {
554
goog.log.warning(
555
goog.net.xpc.logger, 'Peer window had no postMessage function.');
556
return;
557
}
558
559
obj.postMessage(
560
channelName + '|' + service + ':' + payload,
561
transport.peerHostname_);
562
goog.log.fine(
563
goog.net.xpc.logger, 'send(): service=' + service + ' payload=' +
564
payload + ' to hostname=' + transport.peerHostname_);
565
} catch (error) {
566
// There is some evidence (not totally convincing) that postMessage can
567
// be missing or throw errors during a narrow timing window during
568
// startup. This protects against that.
569
goog.log.warning(
570
goog.net.xpc.logger, 'Error performing postMessage, ignoring.',
571
error);
572
}
573
};
574
this.sendTimerId_ = goog.Timer.callOnce(sendFunctor, 0);
575
};
576
this.send(service, payload);
577
};
578
579
580
/**
581
* Notify the channel that this transport is connected. If either transport is
582
* protocol v1, a short delay is required to paper over timing vulnerabilities
583
* in that protocol version.
584
* @private
585
*/
586
goog.net.xpc.NativeMessagingTransport.prototype.notifyConnected_ = function() {
587
var delay = (this.protocolVersion_ == 1 || this.peerProtocolVersion_ == 1) ?
588
goog.net.xpc.NativeMessagingTransport.CONNECTION_DELAY_MS_ :
589
undefined;
590
this.channel_.notifyConnected(delay);
591
};
592
593
594
/** @override */
595
goog.net.xpc.NativeMessagingTransport.prototype.disposeInternal = function() {
596
if (this.initialized_) {
597
var listenWindow = this.getWindow();
598
var uid = goog.getUid(listenWindow);
599
var value = goog.net.xpc.NativeMessagingTransport.activeCount_[uid];
600
goog.net.xpc.NativeMessagingTransport.activeCount_[uid] = value - 1;
601
if (value == 1) {
602
goog.events.unlisten(
603
listenWindow.postMessage ? listenWindow : listenWindow.document,
604
'message', goog.net.xpc.NativeMessagingTransport.messageReceived_,
605
false, goog.net.xpc.NativeMessagingTransport);
606
}
607
}
608
609
if (this.sendTimerId_) {
610
goog.Timer.clear(this.sendTimerId_);
611
this.sendTimerId_ = 0;
612
}
613
614
goog.dispose(this.eventHandler_);
615
delete this.eventHandler_;
616
617
goog.dispose(this.maybeAttemptToConnectTimer_);
618
delete this.maybeAttemptToConnectTimer_;
619
620
this.setupAckReceived_.cancel();
621
delete this.setupAckReceived_;
622
this.setupAckSent_.cancel();
623
delete this.setupAckSent_;
624
this.connected_.cancel();
625
delete this.connected_;
626
627
// Cleaning up this.send as it is an instance method, created in
628
// goog.net.xpc.NativeMessagingTransport.prototype.send and has a closure over
629
// this.channel_.peerWindowObject_.
630
delete this.send;
631
632
goog.net.xpc.NativeMessagingTransport.base(this, 'disposeInternal');
633
};
634
635
636
/**
637
* Parse a transport service payload message. For v1, it is simply expected to
638
* be 'SETUP' or 'SETUP_ACK'. For v2, an example setup message is
639
* 'SETUP_NTPV2,abc123', where the second part is the endpoint id. The v2 setup
640
* ack message is simply 'SETUP_ACK_NTPV2'.
641
* @param {string} payload The payload.
642
* @return {!Array<?string>} An array with the message type as the first member
643
* and the endpoint id as the second, if one was sent, or null otherwise.
644
* @private
645
*/
646
goog.net.xpc.NativeMessagingTransport.parseTransportPayload_ = function(
647
payload) {
648
var transportParts = /** @type {!Array<?string>} */ (
649
payload.split(goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_));
650
transportParts[1] = transportParts[1] || null;
651
return transportParts;
652
};
653
654