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 var chrome = chrome || {}; | 8 var chrome = chrome || {}; |
9 (function() { | 9 (function() { |
10 native function GetChromeHidden(); | 10 native function GetChromeHidden(); |
(...skipping 14 matching lines...) Expand all Loading... |
25 chromeHidden.internalAPIs = internalAPIs; | 25 chromeHidden.internalAPIs = internalAPIs; |
26 | 26 |
27 function forEach(dict, f) { | 27 function forEach(dict, f) { |
28 for (key in dict) { | 28 for (key in dict) { |
29 if (dict.hasOwnProperty(key)) | 29 if (dict.hasOwnProperty(key)) |
30 f(key, dict[key]); | 30 f(key, dict[key]); |
31 } | 31 } |
32 } | 32 } |
33 | 33 |
34 // Validate arguments. | 34 // Validate arguments. |
35 chromeHidden.validationTypes = []; | 35 var schemaValidator = new chromeHidden.JSONSchemaValidator(); |
36 chromeHidden.validate = function(args, schemas) { | 36 chromeHidden.validate = function(args, parameterSchemas) { |
37 if (args.length > schemas.length) | 37 if (args.length > parameterSchemas.length) |
38 throw new Error("Too many arguments."); | 38 throw new Error("Too many arguments."); |
39 | 39 |
40 for (var i = 0; i < schemas.length; i++) { | 40 for (var i = 0; i < parameterSchemas.length; i++) { |
41 if (i in args && args[i] !== null && args[i] !== undefined) { | 41 if (i in args && args[i] !== null && args[i] !== undefined) { |
42 var validator = new chromeHidden.JSONSchemaValidator(); | 42 schemaValidator.resetErrors(); |
43 validator.addTypes(chromeHidden.validationTypes); | 43 schemaValidator.validate(args[i], parameterSchemas[i]); |
44 validator.validate(args[i], schemas[i]); | 44 if (schemaValidator.errors.length == 0) |
45 if (validator.errors.length == 0) | |
46 continue; | 45 continue; |
47 | 46 |
48 var message = "Invalid value for argument " + (i + 1) + ". "; | 47 var message = "Invalid value for argument " + (i + 1) + ". "; |
49 for (var i = 0, err; err = validator.errors[i]; i++) { | 48 for (var i = 0, err; err = schemaValidator.errors[i]; i++) { |
50 if (err.path) { | 49 if (err.path) { |
51 message += "Property '" + err.path + "': "; | 50 message += "Property '" + err.path + "': "; |
52 } | 51 } |
53 message += err.message; | 52 message += err.message; |
54 message = message.substring(0, message.length - 1); | 53 message = message.substring(0, message.length - 1); |
55 message += ", "; | 54 message += ", "; |
56 } | 55 } |
57 message = message.substring(0, message.length - 2); | 56 message = message.substring(0, message.length - 2); |
58 message += "."; | 57 message += "."; |
59 | 58 |
60 throw new Error(message); | 59 throw new Error(message); |
61 } else if (!schemas[i].optional) { | 60 } else if (!parameterSchemas[i].optional) { |
62 throw new Error("Parameter " + (i + 1) + " is required."); | 61 throw new Error("Parameter " + (i + 1) + " is required."); |
63 } | 62 } |
64 } | 63 } |
65 }; | 64 }; |
66 | 65 |
| 66 // Generate all possible signatures for a given API function. |
| 67 function getSignatures(parameterSchemas) { |
| 68 if (parameterSchemas.length === 0) |
| 69 return [[]]; |
| 70 |
| 71 var signatures = []; |
| 72 var remaining = getSignatures(parameterSchemas.slice(1)); |
| 73 for (var i = 0; i < remaining.length; i++) |
| 74 signatures.push([parameterSchemas[0]].concat(remaining[i])) |
| 75 |
| 76 if (parameterSchemas[0].optional) |
| 77 return signatures.concat(remaining); |
| 78 return signatures; |
| 79 }; |
| 80 |
| 81 // Return true if arguments match a given signature's schema. |
| 82 function argumentsMatchSignature(args, candidateSignature) { |
| 83 if (args.length != candidateSignature.length) |
| 84 return false; |
| 85 |
| 86 for (var i = 0; i < candidateSignature.length; i++) { |
| 87 var argType = chromeHidden.JSONSchemaValidator.getType(args[i]); |
| 88 if (!schemaValidator.isValidSchemaType(argType, candidateSignature[i])) |
| 89 return false; |
| 90 } |
| 91 return true; |
| 92 }; |
| 93 |
| 94 // Finds the function signature for the given arguments. |
| 95 function resolveSignature(args, definedSignature) { |
| 96 var candidateSignatures = getSignatures(definedSignature); |
| 97 for (var i = 0; i < candidateSignatures.length; i++) { |
| 98 if (argumentsMatchSignature(args, candidateSignatures[i])) |
| 99 return candidateSignatures[i]; |
| 100 } |
| 101 return null; |
| 102 }; |
| 103 |
| 104 // Returns a string representing the defined signature of the API function. |
| 105 // Example return value for chrome.windows.getCurrent: |
| 106 // "windows.getCurrent(optional object populate, function callback)" |
| 107 function getParameterSignatureString(name, definedSignature) { |
| 108 var getSchemaTypeString = function(schema) { |
| 109 var schemaTypes = schemaValidator.getAllTypesForSchema(schema); |
| 110 var typeName = schemaTypes.join(" or ") + " " + schema.name; |
| 111 if (schema.optional) |
| 112 return "optional " + typeName; |
| 113 return typeName; |
| 114 }; |
| 115 |
| 116 var typeNames = definedSignature.map(getSchemaTypeString); |
| 117 return name + "(" + typeNames.join(", ") + ")"; |
| 118 }; |
| 119 |
| 120 // Returns a string representing a call to an API function. |
| 121 // Example return value for call: chrome.windows.get(1, callback) is: |
| 122 // "windows.get(int, function)" |
| 123 function getArgumentSignatureString(name, args) { |
| 124 var typeNames = args.map(chromeHidden.JSONSchemaValidator.getType); |
| 125 return name + "(" + typeNames.join(", ") + ")"; |
| 126 }; |
| 127 |
| 128 // Finds the correct signature for the given arguments, then validates the |
| 129 // arguments against that signature. Returns a 'normalized' arguments list |
| 130 // where nulls are inserted where optional parameters were omitted. |
| 131 chromeHidden.updateArgumentsValidate = function(args, funDef) { |
| 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 chromeHidden.validate(args, resolvedSignature); |
| 140 |
| 141 var normalizedArgs = []; |
| 142 var ai = 0; |
| 143 for (var si = 0; si < definedSignature.length; si++) { |
| 144 if (definedSignature[si] === resolvedSignature[ai]) |
| 145 normalizedArgs.push(args[ai++]); |
| 146 else |
| 147 normalizedArgs.push(null); |
| 148 } |
| 149 return normalizedArgs; |
| 150 }; |
| 151 |
| 152 // Validates that a given schema for an API function is not ambiguous. |
| 153 function isDefinedSignatureAmbiguous(definedSignature) { |
| 154 var signaturesAmbiguous = function(signature1, signature2) { |
| 155 if (signature1.length != signature2.length) |
| 156 return false; |
| 157 |
| 158 for (var i = 0; i < signature1.length; i++) { |
| 159 if (!schemaValidator.checkSchemaOverlap(signature1[i], signature2[i])) |
| 160 return false; |
| 161 } |
| 162 return true; |
| 163 }; |
| 164 |
| 165 var candidateSignatures = getSignatures(definedSignature); |
| 166 for (var i = 0; i < candidateSignatures.length; i++) { |
| 167 for (var j = i + 1; j < candidateSignatures.length; j++) { |
| 168 if (signaturesAmbiguous(candidateSignatures[i], candidateSignatures[j])) |
| 169 return true; |
| 170 } |
| 171 } |
| 172 return false; |
| 173 }; |
| 174 |
67 // Callback handling. | 175 // Callback handling. |
68 var requests = []; | 176 var requests = []; |
69 chromeHidden.handleResponse = function(requestId, name, | 177 chromeHidden.handleResponse = function(requestId, name, |
70 success, response, error) { | 178 success, response, error) { |
71 try { | 179 try { |
72 var request = requests[requestId]; | 180 var request = requests[requestId]; |
73 if (success) { | 181 if (success) { |
74 delete chrome.extension.lastError; | 182 delete chrome.extension.lastError; |
75 } else { | 183 } else { |
76 if (!error) { | 184 if (!error) { |
(...skipping 351 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
428 | 536 |
429 // See comment on internalAPIs at the top. | 537 // See comment on internalAPIs at the top. |
430 var mod = apiDef.internal ? internalAPIs : chrome; | 538 var mod = apiDef.internal ? internalAPIs : chrome; |
431 | 539 |
432 var namespaces = apiDef.namespace.split('.'); | 540 var namespaces = apiDef.namespace.split('.'); |
433 for (var index = 0, name; name = namespaces[index]; index++) { | 541 for (var index = 0, name; name = namespaces[index]; index++) { |
434 mod[name] = mod[name] || {}; | 542 mod[name] = mod[name] || {}; |
435 mod = mod[name]; | 543 mod = mod[name]; |
436 } | 544 } |
437 | 545 |
438 // Add types to global validationTypes | 546 // Add types to global schemaValidator |
439 if (apiDef.types) { | 547 if (apiDef.types) { |
440 apiDef.types.forEach(function(t) { | 548 apiDef.types.forEach(function(t) { |
441 if (!isSchemaNodeSupported(t, platform, manifestVersion)) | 549 if (!isSchemaNodeSupported(t, platform, manifestVersion)) |
442 return; | 550 return; |
443 | 551 |
444 chromeHidden.validationTypes.push(t); | 552 schemaValidator.addTypes(t); |
445 if (t.type == 'object' && customTypes[t.id]) { | 553 if (t.type == 'object' && customTypes[t.id]) { |
446 customTypes[t.id].prototype.setSchema(t); | 554 customTypes[t.id].prototype.setSchema(t); |
447 } | 555 } |
448 }); | 556 }); |
449 } | 557 } |
450 | 558 |
451 // Returns whether access to the content of a schema should be denied, | 559 // Returns whether access to the content of a schema should be denied, |
452 // based on the presence of "unprivileged" and whether this is an | 560 // based on the presence of "unprivileged" and whether this is an |
453 // extension process (versus e.g. a content script). | 561 // extension process (versus e.g. a content script). |
454 function isSchemaAccessAllowed(itemSchema) { | 562 function isSchemaAccessAllowed(itemSchema) { |
(...skipping 28 matching lines...) Expand all Loading... |
483 } | 591 } |
484 if (!isSchemaAccessAllowed(functionDef)) { | 592 if (!isSchemaAccessAllowed(functionDef)) { |
485 apiFunctions.registerUnavailable(apiFunctionName); | 593 apiFunctions.registerUnavailable(apiFunctionName); |
486 addUnprivilegedAccessGetter(mod, functionDef.name); | 594 addUnprivilegedAccessGetter(mod, functionDef.name); |
487 return; | 595 return; |
488 } | 596 } |
489 | 597 |
490 var apiFunction = {}; | 598 var apiFunction = {}; |
491 apiFunction.definition = functionDef; | 599 apiFunction.definition = functionDef; |
492 apiFunction.name = apiFunctionName; | 600 apiFunction.name = apiFunctionName; |
493 apiFunctions.register(apiFunctionName, apiFunction); | 601 |
| 602 // Validate API for ambiguity only in DEBUG mode. |
| 603 // We do not validate 'extension.sendRequest' because we know it is |
| 604 // ambiguous. We disambiguate calls in 'updateArgumentsPrevalidate'. |
| 605 // TODO(aa): It would be best to run this in a unit test, but in order |
| 606 // to do that we would need to better factor this code so that it |
| 607 // didn't depend on so much v8::Extension machinery. |
| 608 if (chromeHidden.validateAPI && |
| 609 apiFunction.name != "extension.sendRequest" && |
| 610 isDefinedSignatureAmbiguous(apiFunction.definition.parameters)) |
| 611 throw new Error(apiFunction.name + " is ambiguous"); |
| 612 apiFunctions.register(apiFunction.name, apiFunction); |
494 | 613 |
495 mod[functionDef.name] = (function() { | 614 mod[functionDef.name] = (function() { |
496 var args = arguments; | 615 var args = Array.prototype.slice.call(arguments); |
497 if (this.updateArgumentsPreValidate) | 616 if (this.updateArgumentsPreValidate) |
498 args = this.updateArgumentsPreValidate.apply(this, args); | 617 args = this.updateArgumentsPreValidate.apply(this, args); |
499 chromeHidden.validate(args, this.definition.parameters); | 618 |
| 619 args = chromeHidden.updateArgumentsValidate(args, this); |
500 if (this.updateArgumentsPostValidate) | 620 if (this.updateArgumentsPostValidate) |
501 args = this.updateArgumentsPostValidate.apply(this, args); | 621 args = this.updateArgumentsPostValidate.apply(this, args); |
502 | 622 |
503 var retval; | 623 var retval; |
504 if (this.handleRequest) { | 624 if (this.handleRequest) { |
505 retval = this.handleRequest.apply(this, args); | 625 retval = this.handleRequest.apply(this, args); |
506 } else { | 626 } else { |
507 retval = sendRequest(this.name, args, | 627 retval = sendRequest(this.name, args, |
508 this.definition.parameters, | 628 this.definition.parameters, |
509 {customCallback: this.customCallback}); | 629 {customCallback: this.customCallback}); |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
627 // See http://crbug.com/100242 | 747 // See http://crbug.com/100242 |
628 if (chrome.webstorePrivate) { | 748 if (chrome.webstorePrivate) { |
629 chrome.webstorePrivate.beginInstallWithManifest2 = | 749 chrome.webstorePrivate.beginInstallWithManifest2 = |
630 chrome.webstorePrivate.beginInstallWithManifest3; | 750 chrome.webstorePrivate.beginInstallWithManifest3; |
631 } | 751 } |
632 | 752 |
633 if (chrome.test) | 753 if (chrome.test) |
634 chrome.test.getApiDefinitions = GetExtensionAPIDefinition; | 754 chrome.test.getApiDefinitions = GetExtensionAPIDefinition; |
635 }); | 755 }); |
636 })(); | 756 })(); |
OLD | NEW |