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 13 matching lines...) Expand all Loading... | |
24 var internalAPIs = {}; | 24 var internalAPIs = {}; |
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 // Generate all possible signatures for a given API function schema. | |
not at google - send to devlin
2012/02/24 03:59:56
i think these methods can be grouped with the othe
Matt Tytel
2012/02/29 04:36:37
Done.
| |
35 function getSignatures(paramList) { | |
36 if (paramList.length === 0) | |
37 return [[]]; | |
38 | |
39 var signatures = []; | |
40 var remaining = getSignatures(paramList.slice(1)); | |
41 for (var i = 0; i < remaining.length; i++) | |
42 signatures.push([paramList[0]].concat(remaining[i])) | |
43 | |
44 if (paramList[0].optional) | |
45 return signatures.concat(remaining); | |
46 return signatures; | |
47 }; | |
48 | |
49 // Return true if arguments match a given signature's schema. | |
50 function argumentsMatchSignature(args, paramList) { | |
51 if (args.length != paramList.length) | |
52 return false; | |
53 | |
54 for (var i = 0; i < paramList.length; i++) { | |
55 if (!chromeHidden.schemaValidator.isValidSchemaType( | |
56 chromeHidden.JSONSchemaValidator.getType(args[i]), paramList[i])) | |
57 return false; | |
58 } | |
59 return true; | |
60 }; | |
61 | |
62 // Finds the function signature for the given arguments. | |
63 function resolveSignature(args, paramList) { | |
64 var candidateSignatures = getSignatures(paramList); | |
65 for (var i = 0; i < candidateSignatures.length; i++) { | |
66 if (argumentsMatchSignature(args, candidateSignatures[i])) | |
67 return candidateSignatures[i]; | |
68 } | |
69 return null; | |
70 }; | |
71 | |
72 // Validates that a given schema for an API function is not ambiguous. | |
73 function isParameterSchemaAmbiguous(parameterSchema) { | |
74 var signaturesAmbiguous = function(signature1, signature2) { | |
75 if (signature1.length != signature2.length) | |
76 return false; | |
77 | |
78 for (var i = 0; i < signature1.length; i++) { | |
79 if (!chromeHidden.schemaValidator.checkSchemaOverlap(signature1[i], | |
80 signature2[i])) | |
81 return false; | |
82 } | |
83 return true; | |
84 }; | |
85 | |
86 var signatures = getSignatures(parameterSchema); | |
87 for (var i = 0; i < signatures.length; i++) { | |
88 for (var j = i + 1; j < signatures.length; j++) { | |
89 if (signaturesAmbiguous(signatures[i], signatures[j])) | |
90 return true; | |
91 } | |
92 } | |
93 return false; | |
94 }; | |
95 | |
34 // Validate arguments. | 96 // Validate arguments. |
35 chromeHidden.validationTypes = []; | 97 chromeHidden.schemaValidator = new chromeHidden.JSONSchemaValidator(); |
36 chromeHidden.validate = function(args, schemas) { | 98 chromeHidden.validate = function(args, paramList) { |
37 if (args.length > schemas.length) | 99 if (args.length > paramList.length) |
38 throw new Error("Too many arguments."); | 100 throw new Error("Too many arguments."); |
39 | 101 |
40 for (var i = 0; i < schemas.length; i++) { | 102 for (var i = 0; i < paramList.length; i++) { |
41 if (i in args && args[i] !== null && args[i] !== undefined) { | 103 if (i in args && args[i] !== null && args[i] !== undefined) { |
42 var validator = new chromeHidden.JSONSchemaValidator(); | 104 chromeHidden.schemaValidator.resetErrors(); |
43 validator.addTypes(chromeHidden.validationTypes); | 105 chromeHidden.schemaValidator.validate(args[i], paramList[i]); |
44 validator.validate(args[i], schemas[i]); | 106 if (chromeHidden.schemaValidator.errors.length == 0) |
45 if (validator.errors.length == 0) | |
46 continue; | 107 continue; |
47 | 108 |
48 var message = "Invalid value for argument " + (i + 1) + ". "; | 109 var message = "Invalid value for argument " + (i + 1) + ". "; |
49 for (var i = 0, err; err = validator.errors[i]; i++) { | 110 for (var i = 0; i < chromeHidden.schemaValidator.length; i++) { |
111 var err = chromeHidden.schemaValidator.errors[i]; | |
50 if (err.path) { | 112 if (err.path) { |
51 message += "Property '" + err.path + "': "; | 113 message += "Property '" + err.path + "': "; |
52 } | 114 } |
53 message += err.message; | 115 message += err.message; |
54 message = message.substring(0, message.length - 1); | 116 message = message.substring(0, message.length - 1); |
55 message += ", "; | 117 message += ", "; |
56 } | 118 } |
57 message = message.substring(0, message.length - 2); | 119 message = message.substring(0, message.length - 2); |
58 message += "."; | 120 message += "."; |
59 | 121 |
60 throw new Error(message); | 122 throw new Error(message); |
61 } else if (!schemas[i].optional) { | 123 } else if (!paramList[i].optional) { |
62 throw new Error("Parameter " + (i + 1) + " is required."); | 124 throw new Error("Parameter " + (i + 1) + " is required."); |
63 } | 125 } |
64 } | 126 } |
65 }; | 127 }; |
66 | 128 |
129 // Returns a string representing the defined signature of the API function. | |
130 // Example return value for chrome.windows.getCurrent: | |
131 // "windows.getCurrent(optional object populate, function callback)" | |
132 function getParameterSignatureString(name, paramList) { | |
133 var getSchemaTypeString = function(schema) { | |
134 var schemaTypes = | |
135 chromeHidden.schemaValidator.getAllTypesForSchema(schema); | |
136 var typeName = schemaTypes.join(" or ") + " " + schema.name; | |
137 if (schema.optional) | |
138 return "optional " + typeName; | |
139 return typeName; | |
140 }; | |
141 | |
142 var typeNames = paramList.map(getSchemaTypeString); | |
143 return name + "(" + typeNames.join(", ") + ")"; | |
144 }; | |
145 | |
146 // Returns a string representing a call to an API function. | |
147 // Example return value for call: chrome.windows.get(1, callback) is: | |
148 // "windows.get(int, function)" | |
149 function getArgumentSignatureString(name, args) { | |
150 var typeNames = args.map(chromeHidden.JSONSchemaValidator.getType); | |
151 return name + "(" + typeNames.join(", ") + ")"; | |
152 }; | |
153 | |
154 // Finds the correct signature for the given arguments, then validates the | |
155 // arguments against that signature. Returns a 'normalized' arguments list | |
156 // where nulls are inserted where optional parameters were omitted. | |
157 chromeHidden.updateArgumentsValidate = function(args, funDef) { | |
158 var paramList = funDef.definition.parameters; | |
159 var resolvedSignature = resolveSignature(args, paramList); | |
160 if (!resolvedSignature) | |
161 throw new Error("Invocation of form " + | |
162 getArgumentSignatureString(funDef.name, args) + | |
163 " doesn't match definition " + | |
164 getParameterSignatureString(funDef.name, paramList)); | |
165 chromeHidden.validate(args, resolvedSignature); | |
166 | |
167 var normalizedArgs = []; | |
168 var ai = 0; | |
169 for (var si = 0; si < paramList.length; si++) { | |
170 if (paramList[si] === resolvedSignature[ai]) | |
171 normalizedArgs.push(args[ai++]); | |
172 else | |
173 normalizedArgs.push(null); | |
174 } | |
175 return normalizedArgs; | |
176 }; | |
177 | |
67 // Callback handling. | 178 // Callback handling. |
68 var requests = []; | 179 var requests = []; |
69 chromeHidden.handleResponse = function(requestId, name, | 180 chromeHidden.handleResponse = function(requestId, name, |
70 success, response, error) { | 181 success, response, error) { |
71 try { | 182 try { |
72 var request = requests[requestId]; | 183 var request = requests[requestId]; |
73 if (success) { | 184 if (success) { |
74 delete chrome.extension.lastError; | 185 delete chrome.extension.lastError; |
75 } else { | 186 } else { |
76 if (!error) { | 187 if (!error) { |
(...skipping 307 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
384 | 495 |
385 // See comment on internalAPIs at the top. | 496 // See comment on internalAPIs at the top. |
386 var mod = apiDef.internal ? internalAPIs : chrome; | 497 var mod = apiDef.internal ? internalAPIs : chrome; |
387 | 498 |
388 var namespaces = apiDef.namespace.split('.'); | 499 var namespaces = apiDef.namespace.split('.'); |
389 for (var index = 0, name; name = namespaces[index]; index++) { | 500 for (var index = 0, name; name = namespaces[index]; index++) { |
390 mod[name] = mod[name] || {}; | 501 mod[name] = mod[name] || {}; |
391 mod = mod[name]; | 502 mod = mod[name]; |
392 } | 503 } |
393 | 504 |
394 // Add types to global validationTypes | 505 // Add types to global schemaValidator |
395 if (apiDef.types) { | 506 if (apiDef.types) { |
396 apiDef.types.forEach(function(t) { | 507 apiDef.types.forEach(function(t) { |
397 if (!isSchemaNodeSupported(t, platform, manifestVersion)) | 508 if (!isSchemaNodeSupported(t, platform, manifestVersion)) |
398 return; | 509 return; |
399 | 510 |
400 chromeHidden.validationTypes.push(t); | 511 chromeHidden.schemaValidator.addTypes(t); |
401 if (t.type == 'object' && customTypes[t.id]) { | 512 if (t.type == 'object' && customTypes[t.id]) { |
402 customTypes[t.id].prototype.setSchema(t); | 513 customTypes[t.id].prototype.setSchema(t); |
403 } | 514 } |
404 }); | 515 }); |
405 } | 516 } |
406 | 517 |
407 // Returns whether access to the content of a schema should be denied, | 518 // Returns whether access to the content of a schema should be denied, |
408 // based on the presence of "unprivileged" and whether this is an | 519 // based on the presence of "unprivileged" and whether this is an |
409 // extension process (versus e.g. a content script). | 520 // extension process (versus e.g. a content script). |
410 function isSchemaAccessAllowed(itemSchema) { | 521 function isSchemaAccessAllowed(itemSchema) { |
(...skipping 22 matching lines...) Expand all Loading... | |
433 if (!isSchemaNodeSupported(functionDef, platform, manifestVersion)) | 544 if (!isSchemaNodeSupported(functionDef, platform, manifestVersion)) |
434 return; | 545 return; |
435 if (!isSchemaAccessAllowed(functionDef)) { | 546 if (!isSchemaAccessAllowed(functionDef)) { |
436 addUnprivilegedAccessGetter(mod, functionDef.name); | 547 addUnprivilegedAccessGetter(mod, functionDef.name); |
437 return; | 548 return; |
438 } | 549 } |
439 | 550 |
440 var apiFunction = {}; | 551 var apiFunction = {}; |
441 apiFunction.definition = functionDef; | 552 apiFunction.definition = functionDef; |
442 apiFunction.name = apiDef.namespace + "." + functionDef.name; | 553 apiFunction.name = apiDef.namespace + "." + functionDef.name; |
554 | |
555 // Validate API for ambiguity only in DEBUG mode. | |
556 // TODO(aa): It would be best to run this in a unit test, but in order | |
557 // to do that we would need to better factor this code so that it | |
558 // didn't depend on so much v8::Extension machinery. | |
559 if (chromeHidden.validateAPI && | |
560 isParameterSchemaAmbiguous(apiFunction.definition.parameters)) | |
561 throw new Error(apiFunction.name + " is ambiguous"); | |
562 | |
443 apiFunctions.register(apiFunction.name, apiFunction); | 563 apiFunctions.register(apiFunction.name, apiFunction); |
444 | |
445 mod[functionDef.name] = (function() { | 564 mod[functionDef.name] = (function() { |
446 var args = arguments; | 565 var args = Array.prototype.slice.call(arguments); |
447 if (this.updateArgumentsPreValidate) | 566 if (this.updateArgumentsPreValidate) |
448 args = this.updateArgumentsPreValidate.apply(this, args); | 567 args = this.updateArgumentsPreValidate.apply(this, args); |
449 chromeHidden.validate(args, this.definition.parameters); | 568 |
569 args = chromeHidden.updateArgumentsValidate(args, this); | |
450 if (this.updateArgumentsPostValidate) | 570 if (this.updateArgumentsPostValidate) |
451 args = this.updateArgumentsPostValidate.apply(this, args); | 571 args = this.updateArgumentsPostValidate.apply(this, args); |
452 | 572 |
453 var retval; | 573 var retval; |
454 if (this.handleRequest) { | 574 if (this.handleRequest) { |
455 retval = this.handleRequest.apply(this, args); | 575 retval = this.handleRequest.apply(this, args); |
456 } else { | 576 } else { |
457 retval = sendRequest(this.name, args, | 577 retval = sendRequest(this.name, args, |
458 this.definition.parameters, | 578 this.definition.parameters, |
459 {customCallback: this.customCallback}); | 579 {customCallback: this.customCallback}); |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
576 // See http://crbug.com/100242 | 696 // See http://crbug.com/100242 |
577 if (chrome.webstorePrivate) { | 697 if (chrome.webstorePrivate) { |
578 chrome.webstorePrivate.beginInstallWithManifest2 = | 698 chrome.webstorePrivate.beginInstallWithManifest2 = |
579 chrome.webstorePrivate.beginInstallWithManifest3; | 699 chrome.webstorePrivate.beginInstallWithManifest3; |
580 } | 700 } |
581 | 701 |
582 if (chrome.test) | 702 if (chrome.test) |
583 chrome.test.getApiDefinitions = GetExtensionAPIDefinition; | 703 chrome.test.getApiDefinitions = GetExtensionAPIDefinition; |
584 }); | 704 }); |
585 })(); | 705 })(); |
OLD | NEW |