123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652 |
- // Copyright 2007 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview Contains the class which uses native messaging
- * facilities for cross domain communication.
- *
- */
- goog.provide('goog.net.xpc.NativeMessagingTransport');
- goog.require('goog.Timer');
- goog.require('goog.asserts');
- goog.require('goog.async.Deferred');
- goog.require('goog.events');
- goog.require('goog.events.EventHandler');
- goog.require('goog.log');
- goog.require('goog.net.xpc');
- goog.require('goog.net.xpc.CrossPageChannelRole');
- goog.require('goog.net.xpc.Transport');
- goog.require('goog.net.xpc.TransportTypes');
- /**
- * The native messaging transport
- *
- * Uses document.postMessage() to send messages to other documents.
- * Receiving is done by listening on 'message'-events on the document.
- *
- * @param {goog.net.xpc.CrossPageChannel} channel The channel this
- * transport belongs to.
- * @param {string} peerHostname The hostname (protocol, domain, and port) of the
- * peer.
- * @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for
- * finding the correct window/document.
- * @param {boolean=} opt_oneSidedHandshake If this is true, only the outer
- * transport sends a SETUP message and expects a SETUP_ACK. The inner
- * transport goes connected when it receives the SETUP.
- * @param {number=} opt_protocolVersion Which version of its setup protocol the
- * transport should use. The default is '2'.
- * @constructor
- * @extends {goog.net.xpc.Transport}
- * @final
- */
- goog.net.xpc.NativeMessagingTransport = function(
- channel, peerHostname, opt_domHelper, opt_oneSidedHandshake,
- opt_protocolVersion) {
- goog.net.xpc.NativeMessagingTransport.base(
- this, 'constructor', opt_domHelper);
- /**
- * The channel this transport belongs to.
- * @type {goog.net.xpc.CrossPageChannel}
- * @private
- */
- this.channel_ = channel;
- /**
- * Which version of the transport's protocol should be used.
- * @type {number}
- * @private
- */
- this.protocolVersion_ = opt_protocolVersion || 2;
- goog.asserts.assert(this.protocolVersion_ >= 1);
- goog.asserts.assert(this.protocolVersion_ <= 2);
- /**
- * The hostname of the peer. This parameterizes all calls to postMessage, and
- * should contain the precise protocol, domain, and port of the peer window.
- * @type {string}
- * @private
- */
- this.peerHostname_ = peerHostname || '*';
- /**
- * The event handler.
- * @type {!goog.events.EventHandler<!goog.net.xpc.NativeMessagingTransport>}
- * @private
- */
- this.eventHandler_ = new goog.events.EventHandler(this);
- /**
- * Timer for connection reattempts.
- * @type {!goog.Timer}
- * @private
- */
- this.maybeAttemptToConnectTimer_ = new goog.Timer(100, this.getWindow());
- /**
- * Whether one-sided handshakes are enabled.
- * @type {boolean}
- * @private
- */
- this.oneSidedHandshake_ = !!opt_oneSidedHandshake;
- /**
- * Fires once we've received our SETUP_ACK message.
- * @type {!goog.async.Deferred}
- * @private
- */
- this.setupAckReceived_ = new goog.async.Deferred();
- /**
- * Fires once we've sent our SETUP_ACK message.
- * @type {!goog.async.Deferred}
- * @private
- */
- this.setupAckSent_ = new goog.async.Deferred();
- /**
- * Fires once we're marked connected.
- * @type {!goog.async.Deferred}
- * @private
- */
- this.connected_ = new goog.async.Deferred();
- /**
- * The unique ID of this side of the connection. Used to determine when a peer
- * is reloaded.
- * @type {string}
- * @private
- */
- this.endpointId_ = goog.net.xpc.getRandomString(10);
- /**
- * The unique ID of the peer. If we get a message from a peer with an ID we
- * don't expect, we reset the connection.
- * @type {?string}
- * @private
- */
- this.peerEndpointId_ = null;
- // We don't want to mark ourselves connected until we have sent whatever
- // message will cause our counterpart in the other frame to also declare
- // itself connected, if there is such a message. Otherwise we risk a user
- // message being sent in advance of that message, and it being discarded.
- if (this.oneSidedHandshake_) {
- if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.INNER) {
- // One sided handshake, inner frame:
- // SETUP_ACK must be received.
- this.connected_.awaitDeferred(this.setupAckReceived_);
- } else {
- // One sided handshake, outer frame:
- // SETUP_ACK must be sent.
- this.connected_.awaitDeferred(this.setupAckSent_);
- }
- } else {
- // Two sided handshake:
- // SETUP_ACK has to have been received, and sent.
- this.connected_.awaitDeferred(this.setupAckReceived_);
- if (this.protocolVersion_ == 2) {
- this.connected_.awaitDeferred(this.setupAckSent_);
- }
- }
- this.connected_.addCallback(this.notifyConnected_, this);
- this.connected_.callback(true);
- this.eventHandler_.listen(
- this.maybeAttemptToConnectTimer_, goog.Timer.TICK,
- this.maybeAttemptToConnect_);
- goog.log.info(
- goog.net.xpc.logger, 'NativeMessagingTransport created. ' +
- 'protocolVersion=' + this.protocolVersion_ + ', oneSidedHandshake=' +
- this.oneSidedHandshake_ + ', role=' + this.channel_.getRole());
- };
- goog.inherits(goog.net.xpc.NativeMessagingTransport, goog.net.xpc.Transport);
- /**
- * Length of the delay in milliseconds between the channel being connected and
- * the connection callback being called, in cases where coverage of timing flaws
- * is required.
- * @type {number}
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.CONNECTION_DELAY_MS_ = 200;
- /**
- * Current determination of peer's protocol version, or null for unknown.
- * @type {?number}
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.prototype.peerProtocolVersion_ = null;
- /**
- * Flag indicating if this instance of the transport has been initialized.
- * @type {boolean}
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.prototype.initialized_ = false;
- /**
- * The transport type.
- * @type {number}
- * @override
- */
- goog.net.xpc.NativeMessagingTransport.prototype.transportType =
- goog.net.xpc.TransportTypes.NATIVE_MESSAGING;
- /**
- * The delimiter used for transport service messages.
- * @type {string}
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_ = ',';
- /**
- * Tracks the number of NativeMessagingTransport channels that have been
- * initialized but not disposed yet in a map keyed by the UID of the window
- * object. This allows for multiple windows to be initiallized and listening
- * for messages.
- * @type {Object<number>}
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.activeCount_ = {};
- /**
- * Id of a timer user during postMessage sends.
- * @type {number}
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.prototype.sendTimerId_ = 0;
- /**
- * Checks whether the peer transport protocol version could be as indicated.
- * @param {number} version The version to check for.
- * @return {boolean} Whether the peer transport protocol version is as
- * indicated, or null.
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.prototype.couldPeerVersionBe_ = function(
- version) {
- return this.peerProtocolVersion_ == null ||
- this.peerProtocolVersion_ == version;
- };
- /**
- * Initializes this transport. Registers a listener for 'message'-events
- * on the document.
- * @param {Window} listenWindow The window to listen to events on.
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.initialize_ = function(listenWindow) {
- var uid = goog.getUid(listenWindow);
- var value = goog.net.xpc.NativeMessagingTransport.activeCount_[uid];
- if (!goog.isNumber(value)) {
- value = 0;
- }
- if (value == 0) {
- // Listen for message-events. These are fired on window in FF3 and on
- // document in Opera.
- goog.events.listen(
- listenWindow.postMessage ? listenWindow : listenWindow.document,
- 'message', goog.net.xpc.NativeMessagingTransport.messageReceived_,
- false, goog.net.xpc.NativeMessagingTransport);
- }
- goog.net.xpc.NativeMessagingTransport.activeCount_[uid] = value + 1;
- };
- /**
- * Processes an incoming message-event.
- * @param {goog.events.BrowserEvent} msgEvt The message event.
- * @return {boolean} True if message was successfully delivered to a channel.
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.messageReceived_ = function(msgEvt) {
- var data = msgEvt.getBrowserEvent().data;
- if (!goog.isString(data)) {
- return false;
- }
- var headDelim = data.indexOf('|');
- var serviceDelim = data.indexOf(':');
- // make sure we got something reasonable
- if (headDelim == -1 || serviceDelim == -1) {
- return false;
- }
- var channelName = data.substring(0, headDelim);
- var service = data.substring(headDelim + 1, serviceDelim);
- var payload = data.substring(serviceDelim + 1);
- goog.log.fine(
- goog.net.xpc.logger, 'messageReceived: channel=' + channelName +
- ', service=' + service + ', payload=' + payload);
- // Attempt to deliver message to the channel. Keep in mind that it may not
- // exist for several reasons, including but not limited to:
- // - a malformed message
- // - the channel simply has not been created
- // - channel was created in a different namespace
- // - message was sent to the wrong window
- // - channel has become stale (e.g. caching iframes and back clicks)
- var channel = goog.net.xpc.channels[channelName];
- if (channel) {
- channel.xpcDeliver(
- service, payload,
- /** @type {!MessageEvent} */ (msgEvt.getBrowserEvent()).origin);
- return true;
- }
- var transportMessageType =
- goog.net.xpc.NativeMessagingTransport.parseTransportPayload_(payload)[0];
- // Check if there are any stale channel names that can be updated.
- for (var staleChannelName in goog.net.xpc.channels) {
- var staleChannel = goog.net.xpc.channels[staleChannelName];
- if (staleChannel.getRole() == goog.net.xpc.CrossPageChannelRole.INNER &&
- !staleChannel.isConnected() &&
- service == goog.net.xpc.TRANSPORT_SERVICE_ &&
- (transportMessageType == goog.net.xpc.SETUP ||
- transportMessageType == goog.net.xpc.SETUP_NTPV2) &&
- staleChannel.isMessageOriginAcceptable(
- msgEvt.getBrowserEvent().origin)) {
- // Inner peer received SETUP message but channel names did not match.
- // Start using the channel name sent from outer peer. The channel name
- // of the inner peer can easily become out of date, as iframe's and their
- // JS state get cached in many browsers upon page reload or history
- // navigation (particularly Firefox 1.5+). We can trust the outer peer,
- // since we only accept postMessage messages from the same hostname that
- // originally setup the channel.
- staleChannel.updateChannelNameAndCatalog(channelName);
- staleChannel.xpcDeliver(service, payload);
- return true;
- }
- }
- // Failed to find a channel to deliver this message to, so simply ignore it.
- goog.log.info(goog.net.xpc.logger, 'channel name mismatch; message ignored"');
- return false;
- };
- /**
- * Handles transport service messages.
- * @param {string} payload The message content.
- * @override
- */
- goog.net.xpc.NativeMessagingTransport.prototype.transportServiceHandler =
- function(payload) {
- var transportParts =
- goog.net.xpc.NativeMessagingTransport.parseTransportPayload_(payload);
- var transportMessageType = transportParts[0];
- var peerEndpointId = transportParts[1];
- switch (transportMessageType) {
- case goog.net.xpc.SETUP_ACK_:
- this.setPeerProtocolVersion_(1);
- if (!this.setupAckReceived_.hasFired()) {
- this.setupAckReceived_.callback(true);
- }
- break;
- case goog.net.xpc.SETUP_ACK_NTPV2:
- if (this.protocolVersion_ == 2) {
- this.setPeerProtocolVersion_(2);
- if (!this.setupAckReceived_.hasFired()) {
- this.setupAckReceived_.callback(true);
- }
- }
- break;
- case goog.net.xpc.SETUP:
- this.setPeerProtocolVersion_(1);
- this.sendSetupAckMessage_(1);
- break;
- case goog.net.xpc.SETUP_NTPV2:
- if (this.protocolVersion_ == 2) {
- var prevPeerProtocolVersion = this.peerProtocolVersion_;
- this.setPeerProtocolVersion_(2);
- this.sendSetupAckMessage_(2);
- if ((prevPeerProtocolVersion == 1 || this.peerEndpointId_ != null) &&
- this.peerEndpointId_ != peerEndpointId) {
- // Send a new SETUP message since the peer has been replaced.
- goog.log.info(
- goog.net.xpc.logger,
- 'Sending SETUP and changing peer ID to: ' + peerEndpointId);
- this.sendSetupMessage_();
- }
- this.peerEndpointId_ = peerEndpointId;
- }
- break;
- }
- };
- /**
- * Sends a SETUP transport service message of the correct protocol number for
- * our current situation.
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.prototype.sendSetupMessage_ = function() {
- // 'real' (legacy) v1 transports don't know about there being v2 ones out
- // there, and we shouldn't either.
- goog.asserts.assert(
- !(this.protocolVersion_ == 1 && this.peerProtocolVersion_ == 2));
- if (this.protocolVersion_ == 2 && this.couldPeerVersionBe_(2)) {
- var payload = goog.net.xpc.SETUP_NTPV2;
- payload += goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_;
- payload += this.endpointId_;
- this.send(goog.net.xpc.TRANSPORT_SERVICE_, payload);
- }
- // For backward compatibility reasons, the V1 SETUP message can be sent by
- // both V1 and V2 transports. Once a V2 transport has 'heard' another V2
- // transport it starts ignoring V1 messages, so the V2 message must be sent
- // first.
- if (this.couldPeerVersionBe_(1)) {
- this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP);
- }
- };
- /**
- * Sends a SETUP_ACK transport service message of the correct protocol number
- * for our current situation.
- * @param {number} protocolVersion The protocol version of the SETUP message
- * which gave rise to this ack message.
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.prototype.sendSetupAckMessage_ = function(
- protocolVersion) {
- goog.asserts.assert(
- this.protocolVersion_ != 1 || protocolVersion != 2,
- 'Shouldn\'t try to send a v2 setup ack in v1 mode.');
- if (this.protocolVersion_ == 2 && this.couldPeerVersionBe_(2) &&
- protocolVersion == 2) {
- this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_NTPV2);
- } else if (this.couldPeerVersionBe_(1) && protocolVersion == 1) {
- this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
- } else {
- return;
- }
- if (!this.setupAckSent_.hasFired()) {
- this.setupAckSent_.callback(true);
- }
- };
- /**
- * Attempts to set the peer protocol number. Downgrades from 2 to 1 are not
- * permitted.
- * @param {number} version The new protocol number.
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.prototype.setPeerProtocolVersion_ =
- function(version) {
- if (version > this.peerProtocolVersion_) {
- this.peerProtocolVersion_ = version;
- }
- if (this.peerProtocolVersion_ == 1) {
- if (!this.setupAckSent_.hasFired() && !this.oneSidedHandshake_) {
- this.setupAckSent_.callback(true);
- }
- this.peerEndpointId_ = null;
- }
- };
- /**
- * Connects this transport.
- * @override
- */
- goog.net.xpc.NativeMessagingTransport.prototype.connect = function() {
- goog.net.xpc.NativeMessagingTransport.initialize_(this.getWindow());
- this.initialized_ = true;
- this.maybeAttemptToConnect_();
- };
- /**
- * Connects to other peer. In the case of the outer peer, the setup messages are
- * likely sent before the inner peer is ready to receive them. Therefore, this
- * function will continue trying to send the SETUP message until the inner peer
- * responds. In the case of the inner peer, it will occasionally have its
- * channel name fall out of sync with the outer peer, particularly during
- * soft-reloads and history navigations.
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.prototype.maybeAttemptToConnect_ =
- function() {
- // In a one-sided handshake, the outer frame does not send a SETUP message,
- // but the inner frame does.
- var outerFrame =
- this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER;
- if ((this.oneSidedHandshake_ && outerFrame) || this.channel_.isConnected() ||
- this.isDisposed()) {
- this.maybeAttemptToConnectTimer_.stop();
- return;
- }
- this.maybeAttemptToConnectTimer_.start();
- this.sendSetupMessage_();
- };
- /**
- * Sends a message.
- * @param {string} service The name off the service the message is to be
- * delivered to.
- * @param {string} payload The message content.
- * @override
- */
- goog.net.xpc.NativeMessagingTransport.prototype.send = function(
- service, payload) {
- var win = this.channel_.getPeerWindowObject();
- if (!win) {
- goog.log.fine(goog.net.xpc.logger, 'send(): window not ready');
- return;
- }
- this.send = function(service, payload) {
- // In IE8 (and perhaps elsewhere), it seems like postMessage is sometimes
- // implemented as a synchronous call. That is, calling it synchronously
- // calls whatever listeners it has, and control is not returned to the
- // calling thread until those listeners are run. This produces different
- // ordering to all other browsers, and breaks this protocol. This timer
- // callback is introduced to produce standard behavior across all browsers.
- var transport = this;
- var channelName = this.channel_.name;
- var sendFunctor = function() {
- transport.sendTimerId_ = 0;
- try {
- // postMessage is a method of the window object, except in some
- // versions of Opera, where it is a method of the document object. It
- // also seems that the appearance of postMessage on the peer window
- // object can sometimes be delayed.
- var obj = win.postMessage ? win : win.document;
- if (!obj.postMessage) {
- goog.log.warning(
- goog.net.xpc.logger, 'Peer window had no postMessage function.');
- return;
- }
- obj.postMessage(
- channelName + '|' + service + ':' + payload,
- transport.peerHostname_);
- goog.log.fine(
- goog.net.xpc.logger, 'send(): service=' + service + ' payload=' +
- payload + ' to hostname=' + transport.peerHostname_);
- } catch (error) {
- // There is some evidence (not totally convincing) that postMessage can
- // be missing or throw errors during a narrow timing window during
- // startup. This protects against that.
- goog.log.warning(
- goog.net.xpc.logger, 'Error performing postMessage, ignoring.',
- error);
- }
- };
- this.sendTimerId_ = goog.Timer.callOnce(sendFunctor, 0);
- };
- this.send(service, payload);
- };
- /**
- * Notify the channel that this transport is connected. If either transport is
- * protocol v1, a short delay is required to paper over timing vulnerabilities
- * in that protocol version.
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.prototype.notifyConnected_ = function() {
- var delay = (this.protocolVersion_ == 1 || this.peerProtocolVersion_ == 1) ?
- goog.net.xpc.NativeMessagingTransport.CONNECTION_DELAY_MS_ :
- undefined;
- this.channel_.notifyConnected(delay);
- };
- /** @override */
- goog.net.xpc.NativeMessagingTransport.prototype.disposeInternal = function() {
- if (this.initialized_) {
- var listenWindow = this.getWindow();
- var uid = goog.getUid(listenWindow);
- var value = goog.net.xpc.NativeMessagingTransport.activeCount_[uid];
- goog.net.xpc.NativeMessagingTransport.activeCount_[uid] = value - 1;
- if (value == 1) {
- goog.events.unlisten(
- listenWindow.postMessage ? listenWindow : listenWindow.document,
- 'message', goog.net.xpc.NativeMessagingTransport.messageReceived_,
- false, goog.net.xpc.NativeMessagingTransport);
- }
- }
- if (this.sendTimerId_) {
- goog.Timer.clear(this.sendTimerId_);
- this.sendTimerId_ = 0;
- }
- goog.dispose(this.eventHandler_);
- delete this.eventHandler_;
- goog.dispose(this.maybeAttemptToConnectTimer_);
- delete this.maybeAttemptToConnectTimer_;
- this.setupAckReceived_.cancel();
- delete this.setupAckReceived_;
- this.setupAckSent_.cancel();
- delete this.setupAckSent_;
- this.connected_.cancel();
- delete this.connected_;
- // Cleaning up this.send as it is an instance method, created in
- // goog.net.xpc.NativeMessagingTransport.prototype.send and has a closure over
- // this.channel_.peerWindowObject_.
- delete this.send;
- goog.net.xpc.NativeMessagingTransport.base(this, 'disposeInternal');
- };
- /**
- * Parse a transport service payload message. For v1, it is simply expected to
- * be 'SETUP' or 'SETUP_ACK'. For v2, an example setup message is
- * 'SETUP_NTPV2,abc123', where the second part is the endpoint id. The v2 setup
- * ack message is simply 'SETUP_ACK_NTPV2'.
- * @param {string} payload The payload.
- * @return {!Array<?string>} An array with the message type as the first member
- * and the endpoint id as the second, if one was sent, or null otherwise.
- * @private
- */
- goog.net.xpc.NativeMessagingTransport.parseTransportPayload_ = function(
- payload) {
- var transportParts = /** @type {!Array<?string>} */ (
- payload.split(goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_));
- transportParts[1] = transportParts[1] || null;
- return transportParts;
- };
|