Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(149)

Side by Side Diff: chrome/renderer/resources/extensions/renderer_process_bindings.js

Issue 8540012: Enable extension APIs for content scripts. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698