OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * @fileoverview Bridge to aid in communication between a Chrome |
| 7 * background page and scripts injected into a page by a content script. |
| 8 * |
| 9 * This is needed when the extension's content script dynamically loads |
| 10 * most of its code by injecting script tags into the page. To communicate |
| 11 * with the background page, the page script needs to post a message that |
| 12 * the content script can listen to and then forward to the background page. |
| 13 * |
| 14 * To use cvox.ExtensionBridge, this file must be included in all three |
| 15 * contexts: |
| 16 * |
| 17 * 1. From the background page. |
| 18 * 2. From the content script (in the manifest.json after all other scripts). |
| 19 * 3. Inject it into the page from the content script. |
| 20 * |
| 21 * It automatically figures out where it's being run and initializes itself |
| 22 * appropriately. Then just call send() to send a message from the background |
| 23 * to the page or vice versa, and addMessageListener() to provide a message |
| 24 * listener. Messages can be any object that can be serialized using JSON. |
| 25 * |
| 26 * Messages can be sent to the background page from either the page or the |
| 27 * content script, and messages sent from the background page are delivered |
| 28 * to both the content script and the page. |
| 29 */ |
| 30 |
| 31 goog.provide('cvox.ExtensionBridge'); |
| 32 |
| 33 goog.require('cvox.ChromeVoxJSON'); |
| 34 |
| 35 if (BUILD_TYPE == BUILD_TYPE_CHROME) { |
| 36 /** |
| 37 * @constructor |
| 38 */ |
| 39 cvox.ExtensionBridge = function() {}; |
| 40 |
| 41 /** |
| 42 * Initialize the extension bridge. Dynamically figure out whether we're in |
| 43 * the background page, content script, or in a page, and call the |
| 44 * corresponding function for more specific initialization. |
| 45 */ |
| 46 cvox.ExtensionBridge.init = function() { |
| 47 var self = cvox.ExtensionBridge; |
| 48 self.listeners = []; |
| 49 |
| 50 try { |
| 51 if (chrome && chrome.windows) { |
| 52 // This depends on the fact that chrome.windows is only available |
| 53 // from background pages. |
| 54 self.json = JSON; |
| 55 self.context = self.BACKGROUND; |
| 56 self.initBackground(); |
| 57 return; |
| 58 } else { |
| 59 self.json = cvox.ChromeVoxJSON; |
| 60 self.context = self.PAGE; |
| 61 self.initPage(); |
| 62 return; |
| 63 } |
| 64 } catch (e) { |
| 65 // Ignore exception that might be raised if we try to access |
| 66 // chrome.windows from a content script. |
| 67 } |
| 68 |
| 69 if (chrome && chrome.extension) { |
| 70 self.json = JSON; |
| 71 self.context = self.CONTENT_SCRIPT; |
| 72 self.initContentScript(); |
| 73 } |
| 74 }; |
| 75 |
| 76 /** |
| 77 * Constant indicating we're in a background page. |
| 78 * @type {number} |
| 79 * @const |
| 80 */ |
| 81 cvox.ExtensionBridge.BACKGROUND = 0; |
| 82 |
| 83 /** |
| 84 * Constant indicating we're in a content script. |
| 85 * @type {number} |
| 86 * @const |
| 87 */ |
| 88 cvox.ExtensionBridge.CONTENT_SCRIPT = 1; |
| 89 |
| 90 /** |
| 91 * Constant indicating we're in a page. |
| 92 * @type {number} |
| 93 * @const |
| 94 */ |
| 95 cvox.ExtensionBridge.PAGE = 2; |
| 96 |
| 97 /** |
| 98 * The name of the port between the content script and background page. |
| 99 * @type {string} |
| 100 * @const |
| 101 */ |
| 102 cvox.ExtensionBridge.PORT_NAME = 'cvox.ExtensionBridge.Port'; |
| 103 |
| 104 /** |
| 105 * The name of the message between the page and content script that sets |
| 106 * up the bidirectional port between them. |
| 107 * @type {string} |
| 108 * @const |
| 109 */ |
| 110 cvox.ExtensionBridge.PORT_SETUP_MSG = 'cvox.ExtensionBridge.PortSetup'; |
| 111 |
| 112 /** |
| 113 * Send a message. If the context is a page, sends a message to the |
| 114 * extension background page. If the context is a background page, sends |
| 115 * a message to the current active tab (not all tabs). |
| 116 * |
| 117 * @param {Object} message The message to be sent. |
| 118 */ |
| 119 cvox.ExtensionBridge.send = function(message) { |
| 120 var self = cvox.ExtensionBridge; |
| 121 switch (self.context) { |
| 122 case self.BACKGROUND: |
| 123 self.sendBackgroundToContentScript(message); |
| 124 break; |
| 125 case self.CONTENT_SCRIPT: |
| 126 self.sendContentScriptToBackground(message); |
| 127 break; |
| 128 case self.PAGE: |
| 129 self.sendPageToContentScript(message); |
| 130 break; |
| 131 } |
| 132 }; |
| 133 |
| 134 /** |
| 135 * Provide a function to listen to messages. In page context, this |
| 136 * listens to messages from the background. In background context, |
| 137 * this listens to messages from all pages. |
| 138 * |
| 139 * The function gets called with two parameters: the message, and a |
| 140 * port that can be used to send replies. |
| 141 * |
| 142 * @param {function(Object, Port)} listener The message listener. |
| 143 */ |
| 144 cvox.ExtensionBridge.addMessageListener = function(listener) { |
| 145 cvox.ExtensionBridge.listeners.push(listener); |
| 146 }; |
| 147 |
| 148 /** |
| 149 * Initialize the extension bridge in a background page context by registering |
| 150 * a listener for connections from the content script. |
| 151 */ |
| 152 cvox.ExtensionBridge.initBackground = function() { |
| 153 var self = cvox.ExtensionBridge; |
| 154 |
| 155 chrome.extension.onConnect.addListener(function(port) { |
| 156 if (port.name != self.PORT_NAME) { |
| 157 return; |
| 158 } |
| 159 port.onMessage.addListener(function(message) { |
| 160 for (var i = 0; i < self.listeners.length; i++) { |
| 161 self.listeners[i](message, port); |
| 162 } |
| 163 }); |
| 164 }); |
| 165 }; |
| 166 |
| 167 /** |
| 168 * Initialize the extension bridge in a content script context, listening |
| 169 * for messages from the background page and accepting a bidirectional port |
| 170 * from the page. |
| 171 */ |
| 172 cvox.ExtensionBridge.initContentScript = function() { |
| 173 var self = cvox.ExtensionBridge; |
| 174 |
| 175 // Listen to requests from the background that don't come from |
| 176 // our connection port. |
| 177 chrome.extension.onRequest.addListener( |
| 178 function(request, sender, sendResponse) { |
| 179 for (var i = 0; i < self.listeners.length; i++) { |
| 180 self.listeners[i](request, self.backgroundPort); |
| 181 } |
| 182 if (self.port) { |
| 183 self.port.postMessage(self.json.stringify(request)); |
| 184 } |
| 185 sendResponse({}); |
| 186 }); |
| 187 |
| 188 // Listen to events on the main window and wait for a port setup message |
| 189 // from the page to continue. |
| 190 window.addEventListener('message', function(event) { |
| 191 if (event.data == self.PORT_SETUP_MSG) { |
| 192 // Now that we have a page connection, connect to background too. |
| 193 // (Don't do this earlier, otherwise initial messages from the |
| 194 // background wouldn't make it all the way through to the page.) |
| 195 self.backgroundPort = chrome.extension.connect({name: self.PORT_NAME}); |
| 196 self.backgroundPort.onMessage.addListener( |
| 197 function(message) { |
| 198 for (var i = 0; i < self.listeners.length; i++) { |
| 199 self.listeners[i](message, self.backgroundPort); |
| 200 } |
| 201 if (self.port) { |
| 202 self.port.postMessage(self.json.stringify(message)); |
| 203 } |
| 204 }); |
| 205 |
| 206 self.port = event.ports[0]; |
| 207 self.port.onmessage = function(event) { |
| 208 self.backgroundPort.postMessage(self.json.parse(event.data)); |
| 209 }; |
| 210 } |
| 211 }, false); |
| 212 }; |
| 213 |
| 214 /** |
| 215 * Initialize the extension bridge in a page context, creating a |
| 216 * MessageChannel and sending one of the ports to the content script |
| 217 * and then listening for messages on the other port. |
| 218 */ |
| 219 cvox.ExtensionBridge.initPage = function() { |
| 220 var self = cvox.ExtensionBridge; |
| 221 self.channel = new MessageChannel(); |
| 222 |
| 223 // Note: using postMessage.apply rather than just calling postMessage |
| 224 // directly because the 3-argument form of postMessage is still in the |
| 225 // HTML5 draft. |
| 226 window.postMessage.apply( |
| 227 window, [self.PORT_SETUP_MSG, [self.channel.port2], '*']); |
| 228 |
| 229 self.channel.port1.onmessage = function(event) { |
| 230 for (var i = 0; i < self.listeners.length; i++) { |
| 231 self.listeners[i](self.json.parse(event.data), self.channel.port1); |
| 232 } |
| 233 }; |
| 234 }; |
| 235 |
| 236 /** |
| 237 * Send a message from the content script to the background page. |
| 238 * |
| 239 * @param {Object} message The message to send. |
| 240 */ |
| 241 cvox.ExtensionBridge.sendContentScriptToBackground = function(message) { |
| 242 cvox.ExtensionBridge.backgroundPort.postMessage(message); |
| 243 }; |
| 244 |
| 245 /** |
| 246 * Send a message from the background page to the content script of the |
| 247 * current selected tab. |
| 248 * |
| 249 * @param {Object} message The message to send. |
| 250 */ |
| 251 cvox.ExtensionBridge.sendBackgroundToContentScript = function(message) { |
| 252 chrome.tabs.getSelected(null, function(tab) { |
| 253 chrome.tabs.sendRequest(tab.id, message, function() {}); |
| 254 }); |
| 255 }; |
| 256 |
| 257 /** |
| 258 * Send a message from the current page to its content script. |
| 259 * |
| 260 * @param {Object} message The message to send. |
| 261 */ |
| 262 cvox.ExtensionBridge.sendPageToContentScript = function(message) { |
| 263 cvox.ExtensionBridge.channel.port1.postMessage( |
| 264 cvox.ExtensionBridge.json.stringify(message)); |
| 265 }; |
| 266 |
| 267 cvox.ExtensionBridge.init(); |
| 268 } else { |
| 269 if (goog == undefined) { |
| 270 cvox = {}; |
| 271 } |
| 272 cvox.ExtensionBridge = function() {}; |
| 273 } |
OLD | NEW |