| 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 // This script contains unprivileged javascript APIs related to chrome | |
| 6 // extensions. It is loaded by any extension-related context, such as content | |
| 7 // scripts or toolstrips. | |
| 8 // See user_script_slave.cc for script that is loaded by content scripts only. | |
| 9 // TODO(mpcomplete): we also load this in regular web pages, but don't need to. | |
| 10 | |
| 11 var chrome = chrome || {}; | |
| 12 (function () { | |
| 13 native function OpenChannelToExtension(sourceId, targetId, name); | |
| 14 native function CloseChannel(portId, notifyBrowser); | |
| 15 native function PortAddRef(portId); | |
| 16 native function PortRelease(portId); | |
| 17 native function PostMessage(portId, msg); | |
| 18 native function GetChromeHidden(); | |
| 19 native function GetL10nMessage(); | |
| 20 native function Print(); | |
| 21 | |
| 22 var chromeHidden = GetChromeHidden(); | |
| 23 | |
| 24 // The reserved channel name for the sendRequest API. | |
| 25 chromeHidden.kRequestChannel = "chrome.extension.sendRequest"; | |
| 26 | |
| 27 // Map of port IDs to port object. | |
| 28 var ports = {}; | |
| 29 | |
| 30 // Map of port IDs to chromeHidden.onUnload listeners. Keep track of these | |
| 31 // to free the onUnload listeners when ports are closed. | |
| 32 var portReleasers = {}; | |
| 33 | |
| 34 // Change even to odd and vice versa, to get the other side of a given | |
| 35 // channel. | |
| 36 function getOppositePortId(portId) { return portId ^ 1; } | |
| 37 | |
| 38 // Port object. Represents a connection to another script context through | |
| 39 // which messages can be passed. | |
| 40 chrome.Port = function(portId, opt_name) { | |
| 41 this.portId_ = portId; | |
| 42 this.name = opt_name; | |
| 43 this.onDisconnect = new chrome.Event(); | |
| 44 this.onMessage = new chrome.Event(); | |
| 45 }; | |
| 46 | |
| 47 chromeHidden.Port = {}; | |
| 48 | |
| 49 // Returns true if the specified port id is in this context. This is used by | |
| 50 // the C++ to avoid creating the javascript message for all the contexts that | |
| 51 // don't care about a particular message. | |
| 52 chromeHidden.Port.hasPort = function(portId) { | |
| 53 return portId in ports; | |
| 54 }; | |
| 55 | |
| 56 // Hidden port creation function. We don't want to expose an API that lets | |
| 57 // people add arbitrary port IDs to the port list. | |
| 58 chromeHidden.Port.createPort = function(portId, opt_name) { | |
| 59 if (ports[portId]) { | |
| 60 throw new Error("Port '" + portId + "' already exists."); | |
| 61 } | |
| 62 var port = new chrome.Port(portId, opt_name); | |
| 63 ports[portId] = port; | |
| 64 portReleasers[portId] = PortRelease.bind(this, portId); | |
| 65 chromeHidden.onUnload.addListener(portReleasers[portId]); | |
| 66 | |
| 67 PortAddRef(portId); | |
| 68 return port; | |
| 69 }; | |
| 70 | |
| 71 // Called by native code when a channel has been opened to this context. | |
| 72 chromeHidden.Port.dispatchOnConnect = function(portId, channelName, tab, | |
| 73 sourceExtensionId, | |
| 74 targetExtensionId) { | |
| 75 // Only create a new Port if someone is actually listening for a connection. | |
| 76 // In addition to being an optimization, this also fixes a bug where if 2 | |
| 77 // channels were opened to and from the same process, closing one would | |
| 78 // close both. | |
| 79 if (targetExtensionId != chromeHidden.extensionId) | |
| 80 return; // not for us | |
| 81 if (ports[getOppositePortId(portId)]) | |
| 82 return; // this channel was opened by us, so ignore it | |
| 83 | |
| 84 // Determine whether this is coming from another extension, so we can use | |
| 85 // the right event. | |
| 86 var isExternal = sourceExtensionId != chromeHidden.extensionId; | |
| 87 | |
| 88 if (tab) | |
| 89 tab = chromeHidden.JSON.parse(tab); | |
| 90 var sender = {tab: tab, id: sourceExtensionId}; | |
| 91 | |
| 92 // Special case for sendRequest/onRequest. | |
| 93 if (channelName == chromeHidden.kRequestChannel) { | |
| 94 var requestEvent = (isExternal ? | |
| 95 chrome.extension.onRequestExternal : chrome.extension.onRequest); | |
| 96 if (requestEvent.hasListeners()) { | |
| 97 var port = chromeHidden.Port.createPort(portId, channelName); | |
| 98 port.onMessage.addListener(function(request) { | |
| 99 requestEvent.dispatch(request, sender, function(response) { | |
| 100 port.postMessage(response); | |
| 101 port = null; | |
| 102 }); | |
| 103 }); | |
| 104 } | |
| 105 return; | |
| 106 } | |
| 107 | |
| 108 var connectEvent = (isExternal ? | |
| 109 chrome.extension.onConnectExternal : chrome.extension.onConnect); | |
| 110 if (connectEvent.hasListeners()) { | |
| 111 var port = chromeHidden.Port.createPort(portId, channelName); | |
| 112 port.sender = sender; | |
| 113 // TODO(EXTENSIONS_DEPRECATED): port.tab is obsolete. | |
| 114 port.tab = port.sender.tab; | |
| 115 | |
| 116 connectEvent.dispatch(port); | |
| 117 } | |
| 118 }; | |
| 119 | |
| 120 // Called by native code when a channel has been closed. | |
| 121 chromeHidden.Port.dispatchOnDisconnect = function( | |
| 122 portId, connectionInvalid) { | |
| 123 var port = ports[portId]; | |
| 124 if (port) { | |
| 125 // Update the renderer's port bookkeeping, without notifying the browser. | |
| 126 CloseChannel(portId, false); | |
| 127 if (connectionInvalid) { | |
| 128 var errorMsg = | |
| 129 "Could not establish connection. Receiving end does not exist."; | |
| 130 chrome.extension.lastError = {"message": errorMsg}; | |
| 131 console.error("Port error: " + errorMsg); | |
| 132 } | |
| 133 try { | |
| 134 port.onDisconnect.dispatch(port); | |
| 135 } finally { | |
| 136 port.destroy_(); | |
| 137 delete chrome.extension.lastError; | |
| 138 } | |
| 139 } | |
| 140 }; | |
| 141 | |
| 142 // Called by native code when a message has been sent to the given port. | |
| 143 chromeHidden.Port.dispatchOnMessage = function(msg, portId) { | |
| 144 var port = ports[portId]; | |
| 145 if (port) { | |
| 146 if (msg) { | |
| 147 msg = chromeHidden.JSON.parse(msg); | |
| 148 } | |
| 149 port.onMessage.dispatch(msg, port); | |
| 150 } | |
| 151 }; | |
| 152 | |
| 153 // Sends a message asynchronously to the context on the other end of this | |
| 154 // port. | |
| 155 chrome.Port.prototype.postMessage = function(msg) { | |
| 156 // JSON.stringify doesn't support a root object which is undefined. | |
| 157 if (msg === undefined) | |
| 158 msg = null; | |
| 159 PostMessage(this.portId_, chromeHidden.JSON.stringify(msg)); | |
| 160 }; | |
| 161 | |
| 162 // Disconnects the port from the other end. | |
| 163 chrome.Port.prototype.disconnect = function() { | |
| 164 CloseChannel(this.portId_, true); | |
| 165 this.destroy_(); | |
| 166 }; | |
| 167 | |
| 168 chrome.Port.prototype.destroy_ = function() { | |
| 169 var portId = this.portId_; | |
| 170 | |
| 171 this.onDisconnect.destroy_(); | |
| 172 this.onMessage.destroy_(); | |
| 173 | |
| 174 PortRelease(portId); | |
| 175 chromeHidden.onUnload.removeListener(portReleasers[portId]); | |
| 176 | |
| 177 delete ports[portId]; | |
| 178 delete portReleasers[portId]; | |
| 179 }; | |
| 180 | |
| 181 // This function is called on context initialization for both content scripts | |
| 182 // and extension contexts. | |
| 183 chromeHidden.onLoad.addListener(function(extensionId, isExtensionProcess, | |
| 184 inIncognitoContext) { | |
| 185 chromeHidden.extensionId = extensionId; | |
| 186 | |
| 187 chrome.extension = chrome.extension || {}; | |
| 188 chrome.self = chrome.extension; | |
| 189 | |
| 190 chrome.extension.inIncognitoTab = inIncognitoContext; // deprecated | |
| 191 chrome.extension.inIncognitoContext = inIncognitoContext; | |
| 192 | |
| 193 // Events for when a message channel is opened to our extension. | |
| 194 chrome.extension.onConnect = new chrome.Event(); | |
| 195 chrome.extension.onConnectExternal = new chrome.Event(); | |
| 196 chrome.extension.onRequest = new chrome.Event(); | |
| 197 chrome.extension.onRequestExternal = new chrome.Event(); | |
| 198 | |
| 199 // Opens a message channel to the given target extension, or the current one | |
| 200 // if unspecified. Returns a Port for message passing. | |
| 201 chrome.extension.connect = function(targetId_opt, connectInfo_opt) { | |
| 202 var name = ""; | |
| 203 var targetId = extensionId; | |
| 204 var nextArg = 0; | |
| 205 if (typeof(arguments[nextArg]) == "string") | |
| 206 targetId = arguments[nextArg++]; | |
| 207 if (typeof(arguments[nextArg]) == "object") | |
| 208 name = arguments[nextArg++].name || name; | |
| 209 if (nextArg != arguments.length) | |
| 210 throw new Error("Invalid arguments to connect."); | |
| 211 | |
| 212 var portId = OpenChannelToExtension(extensionId, targetId, name); | |
| 213 if (portId >= 0) | |
| 214 return chromeHidden.Port.createPort(portId, name); | |
| 215 throw new Error("Error connecting to extension '" + targetId + "'"); | |
| 216 }; | |
| 217 | |
| 218 chrome.extension.sendRequest = | |
| 219 function(targetId_opt, request, responseCallback_opt) { | |
| 220 var targetId = extensionId; | |
| 221 var responseCallback = null; | |
| 222 var lastArg = arguments.length - 1; | |
| 223 if (typeof(arguments[lastArg]) == "function") | |
| 224 responseCallback = arguments[lastArg--]; | |
| 225 request = arguments[lastArg--]; | |
| 226 if (lastArg >= 0 && typeof(arguments[lastArg]) == "string") | |
| 227 targetId = arguments[lastArg--]; | |
| 228 if (lastArg != -1) | |
| 229 throw new Error("Invalid arguments to sendRequest."); | |
| 230 | |
| 231 var port = chrome.extension.connect(targetId, | |
| 232 {name: chromeHidden.kRequestChannel}); | |
| 233 port.postMessage(request); | |
| 234 port.onDisconnect.addListener(function() { | |
| 235 // For onDisconnects, we only notify the callback if there was an error | |
| 236 try { | |
| 237 if (chrome.extension.lastError && responseCallback) | |
| 238 responseCallback(); | |
| 239 } finally { | |
| 240 port = null; | |
| 241 } | |
| 242 }); | |
| 243 port.onMessage.addListener(function(response) { | |
| 244 try { | |
| 245 if (responseCallback) | |
| 246 responseCallback(response); | |
| 247 } finally { | |
| 248 port.disconnect(); | |
| 249 port = null; | |
| 250 } | |
| 251 }); | |
| 252 }; | |
| 253 | |
| 254 // Returns a resource URL that can be used to fetch a resource from this | |
| 255 // extension. | |
| 256 chrome.extension.getURL = function(path) { | |
| 257 path = String(path); | |
| 258 if (!path.length || path[0] != "/") | |
| 259 path = "/" + path; | |
| 260 return "chrome-extension://" + extensionId + path; | |
| 261 }; | |
| 262 | |
| 263 chrome.i18n = chrome.i18n || {}; | |
| 264 chrome.i18n.getMessage = function(message_name, placeholders) { | |
| 265 return GetL10nMessage(message_name, placeholders, extensionId); | |
| 266 }; | |
| 267 | |
| 268 if (!isExtensionProcess) | |
| 269 setupApiStubs(); | |
| 270 }); | |
| 271 | |
| 272 var notSupportedSuffix = " can only be used in extension processes. " + | |
| 273 "See the content scripts documentation for more details."; | |
| 274 | |
| 275 // Setup to throw an error message when trying to access |name| on the chrome | |
| 276 // object. The |name| can be a dot-separated path. | |
| 277 function createStub(name) { | |
| 278 var module = chrome; | |
| 279 var parts = name.split("."); | |
| 280 for (var i = 0; i < parts.length - 1; i++) { | |
| 281 var nextPart = parts[i]; | |
| 282 // Make sure an object at the path so far is defined. | |
| 283 module[nextPart] = module[nextPart] || {}; | |
| 284 module = module[nextPart]; | |
| 285 } | |
| 286 var finalPart = parts[parts.length-1]; | |
| 287 module.__defineGetter__(finalPart, function() { | |
| 288 throw new Error("chrome." + name + notSupportedSuffix); | |
| 289 }); | |
| 290 } | |
| 291 | |
| 292 // Sets up stubs to throw a better error message for the common case of | |
| 293 // developers trying to call extension API's that aren't allowed to be | |
| 294 // called from content scripts. | |
| 295 function setupApiStubs() { | |
| 296 // TODO(asargent) It would be nice to eventually generate this | |
| 297 // programmatically from extension_api.json (there is already a browser test | |
| 298 // that should prevent it from getting stale). | |
| 299 var privileged = [ | |
| 300 // Entire namespaces. | |
| 301 "bookmarks", | |
| 302 "browserAction", | |
| 303 "chromeAuthPrivate", | |
| 304 "chromePrivate", | |
| 305 "chromeosInfoPrivate", | |
| 306 "contextMenus", | |
| 307 "cookies", | |
| 308 "devtools", | |
| 309 "experimental.accessibility", | |
| 310 "experimental.app", | |
| 311 "experimental.bookmarkManager", | |
| 312 "experimental.clear", | |
| 313 "experimental.contentSettings", | |
| 314 "experimental.debugger", | |
| 315 "experimental.downloads", | |
| 316 "experimental.extension", | |
| 317 "experimental.infobars", | |
| 318 "experimental.input", | |
| 319 "experimental.inputUI", | |
| 320 "experimental.metrics", | |
| 321 "experimental.permissions", | |
| 322 "experimental.settings", | |
| 323 "experimental.popup", | |
| 324 "experimental.processes", | |
| 325 "experimental.privacy", | |
| 326 "experimental.rlz", | |
| 327 "experimental.sidebar", | |
| 328 "experimental.webNavigation", | |
| 329 "experimental.webRequest", | |
| 330 "fileBrowserHandler", | |
| 331 "fileBrowserPrivate", | |
| 332 "fileSystem", | |
| 333 "history", | |
| 334 "idle", | |
| 335 "inputMethodPrivate", | |
| 336 "management", | |
| 337 "mediaPlayerPrivate", | |
| 338 "omnibox", | |
| 339 "pageAction", | |
| 340 "pageActions", | |
| 341 "proxy", | |
| 342 "tabs", | |
| 343 "test", | |
| 344 "toolstrip", | |
| 345 "tts", | |
| 346 "ttsEngine", | |
| 347 "types", | |
| 348 "webSocketProxyPrivate", | |
| 349 "webstorePrivate", | |
| 350 "windows", | |
| 351 | |
| 352 // Functions/events/properties within the extension namespace. | |
| 353 "extension.getBackgroundPage", | |
| 354 "extension.getExtensionTabs", | |
| 355 "extension.getToolstrips", | |
| 356 "extension.getViews", | |
| 357 "extension.isAllowedIncognitoAccess", | |
| 358 "extension.isAllowedFileSchemeAccess", | |
| 359 "extension.onConnectExternal", | |
| 360 "extension.onRequestExternal", | |
| 361 "extension.setUpdateUrlData", | |
| 362 "i18n.getAcceptLanguages" | |
| 363 ]; | |
| 364 for (var i = 0; i < privileged.length; i++) { | |
| 365 createStub(privileged[i]); | |
| 366 } | |
| 367 } | |
| 368 | |
| 369 })(); | |
| OLD | NEW |