summaryrefslogtreecommitdiffstats
path: root/chrome/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/renderer')
-rw-r--r--chrome/renderer/extensions/bindings_utils.cc20
-rw-r--r--chrome/renderer/extensions/bindings_utils.h10
-rw-r--r--chrome/renderer/extensions/event_bindings.cc13
-rw-r--r--chrome/renderer/extensions/event_bindings.h2
-rw-r--r--chrome/renderer/extensions/extension_api_client_unittest.cc7
-rw-r--r--chrome/renderer/extensions/extension_process_bindings.cc10
-rw-r--r--chrome/renderer/renderer_resources.grd2
-rw-r--r--chrome/renderer/resources/event_bindings.js25
-rw-r--r--chrome/renderer/resources/extension_process_bindings.js68
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);
});
}
});