diff options
Diffstat (limited to 'chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js')
-rw-r--r-- | chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js new file mode 100644 index 00000000000..722c7e0b150 --- /dev/null +++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/chrome/extension_bridge.js @@ -0,0 +1,352 @@ +// Copyright 2014 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 Bridge to aid in communication between a Chrome + * background page and content script. + * + * It automatically figures out where it's being run and initializes itself + * appropriately. Then just call send() to send a message from the background + * to the page or vice versa, and addMessageListener() to provide a message + * listener. Messages can be any object that can be serialized using JSON. + * + */ + +goog.provide('cvox.ExtensionBridge'); + +goog.require('cvox.ChromeVoxJSON'); + +/** + * @constructor + */ +cvox.ExtensionBridge = function() {}; + +/** + * Initialize the extension bridge. Dynamically figure out whether we're in + * the background page, content script, or in a page, and call the + * corresponding function for more specific initialization. + */ +cvox.ExtensionBridge.init = function() { + var self = cvox.ExtensionBridge; + self.messageListeners = []; + self.disconnectListeners = []; + + if (/^chrome-extension:\/\/.*background\.html$/.test(window.location.href)) { + // This depends on the fact that the background page has a specific url. We + // should never be loaded into another extension's background page, so this + // is a safe check. + self.context = self.BACKGROUND; + self.initBackground(); + return; + } + + if (chrome && chrome.extension) { + self.context = self.CONTENT_SCRIPT; + self.initContentScript(); + } +}; + +/** + * Constant indicating we're in a background page. + * @type {number} + * @const + */ +cvox.ExtensionBridge.BACKGROUND = 0; + +/** + * Constant indicating we're in a content script. + * @type {number} + * @const + */ +cvox.ExtensionBridge.CONTENT_SCRIPT = 1; + +/** + * The name of the port between the content script and background page. + * @type {string} + * @const + */ +cvox.ExtensionBridge.PORT_NAME = 'cvox.ExtensionBridge.Port'; + +/** + * The name of the message between the content script and background to + * see if they're connected. + * @type {string} + * @const + */ +cvox.ExtensionBridge.PING_MSG = 'cvox.ExtensionBridge.Ping'; + +/** + * The name of the message between the background and content script to + * confirm that they're connected. + * @type {string} + * @const + */ +cvox.ExtensionBridge.PONG_MSG = 'cvox.ExtensionBridge.Pong'; + +/** + * Send a message. If the context is a page, sends a message to the + * extension background page. If the context is a background page, sends + * a message to the current active tab (not all tabs). + * + * @param {Object} message The message to be sent. + */ +cvox.ExtensionBridge.send = function(message) { + var self = cvox.ExtensionBridge; + switch (self.context) { + case self.BACKGROUND: + self.sendBackgroundToContentScript(message); + break; + case self.CONTENT_SCRIPT: + self.sendContentScriptToBackground(message); + break; + } +}; + +/** + * Provide a function to listen to messages. In page context, this + * listens to messages from the background. In background context, + * this listens to messages from all pages. + * + * The function gets called with two parameters: the message, and a + * port that can be used to send replies. + * + * @param {function(Object, Port)} listener The message listener. + */ +cvox.ExtensionBridge.addMessageListener = function(listener) { + cvox.ExtensionBridge.messageListeners.push(listener); +}; + +/** + * Provide a function to be called when the connection is + * disconnected. + * + * @param {function()} listener The listener. + */ +cvox.ExtensionBridge.addDisconnectListener = function(listener) { + cvox.ExtensionBridge.disconnectListeners.push(listener); +}; + +/** + * Removes all message listeners from the extension bridge. + */ +cvox.ExtensionBridge.removeMessageListeners = function() { + cvox.ExtensionBridge.messageListeners.length = 0; +}; + +/** + * Returns a unique id for this instance of the script. + * + * @return {number} + */ +cvox.ExtensionBridge.uniqueId = function() { + return cvox.ExtensionBridge.id_; +}; + +/** + * Initialize the extension bridge in a background page context by registering + * a listener for connections from the content script. + */ +cvox.ExtensionBridge.initBackground = function() { + var self = cvox.ExtensionBridge; + + /** @type {!Array.<Port>} @private */ + self.portCache_ = []; + /** @type {number} */ + self.nextPongId_ = 1; + /** @type {number} */ + self.id_ = 0; + + var onConnectHandler = function(port) { + if (port.name != self.PORT_NAME) { + return; + } + + self.portCache_.push(port); + + port.onMessage.addListener( + function(message) { + if (message[cvox.ExtensionBridge.PING_MSG]) { + var pongMessage = {}; + pongMessage[cvox.ExtensionBridge.PONG_MSG] = self.nextPongId_++; + port.postMessage(pongMessage); + return; + } + for (var i = 0; i < self.messageListeners.length; i++) { + self.messageListeners[i](message, port); + } + }); + + port.onDisconnect.addListener(function(message) { + for (var i = 0; i < self.portCache_.length; i++) { + if (self.portCache_[i] == port) { + self.portCache_.splice(i, 1); + break; + } + } + }); + }; + + chrome.extension.onConnect.addListener(onConnectHandler); +}; + +/** + * Initialize the extension bridge in a content script context, listening + * for messages from the background page. + */ +cvox.ExtensionBridge.initContentScript = function() { + var self = cvox.ExtensionBridge; + self.connected = false; + self.pingAttempts = 0; + self.queuedMessages = []; + /** @type {number} */ + self.id_ = -1; + + var onMessageHandler = function(request, sender, sendResponse) { + if (request && request['srcFile']) { + // TODO (clchen, deboer): Investigate this further and come up with a + // cleaner solution. The root issue is that this should never be run on + // the background page, but it is in the Chrome OS case. + return; + } + if (request[cvox.ExtensionBridge.PONG_MSG]) { + self.gotPongFromBackgroundPage(request[cvox.ExtensionBridge.PONG_MSG]); + } else { + for (var i = 0; i < self.messageListeners.length; i++) { + self.messageListeners[i](request, cvox.ExtensionBridge.backgroundPort); + } + } + sendResponse({}); + }; + + // Listen to requests from the background that don't come from + // our connection port. + chrome.extension.onMessage.addListener(onMessageHandler); + + self.setupBackgroundPort(); + + self.tryToPingBackgroundPage(); +}; + +/** + * Set up the connection to the background page. + */ +cvox.ExtensionBridge.setupBackgroundPort = function() { + // Set up the connection to the background page. + var self = cvox.ExtensionBridge; + self.backgroundPort = chrome.extension.connect({name: self.PORT_NAME}); + self.backgroundPort.onMessage.addListener( + function(message) { + if (message[cvox.ExtensionBridge.PONG_MSG]) { + self.gotPongFromBackgroundPage( + message[cvox.ExtensionBridge.PONG_MSG]); + } else { + for (var i = 0; i < self.messageListeners.length; i++) { + self.messageListeners[i](message, self.backgroundPort); + } + } + }); + self.backgroundPort.onDisconnect.addListener( + function(event) { + // If we're not connected yet, don't give up - try again. + if (!self.connected) { + self.backgroundPort = null; + return; + } + + for (var i = 0; i < self.disconnectListeners.length; i++) { + self.disconnectListeners[i](); + } + }); +}; + +/** + * Try to ping the background page. + */ +cvox.ExtensionBridge.tryToPingBackgroundPage = function() { + var self = cvox.ExtensionBridge; + + // If we already got a pong, great - we're done. + if (self.connected) { + return; + } + + self.pingAttempts++; + if (self.pingAttempts > 5) { + // Could not connect after 5 ping attempts. Call the disconnect + // handlers, which will disable ChromeVox. + for (var i = 0; i < self.disconnectListeners.length; i++) { + self.disconnectListeners[i](); + } + return; + } + + // Send the ping. + var msg = {}; + msg[cvox.ExtensionBridge.PING_MSG] = 1; + if (!self.backgroundPort) { + self.setupBackgroundPort(); + } + self.backgroundPort.postMessage(msg); + + // Check again in 500 ms in case we get no response. + window.setTimeout(cvox.ExtensionBridge.tryToPingBackgroundPage, 500); +}; + +/** + * Got pong from the background page, now we know the connection was + * successful. + * @param {number} pongId unique id assigned to us by the background page + */ +cvox.ExtensionBridge.gotPongFromBackgroundPage = function(pongId) { + var self = cvox.ExtensionBridge; + self.connected = true; + self.id_ = pongId; + + while (self.queuedMessages.length > 0) { + self.sendContentScriptToBackground(self.queuedMessages.shift()); + } +}; + +/** + * Send a message from the content script to the background page. + * + * @param {Object} message The message to send. + */ +cvox.ExtensionBridge.sendContentScriptToBackground = function(message) { + var self = cvox.ExtensionBridge; + if (!self.connected) { + // We're not connected to the background page, so queue this message + // until we're connected. + self.queuedMessages.push(message); + return; + } + + if (cvox.ExtensionBridge.backgroundPort) { + cvox.ExtensionBridge.backgroundPort.postMessage(message); + } else { + chrome.extension.sendMessage(message); + } +}; + +/** + * Send a message from the background page to the content script of the + * current selected tab. + * + * @param {Object} message The message to send. + */ +cvox.ExtensionBridge.sendBackgroundToContentScript = function(message) { + chrome.tabs.query( + {'active': true, 'lastFocusedWindow': true}, + function(tabs) { + if (tabs && tabs.length > 0) { + chrome.tabs.sendMessage(tabs[0].id, message); + } else { + cvox.ExtensionBridge.portCache_.forEach(function(port) { + port.postMessage(message); + }); + } + }); +}; + +cvox.ExtensionBridge.init(); |