| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // This script contains privileged chrome extension related javascript APIs. | 5 // This script contains privileged chrome extension related javascript APIs. |
| 6 // It is loaded by pages whose URL has the chrome-extension protocol. | 6 // It is loaded by pages whose URL has the chrome-extension protocol. |
| 7 | 7 |
| 8 // TODO(battre): cleanup the usage of packages everywhere, as described here | 8 // TODO(battre): cleanup the usage of packages everywhere, as described here |
| 9 // http://codereview.chromium.org/10392008/diff/38/chrome/renderer/resources/e
xtensions/schema_generated_bindings.js | 9 // http://codereview.chromium.org/10392008/diff/38/chrome/renderer/resources/e
xtensions/schema_generated_bindings.js |
| 10 | 10 |
| 11 require('json_schema'); | 11 require('json_schema'); |
| 12 require('event_bindings'); | 12 require('event_bindings'); |
| 13 var GetExtensionAPIDefinition = | 13 var GetExtensionAPIDefinition = |
| 14 requireNative('apiDefinitions').GetExtensionAPIDefinition; | 14 requireNative('apiDefinitions').GetExtensionAPIDefinition; |
| 15 var sendRequest = require('sendRequest').sendRequest; | 15 var sendRequest = require('sendRequest').sendRequest; |
| 16 var utils = require('utils'); | 16 var utils = require('utils'); |
| 17 var isDevChannel = requireNative('channel').IsDevChannel; | 17 var isDevChannel = requireNative('channel').IsDevChannel; |
| 18 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); | 18 var chromeHidden = requireNative('chrome_hidden').GetChromeHidden(); |
| 19 var schemaUtils = require('schemaUtils'); |
| 19 | 20 |
| 20 // The object to generate the bindings for "internal" APIs in, so that | 21 // The object to generate the bindings for "internal" APIs in, so that |
| 21 // extensions can't directly call them (without access to chromeHidden), | 22 // extensions can't directly call them (without access to chromeHidden), |
| 22 // but are still needed for internal mechanisms of extensions (e.g. events). | 23 // but are still needed for internal mechanisms of extensions (e.g. events). |
| 23 // | 24 // |
| 24 // This is distinct to the "*Private" APIs which are controlled via | 25 // This is distinct to the "*Private" APIs which are controlled via |
| 25 // having strict permissions and aren't generated *anywhere* unless needed. | 26 // having strict permissions and aren't generated *anywhere* unless needed. |
| 26 var internalAPIs = {}; | 27 var internalAPIs = {}; |
| 27 chromeHidden.internalAPIs = internalAPIs; | 28 chromeHidden.internalAPIs = internalAPIs; |
| 28 | 29 |
| 29 // Validate arguments. | |
| 30 var schemaValidator = new chromeHidden.JSONSchemaValidator(); | |
| 31 chromeHidden.validate = function(args, parameterSchemas) { | |
| 32 if (args.length > parameterSchemas.length) | |
| 33 throw new Error("Too many arguments."); | |
| 34 | |
| 35 for (var i = 0; i < parameterSchemas.length; i++) { | |
| 36 if (i in args && args[i] !== null && args[i] !== undefined) { | |
| 37 schemaValidator.resetErrors(); | |
| 38 schemaValidator.validate(args[i], parameterSchemas[i]); | |
| 39 if (schemaValidator.errors.length == 0) | |
| 40 continue; | |
| 41 | |
| 42 var message = "Invalid value for argument " + (i + 1) + ". "; | |
| 43 for (var i = 0, err; err = schemaValidator.errors[i]; i++) { | |
| 44 if (err.path) { | |
| 45 message += "Property '" + err.path + "': "; | |
| 46 } | |
| 47 message += err.message; | |
| 48 message = message.substring(0, message.length - 1); | |
| 49 message += ", "; | |
| 50 } | |
| 51 message = message.substring(0, message.length - 2); | |
| 52 message += "."; | |
| 53 | |
| 54 throw new Error(message); | |
| 55 } else if (!parameterSchemas[i].optional) { | |
| 56 throw new Error("Parameter " + (i + 1) + " is required."); | |
| 57 } | |
| 58 } | |
| 59 }; | |
| 60 | |
| 61 // Generate all possible signatures for a given API function. | |
| 62 function getSignatures(parameterSchemas) { | |
| 63 if (parameterSchemas.length === 0) | |
| 64 return [[]]; | |
| 65 | |
| 66 var signatures = []; | |
| 67 var remaining = getSignatures(parameterSchemas.slice(1)); | |
| 68 for (var i = 0; i < remaining.length; i++) | |
| 69 signatures.push([parameterSchemas[0]].concat(remaining[i])) | |
| 70 | |
| 71 if (parameterSchemas[0].optional) | |
| 72 return signatures.concat(remaining); | |
| 73 return signatures; | |
| 74 }; | |
| 75 | |
| 76 // Return true if arguments match a given signature's schema. | |
| 77 function argumentsMatchSignature(args, candidateSignature) { | |
| 78 if (args.length != candidateSignature.length) | |
| 79 return false; | |
| 80 | |
| 81 for (var i = 0; i < candidateSignature.length; i++) { | |
| 82 var argType = chromeHidden.JSONSchemaValidator.getType(args[i]); | |
| 83 if (!schemaValidator.isValidSchemaType(argType, candidateSignature[i])) | |
| 84 return false; | |
| 85 } | |
| 86 return true; | |
| 87 }; | |
| 88 | |
| 89 // Finds the function signature for the given arguments. | |
| 90 function resolveSignature(args, definedSignature) { | |
| 91 var candidateSignatures = getSignatures(definedSignature); | |
| 92 for (var i = 0; i < candidateSignatures.length; i++) { | |
| 93 if (argumentsMatchSignature(args, candidateSignatures[i])) | |
| 94 return candidateSignatures[i]; | |
| 95 } | |
| 96 return null; | |
| 97 }; | |
| 98 | |
| 99 // Returns a string representing the defined signature of the API function. | |
| 100 // Example return value for chrome.windows.getCurrent: | |
| 101 // "windows.getCurrent(optional object populate, function callback)" | |
| 102 function getParameterSignatureString(name, definedSignature) { | |
| 103 var getSchemaTypeString = function(schema) { | |
| 104 var schemaTypes = schemaValidator.getAllTypesForSchema(schema); | |
| 105 var typeName = schemaTypes.join(" or ") + " " + schema.name; | |
| 106 if (schema.optional) | |
| 107 return "optional " + typeName; | |
| 108 return typeName; | |
| 109 }; | |
| 110 | |
| 111 var typeNames = definedSignature.map(getSchemaTypeString); | |
| 112 return name + "(" + typeNames.join(", ") + ")"; | |
| 113 }; | |
| 114 | |
| 115 // Returns a string representing a call to an API function. | |
| 116 // Example return value for call: chrome.windows.get(1, callback) is: | |
| 117 // "windows.get(int, function)" | |
| 118 function getArgumentSignatureString(name, args) { | |
| 119 var typeNames = args.map(chromeHidden.JSONSchemaValidator.getType); | |
| 120 return name + "(" + typeNames.join(", ") + ")"; | |
| 121 }; | |
| 122 | |
| 123 // Finds the correct signature for the given arguments, then validates the | |
| 124 // arguments against that signature. Returns a 'normalized' arguments list | |
| 125 // where nulls are inserted where optional parameters were omitted. | |
| 126 function normalizeArgumentsAndValidate(args, funDef) { | |
| 127 if (funDef.allowAmbiguousOptionalArguments) { | |
| 128 chromeHidden.validate(args, funDef.definition.parameters); | |
| 129 return args; | |
| 130 } | |
| 131 | |
| 132 var definedSignature = funDef.definition.parameters; | |
| 133 var resolvedSignature = resolveSignature(args, definedSignature); | |
| 134 if (!resolvedSignature) | |
| 135 throw new Error("Invocation of form " + | |
| 136 getArgumentSignatureString(funDef.name, args) + | |
| 137 " doesn't match definition " + | |
| 138 getParameterSignatureString(funDef.name, definedSignature)); | |
| 139 | |
| 140 chromeHidden.validate(args, resolvedSignature); | |
| 141 | |
| 142 var normalizedArgs = []; | |
| 143 var ai = 0; | |
| 144 for (var si = 0; si < definedSignature.length; si++) { | |
| 145 if (definedSignature[si] === resolvedSignature[ai]) | |
| 146 normalizedArgs.push(args[ai++]); | |
| 147 else | |
| 148 normalizedArgs.push(null); | |
| 149 } | |
| 150 return normalizedArgs; | |
| 151 }; | |
| 152 | |
| 153 // Validates that a given schema for an API function is not ambiguous. | |
| 154 function isFunctionSignatureAmbiguous(functionDef) { | |
| 155 if (functionDef.allowAmbiguousOptionalArguments) | |
| 156 return false; | |
| 157 | |
| 158 var signaturesAmbiguous = function(signature1, signature2) { | |
| 159 if (signature1.length != signature2.length) | |
| 160 return false; | |
| 161 | |
| 162 for (var i = 0; i < signature1.length; i++) { | |
| 163 if (!schemaValidator.checkSchemaOverlap(signature1[i], signature2[i])) | |
| 164 return false; | |
| 165 } | |
| 166 return true; | |
| 167 }; | |
| 168 | |
| 169 var candidateSignatures = getSignatures(functionDef.parameters); | |
| 170 for (var i = 0; i < candidateSignatures.length; i++) { | |
| 171 for (var j = i + 1; j < candidateSignatures.length; j++) { | |
| 172 if (signaturesAmbiguous(candidateSignatures[i], candidateSignatures[j])) | |
| 173 return true; | |
| 174 } | |
| 175 } | |
| 176 return false; | |
| 177 }; | |
| 178 | |
| 179 // Stores the name and definition of each API function, with methods to | 30 // Stores the name and definition of each API function, with methods to |
| 180 // modify their behaviour (such as a custom way to handle requests to the | 31 // modify their behaviour (such as a custom way to handle requests to the |
| 181 // API, a custom callback, etc). | 32 // API, a custom callback, etc). |
| 182 function APIFunctions() { | 33 function APIFunctions() { |
| 183 this._apiFunctions = {}; | 34 this._apiFunctions = {}; |
| 184 this._unavailableApiFunctions = {}; | 35 this._unavailableApiFunctions = {}; |
| 185 } | 36 } |
| 186 APIFunctions.prototype.register = function(apiName, apiFunction) { | 37 APIFunctions.prototype.register = function(apiName, apiFunction) { |
| 187 this._apiFunctions[apiName] = apiFunction; | 38 this._apiFunctions[apiName] = apiFunction; |
| 188 }; | 39 }; |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 288 } | 139 } |
| 289 customHooks[namespace] = fn; | 140 customHooks[namespace] = fn; |
| 290 }; | 141 }; |
| 291 | 142 |
| 292 function CustomBindingsObject() { | 143 function CustomBindingsObject() { |
| 293 } | 144 } |
| 294 CustomBindingsObject.prototype.setSchema = function(schema) { | 145 CustomBindingsObject.prototype.setSchema = function(schema) { |
| 295 // The functions in the schema are in list form, so we move them into a | 146 // The functions in the schema are in list form, so we move them into a |
| 296 // dictionary for easier access. | 147 // dictionary for easier access. |
| 297 var self = this; | 148 var self = this; |
| 298 self.parameters = {}; | 149 self.functionSchemas = {}; |
| 299 schema.functions.forEach(function(f) { | 150 schema.functions.forEach(function(f) { |
| 300 self.parameters[f.name] = f.parameters; | 151 self.functionSchemas[f.name] = { |
| 152 name: f.name, |
| 153 definition: f |
| 154 } |
| 301 }); | 155 }); |
| 302 }; | 156 }; |
| 303 | 157 |
| 304 // Registers a custom type referenced via "$ref" fields in the API schema | 158 // Registers a custom type referenced via "$ref" fields in the API schema |
| 305 // JSON. | 159 // JSON. |
| 306 var customTypes = {}; | 160 var customTypes = {}; |
| 307 chromeHidden.registerCustomType = function(typeName, customTypeFactory) { | 161 chromeHidden.registerCustomType = function(typeName, customTypeFactory) { |
| 308 var customType = customTypeFactory(); | 162 var customType = customTypeFactory(); |
| 309 customType.prototype = new CustomBindingsObject(); | 163 customType.prototype = new CustomBindingsObject(); |
| 310 customTypes[typeName] = customType; | 164 customTypes[typeName] = customType; |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 383 mod[name] = mod[name] || {}; | 237 mod[name] = mod[name] || {}; |
| 384 mod = mod[name]; | 238 mod = mod[name]; |
| 385 } | 239 } |
| 386 | 240 |
| 387 // Add types to global schemaValidator | 241 // Add types to global schemaValidator |
| 388 if (apiDef.types) { | 242 if (apiDef.types) { |
| 389 apiDef.types.forEach(function(t) { | 243 apiDef.types.forEach(function(t) { |
| 390 if (!isSchemaNodeSupported(t, platform, manifestVersion)) | 244 if (!isSchemaNodeSupported(t, platform, manifestVersion)) |
| 391 return; | 245 return; |
| 392 | 246 |
| 393 schemaValidator.addTypes(t); | 247 schemaUtils.schemaValidator.addTypes(t); |
| 394 if (t.type == 'object' && customTypes[t.id]) { | 248 if (t.type == 'object' && customTypes[t.id]) { |
| 395 customTypes[t.id].prototype.setSchema(t); | 249 customTypes[t.id].prototype.setSchema(t); |
| 396 } | 250 } |
| 397 }); | 251 }); |
| 398 } | 252 } |
| 399 | 253 |
| 400 // Returns whether access to the content of a schema should be denied, | 254 // Returns whether access to the content of a schema should be denied, |
| 401 // based on the presence of "unprivileged" and whether this is an | 255 // based on the presence of "unprivileged" and whether this is an |
| 402 // extension process (versus e.g. a content script). | 256 // extension process (versus e.g. a content script). |
| 403 function isSchemaAccessAllowed(itemSchema) { | 257 function isSchemaAccessAllowed(itemSchema) { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 437 } | 291 } |
| 438 | 292 |
| 439 var apiFunction = {}; | 293 var apiFunction = {}; |
| 440 apiFunction.definition = functionDef; | 294 apiFunction.definition = functionDef; |
| 441 apiFunction.name = apiFunctionName; | 295 apiFunction.name = apiFunctionName; |
| 442 | 296 |
| 443 // TODO(aa): It would be best to run this in a unit test, but in order | 297 // TODO(aa): It would be best to run this in a unit test, but in order |
| 444 // to do that we would need to better factor this code so that it | 298 // to do that we would need to better factor this code so that it |
| 445 // doesn't depend on so much v8::Extension machinery. | 299 // doesn't depend on so much v8::Extension machinery. |
| 446 if (chromeHidden.validateAPI && | 300 if (chromeHidden.validateAPI && |
| 447 isFunctionSignatureAmbiguous(apiFunction.definition)) { | 301 schemaUtils.isFunctionSignatureAmbiguous( |
| 302 apiFunction.definition)) { |
| 448 throw new Error( | 303 throw new Error( |
| 449 apiFunction.name + ' has ambiguous optional arguments. ' + | 304 apiFunction.name + ' has ambiguous optional arguments. ' + |
| 450 'To implement custom disambiguation logic, add ' + | 305 'To implement custom disambiguation logic, add ' + |
| 451 '"allowAmbiguousOptionalArguments" to the function\'s schema.'); | 306 '"allowAmbiguousOptionalArguments" to the function\'s schema.'); |
| 452 } | 307 } |
| 453 | 308 |
| 454 apiFunctions.register(apiFunction.name, apiFunction); | 309 apiFunctions.register(apiFunction.name, apiFunction); |
| 455 | 310 |
| 456 mod[functionDef.name] = (function() { | 311 mod[functionDef.name] = (function() { |
| 457 var args = Array.prototype.slice.call(arguments); | 312 var args = Array.prototype.slice.call(arguments); |
| 458 if (this.updateArgumentsPreValidate) | 313 if (this.updateArgumentsPreValidate) |
| 459 args = this.updateArgumentsPreValidate.apply(this, args); | 314 args = this.updateArgumentsPreValidate.apply(this, args); |
| 460 | 315 |
| 461 args = normalizeArgumentsAndValidate(args, this); | 316 args = schemaUtils.normalizeArgumentsAndValidate(args, this); |
| 462 if (this.updateArgumentsPostValidate) | 317 if (this.updateArgumentsPostValidate) |
| 463 args = this.updateArgumentsPostValidate.apply(this, args); | 318 args = this.updateArgumentsPostValidate.apply(this, args); |
| 464 | 319 |
| 465 var retval; | 320 var retval; |
| 466 if (this.handleRequest) { | 321 if (this.handleRequest) { |
| 467 retval = this.handleRequest.apply(this, args); | 322 retval = this.handleRequest.apply(this, args); |
| 468 } else { | 323 } else { |
| 469 retval = sendRequest(this.name, args, | 324 retval = sendRequest(this.name, args, |
| 470 this.definition.parameters, | 325 this.definition.parameters, |
| 471 {customCallback: this.customCallback}); | 326 {customCallback: this.customCallback}); |
| 472 } | 327 } |
| 473 | 328 |
| 474 // Validate return value if defined - only in debug. | 329 // Validate return value if defined - only in debug. |
| 475 if (chromeHidden.validateCallbacks && | 330 if (chromeHidden.validateCallbacks && |
| 476 chromeHidden.validate && | |
| 477 this.definition.returns) { | 331 this.definition.returns) { |
| 478 chromeHidden.validate([retval], [this.definition.returns]); | 332 schemaUtils.validate([retval], [this.definition.returns]); |
| 479 } | 333 } |
| 480 return retval; | 334 return retval; |
| 481 }).bind(apiFunction); | 335 }).bind(apiFunction); |
| 482 }); | 336 }); |
| 483 } | 337 } |
| 484 | 338 |
| 485 // Setup Events | 339 // Setup Events |
| 486 if (apiDef.events) { | 340 if (apiDef.events) { |
| 487 apiDef.events.forEach(function(eventDef) { | 341 apiDef.events.forEach(function(eventDef) { |
| 488 if (eventDef.name in mod) { | 342 if (eventDef.name in mod) { |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 589 // beginInstallWithManifest2. | 443 // beginInstallWithManifest2. |
| 590 // See http://crbug.com/100242 | 444 // See http://crbug.com/100242 |
| 591 if (chrome.webstorePrivate) { | 445 if (chrome.webstorePrivate) { |
| 592 chrome.webstorePrivate.beginInstallWithManifest2 = | 446 chrome.webstorePrivate.beginInstallWithManifest2 = |
| 593 chrome.webstorePrivate.beginInstallWithManifest3; | 447 chrome.webstorePrivate.beginInstallWithManifest3; |
| 594 } | 448 } |
| 595 | 449 |
| 596 if (chrome.test) | 450 if (chrome.test) |
| 597 chrome.test.getApiDefinitions = GetExtensionAPIDefinition; | 451 chrome.test.getApiDefinitions = GetExtensionAPIDefinition; |
| 598 }); | 452 }); |
| OLD | NEW |