1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
|
// Copyright (c) 2009 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.
// This script contains unprivileged javascript APIs related to chrome
// extensions. It is loaded by any extension-related context, such as content
// scripts or toolstrips.
// See user_script_slave.cc for script that is loaded by content scripts only.
// TODO(mpcomplete): we also load this in regular web pages, but don't need
// to.
var chrome = chrome || {};
(function () {
native function OpenChannelToExtension(sourceId, targetId, name);
native function CloseChannel(portId);
native function PortAddRef(portId);
native function PortRelease(portId);
native function PostMessage(portId, msg);
native function GetChromeHidden();
native function GetL10nMessage();
var chromeHidden = GetChromeHidden();
// The reserved channel name for the sendRequest API.
chromeHidden.kRequestChannel = "chrome.extension.sendRequest";
// Map of port IDs to port object.
var ports = {};
// Change even to odd and vice versa, to get the other side of a given
// channel.
function getOppositePortId(portId) { return portId ^ 1; }
// Port object. Represents a connection to another script context through
// which messages can be passed.
chrome.Port = function(portId, opt_name) {
this.portId_ = portId;
this.name = opt_name;
this.onDisconnect = new chrome.Event();
this.onMessage = new chrome.Event();
};
chromeHidden.Port = {};
// Hidden port creation function. We don't want to expose an API that lets
// people add arbitrary port IDs to the port list.
chromeHidden.Port.createPort = function(portId, opt_name) {
if (ports[portId]) {
throw new Error("Port '" + portId + "' already exists.");
}
var port = new chrome.Port(portId, opt_name);
ports[portId] = port;
chromeHidden.onUnload.addListener(function() {
PortRelease(portId);
});
PortAddRef(portId);
return port;
}
// Called by native code when a channel has been opened to this context.
chromeHidden.Port.dispatchOnConnect = function(portId, channelName, tab,
sourceExtensionId,
targetExtensionId) {
// Only create a new Port if someone is actually listening for a connection.
// In addition to being an optimization, this also fixes a bug where if 2
// channels were opened to and from the same process, closing one would
// close both.
if (targetExtensionId != chromeHidden.extensionId)
return; // not for us
if (ports[getOppositePortId(portId)])
return; // this channel was opened by us, so ignore it
// Determine whether this is coming from another extension, so we can use
// the right event.
var isExternal = sourceExtensionId != chromeHidden.extensionId;
if (tab)
tab = JSON.parse(tab);
var sender = {tab: tab, id: sourceExtensionId};
// Special case for sendRequest/onRequest.
if (channelName == chromeHidden.kRequestChannel) {
var requestEvent = (isExternal ?
chrome.extension.onRequestExternal : chrome.extension.onRequest);
if (requestEvent.hasListeners()) {
var port = chromeHidden.Port.createPort(portId, channelName);
port.onMessage.addListener(function(request) {
requestEvent.dispatch(request, sender, function(response) {
port.postMessage(response);
});
});
}
return;
}
var connectEvent = (isExternal ?
chrome.extension.onConnectExternal : chrome.extension.onConnect);
if (connectEvent.hasListeners()) {
var port = chromeHidden.Port.createPort(portId, channelName);
port.sender = sender;
// TODO(EXTENSIONS_DEPRECATED): port.tab is obsolete.
port.tab = port.sender.tab;
connectEvent.dispatch(port);
}
};
// Called by native code when a channel has been closed.
chromeHidden.Port.dispatchOnDisconnect = function(portId) {
var port = ports[portId];
if (port) {
port.onDisconnect.dispatch(port);
delete ports[portId];
}
};
// Called by native code when a message has been sent to the given port.
chromeHidden.Port.dispatchOnMessage = function(msg, portId) {
var port = ports[portId];
if (port) {
if (msg) {
msg = JSON.parse(msg);
}
port.onMessage.dispatch(msg, port);
}
};
// Sends a message asynchronously to the context on the other end of this
// port.
chrome.Port.prototype.postMessage = function(msg) {
// JSON.stringify doesn't support a root object which is undefined.
if (msg === undefined)
msg = null;
PostMessage(this.portId_, JSON.stringify(msg));
};
// Disconnects the port from the other end.
chrome.Port.prototype.disconnect = function() {
delete ports[this.portId_];
CloseChannel(this.portId_);
}
// This function is called on context initialization for both content scripts
// and extension contexts.
chrome.initExtension = function(extensionId, warnOnPrivilegedApiAccess) {
delete chrome.initExtension;
chromeHidden.extensionId = extensionId;
chrome.extension = chrome.extension || {};
// TODO(EXTENSIONS_DEPRECATED): chrome.self is obsolete.
// http://code.google.com/p/chromium/issues/detail?id=16356
chrome.self = chrome.extension;
// Events for when a message channel is opened to our extension.
chrome.extension.onConnect = new chrome.Event();
chrome.extension.onConnectExternal = new chrome.Event();
chrome.extension.onRequest = new chrome.Event();
chrome.extension.onRequestExternal = new chrome.Event();
// Opens a message channel to the given target extension, or the current one
// if unspecified. Returns a Port for message passing.
chrome.extension.connect = function(targetId_opt, connectInfo_opt) {
var name = "";
var targetId = extensionId;
var nextArg = 0;
if (typeof(arguments[nextArg]) == "string")
targetId = arguments[nextArg++];
if (typeof(arguments[nextArg]) == "object")
name = arguments[nextArg++].name || name;
var portId = OpenChannelToExtension(extensionId, targetId, name);
if (portId >= 0)
return chromeHidden.Port.createPort(portId, name);
throw new Error("Error connecting to extension '" + targetId + "'");
};
chrome.extension.sendRequest =
function(targetId_opt, request, responseCallback_opt) {
var targetId = extensionId;
var responseCallback = null;
var lastArg = arguments.length - 1;
if (typeof(arguments[lastArg]) == "function")
responseCallback = arguments[lastArg--];
request = arguments[lastArg--];
if (lastArg >= 0)
targetId = arguments[lastArg--];
var port = chrome.extension.connect(targetId,
{name: chromeHidden.kRequestChannel});
port.postMessage(request);
port.onMessage.addListener(function(response) {
if (responseCallback)
responseCallback(response);
port.disconnect();
});
};
// Returns a resource URL that can be used to fetch a resource from this
// extension.
chrome.extension.getURL = function(path) {
return "chrome-extension://" + extensionId + "/" + path;
};
chrome.i18n = chrome.i18n || {};
chrome.i18n.getMessage = function(message_name, placeholders) {
return GetL10nMessage(message_name, placeholders, extensionId);
};
if (warnOnPrivilegedApiAccess) {
setupApiStubs();
}
}
var notSupportedSuffix = " is not supported in content scripts. " +
"See the content scripts documentation for more details.";
// Setup to throw an error message when trying to access |name| on the chrome
// object. The |name| can be a dot-separated path.
function createStub(name) {
var module = chrome;
var parts = name.split(".");
for (var i = 0; i < parts.length - 1; i++) {
var nextPart = parts[i];
// Make sure an object at the path so far is defined.
module[nextPart] = module[nextPart] || {};
module = module[nextPart];
}
var finalPart = parts[parts.length-1];
module.__defineGetter__(finalPart, function() {
throw new Error("chrome." + name + notSupportedSuffix);
});
}
// Sets up stubs to throw a better error message for the common case of
// developers trying to call extension API's that aren't allowed to be
// called from content scripts.
function setupApiStubs() {
// TODO(asargent) It would be nice to eventually generate this
// programmatically from extension_api.json (there is already a browser test
// that should prevent it from getting stale).
var privileged = [
// Entire namespaces.
"bookmarks", "browserAction", "devtools", "experimental.extension",
"experimental.history", "experimental.popup", "pageAction", "pageActions",
"tabs", "test", "toolstrip", "windows",
// Functions/events/properties within the extension namespace.
"extension.getBackgroundPage", "extension.getExtensionTabs",
"extension.getToolstrips", "extension.getViews", "extension.lastError",
"extension.onConnectExternal", "extension.onRequestExternal",
"i18n.getAcceptLanguages"
];
for (var i = 0; i < privileged.length; i++) {
createStub(privileged[i]);
}
}
})();
|