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

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

Issue 11571014: Lazy load chrome.* APIs (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: apitest.js Created 7 years, 10 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 require('json_schema');
6 var schemaRegistry = requireNative('schema_registry');
7 var sendRequest = require('sendRequest').sendRequest;
8 var utils = require('utils');
9 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();
10 var chrome = requireNative('chrome').GetChrome();
11 var schemaUtils = require('schemaUtils');
12 var process = requireNative('process');
13 var manifestVersion = process.GetManifestVersion();
14 var extensionId = process.GetExtensionId();
15 var contextType = process.GetContextType();
16 var GetAvailability = requireNative('v8_context').GetAvailability;
17
18 // Stores the name and definition of each API function, with methods to
19 // modify their behaviour (such as a custom way to handle requests to the
20 // API, a custom callback, etc).
21 function APIFunctions() {
22 this._apiFunctions = {};
23 this._unavailableApiFunctions = {};
24 }
25
26 APIFunctions.prototype.register = function(apiName, apiFunction) {
27 this._apiFunctions[apiName] = apiFunction;
28 };
29
30 // Registers a function as existing but not available, meaning that calls to
31 // the set* methods that reference this function should be ignored rather
32 // than throwing Errors.
33 APIFunctions.prototype.registerUnavailable = function(apiName) {
34 this._unavailableApiFunctions[apiName] = apiName;
35 };
36
37 APIFunctions.prototype._setHook =
38 function(apiName, propertyName, customizedFunction) {
39 if (this._unavailableApiFunctions.hasOwnProperty(apiName))
40 return;
41 if (!this._apiFunctions.hasOwnProperty(apiName))
42 throw new Error('Tried to set hook for unknown API "' + apiName + '"');
43 this._apiFunctions[apiName][propertyName] = customizedFunction;
44 };
45
46 APIFunctions.prototype.setHandleRequest =
47 function(apiName, customizedFunction) {
48 return this._setHook(apiName, 'handleRequest', customizedFunction);
49 };
50
51 APIFunctions.prototype.setUpdateArgumentsPostValidate =
52 function(apiName, customizedFunction) {
53 return this._setHook(
54 apiName, 'updateArgumentsPostValidate', customizedFunction);
55 };
56
57 APIFunctions.prototype.setUpdateArgumentsPreValidate =
58 function(apiName, customizedFunction) {
59 return this._setHook(
60 apiName, 'updateArgumentsPreValidate', customizedFunction);
61 };
62
63 APIFunctions.prototype.setCustomCallback =
64 function(apiName, customizedFunction) {
65 return this._setHook(apiName, 'customCallback', customizedFunction);
66 };
67
68 function CustomBindingsObject() {
69 }
70
71 CustomBindingsObject.prototype.setSchema = function(schema) {
72 // The functions in the schema are in list form, so we move them into a
73 // dictionary for easier access.
74 var self = this;
75 self.functionSchemas = {};
76 schema.functions.forEach(function(f) {
77 self.functionSchemas[f.name] = {
78 name: f.name,
79 definition: f
80 }
81 });
82 };
83
84 // Get the platform from navigator.appVersion.
85 function getPlatform() {
86 var platforms = [
87 [/CrOS Touch/, "chromeos touch"],
88 [/CrOS/, "chromeos"],
89 [/Linux/, "linux"],
90 [/Mac/, "mac"],
91 [/Win/, "win"],
92 ];
93
94 for (var i = 0; i < platforms.length; i++) {
95 if (platforms[i][0].test(navigator.appVersion)) {
96 return platforms[i][1];
97 }
98 }
99 return "unknown";
100 }
101
102 function isPlatformSupported(schemaNode, platform) {
103 return !schemaNode.platforms ||
104 schemaNode.platforms.indexOf(platform) > -1;
105 }
106
107 function isManifestVersionSupported(schemaNode, manifestVersion) {
108 return !schemaNode.maximumManifestVersion ||
109 manifestVersion <= schemaNode.maximumManifestVersion;
110 }
111
112 function isSchemaNodeSupported(schemaNode, platform, manifestVersion) {
113 return isPlatformSupported(schemaNode, platform) &&
114 isManifestVersionSupported(schemaNode, manifestVersion);
115 }
116
117 var platform = getPlatform();
118
119 function Binding(schema) {
120 this._schema = schema;
121 this._apiFunctions = new APIFunctions();
122 this._customEvent = null;
123 this._customTypes = {};
124 this._customHooks = [];
125 };
126
127 Binding.create = function(apiName) {
128 return new Binding(schemaRegistry.GetSchema(apiName));
129 };
130
131 Binding.prototype = {
132 // The API through which the ${api_name}_custom_bindings.js files customize
133 // their API bindings beyond what can be generated.
134 //
135 // There are 2 types of customizations available: those which are required in
136 // order to do the schema generation (registerCustomEvent and
137 // registerCustomType), and those which can only run after the bindings have
138 // been generated (registerCustomHook).
139 //
140
141 // Registers a custom type referenced via "$ref" fields in the API schema
142 // JSON.
143 registerCustomType: function(typeName, customTypeFactory) {
144 var customType = customTypeFactory();
145 customType.prototype = new CustomBindingsObject();
146 this._customTypes[typeName] = customType;
147 },
148
149 // Registers a custom event type for the API identified by |namespace|.
150 // |event| is the event's constructor.
151 registerCustomEvent: function(event) {
152 this._customEvent = event;
153 },
154
155 // Registers a function |hook| to run after the schema for all APIs has been
156 // generated. The hook is passed as its first argument an "API" object to
157 // interact with, and second the current extension ID. See where
158 // |customHooks| is used.
159 registerCustomHook: function(fn) {
160 this._customHooks.push(fn);
161 },
162
163 _runHooks: function(api) {
164 this._customHooks.forEach(function(hook) {
165 if (!isSchemaNodeSupported(this._schema, platform, manifestVersion))
166 return;
167
168 if (!hook)
169 return;
170
171 hook({
not at google - send to devlin 2013/02/13 01:45:49 There's actually not really any reason to have thi
cduvall 2013/02/15 00:40:28 Done.
172 apiFunctions: this._apiFunctions,
173 schema: this._schema,
174 compiledApi: api
175 }, extensionId, contextType);
176 }, this);
177 },
178
179 generate: function() {
not at google - send to devlin 2013/02/13 01:45:49 comment
cduvall 2013/02/15 00:40:28 Done.
180 var schema = this._schema;
181 var customTypes = this._customTypes;
182 if (!isSchemaNodeSupported(schema, platform, manifestVersion))
183 return;
not at google - send to devlin 2013/02/13 01:45:49 (see below comment: we do anyway). Hm, this is an
cduvall 2013/02/15 00:40:28 Done.
184
185 var availability = GetAvailability(schema.namespace);
186 if (!availability.is_available) {
187 throw new Error('chrome.' + schema.namespace + ' is not available: ' +
188 availability.message);
189 }
not at google - send to devlin 2013/02/13 01:45:49 I can see that this exception throwing stuff is ca
cduvall 2013/02/15 00:40:28 Done.
190
191 // See comment on internalAPIs at the top.
192 var mod = {};
193
194 var namespaces = schema.namespace.split('.');
195 for (var index = 0, name; name = namespaces[index]; index++) {
196 mod[name] = mod[name] || {};
197 mod = mod[name];
198 }
199
200 // Add types to global schemaValidator
201 if (schema.types) {
202 schema.types.forEach(function(t) {
203 if (!isSchemaNodeSupported(t, platform, manifestVersion))
204 return;
205
206 schemaUtils.schemaValidator.addTypes(t);
207 if (t.type == 'object' && this._customTypes[t.id]) {
208 var parts = t.id.split(".");
209 this._customTypes[t.id].prototype.setSchema(t);
210 mod[parts[parts.length - 1]] = this._customTypes[t.id];
211 }
212 }, this);
213 }
214
215 // Returns whether access to the content of a schema should be denied,
216 // based on the presence of "unprivileged" and whether this is an
217 // extension process (versus e.g. a content script).
218 function isSchemaAccessAllowed(itemSchema) {
219 return (contextType == 'BLESSED_EXTENSION') ||
220 schema.unprivileged ||
221 itemSchema.unprivileged;
222 };
223
224 // Adds a getter that throws an access denied error to object |mod|
225 // for property |name|.
226 function addUnprivilegedAccessGetter(mod, name) {
227 mod.__defineGetter__(name, function() {
228 throw new Error(
229 '"' + name + '" can only be used in extension processes. See ' +
230 'the content scripts documentation for more details.');
231 });
232 }
233
234 // Setup Functions.
235 if (schema.functions) {
236 schema.functions.forEach(function(functionDef) {
237 if (functionDef.name in mod) {
238 throw new Error('Function ' + functionDef.name +
239 ' already defined in ' + schema.namespace);
240 }
241
242 if (!isSchemaNodeSupported(functionDef, platform, manifestVersion)) {
243 this._apiFunctions.registerUnavailable(functionDef.name);
244 return;
245 }
246 if (!isSchemaAccessAllowed(functionDef)) {
247 this._apiFunctions.registerUnavailable(functionDef.name);
248 addUnprivilegedAccessGetter(mod, functionDef.name);
249 return;
250 }
251
252 var apiFunction = {};
253 apiFunction.definition = functionDef;
254 apiFunction.name = schema.namespace + '.' + functionDef.name;
255
256 // TODO(aa): It would be best to run this in a unit test, but in order
257 // to do that we would need to better factor this code so that it
258 // doesn't depend on so much v8::Extension machinery.
259 if (chromeHidden.validateAPI &&
260 schemaUtils.isFunctionSignatureAmbiguous(
261 apiFunction.definition)) {
262 throw new Error(
263 apiFunction.name + ' has ambiguous optional arguments. ' +
264 'To implement custom disambiguation logic, add ' +
265 '"allowAmbiguousOptionalArguments" to the function\'s schema.');
266 }
267
268 this._apiFunctions.register(functionDef.name, apiFunction);
269
270 mod[functionDef.name] = (function() {
271 var args = Array.prototype.slice.call(arguments);
272 if (this.updateArgumentsPreValidate)
273 args = this.updateArgumentsPreValidate.apply(this, args);
274
275 args = schemaUtils.normalizeArgumentsAndValidate(args, this);
276 if (this.updateArgumentsPostValidate)
277 args = this.updateArgumentsPostValidate.apply(this, args);
278
279 var retval;
280 if (this.handleRequest) {
281 retval = this.handleRequest.apply(this, args);
282 } else {
283 var optArgs = {
284 customCallback: this.customCallback
285 };
286 retval = sendRequest(this.name, args,
287 this.definition.parameters,
288 optArgs);
289 }
290
291 // Validate return value if defined - only in debug.
292 if (chromeHidden.validateCallbacks &&
293 this.definition.returns) {
294 schemaUtils.validate([retval], [this.definition.returns]);
295 }
296 return retval;
297 }).bind(apiFunction);
298 }, this);
299 }
300
301 // Setup Events
302 if (schema.events) {
303 schema.events.forEach(function(eventDef) {
304 if (eventDef.name in mod) {
305 throw new Error('Event ' + eventDef.name +
306 ' already defined in ' + schema.namespace);
307 }
308 if (!isSchemaNodeSupported(eventDef, platform, manifestVersion))
309 return;
310 if (!isSchemaAccessAllowed(eventDef)) {
311 addUnprivilegedAccessGetter(mod, eventDef.name);
312 return;
313 }
314
315 var eventName = schema.namespace + "." + eventDef.name;
316 var options = eventDef.options || {};
317
318 if (eventDef.filters && eventDef.filters.length > 0)
319 options.supportsFilters = true;
320
321 if (this._customEvent) {
322 mod[eventDef.name] = new this._customEvent(
323 eventName, eventDef.parameters, eventDef.extraParameters,
324 options);
325 } else if (eventDef.anonymous) {
326 mod[eventDef.name] = new chrome.Event();
327 } else {
328 mod[eventDef.name] = new chrome.Event(
329 eventName, eventDef.parameters, options);
330 }
331 }, this);
332 }
333
334 function addProperties(m, parentDef) {
335 var properties = parentDef.properties;
336 if (!properties)
337 return;
338
339 utils.forEach(properties, function(propertyName, propertyDef) {
340 if (propertyName in m)
341 return; // TODO(kalman): be strict like functions/events somehow.
342 if (!isSchemaNodeSupported(propertyDef, platform, manifestVersion))
343 return;
344 if (!isSchemaAccessAllowed(propertyDef)) {
345 addUnprivilegedAccessGetter(m, propertyName);
346 return;
347 }
348
349 var value = propertyDef.value;
350 if (value) {
351 // Values may just have raw types as defined in the JSON, such
352 // as "WINDOW_ID_NONE": { "value": -1 }. We handle this here.
353 // TODO(kalman): enforce that things with a "value" property can't
354 // define their own types.
355 var type = propertyDef.type || typeof(value);
356 if (type === 'integer' || type === 'number') {
357 value = parseInt(value);
358 } else if (type === 'boolean') {
359 value = value === 'true';
360 } else if (propertyDef['$ref']) {
361 if (propertyDef['$ref'] in customTypes) {
362 var constructor = customTypes[propertyDef['$ref']];
363 } else {
364 var refParts = propertyDef['$ref'].split('.');
365 // This should never try to load a $ref in the current namespace.
366 var constructor = utils.loadRefDependency(
367 propertyDef['$ref'])[refParts[refParts.length - 1]];
368 }
369 if (!constructor)
370 throw new Error('No custom binding for ' + propertyDef['$ref']);
371 var args = value;
372 // For an object propertyDef, |value| is an array of constructor
373 // arguments, but we want to pass the arguments directly (i.e.
374 // not as an array), so we have to fake calling |new| on the
375 // constructor.
376 value = { __proto__: constructor.prototype };
377 constructor.apply(value, args);
378 // Recursively add properties.
379 addProperties(value, propertyDef);
380 } else if (type === 'object') {
381 // Recursively add properties.
382 addProperties(value, propertyDef);
383 } else if (type !== 'string') {
384 throw new Error('NOT IMPLEMENTED (extension_api.json error): ' +
385 'Cannot parse values for type "' + type + '"');
386 }
387 m[propertyName] = value;
388 }
389 });
390 };
391
392 addProperties(mod, schema);
393 this._runHooks(mod);
394 return mod;
395 }
396 };
397
398 exports.Binding = Binding;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698