| 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 background pages. | |
| 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 if (port) { | |
| 101 port.postMessage(response); | |
| 102 port = null; | |
| 103 } else { | |
| 104 // We nulled out port when sending the response, and now the page | |
| 105 // is trying to send another response for the same request. | |
| 106 var errorMsg = | |
| 107 "Cannot send a response more than once per " + | |
| 108 "chrome.extension.onRequest listener per document (message " + | |
| 109 "was sent by extension " + sourceExtensionId; | |
| 110 if (sourceExtensionId != targetExtensionId) { | |
| 111 errorMsg += " for extension " + targetExtensionId; | |
| 112 } | |
| 113 errorMsg += ")."; | |
| 114 chrome.extension.lastError = {"message": errorMsg}; | |
| 115 console.error("Could not send response: " + errorMsg); | |
| 116 } | |
| 117 }); | |
| 118 }); | |
| 119 } | |
| 120 return; | |
| 121 } | |
| 122 | |
| 123 var connectEvent = (isExternal ? | |
| 124 chrome.extension.onConnectExternal : chrome.extension.onConnect); | |
| 125 if (connectEvent.hasListeners()) { | |
| 126 var port = chromeHidden.Port.createPort(portId, channelName); | |
| 127 port.sender = sender; | |
| 128 // TODO(EXTENSIONS_DEPRECATED): port.tab is obsolete. | |
| 129 port.tab = port.sender.tab; | |
| 130 | |
| 131 connectEvent.dispatch(port); | |
| 132 } | |
| 133 }; | |
| 134 | |
| 135 // Called by native code when a channel has been closed. | |
| 136 chromeHidden.Port.dispatchOnDisconnect = function( | |
| 137 portId, connectionInvalid) { | |
| 138 var port = ports[portId]; | |
| 139 if (port) { | |
| 140 // Update the renderer's port bookkeeping, without notifying the browser. | |
| 141 CloseChannel(portId, false); | |
| 142 if (connectionInvalid) { | |
| 143 var errorMsg = | |
| 144 "Could not establish connection. Receiving end does not exist."; | |
| 145 chrome.extension.lastError = {"message": errorMsg}; | |
| 146 console.error("Port error: " + errorMsg); | |
| 147 } | |
| 148 try { | |
| 149 port.onDisconnect.dispatch(port); | |
| 150 } finally { | |
| 151 port.destroy_(); | |
| 152 delete chrome.extension.lastError; | |
| 153 } | |
| 154 } | |
| 155 }; | |
| 156 | |
| 157 // Called by native code when a message has been sent to the given port. | |
| 158 chromeHidden.Port.dispatchOnMessage = function(msg, portId) { | |
| 159 var port = ports[portId]; | |
| 160 if (port) { | |
| 161 if (msg) { | |
| 162 msg = chromeHidden.JSON.parse(msg); | |
| 163 } | |
| 164 port.onMessage.dispatch(msg, port); | |
| 165 } | |
| 166 }; | |
| 167 | |
| 168 // Sends a message asynchronously to the context on the other end of this | |
| 169 // port. | |
| 170 chrome.Port.prototype.postMessage = function(msg) { | |
| 171 // JSON.stringify doesn't support a root object which is undefined. | |
| 172 if (msg === undefined) | |
| 173 msg = null; | |
| 174 PostMessage(this.portId_, chromeHidden.JSON.stringify(msg)); | |
| 175 }; | |
| 176 | |
| 177 // Disconnects the port from the other end. | |
| 178 chrome.Port.prototype.disconnect = function() { | |
| 179 CloseChannel(this.portId_, true); | |
| 180 this.destroy_(); | |
| 181 }; | |
| 182 | |
| 183 chrome.Port.prototype.destroy_ = function() { | |
| 184 var portId = this.portId_; | |
| 185 | |
| 186 this.onDisconnect.destroy_(); | |
| 187 this.onMessage.destroy_(); | |
| 188 | |
| 189 PortRelease(portId); | |
| 190 chromeHidden.onUnload.removeListener(portReleasers[portId]); | |
| 191 | |
| 192 delete ports[portId]; | |
| 193 delete portReleasers[portId]; | |
| 194 }; | |
| 195 | |
| 196 // This function is called on context initialization for both content scripts | |
| 197 // and extension contexts. | |
| 198 chromeHidden.onLoad.addListener(function(extensionId, isExtensionProcess, | |
| 199 inIncognitoContext) { | |
| 200 chromeHidden.extensionId = extensionId; | |
| 201 | |
| 202 chrome.extension = chrome.extension || {}; | |
| 203 chrome.self = chrome.extension; | |
| 204 | |
| 205 chrome.extension.inIncognitoTab = inIncognitoContext; // deprecated | |
| 206 chrome.extension.inIncognitoContext = inIncognitoContext; | |
| 207 | |
| 208 // Events for when a message channel is opened to our extension. | |
| 209 chrome.extension.onConnect = new chrome.Event(); | |
| 210 chrome.extension.onConnectExternal = new chrome.Event(); | |
| 211 chrome.extension.onRequest = new chrome.Event(); | |
| 212 chrome.extension.onRequestExternal = new chrome.Event(); | |
| 213 | |
| 214 // Opens a message channel to the given target extension, or the current one | |
| 215 // if unspecified. Returns a Port for message passing. | |
| 216 chrome.extension.connect = function(targetId_opt, connectInfo_opt) { | |
| 217 var name = ""; | |
| 218 var targetId = extensionId; | |
| 219 var nextArg = 0; | |
| 220 if (typeof(arguments[nextArg]) == "string") | |
| 221 targetId = arguments[nextArg++]; | |
| 222 if (typeof(arguments[nextArg]) == "object") | |
| 223 name = arguments[nextArg++].name || name; | |
| 224 if (nextArg != arguments.length) | |
| 225 throw new Error("Invalid arguments to connect."); | |
| 226 | |
| 227 var portId = OpenChannelToExtension(extensionId, targetId, name); | |
| 228 if (portId >= 0) | |
| 229 return chromeHidden.Port.createPort(portId, name); | |
| 230 throw new Error("Error connecting to extension '" + targetId + "'"); | |
| 231 }; | |
| 232 | |
| 233 chrome.extension.sendRequest = | |
| 234 function(targetId_opt, request, responseCallback_opt) { | |
| 235 var targetId = extensionId; | |
| 236 var responseCallback = null; | |
| 237 var lastArg = arguments.length - 1; | |
| 238 if (typeof(arguments[lastArg]) == "function") | |
| 239 responseCallback = arguments[lastArg--]; | |
| 240 request = arguments[lastArg--]; | |
| 241 if (lastArg >= 0 && typeof(arguments[lastArg]) == "string") | |
| 242 targetId = arguments[lastArg--]; | |
| 243 if (lastArg != -1) | |
| 244 throw new Error("Invalid arguments to sendRequest."); | |
| 245 | |
| 246 var port = chrome.extension.connect(targetId, | |
| 247 {name: chromeHidden.kRequestChannel}); | |
| 248 port.postMessage(request); | |
| 249 port.onDisconnect.addListener(function() { | |
| 250 // For onDisconnects, we only notify the callback if there was an error | |
| 251 try { | |
| 252 if (chrome.extension.lastError && responseCallback) | |
| 253 responseCallback(); | |
| 254 } finally { | |
| 255 port = null; | |
| 256 } | |
| 257 }); | |
| 258 port.onMessage.addListener(function(response) { | |
| 259 try { | |
| 260 if (responseCallback) | |
| 261 responseCallback(response); | |
| 262 } finally { | |
| 263 port.disconnect(); | |
| 264 port = null; | |
| 265 } | |
| 266 }); | |
| 267 }; | |
| 268 | |
| 269 // Returns a resource URL that can be used to fetch a resource from this | |
| 270 // extension. | |
| 271 chrome.extension.getURL = function(path) { | |
| 272 path = String(path); | |
| 273 if (!path.length || path[0] != "/") | |
| 274 path = "/" + path; | |
| 275 return "chrome-extension://" + extensionId + path; | |
| 276 }; | |
| 277 | |
| 278 chrome.i18n = chrome.i18n || {}; | |
| 279 chrome.i18n.getMessage = function(message_name, placeholders) { | |
| 280 return GetL10nMessage(message_name, placeholders, extensionId); | |
| 281 }; | |
| 282 | |
| 283 if (!isExtensionProcess) | |
| 284 setupApiStubs(); | |
| 285 }); | |
| 286 | |
| 287 var notSupportedSuffix = " can only be used in extension processes. " + | |
| 288 "See the content scripts documentation for more details."; | |
| 289 | |
| 290 // Setup to throw an error message when trying to access |name| on the chrome | |
| 291 // object. The |name| can be a dot-separated path. | |
| 292 function createStub(name) { | |
| 293 var module = chrome; | |
| 294 var parts = name.split("."); | |
| 295 for (var i = 0; i < parts.length - 1; i++) { | |
| 296 var nextPart = parts[i]; | |
| 297 // Make sure an object at the path so far is defined. | |
| 298 module[nextPart] = module[nextPart] || {}; | |
| 299 module = module[nextPart]; | |
| 300 } | |
| 301 var finalPart = parts[parts.length-1]; | |
| 302 module.__defineGetter__(finalPart, function() { | |
| 303 throw new Error("chrome." + name + notSupportedSuffix); | |
| 304 }); | |
| 305 } | |
| 306 | |
| 307 // Sets up stubs to throw a better error message for the common case of | |
| 308 // developers trying to call extension API's that aren't allowed to be | |
| 309 // called from content scripts. | |
| 310 function setupApiStubs() { | |
| 311 // TODO(asargent) It would be nice to eventually generate this | |
| 312 // programmatically from extension_api.json (there is already a browser test | |
| 313 // that should prevent it from getting stale). | |
| 314 var privileged = [ | |
| 315 // Entire namespaces. | |
| 316 "bookmarks", | |
| 317 "browserAction", | |
| 318 "chromeAuthPrivate", | |
| 319 "chromePrivate", | |
| 320 "chromeosInfoPrivate", | |
| 321 "contentSettings", | |
| 322 "contextMenus", | |
| 323 "cookies", | |
| 324 "devtools", | |
| 325 "experimental.accessibility", | |
| 326 "experimental.app", | |
| 327 "experimental.bookmarkManager", | |
| 328 "experimental.clear", | |
| 329 "experimental.clipboard", | |
| 330 "experimental.debugger", | |
| 331 "experimental.downloads", | |
| 332 "experimental.extension", | |
| 333 "experimental.infobars", | |
| 334 "experimental.input", | |
| 335 "experimental.inputUI", | |
| 336 "experimental.metrics", | |
| 337 "experimental.settings", | |
| 338 "experimental.popup", | |
| 339 "experimental.processes", | |
| 340 "experimental.privacy", | |
| 341 "experimental.rlz", | |
| 342 "experimental.savePage", | |
| 343 "experimental.sidebar", | |
| 344 "experimental.speechInput", | |
| 345 "experimental.topSites", | |
| 346 "experimental.webRequest", | |
| 347 "fileBrowserHandler", | |
| 348 "fileBrowserPrivate", | |
| 349 "fileSystem", | |
| 350 "history", | |
| 351 "idle", | |
| 352 "inputMethodPrivate", | |
| 353 "management", | |
| 354 "mediaPlayerPrivate", | |
| 355 "omnibox", | |
| 356 "pageAction", | |
| 357 "pageActions", | |
| 358 "permissions", | |
| 359 "proxy", | |
| 360 "tabs", | |
| 361 "test", | |
| 362 "tts", | |
| 363 "ttsEngine", | |
| 364 "types", | |
| 365 "webNavigation", | |
| 366 "webSocketProxyPrivate", | |
| 367 "webstorePrivate", | |
| 368 "windows", | |
| 369 | |
| 370 // Functions/events/properties within the extension namespace. | |
| 371 "extension.getBackgroundPage", | |
| 372 "extension.getExtensionTabs", | |
| 373 "extension.getViews", | |
| 374 "extension.isAllowedIncognitoAccess", | |
| 375 "extension.isAllowedFileSchemeAccess", | |
| 376 "extension.onConnectExternal", | |
| 377 "extension.onRequestExternal", | |
| 378 "extension.setUpdateUrlData", | |
| 379 "i18n.getAcceptLanguages" | |
| 380 ]; | |
| 381 for (var i = 0; i < privileged.length; i++) { | |
| 382 createStub(privileged[i]); | |
| 383 } | |
| 384 } | |
| 385 | |
| 386 })(); | |
| OLD | NEW |