| OLD | NEW |
| 1 // Copyright (c) 2009 The chrome Authors. All rights reserved. | 1 // Copyright (c) 2009 The chrome 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 // ----------------------------------------------------------------------------- | 5 // ----------------------------------------------------------------------------- |
| 6 // NOTE: If you change this file you need to touch renderer_resources.grd to | 6 // NOTE: If you change this file you need to touch renderer_resources.grd to |
| 7 // have your change take effect. | 7 // have your change take effect. |
| 8 // ----------------------------------------------------------------------------- | 8 // ----------------------------------------------------------------------------- |
| 9 | 9 |
| 10 // This script contains privileged chrome extension related javascript APIs. | 10 // This script contains privileged chrome extension related javascript APIs. |
| 11 // It is loaded by pages whose URL has the chrome-extension protocol. | 11 // It is loaded by pages whose URL has the chrome-extension protocol. |
| 12 | 12 |
| 13 var chrome = chrome || {}; | 13 var chrome = chrome || {}; |
| 14 (function() { | 14 (function() { |
| 15 native function GetExtensionAPIDefinition(); | 15 native function GetExtensionAPIDefinition(); |
| 16 native function StartRequest(); | 16 native function StartRequest(); |
| 17 native function GetCurrentPageActions(extensionId); | 17 native function GetCurrentPageActions(extensionId); |
| 18 native function GetExtensionViews(); | 18 native function GetExtensionViews(); |
| 19 native function GetChromeHidden(); | 19 native function GetChromeHidden(); |
| 20 native function GetNextRequestId(); | 20 native function GetNextRequestId(); |
| 21 native function OpenChannelToTab(); | 21 native function OpenChannelToTab(); |
| 22 | 22 |
| 23 if (!chrome) | 23 if (!chrome) |
| 24 chrome = {}; | 24 chrome = {}; |
| 25 | 25 |
| 26 var chromeHidden = GetChromeHidden(); | 26 var chromeHidden = GetChromeHidden(); |
| 27 | 27 |
| 28 // Validate arguments. | 28 // Validate arguments. |
| 29 function validate(args, schemas) { | 29 chromeHidden.validationTypes = []; |
| 30 chromeHidden.validate = function(args, schemas) { |
| 30 if (args.length > schemas.length) | 31 if (args.length > schemas.length) |
| 31 throw new Error("Too many arguments."); | 32 throw new Error("Too many arguments."); |
| 32 | 33 |
| 33 for (var i = 0; i < schemas.length; i++) { | 34 for (var i = 0; i < schemas.length; i++) { |
| 34 if (i in args && args[i] !== null && args[i] !== undefined) { | 35 if (i in args && args[i] !== null && args[i] !== undefined) { |
| 35 var validator = new chrome.JSONSchemaValidator(); | 36 var validator = new chrome.JSONSchemaValidator(); |
| 37 validator.addTypes(chromeHidden.validationTypes); |
| 36 validator.validate(args[i], schemas[i]); | 38 validator.validate(args[i], schemas[i]); |
| 37 if (validator.errors.length == 0) | 39 if (validator.errors.length == 0) |
| 38 continue; | 40 continue; |
| 39 | 41 |
| 40 var message = "Invalid value for argument " + i + ". "; | 42 var message = "Invalid value for argument " + i + ". "; |
| 41 for (var i = 0, err; err = validator.errors[i]; i++) { | 43 for (var i = 0, err; err = validator.errors[i]; i++) { |
| 42 if (err.path) { | 44 if (err.path) { |
| 43 message += "Property '" + err.path + "': "; | 45 message += "Property '" + err.path + "': "; |
| 44 } | 46 } |
| 45 message += err.message; | 47 message += err.message; |
| 46 message = message.substring(0, message.length - 1); | 48 message = message.substring(0, message.length - 1); |
| 47 message += ", "; | 49 message += ", "; |
| 48 } | 50 } |
| 49 message = message.substring(0, message.length - 2); | 51 message = message.substring(0, message.length - 2); |
| 50 message += "."; | 52 message += "."; |
| 51 | 53 |
| 52 throw new Error(message); | 54 throw new Error(message); |
| 53 } else if (!schemas[i].optional) { | 55 } else if (!schemas[i].optional) { |
| 54 throw new Error("Parameter " + i + " is required."); | 56 throw new Error("Parameter " + i + " is required."); |
| 55 } | 57 } |
| 56 } | 58 } |
| 57 } | 59 } |
| 58 | 60 |
| 59 // Callback handling. | 61 // Callback handling. |
| 60 var callbacks = []; | 62 var requests = []; |
| 61 chromeHidden.handleResponse = function(requestId, name, | 63 chromeHidden.handleResponse = function(requestId, name, |
| 62 success, response, error) { | 64 success, response, error) { |
| 63 try { | 65 try { |
| 66 var request = requests[requestId]; |
| 64 if (success) { | 67 if (success) { |
| 65 delete chrome.extension.lastError; | 68 delete chrome.extension.lastError; |
| 66 } else { | 69 } else { |
| 67 if (!error) { | 70 if (!error) { |
| 68 error = "Unknown error." | 71 error = "Unknown error." |
| 69 } | 72 } |
| 70 console.error("Error during " + name + ": " + error); | 73 console.error("Error during " + name + ": " + error); |
| 71 chrome.extension.lastError = { | 74 chrome.extension.lastError = { |
| 72 "message": error | 75 "message": error |
| 73 }; | 76 }; |
| 74 } | 77 } |
| 78 |
| 79 if (request.callback) { |
| 80 // Callbacks currently only support one callback argument. |
| 81 var callbackArgs = response ? [JSON.parse(response)] : []; |
| 75 | 82 |
| 76 if (callbacks[requestId]) { | 83 // Validate callback in debug only -- and only when the |
| 84 // caller has provided a callback. Implementations of api |
| 85 // calls my not return data if they observe the caller |
| 86 // has not provided a callback. |
| 87 if (chromeHidden.validateCallbacks && !error) { |
| 88 try { |
| 89 if (!request.callbackSchema.parameters) { |
| 90 throw "No callback schemas defined"; |
| 91 } |
| 92 |
| 93 if (request.callbackSchema.parameters.length > 1) { |
| 94 throw "Callbacks may only define one parameter"; |
| 95 } |
| 96 |
| 97 chromeHidden.validate(callbackArgs, |
| 98 request.callbackSchema.parameters); |
| 99 } catch (exception) { |
| 100 return "Callback validation error during " + name + " -- " + |
| 101 exception; |
| 102 } |
| 103 } |
| 104 |
| 77 if (response) { | 105 if (response) { |
| 78 callbacks[requestId](JSON.parse(response)); | 106 request.callback(callbackArgs[0]); |
| 79 } else { | 107 } else { |
| 80 callbacks[requestId](); | 108 request.callback(); |
| 81 } | 109 } |
| 82 } | 110 } |
| 83 } finally { | 111 } finally { |
| 84 delete callbacks[requestId]; | 112 delete requests[requestId]; |
| 85 delete chrome.extension.lastError; | 113 delete chrome.extension.lastError; |
| 86 } | 114 } |
| 87 }; | 115 }; |
| 88 | 116 |
| 89 function prepareRequest(args, argSchemas) { | 117 function prepareRequest(args, argSchemas) { |
| 90 var request = {}; | 118 var request = {}; |
| 91 var argCount = args.length; | 119 var argCount = args.length; |
| 92 | 120 |
| 93 // Look for callback param. | 121 // Look for callback param. |
| 94 if (argSchemas.length > 0 && | 122 if (argSchemas.length > 0 && |
| 95 args.length == argSchemas.length && | 123 args.length == argSchemas.length && |
| 96 argSchemas[argSchemas.length - 1].type == "function") { | 124 argSchemas[argSchemas.length - 1].type == "function") { |
| 97 request.callback = args[argSchemas.length - 1]; | 125 request.callback = args[argSchemas.length - 1]; |
| 126 request.callbackSchema = argSchemas[argSchemas.length - 1]; |
| 98 --argCount; | 127 --argCount; |
| 99 } | 128 } |
| 100 | 129 |
| 101 // Calls with one argument expect singular argument. Calls with multiple | 130 // Calls with one argument expect singular argument. Calls with multiple |
| 102 // expect a list. | 131 // expect a list. |
| 103 if (argCount == 1) { | 132 if (argCount == 1) { |
| 104 request.args = args[0]; | 133 request.args = args[0]; |
| 105 } | 134 } |
| 106 if (argCount > 1) { | 135 if (argCount > 1) { |
| 107 request.args = []; | 136 request.args = []; |
| 108 for (var k = 0; k < argCount; k++) { | 137 for (var k = 0; k < argCount; k++) { |
| 109 request.args[k] = args[k]; | 138 request.args[k] = args[k]; |
| 110 } | 139 } |
| 111 } | 140 } |
| 112 | 141 |
| 113 return request; | 142 return request; |
| 114 } | 143 } |
| 115 | 144 |
| 116 // Send an API request and optionally register a callback. | 145 // Send an API request and optionally register a callback. |
| 117 function sendRequest(functionName, args, argSchemas) { | 146 function sendRequest(functionName, args, argSchemas) { |
| 118 var request = prepareRequest(args, argSchemas); | 147 var request = prepareRequest(args, argSchemas); |
| 119 // JSON.stringify doesn't support a root object which is undefined. | 148 // JSON.stringify doesn't support a root object which is undefined. |
| 120 if (request.args === undefined) | 149 if (request.args === undefined) |
| 121 request.args = null; | 150 request.args = null; |
| 122 var sargs = JSON.stringify(request.args); | 151 var sargs = JSON.stringify(request.args); |
| 123 var requestId = GetNextRequestId(); | 152 var requestId = GetNextRequestId(); |
| 124 var hasCallback = false; | 153 requests[requestId] = request; |
| 125 if (request.callback) { | 154 return StartRequest(functionName, sargs, requestId, |
| 126 hasCallback = true; | 155 request.callback ? true : false); |
| 127 callbacks[requestId] = request.callback; | |
| 128 } | |
| 129 return StartRequest(functionName, sargs, requestId, hasCallback); | |
| 130 } | 156 } |
| 131 | 157 |
| 132 // Using forEach for convenience, and to bind |module|s & |apiDefs|s via | 158 // Using forEach for convenience, and to bind |module|s & |apiDefs|s via |
| 133 // closures. | 159 // closures. |
| 134 function forEach(a, f) { | 160 function forEach(a, f) { |
| 135 for (var i = 0; i < a.length; i++) { | 161 for (var i = 0; i < a.length; i++) { |
| 136 f(a[i], i); | 162 f(a[i], i); |
| 137 } | 163 } |
| 138 } | 164 } |
| 139 | 165 |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 172 // TODO(rafaelw): Consider defining a json schema for an api definition | 198 // TODO(rafaelw): Consider defining a json schema for an api definition |
| 173 // and validating either here, in a unit_test or both. | 199 // and validating either here, in a unit_test or both. |
| 174 // TODO(rafaelw): Handle synchronous functions. | 200 // TODO(rafaelw): Handle synchronous functions. |
| 175 // TOOD(rafaelw): Consider providing some convenient override points | 201 // TOOD(rafaelw): Consider providing some convenient override points |
| 176 // for api functions that wish to insert themselves into the call. | 202 // for api functions that wish to insert themselves into the call. |
| 177 var apiDefinitions = JSON.parse(GetExtensionAPIDefinition()); | 203 var apiDefinitions = JSON.parse(GetExtensionAPIDefinition()); |
| 178 | 204 |
| 179 forEach(apiDefinitions, function(apiDef) { | 205 forEach(apiDefinitions, function(apiDef) { |
| 180 chrome[apiDef.namespace] = chrome[apiDef.namespace] || {}; | 206 chrome[apiDef.namespace] = chrome[apiDef.namespace] || {}; |
| 181 var module = chrome[apiDef.namespace]; | 207 var module = chrome[apiDef.namespace]; |
| 182 | 208 |
| 209 // Add types to global validationTypes |
| 210 if (apiDef.types) { |
| 211 forEach(apiDef.types, function(t) { |
| 212 chromeHidden.validationTypes.push(t); |
| 213 }); |
| 214 } |
| 215 |
| 183 // Setup Functions. | 216 // Setup Functions. |
| 184 if (apiDef.functions) { | 217 if (apiDef.functions) { |
| 185 forEach(apiDef.functions, function(functionDef) { | 218 forEach(apiDef.functions, function(functionDef) { |
| 186 // Module functions may have been defined earlier by hand. Don't | 219 // Module functions may have been defined earlier by hand. Don't |
| 187 // clobber them. | 220 // clobber them. |
| 188 if (module[functionDef.name]) | 221 if (module[functionDef.name]) |
| 189 return; | 222 return; |
| 190 | 223 |
| 191 var apiFunction = {}; | 224 var apiFunction = {}; |
| 192 apiFunction.definition = functionDef; | 225 apiFunction.definition = functionDef; |
| 193 apiFunction.name = apiDef.namespace + "." + functionDef.name;; | 226 apiFunction.name = apiDef.namespace + "." + functionDef.name;; |
| 194 apiFunctions[apiFunction.name] = apiFunction; | 227 apiFunctions[apiFunction.name] = apiFunction; |
| 195 | 228 |
| 196 module[functionDef.name] = bind(apiFunction, function() { | 229 module[functionDef.name] = bind(apiFunction, function() { |
| 197 validate(arguments, this.definition.parameters); | 230 chromeHidden.validate(arguments, this.definition.parameters); |
| 198 | 231 |
| 199 if (this.handleRequest) | 232 if (this.handleRequest) |
| 200 return this.handleRequest.apply(this, arguments); | 233 return this.handleRequest.apply(this, arguments); |
| 201 else | 234 else |
| 202 return sendRequest(this.name, arguments, | 235 return sendRequest(this.name, arguments, |
| 203 this.definition.parameters); | 236 this.definition.parameters); |
| 204 }); | 237 }); |
| 205 }); | 238 }); |
| 206 } | 239 } |
| 207 | 240 |
| 208 // Setup Events | 241 // Setup Events |
| 209 if (apiDef.events) { | 242 if (apiDef.events) { |
| 210 forEach(apiDef.events, function(eventDef) { | 243 forEach(apiDef.events, function(eventDef) { |
| 211 // Module events may have been defined earlier by hand. Don't clobber | 244 // Module events may have been defined earlier by hand. Don't clobber |
| 212 // them. | 245 // them. |
| 213 if (module[eventDef.name]) | 246 if (module[eventDef.name]) |
| 214 return; | 247 return; |
| 215 | 248 |
| 216 var eventName = apiDef.namespace + "." + eventDef.name; | 249 var eventName = apiDef.namespace + "." + eventDef.name; |
| 217 module[eventDef.name] = new chrome.Event(eventName); | 250 module[eventDef.name] = new chrome.Event(eventName, |
| 251 eventDef.parameters); |
| 218 }); | 252 }); |
| 219 } | 253 } |
| 220 }); | 254 }); |
| 221 | 255 |
| 222 apiFunctions["tabs.connect"].handleRequest = function(tabId, opt_name) { | 256 apiFunctions["tabs.connect"].handleRequest = function(tabId, opt_name) { |
| 223 var portId = OpenChannelToTab( | 257 var portId = OpenChannelToTab( |
| 224 tabId, chrome.extension.id_, opt_name || ""); | 258 tabId, chrome.extension.id_, opt_name || ""); |
| 225 return chromeHidden.Port.createPort(portId, opt_name); | 259 return chromeHidden.Port.createPort(portId, opt_name); |
| 226 } | 260 } |
| 227 | 261 |
| (...skipping 15 matching lines...) Expand all Loading... |
| 243 apiFunctions["extension.getTabContentses"].handleRequest = | 277 apiFunctions["extension.getTabContentses"].handleRequest = |
| 244 function(windowId) { | 278 function(windowId) { |
| 245 if (typeof(windowId) == "undefined") | 279 if (typeof(windowId) == "undefined") |
| 246 windowId = -1; | 280 windowId = -1; |
| 247 return GetExtensionViews(windowId, "TAB"); | 281 return GetExtensionViews(windowId, "TAB"); |
| 248 } | 282 } |
| 249 | 283 |
| 250 setupPageActionEvents(extensionId); | 284 setupPageActionEvents(extensionId); |
| 251 }); | 285 }); |
| 252 })(); | 286 })(); |
| OLD | NEW |