| Index: chrome/browser/resources/gaia_auth_host/post_message_channel.js
|
| diff --git a/chrome/browser/resources/gaia_auth_host/post_message_channel.js b/chrome/browser/resources/gaia_auth_host/post_message_channel.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b63f93b20391bba911cadee173aae42f32eadfae
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/gaia_auth_host/post_message_channel.js
|
| @@ -0,0 +1,372 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +/**
|
| + * @fileoverview
|
| + * Provides a HTML5 postMessage channel to the injected JS to talk back
|
| + * to Authenticator.
|
| + */
|
| +'use strict';
|
| +
|
| +<include src="../gaia_auth/channel.js">
|
| +
|
| +var PostMessageChannel = (function() {
|
| + /**
|
| + * Allowed origins of the hosting page.
|
| + * @type {Array.<string>}
|
| + */
|
| + var ALLOWED_ORIGINS = [
|
| + 'chrome://oobe',
|
| + 'chrome://chrome-signin'
|
| + ];
|
| +
|
| + /** @const */
|
| + var PORT_MESSAGE = 'post-message-port-message';
|
| +
|
| + /** @const */
|
| + var CHANNEL_INIT_MESSAGE = 'post-message-channel-init';
|
| +
|
| + /** @const */
|
| + var CHANNEL_CONNECT_MESSAGE = 'post-message-channel-connect';
|
| +
|
| + /**
|
| + * Whether the script runs in a top level window.
|
| + */
|
| + function isTopLevel() {
|
| + return window === window.top;
|
| + }
|
| +
|
| + /**
|
| + * A simple event target.
|
| + */
|
| + function EventTarget() {
|
| + this.listeners_ = [];
|
| + }
|
| +
|
| + EventTarget.prototype = {
|
| + /**
|
| + * Add an event listener.
|
| + */
|
| + addListener: function(listener) {
|
| + this.listeners_.push(listener);
|
| + },
|
| +
|
| + /**
|
| + * Dispatches a given event to all listeners.
|
| + */
|
| + dispatch: function(e) {
|
| + for (var i = 0; i < this.listeners_.length; ++i) {
|
| + this.listeners_[i].call(undefined, e);
|
| + }
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * ChannelManager handles window message events by dispatching them to
|
| + * PostMessagePorts or forwarding to other windows (up/down the hierarchy).
|
| + * @constructor
|
| + */
|
| + function ChannelManager() {
|
| + /**
|
| + * Window and origin to forward message up the hierarchy. For subframes,
|
| + * they defaults to window.parent and any origin. For top level window,
|
| + * this would be set to the hosting webview on CHANNEL_INIT_MESSAGE.
|
| + */
|
| + this.upperWindow = isTopLevel() ? null : window.parent;
|
| + this.upperOrigin = isTopLevel() ? '' : '*';
|
| +
|
| + /**
|
| + * Channle Id to port map.
|
| + * @type {Object.<number, PostMessagePort>}
|
| + */
|
| + this.channels_ = {};
|
| +
|
| + /**
|
| + * Deferred messages to be posted to |upperWindow|.
|
| + * @type {Array}
|
| + */
|
| + this.deferredUpperWindowMessages_ = [];
|
| +
|
| + /**
|
| + * Ports that depend on upperWindow and need to be setup when its available.
|
| + */
|
| + this.deferredUpperWindowPorts_ = [];
|
| +
|
| + /**
|
| + * Whether the ChannelManager runs in daemon mode and accepts connections.
|
| + */
|
| + this.isDaemon = false;
|
| +
|
| + /**
|
| + * Fires when ChannelManager is in listening mode and a
|
| + * CHANNEL_CONNECT_MESSAGE is received.
|
| + */
|
| + this.onConnect = new EventTarget();
|
| +
|
| + window.addEventListener('message', this.onMessage_.bind(this));
|
| + }
|
| +
|
| + ChannelManager.prototype = {
|
| + /**
|
| + * Gets a global unique id to use.
|
| + * @return {number}
|
| + */
|
| + createChannelId_: function() {
|
| + return (new Date()).getTime();
|
| + },
|
| +
|
| + /**
|
| + * Posts data to upperWindow. Queue it if upperWindow is not available.
|
| + */
|
| + postToUpperWindow: function(data) {
|
| + if (this.upperWindow == null) {
|
| + this.deferredUpperWindowMessages_.push(data);
|
| + return;
|
| + }
|
| +
|
| + this.upperWindow.postMessage(data, this.upperOrigin);
|
| + },
|
| +
|
| + /**
|
| + * Creates a port and register it in |channels_|.
|
| + * @param {number} channelId
|
| + * @param {string} channelName
|
| + * @param {DOMWindow=} opt_targetWindow
|
| + * @param {string=} opt_targetOrigin
|
| + */
|
| + createPort: function(
|
| + channelId, channelName, opt_targetWindow, opt_targetOrigin) {
|
| + var port = new PostMessagePort(channelId, channelName);
|
| + if (opt_targetWindow)
|
| + port.setTarget(opt_targetWindow, opt_targetOrigin);
|
| + this.channels_[channelId] = port;
|
| + return port;
|
| + },
|
| +
|
| + /*
|
| + * Returns a message forward handler for the given proxy port.
|
| + * @private
|
| + */
|
| + getProxyPortForwardHandler_: function(proxyPort) {
|
| + return function(msg) { proxyPort.postMessage(msg); };
|
| + },
|
| +
|
| + /**
|
| + * Creates a forwarding porxy port.
|
| + * @param {number} channelId
|
| + * @param {string} channelName
|
| + * @param {!DOMWindow} targetWindow
|
| + * @param {!string} targetOrigin
|
| + */
|
| + createProxyPort: function(
|
| + channelId, channelName, targetWindow, targetOrigin) {
|
| + var port = this.createPort(
|
| + channelId, channelName, targetWindow, targetOrigin);
|
| + port.onMessage.addListener(this.getProxyPortForwardHandler_(port));
|
| + return port;
|
| + },
|
| +
|
| + /**
|
| + * Creates a connecting port to the daemon and request connection.
|
| + * @param {string} name
|
| + * @return {PostMessagePort}
|
| + */
|
| + connectToDaemon: function(name) {
|
| + if (this.isDaemon) {
|
| + console.error(
|
| + 'Error: Connecting from the daemon page is not supported.');
|
| + return;
|
| + }
|
| +
|
| + var port = this.createPort(this.createChannelId_(), name);
|
| + if (this.upperWindow) {
|
| + port.setTarget(this.upperWindow, this.upperOrigin);
|
| + } else {
|
| + this.deferredUpperWindowPorts_.push(port);
|
| + }
|
| +
|
| + this.postToUpperWindow({
|
| + type: CHANNEL_CONNECT_MESSAGE,
|
| + channelId: port.channelId,
|
| + channelName: port.name
|
| + });
|
| + return port;
|
| + },
|
| +
|
| + /**
|
| + * Dispatches a 'message' event to port.
|
| + * @private
|
| + */
|
| + dispatchMessageToPort_: function(e) {
|
| + var channelId = e.data.channelId;
|
| + var port = this.channels_[channelId];
|
| + if (!port) {
|
| + console.error('Error: Unable to dispatch message. Unknown channel.');
|
| + return;
|
| + }
|
| +
|
| + port.handleWindowMessage(e);
|
| + },
|
| +
|
| + /**
|
| + * Window 'message' handler.
|
| + */
|
| + onMessage_: function(e) {
|
| + if (typeof e.data != 'object' ||
|
| + !e.data.hasOwnProperty('type')) {
|
| + return;
|
| + }
|
| +
|
| + if (e.data.type === PORT_MESSAGE) {
|
| + // Dispatch port message to ports if this is the daemon page or
|
| + // the message is from upperWindow. In case of null upperWindow,
|
| + // the message is assumed to be forwarded to upperWindow and queued.
|
| + if (this.isDaemon ||
|
| + (this.upperWindow && e.source === this.upperWindow)) {
|
| + this.dispatchMessageToPort_(e);
|
| + } else {
|
| + this.postToUpperWindow(e.data);
|
| + }
|
| + } else if (e.data.type === CHANNEL_CONNECT_MESSAGE) {
|
| + var channelId = e.data.channelId;
|
| + var channelName = e.data.channelName;
|
| +
|
| + if (this.isDaemon) {
|
| + var port = this.createPort(
|
| + channelId, channelName, e.source, e.origin);
|
| + this.onConnect.dispatch(port);
|
| + } else {
|
| + this.createProxyPort(channelId, channelName, e.source, e.origin);
|
| + this.postToUpperWindow(e.data);
|
| + }
|
| + } else if (e.data.type === CHANNEL_INIT_MESSAGE) {
|
| + if (ALLOWED_ORIGINS.indexOf(e.origin) == -1)
|
| + return;
|
| +
|
| + this.upperWindow = e.source;
|
| + this.upperOrigin = e.origin;
|
| +
|
| + for (var i = 0; i < this.deferredUpperWindowMessages_.length; ++i) {
|
| + this.upperWindow.postMessage(this.deferredUpperWindowMessages_[i],
|
| + this.upperOrigin);
|
| + }
|
| + this.deferredUpperWindowMessages_ = [];
|
| +
|
| + for (var i = 0; i < this.deferredUpperWindowPorts_.length; ++i) {
|
| + this.deferredUpperWindowPorts_[i].setTarget(this.upperWindow,
|
| + this.upperOrigin);
|
| + }
|
| + this.deferredUpperWindowPorts_ = [];
|
| + }
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * Singleton instance of ChannelManager.
|
| + * @type {ChannelManager}
|
| + */
|
| + var channelManager = new ChannelManager();
|
| +
|
| + /**
|
| + * A HTML5 postMessage based port that provides the same port interface
|
| + * as the messaging API port.
|
| + * @param {number} channelId
|
| + * @param {string} name
|
| + */
|
| + function PostMessagePort(channelId, name) {
|
| + this.channelId = channelId;
|
| + this.name = name;
|
| + this.targetWindow = null;
|
| + this.targetOrigin = '';
|
| + this.deferredMessages_ = [];
|
| +
|
| + this.onMessage = new EventTarget();
|
| + };
|
| +
|
| + PostMessagePort.prototype = {
|
| + /**
|
| + * Sets the target window and origin.
|
| + * @param {DOMWindow} targetWindow
|
| + * @param {string} targetOrigin
|
| + */
|
| + setTarget: function(targetWindow, targetOrigin) {
|
| + this.targetWindow = targetWindow;
|
| + this.targetOrigin = targetOrigin;
|
| +
|
| + for (var i = 0; i < this.deferredMessages_.length; ++i) {
|
| + this.postMessage(this.deferredMessages_[i]);
|
| + }
|
| + this.deferredMessages_ = [];
|
| + },
|
| +
|
| + postMessage: function(msg) {
|
| + if (!this.targetWindow) {
|
| + this.deferredMessages_.push(msg);
|
| + return;
|
| + }
|
| +
|
| + this.targetWindow.postMessage({
|
| + type: PORT_MESSAGE,
|
| + channelId: this.channelId,
|
| + payload: msg
|
| + }, this.targetOrigin);
|
| + },
|
| +
|
| + handleWindowMessage: function(e) {
|
| + this.onMessage.dispatch(e.data.payload);
|
| + }
|
| + };
|
| +
|
| + /**
|
| + * A message channel based on PostMessagePort.
|
| + * @extends {Channel}
|
| + * @constructor
|
| + */
|
| + function PostMessageChannel() {
|
| + };
|
| +
|
| + PostMessageChannel.prototype = {
|
| + __proto__: Channel.prototype,
|
| +
|
| + /** @override */
|
| + connect: function(name) {
|
| + this.port_ = channelManager.connectToDaemon(name);
|
| + this.port_.onMessage.addListener(this.onMessage_.bind(this));
|
| + },
|
| + };
|
| +
|
| + /**
|
| + * Initialize webview content window for postMessage channel.
|
| + * @param {DOMWindow} webViewContentWindow Content window of the webview.
|
| + */
|
| + PostMessageChannel.init = function(webViewContentWindow) {
|
| + webViewContentWindow.postMessage({
|
| + type: CHANNEL_INIT_MESSAGE
|
| + }, '*');
|
| + };
|
| +
|
| + /**
|
| + * Run in daemon mode and listen for incoming connections. Note that the
|
| + * current implementation assumes the daemon runs in the hosting page
|
| + * at the upper layer of the DOM tree. That is, all connect requests go
|
| + * up the DOM tree instead of going into sub frames.
|
| + * @param {function(PostMessagePort)} callback Invoked when a connection is
|
| + * made.
|
| + */
|
| + PostMessageChannel.runAsDaemon = function(callback) {
|
| + channelManager.isDaemon = true;
|
| +
|
| + var onConnect = function(port) {
|
| + callback(port);
|
| + };
|
| + channelManager.onConnect.addListener(onConnect);
|
| + };
|
| +
|
| + return PostMessageChannel;
|
| +})();
|
| +
|
| +/** @override */
|
| +Channel.create = function() {
|
| + return new PostMessageChannel();
|
| +};
|
|
|