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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
|
// Copyright (c) 2009 The chrome Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// -----------------------------------------------------------------------------
// NOTE: If you change this file you need to touch renderer_resources.grd to
// have your change take effect.
// -----------------------------------------------------------------------------
// This script contains privileged chrome extension related javascript APIs.
// It is loaded by pages whose URL has the chrome-extension protocol.
var chrome = chrome || {};
(function() {
native function GetExtensionAPIDefinition();
native function StartRequest();
native function GetCurrentPageActions(extensionId);
native function GetExtensionViews();
native function GetChromeHidden();
native function GetNextRequestId();
native function OpenChannelToTab();
native function GetRenderViewId();
native function GetL10nMessage();
if (!chrome)
chrome = {};
var chromeHidden = GetChromeHidden();
// Validate arguments.
chromeHidden.validationTypes = [];
chromeHidden.validate = function(args, schemas) {
if (args.length > schemas.length)
throw new Error("Too many arguments.");
for (var i = 0; i < schemas.length; i++) {
if (i in args && args[i] !== null && args[i] !== undefined) {
var validator = new chromeHidden.JSONSchemaValidator();
validator.addTypes(chromeHidden.validationTypes);
validator.validate(args[i], schemas[i]);
if (validator.errors.length == 0)
continue;
var message = "Invalid value for argument " + i + ". ";
for (var i = 0, err; err = validator.errors[i]; i++) {
if (err.path) {
message += "Property '" + err.path + "': ";
}
message += err.message;
message = message.substring(0, message.length - 1);
message += ", ";
}
message = message.substring(0, message.length - 2);
message += ".";
throw new Error(message);
} else if (!schemas[i].optional) {
throw new Error("Parameter " + i + " is required.");
}
}
}
// Callback handling.
var requests = [];
chromeHidden.handleResponse = function(requestId, name,
success, response, error) {
try {
var request = requests[requestId];
if (success) {
delete chrome.extension.lastError;
} else {
if (!error) {
error = "Unknown error."
}
console.error("Error during " + name + ": " + error);
chrome.extension.lastError = {
"message": error
};
}
if (request.callback) {
// Callbacks currently only support one callback argument.
var callbackArgs = response ? [JSON.parse(response)] : [];
// Validate callback in debug only -- and only when the
// caller has provided a callback. Implementations of api
// calls my not return data if they observe the caller
// has not provided a callback.
if (chromeHidden.validateCallbacks && !error) {
try {
if (!request.callbackSchema.parameters) {
throw "No callback schemas defined";
}
if (request.callbackSchema.parameters.length > 1) {
throw "Callbacks may only define one parameter";
}
chromeHidden.validate(callbackArgs,
request.callbackSchema.parameters);
} catch (exception) {
return "Callback validation error during " + name + " -- " +
exception;
}
}
if (response) {
request.callback(callbackArgs[0]);
} else {
request.callback();
}
}
} finally {
delete requests[requestId];
delete chrome.extension.lastError;
}
};
chromeHidden.setViewType = function(type) {
var modeClass = "chrome-" + type;
var className = document.documentElement.className;
if (className && className.length) {
var classes = className.split(" ");
var new_classes = [];
classes.forEach(function(cls) {
if (cls.indexOf("chrome-") != 0) {
new_classes.push(cls);
}
});
new_classes.push(modeClass);
document.documentElement.className = new_classes.join(" ");
} else {
document.documentElement.className = modeClass;
}
};
function prepareRequest(args, argSchemas) {
var request = {};
var argCount = args.length;
// Look for callback param.
if (argSchemas.length > 0 &&
args.length == argSchemas.length &&
argSchemas[argSchemas.length - 1].type == "function") {
request.callback = args[argSchemas.length - 1];
request.callbackSchema = argSchemas[argSchemas.length - 1];
--argCount;
}
// Calls with one argument expect singular argument. Calls with multiple
// expect a list.
if (argCount == 1) {
request.args = args[0];
}
if (argCount > 1) {
request.args = [];
for (var k = 0; k < argCount; k++) {
request.args[k] = args[k];
}
}
return request;
}
// Send an API request and optionally register a callback.
function sendRequest(functionName, args, argSchemas) {
var request = prepareRequest(args, argSchemas);
// JSON.stringify doesn't support a root object which is undefined.
if (request.args === undefined)
request.args = null;
var sargs = JSON.stringify(request.args);
var requestId = GetNextRequestId();
requests[requestId] = request;
return StartRequest(functionName, sargs, requestId,
request.callback ? true : false);
}
function bind(obj, func) {
return function() {
return func.apply(obj, arguments);
};
}
// --- Setup additional api's not currently handled in common/extensions/api
// Page action events send (pageActionId, {tabId, tabUrl}).
function setupPageActionEvents(extensionId) {
var pageActions = GetCurrentPageActions(extensionId);
var eventName = "";
for (var i = 0; i < pageActions.length; ++i) {
eventName = extensionId + "/" + pageActions[i];
// Setup events for each extension_id/page_action_id string we find.
chrome.pageActions[pageActions[i]] = new chrome.Event(eventName);
}
}
// Browser action events send {windowpId}.
function setupBrowserActionEvent(extensionId) {
var eventName = "browserAction/" + extensionId;
chrome.browserAction = chrome.browserAction || {};
chrome.browserAction.onClicked = new chrome.Event(eventName);
}
function setupToolstripEvents(renderViewId) {
chrome.toolstrip = chrome.toolstrip || {};
chrome.toolstrip.onExpanded =
new chrome.Event("toolstrip.onExpanded." + renderViewId);
chrome.toolstrip.onCollapsed =
new chrome.Event("toolstrip.onCollapsed." + renderViewId);
}
chromeHidden.onLoad.addListener(function (extensionId) {
chrome.extension = new chrome.Extension(extensionId);
// TODO(mpcomplete): chrome.self is deprecated. Remove it at 1.0.
// http://code.google.com/p/chromium/issues/detail?id=16356
chrome.self = chrome.extension;
// |apiFunctions| is a hash of name -> object that stores the
// name & definition of the apiFunction. Custom handling of api functions
// is implemented by adding a "handleRequest" function to the object.
var apiFunctions = {};
// Read api definitions and setup api functions in the chrome namespace.
// TODO(rafaelw): Consider defining a json schema for an api definition
// and validating either here, in a unit_test or both.
// TODO(rafaelw): Handle synchronous functions.
// TOOD(rafaelw): Consider providing some convenient override points
// for api functions that wish to insert themselves into the call.
var apiDefinitions = JSON.parse(GetExtensionAPIDefinition());
apiDefinitions.forEach(function(apiDef) {
chrome[apiDef.namespace] = chrome[apiDef.namespace] || {};
var module = chrome[apiDef.namespace];
// Add types to global validationTypes
if (apiDef.types) {
apiDef.types.forEach(function(t) {
chromeHidden.validationTypes.push(t);
});
}
// Setup Functions.
if (apiDef.functions) {
apiDef.functions.forEach(function(functionDef) {
// Module functions may have been defined earlier by hand. Don't
// clobber them.
if (module[functionDef.name])
return;
var apiFunction = {};
apiFunction.definition = functionDef;
apiFunction.name = apiDef.namespace + "." + functionDef.name;;
apiFunctions[apiFunction.name] = apiFunction;
module[functionDef.name] = bind(apiFunction, function() {
chromeHidden.validate(arguments, this.definition.parameters);
if (this.handleRequest)
return this.handleRequest.apply(this, arguments);
else
return sendRequest(this.name, arguments,
this.definition.parameters);
});
});
}
// Setup Events
if (apiDef.events) {
apiDef.events.forEach(function(eventDef) {
// Module events may have been defined earlier by hand. Don't clobber
// them.
if (module[eventDef.name])
return;
var eventName = apiDef.namespace + "." + eventDef.name;
module[eventDef.name] = new chrome.Event(eventName,
eventDef.parameters);
});
}
});
apiFunctions["tabs.connect"].handleRequest = function(tabId, connectInfo) {
var name = "";
if (connectInfo) {
name = connectInfo.name || name;
}
var portId = OpenChannelToTab(
tabId, chrome.extension.id_, name);
return chromeHidden.Port.createPort(portId, name);
}
apiFunctions["extension.getViews"].handleRequest = function() {
return GetExtensionViews(-1, "ALL");
}
apiFunctions["extension.getBackgroundPage"].handleRequest = function() {
return GetExtensionViews(-1, "BACKGROUND")[0] || null;
}
apiFunctions["extension.getToolstrips"].handleRequest =
function(windowId) {
if (typeof(windowId) == "undefined")
windowId = -1;
return GetExtensionViews(windowId, "TOOLSTRIP");
}
apiFunctions["extension.getTabContentses"].handleRequest =
function(windowId) {
if (typeof(windowId) == "undefined")
windowId = -1;
return GetExtensionViews(windowId, "TAB");
}
apiFunctions["devtools.getTabEvents"].handleRequest = function(tabId) {
var tabIdProxy = {};
var functions = ["onPageEvent", "onTabUrlChange", "onTabClose"];
functions.forEach(function(name) {
// Event disambiguation is handled by name munging. See
// chrome/browser/extensions/extension_devtools_events.h for the C++
// equivalent of this logic.
tabIdProxy[name] = new chrome.Event("devtools." + tabId + "." + name);
});
return tabIdProxy;
}
apiFunctions["i18n.getMessage"].handleRequest =
function(message_name, placeholders) {
return GetL10nMessage(message_name, placeholders);
}
setupPageActionEvents(extensionId);
setupBrowserActionEvent(extensionId);
setupToolstripEvents(GetRenderViewId());
});
})();
|