diff options
Diffstat (limited to 'chrome/renderer')
-rw-r--r-- | chrome/renderer/extensions/bindings_utils.cc | 20 | ||||
-rw-r--r-- | chrome/renderer/extensions/bindings_utils.h | 10 | ||||
-rw-r--r-- | chrome/renderer/extensions/event_bindings.cc | 13 | ||||
-rw-r--r-- | chrome/renderer/extensions/event_bindings.h | 2 | ||||
-rw-r--r-- | chrome/renderer/extensions/extension_api_client_unittest.cc | 7 | ||||
-rw-r--r-- | chrome/renderer/extensions/extension_process_bindings.cc | 10 | ||||
-rw-r--r-- | chrome/renderer/renderer_resources.grd | 2 | ||||
-rw-r--r-- | chrome/renderer/resources/event_bindings.js | 25 | ||||
-rw-r--r-- | chrome/renderer/resources/extension_process_bindings.js | 68 |
9 files changed, 124 insertions, 33 deletions
diff --git a/chrome/renderer/extensions/bindings_utils.cc b/chrome/renderer/extensions/bindings_utils.cc index 13deb2a..d96f8d7 100644 --- a/chrome/renderer/extensions/bindings_utils.cc +++ b/chrome/renderer/extensions/bindings_utils.cc @@ -13,6 +13,7 @@ using WebKit::WebFrame; namespace bindings_utils { const char* kChromeHidden = "chromeHidden"; +const char* kValidateCallbacks = "validateCallbacks"; struct SingletonData { ContextList contexts; @@ -40,6 +41,13 @@ v8::Handle<v8::Value> ExtensionBase::GetChromeHidden( if (hidden.IsEmpty() || hidden->IsUndefined()) { hidden = v8::Object::New(); global->SetHiddenValue(v8::String::New(kChromeHidden), hidden); + +#ifdef _DEBUG + // Tell extension_process_bindings.js to validate callbacks and events + // against their schema definitions in api/extension_api.json. + v8::Local<v8::Object>::Cast(hidden) + ->Set(v8::String::New(kValidateCallbacks), v8::True()); +#endif } DCHECK(hidden->IsObject()); @@ -94,9 +102,9 @@ RenderView* GetRenderViewForCurrentContext() { return renderview; } -void CallFunctionInContext(v8::Handle<v8::Context> context, - const std::string& function_name, int argc, - v8::Handle<v8::Value>* argv) { +v8::Handle<v8::Value> CallFunctionInContext(v8::Handle<v8::Context> context, + const std::string& function_name, int argc, + v8::Handle<v8::Value>* argv) { v8::Context::Scope context_scope(context); // Look up the function name, which may be a sub-property like @@ -111,12 +119,14 @@ void CallFunctionInContext(v8::Handle<v8::Context> context, } if (value.IsEmpty() || !value->IsFunction()) { NOTREACHED(); - return; + return v8::Undefined(); } v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(value); if (!function.IsEmpty()) - function->Call(v8::Object::New(), argc, argv); + return function->Call(v8::Object::New(), argc, argv); + + return v8::Undefined(); } } // namespace bindings_utils diff --git a/chrome/renderer/extensions/bindings_utils.h b/chrome/renderer/extensions/bindings_utils.h index 604cb2c..7354c72 100644 --- a/chrome/renderer/extensions/bindings_utils.h +++ b/chrome/renderer/extensions/bindings_utils.h @@ -106,10 +106,12 @@ RenderView* GetRenderViewForCurrentContext(); // Call the named javascript function with the given arguments in a context. // The function name should be reachable from the chromeHidden object, and can -// be a sub-property like "Port.dispatchOnMessage". -void CallFunctionInContext(v8::Handle<v8::Context> context, - const std::string& function_name, int argc, - v8::Handle<v8::Value>* argv); +// be a sub-property like "Port.dispatchOnMessage". Returns the result of +// the function call. If an exception is thrown an empty Handle will be +// returned. +v8::Handle<v8::Value> CallFunctionInContext(v8::Handle<v8::Context> context, + const std::string& function_name, int argc, + v8::Handle<v8::Value>* argv); } // namespace bindings_utils diff --git a/chrome/renderer/extensions/event_bindings.cc b/chrome/renderer/extensions/event_bindings.cc index 5003b82..b2cecdc 100644 --- a/chrome/renderer/extensions/event_bindings.cc +++ b/chrome/renderer/extensions/event_bindings.cc @@ -287,6 +287,17 @@ void EventBindings::CallFunction(const std::string& function_name, it != GetContexts().end(); ++it) { if (render_view && render_view != (*it)->render_view) continue; - CallFunctionInContext((*it)->context, function_name, argc, argv); + v8::Handle<v8::Value> retval = CallFunctionInContext((*it)->context, + function_name, argc, argv); + // In debug, the js will validate the event parameters and return a + // string if a validation error has occured. + // TODO(rafaelw): Consider only doing this check if function_name == + // "Event.dispatchJSON". +#ifdef _DEBUG + if (!retval.IsEmpty() && !retval->IsUndefined()) { + std::string error = *v8::String::AsciiValue(retval); + DCHECK(false) << error; + } +#endif } } diff --git a/chrome/renderer/extensions/event_bindings.h b/chrome/renderer/extensions/event_bindings.h index 022fe09..736cb8a 100644 --- a/chrome/renderer/extensions/event_bindings.h +++ b/chrome/renderer/extensions/event_bindings.h @@ -35,6 +35,8 @@ class EventBindings { // events. If render_view is non-NULL, only call the function in contexts // belonging to that view. See comments on // bindings_utils::CallFunctionInContext for more details. + // The called javascript function should not return a value other than + // v8::Undefined(). A DCHECK is setup to break if it is otherwise. static void CallFunction(const std::string& function_name, int argc, v8::Handle<v8::Value>* argv, RenderView* render_view); diff --git a/chrome/renderer/extensions/extension_api_client_unittest.cc b/chrome/renderer/extensions/extension_api_client_unittest.cc index 49136c5..d5f73bb 100644 --- a/chrome/renderer/extensions/extension_api_client_unittest.cc +++ b/chrome/renderer/extensions/extension_api_client_unittest.cc @@ -71,7 +71,9 @@ TEST_F(ExtensionAPIClientTest, CallbackDispatching) { "}" "function callback(result) {" " assert(typeof result == 'object', 'result not object');" - " assert(JSON.stringify(result) == '{\"foo\":\"bar\"}', " + " assert(JSON.stringify(result) == '{\"id\":1,\"index\":1,\"windowId\":1," + "\"selected\":true," + "\"url\":\"http://www.google.com/\"}'," " 'incorrect result');" " console.log('pass')" "}" @@ -92,7 +94,8 @@ TEST_F(ExtensionAPIClientTest, CallbackDispatching) { // Now send the callback a response ExtensionProcessBindings::HandleResponse( - callback_id, true, "{\"foo\":\"bar\"}", ""); + callback_id, true, "{\"id\":1,\"index\":1,\"windowId\":1,\"selected\":true," + "\"url\":\"http://www.google.com/\"}", ""); // And verify that it worked ASSERT_EQ("pass", GetConsoleMessage()); diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc index 8d07d82..d28adb0 100644 --- a/chrome/renderer/extensions/extension_process_bindings.cc +++ b/chrome/renderer/extensions/extension_process_bindings.cc @@ -298,8 +298,16 @@ void ExtensionProcessBindings::HandleResponse(int request_id, bool success, argv[2] = v8::Boolean::New(success); argv[3] = v8::String::New(response.c_str()); argv[4] = v8::String::New(error.c_str()); - bindings_utils::CallFunctionInContext( + v8::Handle<v8::Value> retval = bindings_utils::CallFunctionInContext( request->second->context, "handleResponse", arraysize(argv), argv); + // In debug, the js will validate the callback parameters and return a + // string if a validation error has occured. +#ifdef _DEBUG + if (!retval.IsEmpty() && !retval->IsUndefined()) { + std::string error = *v8::String::AsciiValue(retval); + DCHECK(false) << error; + } +#endif pending_requests.erase(request); } diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd index 5ec2309..e5c7fcf 100644 --- a/chrome/renderer/renderer_resources.grd +++ b/chrome/renderer/renderer_resources.grd @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- This comment is only here because changes to resources are not picked up -without changes to the corresponding grd file. rw --> +without changes to the corresponding grd file. rw2 --> <grit latest_public_release="0" current_release="1"> <outputs> <output filename="grit/renderer_resources.h" type="rc_header"> diff --git a/chrome/renderer/resources/event_bindings.js b/chrome/renderer/resources/event_bindings.js index 5fb16f9..3def161 100644 --- a/chrome/renderer/resources/event_bindings.js +++ b/chrome/renderer/resources/event_bindings.js @@ -24,9 +24,24 @@ var chrome = chrome || {}; // chrome.tabs.onChanged.addListener(function(data) { alert(data); }); // chromeHidden.Event.dispatch("tab-changed", "hi"); // will result in an alert dialog that says 'hi'. - chrome.Event = function(opt_eventName) { + chrome.Event = function(opt_eventName, opt_argSchemas) { this.eventName_ = opt_eventName; this.listeners_ = []; + + // Validate event parameters if we are in debug. + if (opt_argSchemas && + chromeHidden.validateCallbacks && + chromeHidden.validate) { + + this.validate_ = function(args) { + try { + chromeHidden.validate(args, opt_argSchemas); + } catch (exception) { + return "Event validation error during " + opt_eventName + " -- " + + exception; + } + } + } }; // A map of event names to the event object that is registered to that name. @@ -45,7 +60,7 @@ var chrome = chrome || {}; if (args) { args = JSON.parse(args); } - attachedNamedEvents[name].dispatch.apply( + return attachedNamedEvents[name].dispatch.apply( attachedNamedEvents[name], args); } }; @@ -106,6 +121,12 @@ var chrome = chrome || {}; // arguments to this function each listener. chrome.Event.prototype.dispatch = function(varargs) { var args = Array.prototype.slice.call(arguments); + if (this.validate_) { + var validationErrors = this.validate_(args); + if (validationErrors) { + return validationErrors; + } + } for (var i = 0; i < this.listeners_.length; i++) { try { this.listeners_[i].apply(null, args); diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js index 3155c05..66872f9 100644 --- a/chrome/renderer/resources/extension_process_bindings.js +++ b/chrome/renderer/resources/extension_process_bindings.js @@ -26,13 +26,15 @@ var chrome = chrome || {}; var chromeHidden = GetChromeHidden(); // Validate arguments. - function validate(args, schemas) { + chromeHidden.validationTypes = []; + chromeHidden.validate = function(args, schemas) { if (args.length > schemas.length) throw new Error("Too many arguments."); for (var i = 0; i < schemas.length; i++) { if (i in args && args[i] !== null && args[i] !== undefined) { var validator = new chrome.JSONSchemaValidator(); + validator.addTypes(chromeHidden.validationTypes); validator.validate(args[i], schemas[i]); if (validator.errors.length == 0) continue; @@ -57,10 +59,11 @@ var chrome = chrome || {}; } // Callback handling. - var callbacks = []; + var requests = []; chromeHidden.handleResponse = function(requestId, name, success, response, error) { try { + var request = requests[requestId]; if (success) { delete chrome.extension.lastError; } else { @@ -72,16 +75,41 @@ var chrome = chrome || {}; "message": error }; } + + if (request.callback) { + // Callbacks currently only support one callback argument. + var callbackArgs = response ? [JSON.parse(response)] : []; + + // Validate callback in debug only -- and only when the + // caller has provided a callback. Implementations of api + // calls my not return data if they observe the caller + // has not provided a callback. + if (chromeHidden.validateCallbacks && !error) { + try { + if (!request.callbackSchema.parameters) { + throw "No callback schemas defined"; + } + + if (request.callbackSchema.parameters.length > 1) { + throw "Callbacks may only define one parameter"; + } + + chromeHidden.validate(callbackArgs, + request.callbackSchema.parameters); + } catch (exception) { + return "Callback validation error during " + name + " -- " + + exception; + } + } - if (callbacks[requestId]) { if (response) { - callbacks[requestId](JSON.parse(response)); + request.callback(callbackArgs[0]); } else { - callbacks[requestId](); + request.callback(); } } } finally { - delete callbacks[requestId]; + delete requests[requestId]; delete chrome.extension.lastError; } }; @@ -89,15 +117,16 @@ var chrome = chrome || {}; function prepareRequest(args, argSchemas) { var request = {}; var argCount = args.length; - + // Look for callback param. if (argSchemas.length > 0 && args.length == argSchemas.length && argSchemas[argSchemas.length - 1].type == "function") { request.callback = args[argSchemas.length - 1]; + request.callbackSchema = argSchemas[argSchemas.length - 1]; --argCount; } - + // Calls with one argument expect singular argument. Calls with multiple // expect a list. if (argCount == 1) { @@ -121,12 +150,9 @@ var chrome = chrome || {}; request.args = null; var sargs = JSON.stringify(request.args); var requestId = GetNextRequestId(); - var hasCallback = false; - if (request.callback) { - hasCallback = true; - callbacks[requestId] = request.callback; - } - return StartRequest(functionName, sargs, requestId, hasCallback); + requests[requestId] = request; + return StartRequest(functionName, sargs, requestId, + request.callback ? true : false); } // Using forEach for convenience, and to bind |module|s & |apiDefs|s via @@ -179,7 +205,14 @@ var chrome = chrome || {}; forEach(apiDefinitions, function(apiDef) { chrome[apiDef.namespace] = chrome[apiDef.namespace] || {}; var module = chrome[apiDef.namespace]; - + + // Add types to global validationTypes + if (apiDef.types) { + forEach(apiDef.types, function(t) { + chromeHidden.validationTypes.push(t); + }); + } + // Setup Functions. if (apiDef.functions) { forEach(apiDef.functions, function(functionDef) { @@ -194,7 +227,7 @@ var chrome = chrome || {}; apiFunctions[apiFunction.name] = apiFunction; module[functionDef.name] = bind(apiFunction, function() { - validate(arguments, this.definition.parameters); + chromeHidden.validate(arguments, this.definition.parameters); if (this.handleRequest) return this.handleRequest.apply(this, arguments); @@ -214,7 +247,8 @@ var chrome = chrome || {}; return; var eventName = apiDef.namespace + "." + eventDef.name; - module[eventDef.name] = new chrome.Event(eventName); + module[eventDef.name] = new chrome.Event(eventName, + eventDef.parameters); }); } }); |