[Extensions Bindings] Add a native ValidateType method
Add a utility method exposed to JS to allow bindings to validate a type
against an expected schema. This is used to e.g. match expected
instances used in the declarative APIs.
This eliminates one of the remaining reliances on the utils/schemaUtils
JS files for native bindings.
Bug: 810487
Change-Id: Id87d38f8196f56b285e9a69221a34f643957d237
Reviewed-on: https://chromium-review.googlesource.com/905422
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Reviewed-by: Jeremy Roman <jbroman@chromium.org>
Reviewed-by: Istiaque Ahmed <lazyboy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#536374}
diff --git a/extensions/renderer/bindings/api_binding_js_util.cc b/extensions/renderer/bindings/api_binding_js_util.cc
index 6d75855c..3f3962b 100644
--- a/extensions/renderer/bindings/api_binding_js_util.cc
+++ b/extensions/renderer/bindings/api_binding_js_util.cc
@@ -10,6 +10,7 @@
#include "extensions/renderer/bindings/api_request_handler.h"
#include "extensions/renderer/bindings/api_signature.h"
#include "extensions/renderer/bindings/api_type_reference_map.h"
+#include "extensions/renderer/bindings/argument_spec.h"
#include "extensions/renderer/bindings/declarative_event.h"
#include "extensions/renderer/bindings/exception_handler.h"
#include "extensions/renderer/bindings/js_runner.h"
@@ -49,7 +50,8 @@
.SetMethod("runCallbackWithLastError",
&APIBindingJSUtil::RunCallbackWithLastError)
.SetMethod("handleException", &APIBindingJSUtil::HandleException)
- .SetMethod("setExceptionHandler", &APIBindingJSUtil::SetExceptionHandler);
+ .SetMethod("setExceptionHandler", &APIBindingJSUtil::SetExceptionHandler)
+ .SetMethod("validateType", &APIBindingJSUtil::ValidateType);
}
void APIBindingJSUtil::SendRequest(
@@ -245,4 +247,26 @@
arguments->GetHolderCreationContext(), handler);
}
+void APIBindingJSUtil::ValidateType(gin::Arguments* arguments,
+ const std::string& type_name,
+ v8::Local<v8::Value> value) {
+ v8::Isolate* isolate = arguments->isolate();
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
+
+ const ArgumentSpec* spec = type_refs_->GetSpec(type_name);
+ if (!spec) {
+ // We shouldn't be asked to validate unknown specs, but since this comes
+ // from JS, assume nothing.
+ NOTREACHED();
+ return;
+ }
+
+ std::string error;
+ if (!spec->ParseArgument(context, value, *type_refs_, nullptr, nullptr,
+ &error)) {
+ arguments->ThrowTypeError(error);
+ }
+}
+
} // namespace extensions
diff --git a/extensions/renderer/bindings/api_binding_js_util.h b/extensions/renderer/bindings/api_binding_js_util.h
index e2d3012..cfb7c16 100644
--- a/extensions/renderer/bindings/api_binding_js_util.h
+++ b/extensions/renderer/bindings/api_binding_js_util.h
@@ -100,6 +100,13 @@
void SetExceptionHandler(gin::Arguments* arguments,
v8::Local<v8::Function> handler);
+ // Validates a given |value| against the specification for the type with
+ // |type_name|. Throws an error if the validation fails; otherwise returns
+ // undefined.
+ void ValidateType(gin::Arguments* arguments,
+ const std::string& type_name,
+ v8::Local<v8::Value> value);
+
// Type references. Guaranteed to outlive this object.
APITypeReferenceMap* const type_refs_;
diff --git a/extensions/renderer/bindings/api_binding_js_util_unittest.cc b/extensions/renderer/bindings/api_binding_js_util_unittest.cc
index c3cb077..c9d754f 100644
--- a/extensions/renderer/bindings/api_binding_js_util_unittest.cc
+++ b/extensions/renderer/bindings/api_binding_js_util_unittest.cc
@@ -5,9 +5,11 @@
#include "extensions/renderer/bindings/api_binding_js_util.h"
#include "base/bind.h"
+#include "base/optional.h"
#include "extensions/renderer/bindings/api_binding_test_util.h"
#include "extensions/renderer/bindings/api_bindings_system.h"
#include "extensions/renderer/bindings/api_bindings_system_unittest.h"
+#include "extensions/renderer/bindings/api_invocation_errors.h"
#include "gin/arguments.h"
#include "gin/handle.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -287,4 +289,44 @@
EXPECT_EQ("\"some error\"", error_info.exception_message);
}
+TEST_F(APIBindingJSUtilUnittest, TestValidateType) {
+ v8::HandleScope handle_scope(isolate());
+ v8::Local<v8::Context> context = MainContext();
+
+ gin::Handle<APIBindingJSUtil> util = CreateUtil();
+ v8::Local<v8::Object> v8_util = util.ToV8().As<v8::Object>();
+
+ auto call_validate_type = [context, v8_util](
+ const char* function,
+ base::Optional<std::string> expected_error) {
+ v8::Local<v8::Function> v8_function = FunctionFromString(context, function);
+ v8::Local<v8::Value> args[] = {v8_util};
+ if (expected_error) {
+ RunFunctionAndExpectError(v8_function, context, arraysize(args), args,
+ *expected_error);
+ } else {
+ RunFunction(v8_function, context, arraysize(args), args);
+ }
+ };
+
+ // Test a case that should succeed (a valid value).
+ call_validate_type(
+ R"((function(util) {
+ util.validateType('alpha.objRef', {prop1: 'hello'});
+ }))",
+ base::nullopt);
+
+ // Test a failing case (prop1 is supposed to be a string).
+ std::string expected_error =
+ "Uncaught TypeError: " +
+ api_errors::PropertyError(
+ "prop1", api_errors::InvalidType(api_errors::kTypeString,
+ api_errors::kTypeInteger));
+ call_validate_type(
+ R"((function(util) {
+ util.validateType('alpha.objRef', {prop1: 2});
+ }))",
+ expected_error);
+}
+
} // namespace extensions
diff --git a/extensions/renderer/resources/declarative_webrequest_custom_bindings.js b/extensions/renderer/resources/declarative_webrequest_custom_bindings.js
index b905bbd3..2727bc3 100644
--- a/extensions/renderer/resources/declarative_webrequest_custom_bindings.js
+++ b/extensions/renderer/resources/declarative_webrequest_custom_bindings.js
@@ -7,19 +7,21 @@
var binding =
apiBridge || require('binding').Binding.create('declarativeWebRequest');
-var utils = require('utils');
-var validate = require('schemaUtils').validate;
+var utils = bindingUtil ? undefined : require('utils');
+var validate = bindingUtil ? undefined : require('schemaUtils').validate;
+
+function validateType(schemaTypes, typeName, value) {
+ if (bindingUtil) {
+ bindingUtil.validateType(typeName, value);
+ } else {
+ var schema = utils.lookup(schemaTypes, 'id', typeName);
+ validate([value], [schema]);
+ }
+}
binding.registerCustomHook(function(api) {
var declarativeWebRequest = api.compiledApi;
- // Returns the schema definition of type |typeId| defined in |namespace|.
- function getSchema(typeId) {
- return utils.lookup(api.schema.types,
- 'id',
- 'declarativeWebRequest.' + typeId);
- }
-
// Helper function for the constructor of concrete datatypes of the
// declarative webRequest API.
// Makes sure that |this| contains the union of parameters and
@@ -31,15 +33,11 @@
instance[key] = parameters[key];
}
}
- instance.instanceType = 'declarativeWebRequest.' + typeId;
- if (!apiBridge) {
- var schema = getSchema(typeId);
- // TODO(devlin): This won't work with native bindings, but it's lower
- // priority. declarativeWebRequest never shipped, and validation will
- // fail later when trying to use the created object. Still, it'd be
- // potentially nice to fix.
- validate([instance], [schema]);
- }
+
+ var qualifiedType = 'declarativeWebRequest.' + typeId;
+ instance.instanceType = qualifiedType;
+ validateType(bindingUtil ? undefined : api.schema.types, qualifiedType,
+ instance);
}
// Setup all data types for the declarative webRequest API.
diff --git a/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js b/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js
index 3fd27b58..09cfa8c 100644
--- a/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js
+++ b/extensions/renderer/resources/guest_view/web_view/web_view_request_custom_bindings.js
@@ -8,20 +8,22 @@
var declarativeWebRequestSchema =
requireNative('schema_registry').GetSchema('declarativeWebRequest');
-var utils = require('utils');
-var validate = require('schemaUtils').validate;
+
+var utils = bindingUtil ? undefined : require('utils');
+var validate = bindingUtil ? undefined : require('schemaUtils').validate;
+
+function validateType(schemaTypes, typeName, value) {
+ if (bindingUtil) {
+ bindingUtil.validateType(typeName, value);
+ } else {
+ var schema = utils.lookup(schemaTypes, 'id', typeName);
+ validate([value], [schema]);
+ }
+}
binding.registerCustomHook(function(api) {
var webViewRequest = api.compiledApi;
- // Returns the schema definition of type |typeId| defined in
- // |declarativeWebRequestScheme.types|.
- function getSchema(typeId) {
- return utils.lookup(declarativeWebRequestSchema.types,
- 'id',
- 'declarativeWebRequest.' + typeId);
- }
-
// Helper function for the constructor of concrete datatypes of the
// declarative webRequest API.
// Makes sure that |this| contains the union of parameters and
@@ -34,9 +36,10 @@
}
}
- instance.instanceType = 'declarativeWebRequest.' + typeId;
- var schema = getSchema(typeId);
- validate([instance], [schema]);
+ var qualifiedType = 'declarativeWebRequest.' + typeId;
+ instance.instanceType = qualifiedType;
+ validateType(bindingUtil ? undefined : declarativeWebRequestSchema.types,
+ qualifiedType, instance);
}
// Setup all data types for the declarative webRequest API from the schema.