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

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

Issue 11571014: Lazy load chrome.* APIs (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: android compilation Created 7 years, 9 months 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) 2012 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 // Generates the chrome.* API bindings from a list of schemas.
6
7 // TODO(battre): cleanup the usage of packages everywhere, as described here
8 // http://codereview.chromium.org/10392008/diff/38/chrome/renderer/resources/e xtensions/schema_generated_bindings.js
9
10 require('json_schema');
11 require('event_bindings');
12 var GetExtensionAPIDefinition =
13 requireNative('apiDefinitions').GetExtensionAPIDefinition;
14 var sendRequest = require('sendRequest').sendRequest;
15 var utils = require('utils');
16 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();
17 var schemaUtils = require('schemaUtils');
18
19 // The object to generate the bindings for "internal" APIs in, so that
20 // extensions can't directly call them (without access to chromeHidden),
21 // but are still needed for internal mechanisms of extensions (e.g. events).
22 //
23 // This is distinct to the "*Private" APIs which are controlled via
24 // having strict permissions and aren't generated *anywhere* unless needed.
25 var internalAPIs = {};
26 chromeHidden.internalAPIs = internalAPIs;
27
28 // Stores the name and definition of each API function, with methods to
29 // modify their behaviour (such as a custom way to handle requests to the
30 // API, a custom callback, etc).
31 function APIFunctions() {
32 this._apiFunctions = {};
33 this._unavailableApiFunctions = {};
34 }
35 APIFunctions.prototype.register = function(apiName, apiFunction) {
36 this._apiFunctions[apiName] = apiFunction;
37 };
38 // Registers a function as existing but not available, meaning that calls to
39 // the set* methods that reference this function should be ignored rather
40 // than throwing Errors.
41 APIFunctions.prototype.registerUnavailable = function(apiName) {
42 this._unavailableApiFunctions[apiName] = apiName;
43 };
44 APIFunctions.prototype._setHook =
45 function(apiName, propertyName, customizedFunction) {
46 if (this._unavailableApiFunctions.hasOwnProperty(apiName))
47 return;
48 if (!this._apiFunctions.hasOwnProperty(apiName))
49 throw new Error('Tried to set hook for unknown API "' + apiName + '"');
50 this._apiFunctions[apiName][propertyName] = customizedFunction;
51 };
52 APIFunctions.prototype.setHandleRequest =
53 function(apiName, customizedFunction) {
54 return this._setHook(apiName, 'handleRequest', customizedFunction);
55 };
56 APIFunctions.prototype.setUpdateArgumentsPostValidate =
57 function(apiName, customizedFunction) {
58 return this._setHook(
59 apiName, 'updateArgumentsPostValidate', customizedFunction);
60 };
61 APIFunctions.prototype.setUpdateArgumentsPreValidate =
62 function(apiName, customizedFunction) {
63 return this._setHook(
64 apiName, 'updateArgumentsPreValidate', customizedFunction);
65 };
66 APIFunctions.prototype.setCustomCallback =
67 function(apiName, customizedFunction) {
68 return this._setHook(apiName, 'customCallback', customizedFunction);
69 };
70
71 var apiFunctions = new APIFunctions();
72
73 // Wraps the calls to the set* methods of APIFunctions with the namespace of
74 // an API, and validates that all calls to set* methods aren't prefixed with
75 // a namespace.
76 //
77 // For example, if constructed with 'browserAction', a call to
78 // handleRequest('foo') will be transformed into
79 // handleRequest('browserAction.foo').
80 //
81 // Likewise, if a call to handleRequest is called with 'browserAction.foo',
82 // it will throw an error.
83 //
84 // These help with isolating custom bindings from each other.
85 function NamespacedAPIFunctions(namespace, delegate) {
86 var self = this;
87 function wrap(methodName) {
88 self[methodName] = function(apiName, customizedFunction) {
89 var prefix = namespace + '.';
90 if (apiName.indexOf(prefix) === 0) {
91 throw new Error(methodName + ' called with "' + apiName +
92 '" which has a "' + prefix + '" prefix. ' +
93 'This is unnecessary and must be left out.');
94 }
95 return delegate[methodName].call(delegate,
96 prefix + apiName, customizedFunction);
97 };
98 }
99
100 wrap('contains');
101 wrap('setHandleRequest');
102 wrap('setUpdateArgumentsPostValidate');
103 wrap('setUpdateArgumentsPreValidate');
104 wrap('setCustomCallback');
105 }
106
107 //
108 // The API through which the ${api_name}_custom_bindings.js files customize
109 // their API bindings beyond what can be generated.
110 //
111 // There are 2 types of customizations available: those which are required in
112 // order to do the schema generation (registerCustomEvent and
113 // registerCustomType), and those which can only run after the bindings have
114 // been generated (registerCustomHook).
115 //
116
117 // Registers a custom event type for the API identified by |namespace|.
118 // |event| is the event's constructor.
119 var customEvents = {};
120 chromeHidden.registerCustomEvent = function(namespace, event) {
121 if (typeof(namespace) !== 'string') {
122 throw new Error("registerCustomEvent requires the namespace of the " +
123 "API as its first argument");
124 }
125 customEvents[namespace] = event;
126 };
127
128 // Registers a function |hook| to run after the schema for all APIs has been
129 // generated. The hook is passed as its first argument an "API" object to
130 // interact with, and second the current extension ID. See where
131 // |customHooks| is used.
132 var customHooks = {};
133 chromeHidden.registerCustomHook = function(namespace, fn) {
134 if (typeof(namespace) !== 'string') {
135 throw new Error("registerCustomHook requires the namespace of the " +
136 "API as its first argument");
137 }
138 customHooks[namespace] = fn;
139 };
140
141 function CustomBindingsObject() {
142 }
143 CustomBindingsObject.prototype.setSchema = function(schema) {
144 // The functions in the schema are in list form, so we move them into a
145 // dictionary for easier access.
146 var self = this;
147 self.functionSchemas = {};
148 schema.functions.forEach(function(f) {
149 self.functionSchemas[f.name] = {
150 name: f.name,
151 definition: f
152 }
153 });
154 };
155
156 // Registers a custom type referenced via "$ref" fields in the API schema
157 // JSON.
158 var customTypes = {};
159 chromeHidden.registerCustomType = function(typeName, customTypeFactory) {
160 var customType = customTypeFactory();
161 customType.prototype = new CustomBindingsObject();
162 customTypes[typeName] = customType;
163 };
164
165 // Get the platform from navigator.appVersion.
166 function getPlatform() {
167 var platforms = [
168 [/CrOS Touch/, "chromeos touch"],
169 [/CrOS/, "chromeos"],
170 [/Linux/, "linux"],
171 [/Mac/, "mac"],
172 [/Win/, "win"],
173 ];
174
175 for (var i = 0; i < platforms.length; i++) {
176 if (platforms[i][0].test(navigator.appVersion)) {
177 return platforms[i][1];
178 }
179 }
180 return "unknown";
181 }
182
183 function isPlatformSupported(schemaNode, platform) {
184 return !schemaNode.platforms ||
185 schemaNode.platforms.indexOf(platform) > -1;
186 }
187
188 function isManifestVersionSupported(schemaNode, manifestVersion) {
189 return !schemaNode.maximumManifestVersion ||
190 manifestVersion <= schemaNode.maximumManifestVersion;
191 }
192
193 function isSchemaNodeSupported(schemaNode, platform, manifestVersion) {
194 return isPlatformSupported(schemaNode, platform) &&
195 isManifestVersionSupported(schemaNode, manifestVersion);
196 }
197
198 chromeHidden.onLoad.addListener(function(extensionId,
199 contextType,
200 isIncognitoProcess,
201 manifestVersion) {
202 var apiDefinitions = GetExtensionAPIDefinition();
203
204 // Read api definitions and setup api functions in the chrome namespace.
205 var platform = getPlatform();
206
207 apiDefinitions.forEach(function(apiDef) {
208 // TODO(kalman): Remove this, or refactor schema_generated_bindings.js so
209 // that it isn't necessary. For now, chrome.app and chrome.webstore are
210 // entirely handwritten.
211 if (['app', 'webstore'].indexOf(apiDef.namespace) >= 0)
212 return;
213
214 if (!isSchemaNodeSupported(apiDef, platform, manifestVersion))
215 return;
216
217 // See comment on internalAPIs at the top.
218 var mod = apiDef.internal ? internalAPIs : chrome;
219
220 var namespaces = apiDef.namespace.split('.');
221 for (var index = 0, name; name = namespaces[index]; index++) {
222 mod[name] = mod[name] || {};
223 mod = mod[name];
224 }
225
226 // Add types to global schemaValidator
227 if (apiDef.types) {
228 apiDef.types.forEach(function(t) {
229 if (!isSchemaNodeSupported(t, platform, manifestVersion))
230 return;
231
232 schemaUtils.schemaValidator.addTypes(t);
233 if (t.type == 'object' && customTypes[t.id]) {
234 customTypes[t.id].prototype.setSchema(t);
235 }
236 });
237 }
238
239 // Returns whether access to the content of a schema should be denied,
240 // based on the presence of "unprivileged" and whether this is an
241 // extension process (versus e.g. a content script).
242 function isSchemaAccessAllowed(itemSchema) {
243 return (contextType == 'BLESSED_EXTENSION') ||
244 apiDef.unprivileged ||
245 itemSchema.unprivileged;
246 }
247
248 // Adds a getter that throws an access denied error to object |mod|
249 // for property |name|.
250 function addUnprivilegedAccessGetter(mod, name) {
251 mod.__defineGetter__(name, function() {
252 throw new Error(
253 '"' + name + '" can only be used in extension processes. See ' +
254 'the content scripts documentation for more details.');
255 });
256 }
257
258 // Setup Functions.
259 if (apiDef.functions) {
260 apiDef.functions.forEach(function(functionDef) {
261 if (functionDef.name in mod) {
262 throw new Error('Function ' + functionDef.name +
263 ' already defined in ' + apiDef.namespace);
264 }
265
266 var apiFunctionName = apiDef.namespace + "." + functionDef.name;
267
268 if (!isSchemaNodeSupported(functionDef, platform, manifestVersion)) {
269 apiFunctions.registerUnavailable(apiFunctionName);
270 return;
271 }
272 if (!isSchemaAccessAllowed(functionDef)) {
273 apiFunctions.registerUnavailable(apiFunctionName);
274 addUnprivilegedAccessGetter(mod, functionDef.name);
275 return;
276 }
277
278 var apiFunction = {};
279 apiFunction.definition = functionDef;
280 apiFunction.name = apiFunctionName;
281
282 // TODO(aa): It would be best to run this in a unit test, but in order
283 // to do that we would need to better factor this code so that it
284 // doesn't depend on so much v8::Extension machinery.
285 if (chromeHidden.validateAPI &&
286 schemaUtils.isFunctionSignatureAmbiguous(
287 apiFunction.definition)) {
288 throw new Error(
289 apiFunction.name + ' has ambiguous optional arguments. ' +
290 'To implement custom disambiguation logic, add ' +
291 '"allowAmbiguousOptionalArguments" to the function\'s schema.');
292 }
293
294 apiFunctions.register(apiFunction.name, apiFunction);
295
296 mod[functionDef.name] = (function() {
297 var args = Array.prototype.slice.call(arguments);
298 if (this.updateArgumentsPreValidate)
299 args = this.updateArgumentsPreValidate.apply(this, args);
300
301 args = schemaUtils.normalizeArgumentsAndValidate(args, this);
302 if (this.updateArgumentsPostValidate)
303 args = this.updateArgumentsPostValidate.apply(this, args);
304
305 var retval;
306 if (this.handleRequest) {
307 retval = this.handleRequest.apply(this, args);
308 } else {
309 var optArgs = {
310 customCallback: this.customCallback
311 };
312 retval = sendRequest(this.name, args,
313 this.definition.parameters,
314 optArgs);
315 }
316
317 // Validate return value if defined - only in debug.
318 if (chromeHidden.validateCallbacks &&
319 this.definition.returns) {
320 schemaUtils.validate([retval], [this.definition.returns]);
321 }
322 return retval;
323 }).bind(apiFunction);
324 });
325 }
326
327 // Setup Events
328 if (apiDef.events) {
329 apiDef.events.forEach(function(eventDef) {
330 if (eventDef.name in mod) {
331 throw new Error('Event ' + eventDef.name +
332 ' already defined in ' + apiDef.namespace);
333 }
334 if (!isSchemaNodeSupported(eventDef, platform, manifestVersion))
335 return;
336 if (!isSchemaAccessAllowed(eventDef)) {
337 addUnprivilegedAccessGetter(mod, eventDef.name);
338 return;
339 }
340
341 var eventName = apiDef.namespace + "." + eventDef.name;
342 var customEvent = customEvents[apiDef.namespace];
343 var options = eventDef.options || {};
344
345 if (eventDef.filters && eventDef.filters.length > 0)
346 options.supportsFilters = true;
347
348 if (customEvent) {
349 mod[eventDef.name] = new customEvent(
350 eventName, eventDef.parameters, eventDef.extraParameters,
351 options);
352 } else if (eventDef.anonymous) {
353 mod[eventDef.name] = new chrome.Event();
354 } else {
355 mod[eventDef.name] = new chrome.Event(
356 eventName, eventDef.parameters, options);
357 }
358 });
359 }
360
361 function addProperties(m, parentDef) {
362 var properties = parentDef.properties;
363 if (!properties)
364 return;
365
366 utils.forEach(properties, function(propertyName, propertyDef) {
367 if (propertyName in m)
368 return; // TODO(kalman): be strict like functions/events somehow.
369 if (!isSchemaNodeSupported(propertyDef, platform, manifestVersion))
370 return;
371 if (!isSchemaAccessAllowed(propertyDef)) {
372 addUnprivilegedAccessGetter(m, propertyName);
373 return;
374 }
375
376 var value = propertyDef.value;
377 if (value) {
378 // Values may just have raw types as defined in the JSON, such
379 // as "WINDOW_ID_NONE": { "value": -1 }. We handle this here.
380 // TODO(kalman): enforce that things with a "value" property can't
381 // define their own types.
382 var type = propertyDef.type || typeof(value);
383 if (type === 'integer' || type === 'number') {
384 value = parseInt(value);
385 } else if (type === 'boolean') {
386 value = value === "true";
387 } else if (propertyDef["$ref"]) {
388 var constructor = customTypes[propertyDef["$ref"]];
389 if (!constructor)
390 throw new Error("No custom binding for " + propertyDef["$ref"]);
391 var args = value;
392 // For an object propertyDef, |value| is an array of constructor
393 // arguments, but we want to pass the arguments directly (i.e.
394 // not as an array), so we have to fake calling |new| on the
395 // constructor.
396 value = { __proto__: constructor.prototype };
397 constructor.apply(value, args);
398 // Recursively add properties.
399 addProperties(value, propertyDef);
400 } else if (type === 'object') {
401 // Recursively add properties.
402 addProperties(value, propertyDef);
403 } else if (type !== 'string') {
404 throw new Error("NOT IMPLEMENTED (extension_api.json error): " +
405 "Cannot parse values for type \"" + type + "\"");
406 }
407 m[propertyName] = value;
408 }
409 });
410 }
411
412 addProperties(mod, apiDef);
413 });
414
415 // Run the non-declarative custom hooks after all the schemas have been
416 // generated, in case hooks depend on other APIs being available.
417 apiDefinitions.forEach(function(apiDef) {
418 if (!isSchemaNodeSupported(apiDef, platform, manifestVersion))
419 return;
420
421 var hook = customHooks[apiDef.namespace];
422 if (!hook)
423 return;
424
425 // Pass through the public API of schema_generated_bindings, to be used
426 // by custom bindings JS files. Create a new one so that bindings can't
427 // interfere with each other.
428 hook({
429 apiFunctions: new NamespacedAPIFunctions(apiDef.namespace,
430 apiFunctions),
431 apiDefinitions: apiDefinitions,
432 }, extensionId, contextType);
433 });
434
435 if (chrome.test)
436 chrome.test.getApiDefinitions = GetExtensionAPIDefinition;
437 });
OLDNEW
« no previous file with comments | « chrome/renderer/resources/extensions/runtime_custom_bindings.js ('k') | chrome/renderer/resources/extensions/schema_utils.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698