Commit 9f8faccf authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Extensions Bindings] Allow native custom signature parsing

A few APIs validate arguments against a signature specified in an event
call, rather than as an API method. Allow this validation to happen
natively when native bindings are enabled by adding a JS utility to add
a custom signature and validate against it.

Use this for certificateProvider, printerProvider, and webRequest custom
bindings instead of the JS validation.

Add unittests for the new JS utility method.

Bug: 810487

Change-Id: Ifbc8480ae15acbdaa9ecdffcfb7bfe159e201510
Reviewed-on: https://chromium-review.googlesource.com/914808Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Reviewed-by: default avatarIstiaque Ahmed <lazyboy@chromium.org>
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#545806}
parent b1a8b203
...@@ -16,7 +16,16 @@ var registerArgumentMassager = bindingUtil ? ...@@ -16,7 +16,16 @@ var registerArgumentMassager = bindingUtil ?
var certificateProviderSchema = var certificateProviderSchema =
requireNative('schema_registry').GetSchema('certificateProvider') requireNative('schema_registry').GetSchema('certificateProvider')
var utils = require('utils'); var utils = require('utils');
var validate = require('schemaUtils').validate; var validate = bindingUtil ? undefined : require('schemaUtils').validate;
// Validates that the result passed by the extension to the event callback
// matches the callback schema. Throws an exception in case of an error.
function validateListenerResponse(eventName, expectedSchema, listenerResponse) {
if (bindingUtil)
bindingUtil.validateCustomSignature(eventName, listenerResponse);
else
validate(listenerResponse, expectedSchema);
}
// Custom bindings for chrome.certificateProvider API. // Custom bindings for chrome.certificateProvider API.
// The bindings are used to implement callbacks for the API events. Internally // The bindings are used to implement callbacks for the API events. Internally
...@@ -41,10 +50,14 @@ var validate = require('schemaUtils').validate; ...@@ -41,10 +50,14 @@ var validate = require('schemaUtils').validate;
function handleEvent(eventName, internalReportFunc) { function handleEvent(eventName, internalReportFunc) {
var eventSchema = var eventSchema =
utils.lookup(certificateProviderSchema.events, 'name', eventName); utils.lookup(certificateProviderSchema.events, 'name', eventName);
var callbackSchema = utils.lookup(eventSchema.parameters, 'type', 'function'); var callbackSchema =
utils.lookup(eventSchema.parameters, 'type', 'function').parameters;
var fullEventName = 'certificateProvider.' + eventName;
if (bindingUtil)
bindingUtil.addCustomSignature(fullEventName, callbackSchema);
registerArgumentMassager('certificateProvider.' + eventName, registerArgumentMassager(fullEventName, function(args, dispatch) {
function(args, dispatch) {
var responded = false; var responded = false;
// Function provided to the extension as the event callback argument. // Function provided to the extension as the event callback argument.
...@@ -63,7 +76,7 @@ function handleEvent(eventName, internalReportFunc) { ...@@ -63,7 +76,7 @@ function handleEvent(eventName, internalReportFunc) {
// Validates that the results reported by the extension matche the // Validates that the results reported by the extension matche the
// callback schema of the event. Throws an exception in case of an // callback schema of the event. Throws an exception in case of an
// error. // error.
validate(reportArgs, callbackSchema.parameters); validateListenerResponse(fullEventName, callbackSchema, reportArgs);
finalArgs = reportArgs; finalArgs = reportArgs;
} finally { } finally {
responded = true; responded = true;
......
...@@ -60,6 +60,12 @@ chrome.test.runTests([ ...@@ -60,6 +60,12 @@ chrome.test.runTests([
function(details) {}, function(details) {},
goodFilter, goodExtraInfo); goodFilter, goodExtraInfo);
function isArgumentParseError(error) {
// Native and JS bindings have slightly different errors surfaced.
return error.search('Invalid value') >= 0 ||
error.search('Error at parameter') >= 0;
}
// Try a bad RequestFilter. // Try a bad RequestFilter.
try { try {
chrome.webRequest.onBeforeRequest.addListener( chrome.webRequest.onBeforeRequest.addListener(
...@@ -67,7 +73,7 @@ chrome.test.runTests([ ...@@ -67,7 +73,7 @@ chrome.test.runTests([
{badFilter: 42, urls: ["<all_urls>"]}, goodExtraInfo); {badFilter: 42, urls: ["<all_urls>"]}, goodExtraInfo);
chrome.test.fail(); chrome.test.fail();
} catch (e) { } catch (e) {
chrome.test.assertTrue(e.message.search("Invalid value") >= 0); chrome.test.assertTrue(isArgumentParseError(e.message), e.message);
} }
// Try a bad ExtraInfoSpec. // Try a bad ExtraInfoSpec.
...@@ -77,7 +83,7 @@ chrome.test.runTests([ ...@@ -77,7 +83,7 @@ chrome.test.runTests([
goodFilter, ["badExtraInfo"]); goodFilter, ["badExtraInfo"]);
chrome.test.fail(); chrome.test.fail();
} catch (e) { } catch (e) {
chrome.test.assertTrue(e.message.search("Invalid value") >= 0); chrome.test.assertTrue(isArgumentParseError(e.message), e.message);
} }
// This extraInfoSpec should only work for onBeforeSendHeaders. // This extraInfoSpec should only work for onBeforeSendHeaders.
...@@ -91,7 +97,7 @@ chrome.test.runTests([ ...@@ -91,7 +97,7 @@ chrome.test.runTests([
goodFilter, headersExtraInfo); goodFilter, headersExtraInfo);
chrome.test.fail(); chrome.test.fail();
} catch (e) { } catch (e) {
chrome.test.assertTrue(e.message.search("Invalid value") >= 0); chrome.test.assertTrue(isArgumentParseError(e.message), e.message);
} }
// ExtraInfoSpec with "responseHeaders" should work for onCompleted. // ExtraInfoSpec with "responseHeaders" should work for onCompleted.
...@@ -105,7 +111,7 @@ chrome.test.runTests([ ...@@ -105,7 +111,7 @@ chrome.test.runTests([
goodFilter, headersExtraInfo); goodFilter, headersExtraInfo);
chrome.test.fail(); chrome.test.fail();
} catch (e) { } catch (e) {
chrome.test.assertTrue(e.message.search("Invalid value") >= 0); chrome.test.assertTrue(isArgumentParseError(e.message), e.message);
} }
// Try a bad URL pattern. The error happens asynchronously. We're just // Try a bad URL pattern. The error happens asynchronously. We're just
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/values.h" #include "base/values.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/renderer/bindings/api_event_handler.h" #include "extensions/renderer/bindings/api_event_handler.h"
#include "extensions/renderer/bindings/api_request_handler.h" #include "extensions/renderer/bindings/api_request_handler.h"
#include "extensions/renderer/bindings/api_signature.h" #include "extensions/renderer/bindings/api_signature.h"
...@@ -51,7 +52,10 @@ gin::ObjectTemplateBuilder APIBindingJSUtil::GetObjectTemplateBuilder( ...@@ -51,7 +52,10 @@ gin::ObjectTemplateBuilder APIBindingJSUtil::GetObjectTemplateBuilder(
&APIBindingJSUtil::RunCallbackWithLastError) &APIBindingJSUtil::RunCallbackWithLastError)
.SetMethod("handleException", &APIBindingJSUtil::HandleException) .SetMethod("handleException", &APIBindingJSUtil::HandleException)
.SetMethod("setExceptionHandler", &APIBindingJSUtil::SetExceptionHandler) .SetMethod("setExceptionHandler", &APIBindingJSUtil::SetExceptionHandler)
.SetMethod("validateType", &APIBindingJSUtil::ValidateType); .SetMethod("validateType", &APIBindingJSUtil::ValidateType)
.SetMethod("validateCustomSignature",
&APIBindingJSUtil::ValidateCustomSignature)
.SetMethod("addCustomSignature", &APIBindingJSUtil::AddCustomSignature);
} }
void APIBindingJSUtil::SendRequest( void APIBindingJSUtil::SendRequest(
...@@ -269,4 +273,62 @@ void APIBindingJSUtil::ValidateType(gin::Arguments* arguments, ...@@ -269,4 +273,62 @@ void APIBindingJSUtil::ValidateType(gin::Arguments* arguments,
} }
} }
void APIBindingJSUtil::AddCustomSignature(
gin::Arguments* arguments,
const std::string& custom_signature_name,
v8::Local<v8::Value> signature) {
v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
// JS bindings run per-context, so it's expected that they will call this
// multiple times in the lifetime of the renderer process. Only handle the
// first call.
if (type_refs_->GetCustomSignature(custom_signature_name))
return;
if (!signature->IsArray()) {
NOTREACHED();
return;
}
std::unique_ptr<base::Value> base_signature =
content::V8ValueConverter::Create()->FromV8Value(signature, context);
if (!base_signature->is_list()) {
NOTREACHED();
return;
}
type_refs_->AddCustomSignature(
custom_signature_name,
std::make_unique<APISignature>(
*base::ListValue::From(std::move(base_signature))));
}
void APIBindingJSUtil::ValidateCustomSignature(
gin::Arguments* arguments,
const std::string& custom_signature_name,
v8::Local<v8::Value> arguments_to_validate) {
v8::Isolate* isolate = arguments->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = arguments->GetHolderCreationContext();
const APISignature* signature =
type_refs_->GetCustomSignature(custom_signature_name);
if (!signature) {
NOTREACHED();
}
std::vector<v8::Local<v8::Value>> vector_arguments;
if (!gin::ConvertFromV8(isolate, arguments_to_validate, &vector_arguments)) {
NOTREACHED();
return;
}
std::vector<v8::Local<v8::Value>> args_out;
std::string parse_error;
if (!signature->ParseArgumentsToV8(context, vector_arguments, *type_refs_,
&args_out, &parse_error)) {
arguments->ThrowTypeError(parse_error);
}
}
} // namespace extensions } // namespace extensions
...@@ -107,6 +107,21 @@ class APIBindingJSUtil final : public gin::Wrappable<APIBindingJSUtil> { ...@@ -107,6 +107,21 @@ class APIBindingJSUtil final : public gin::Wrappable<APIBindingJSUtil> {
const std::string& type_name, const std::string& type_name,
v8::Local<v8::Value> value); v8::Local<v8::Value> value);
// Allows custom bindings to add a signature with the given
// |custom_signature_name| to use later in argument validation. The signature
// is expected to be an array of expected types, that can be passed to
// construct an APISignature.
void AddCustomSignature(gin::Arguments* arguments,
const std::string& custom_signature_name,
v8::Local<v8::Value> signature);
// Looks up the signature with the given |custom_signature_name| and validates
// |arguments_to_validate| against it, throwing an error if the arguments
// don't match.
void ValidateCustomSignature(gin::Arguments* arguments,
const std::string& custom_signature_name,
v8::Local<v8::Value> arguments_to_validate);
// Type references. Guaranteed to outlive this object. // Type references. Guaranteed to outlive this object.
APITypeReferenceMap* const type_refs_; APITypeReferenceMap* const type_refs_;
......
...@@ -329,4 +329,65 @@ TEST_F(APIBindingJSUtilUnittest, TestValidateType) { ...@@ -329,4 +329,65 @@ TEST_F(APIBindingJSUtilUnittest, TestValidateType) {
expected_error); expected_error);
} }
TEST_F(APIBindingJSUtilUnittest, TestValidateCustomSignature) {
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>();
constexpr char kSignatureName[] = "custom_signature";
EXPECT_FALSE(bindings_system()->type_reference_map()->GetCustomSignature(
kSignatureName));
{
constexpr char kAddSignature[] =
R"((function(util) {
util.addCustomSignature(
'custom_signature',
[{type: 'integer'}, {'type': 'string'}]);
}))";
v8::Local<v8::Function> add_signature =
FunctionFromString(context, kAddSignature);
v8::Local<v8::Value> args[] = {v8_util};
RunFunction(add_signature, context, arraysize(args), args);
}
EXPECT_TRUE(bindings_system()->type_reference_map()->GetCustomSignature(
kSignatureName));
auto call_validate_signature =
[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_signature(
R"((function(util) {
util.validateCustomSignature('custom_signature', [1, 'foo']);
}))",
base::nullopt);
// Test a failing case (prop1 is supposed to be a string).
std::string expected_error =
"Uncaught TypeError: " +
api_errors::ArgumentError(
"", api_errors::InvalidType(api_errors::kTypeString,
api_errors::kTypeInteger));
call_validate_signature(
R"((function(util) {
util.validateCustomSignature('custom_signature', [1, 2]);
}))",
expected_error);
}
} // namespace extensions } // namespace extensions
...@@ -76,4 +76,18 @@ bool APITypeReferenceMap::HasTypeMethodSignature( ...@@ -76,4 +76,18 @@ bool APITypeReferenceMap::HasTypeMethodSignature(
return type_methods_.find(name) != type_methods_.end(); return type_methods_.find(name) != type_methods_.end();
} }
void APITypeReferenceMap::AddCustomSignature(
const std::string& name,
std::unique_ptr<APISignature> signature) {
DCHECK(custom_signatures_.find(name) == custom_signatures_.end())
<< "Cannot re-register signature for: " << name;
custom_signatures_[name] = std::move(signature);
}
const APISignature* APITypeReferenceMap::GetCustomSignature(
const std::string& name) const {
auto iter = custom_signatures_.find(name);
return iter != custom_signatures_.end() ? iter->second.get() : nullptr;
}
} // namespace extensions } // namespace extensions
...@@ -57,6 +57,13 @@ class APITypeReferenceMap { ...@@ -57,6 +57,13 @@ class APITypeReferenceMap {
// an API. // an API.
bool HasTypeMethodSignature(const std::string& name) const; bool HasTypeMethodSignature(const std::string& name) const;
// Adds a custom signature for bindings to use.
void AddCustomSignature(const std::string& name,
std::unique_ptr<APISignature> signature);
// Looks up a custom signature that was previously added.
const APISignature* GetCustomSignature(const std::string& name) const;
bool empty() const { return type_refs_.empty(); } bool empty() const { return type_refs_.empty(); }
size_t size() const { return type_refs_.size(); } size_t size() const { return type_refs_.size(); }
...@@ -66,6 +73,7 @@ class APITypeReferenceMap { ...@@ -66,6 +73,7 @@ class APITypeReferenceMap {
std::map<std::string, std::unique_ptr<ArgumentSpec>> type_refs_; std::map<std::string, std::unique_ptr<ArgumentSpec>> type_refs_;
std::map<std::string, std::unique_ptr<APISignature>> api_methods_; std::map<std::string, std::unique_ptr<APISignature>> api_methods_;
std::map<std::string, std::unique_ptr<APISignature>> type_methods_; std::map<std::string, std::unique_ptr<APISignature>> type_methods_;
std::map<std::string, std::unique_ptr<APISignature>> custom_signatures_;
DISALLOW_COPY_AND_ASSIGN(APITypeReferenceMap); DISALLOW_COPY_AND_ASSIGN(APITypeReferenceMap);
}; };
......
...@@ -14,8 +14,18 @@ var blobNatives = requireNative('blob_natives'); ...@@ -14,8 +14,18 @@ var blobNatives = requireNative('blob_natives');
var printerProviderSchema = var printerProviderSchema =
requireNative('schema_registry').GetSchema('printerProvider') requireNative('schema_registry').GetSchema('printerProvider')
var utils = require('utils'); var utils = require('utils');
var validate = require('schemaUtils').validate; var validate = bindingUtil ? undefined : require('schemaUtils').validate;
// Validates that the result passed by the extension to the event callback
// matches the callback schema. Throws an exception in case of an error.
function validateListenerResponse(eventName, expectedSchema, listenerResponse) {
if (bindingUtil)
bindingUtil.validateCustomSignature(eventName, listenerResponse);
else
validate(listenerResponse, expectedSchema);
}
// Custom bindings for chrome.printerProvider API. // Custom bindings for chrome.printerProvider API.
// The bindings are used to implement callbacks for the API events. Internally // The bindings are used to implement callbacks for the API events. Internally
...@@ -43,21 +53,17 @@ var validate = require('schemaUtils').validate; ...@@ -43,21 +53,17 @@ var validate = require('schemaUtils').validate;
// |resultreporter|: The function that should be called to report event result. // |resultreporter|: The function that should be called to report event result.
// One of chrome.printerProviderInternal API functions. // One of chrome.printerProviderInternal API functions.
function handleEvent(eventName, prepareArgsForDispatch, resultReporter) { function handleEvent(eventName, prepareArgsForDispatch, resultReporter) {
registerArgumentMassager('printerProvider.' + eventName,
function(args, dispatch) {
var responded = false;
// Validates that the result passed by the extension to the event
// callback matches the callback schema. Throws an exception in case of
// an error.
var validateResult = function(result) {
var eventSchema = var eventSchema =
utils.lookup(printerProviderSchema.events, 'name', eventName); utils.lookup(printerProviderSchema.events, 'name', eventName);
var callbackSchema = var callbackSchema =
utils.lookup(eventSchema.parameters, 'type', 'function'); utils.lookup(eventSchema.parameters, 'type', 'function').parameters;
var fullEventName = 'printerProvider.' + eventName;
validate([result], callbackSchema.parameters); if (bindingUtil)
}; bindingUtil.addCustomSignature(fullEventName, callbackSchema);
registerArgumentMassager(fullEventName, function(args, dispatch) {
var responded = false;
// Function provided to the extension as the event callback argument. // Function provided to the extension as the event callback argument.
// It makes sure that the event result hasn't previously been returned // It makes sure that the event result hasn't previously been returned
...@@ -69,7 +75,8 @@ function handleEvent(eventName, prepareArgsForDispatch, resultReporter) { ...@@ -69,7 +75,8 @@ function handleEvent(eventName, prepareArgsForDispatch, resultReporter) {
var finalResult = null; var finalResult = null;
try { try {
validateResult(result); // throws on failure // throws on failure
validateListenerResponse(fullEventName, callbackSchema, [result]);
finalResult = result; finalResult = result;
} finally { } finally {
responded = true; responded = true;
...@@ -121,4 +128,5 @@ handleEvent('onGetUsbPrinterInfoRequested', ...@@ -121,4 +128,5 @@ handleEvent('onGetUsbPrinterInfoRequested',
function(args, callback) { callback(true); }, function(args, callback) { callback(true); },
printerProviderInternal.reportUsbPrinterInfo); printerProviderInternal.reportUsbPrinterInfo);
exports.$set('binding', binding.generate()); if (!apiBridge)
exports.$set('binding', binding.generate());
...@@ -6,11 +6,19 @@ var CHECK = requireNative('logging').CHECK; ...@@ -6,11 +6,19 @@ var CHECK = requireNative('logging').CHECK;
var eventBindings = bindingUtil ? undefined : require('event_bindings'); var eventBindings = bindingUtil ? undefined : require('event_bindings');
var idGeneratorNatives = requireNative('id_generator'); var idGeneratorNatives = requireNative('id_generator');
var utils = require('utils'); var utils = require('utils');
var validate = require('schemaUtils').validate; var validate = bindingUtil ? undefined : require('schemaUtils').validate;
var webRequestInternal = getInternalApi ? var webRequestInternal = getInternalApi ?
getInternalApi('webRequestInternal') : getInternalApi('webRequestInternal') :
require('binding').Binding.create('webRequestInternal').generate(); require('binding').Binding.create('webRequestInternal').generate();
function validateListenerArguments(
eventName, extraArgSchemas, listenerArguments) {
if (bindingUtil)
bindingUtil.validateCustomSignature(eventName, listenerArguments);
else
validate(listenerArguments, extraArgSchemas);
}
function getUniqueSubEventName(eventName) { function getUniqueSubEventName(eventName) {
return eventName + '/' + idGeneratorNatives.GetNextId(); return eventName + '/' + idGeneratorNatives.GetNextId();
} }
...@@ -41,6 +49,9 @@ function WebRequestEventImpl(eventName, opt_argSchemas, opt_extraArgSchemas, ...@@ -41,6 +49,9 @@ function WebRequestEventImpl(eventName, opt_argSchemas, opt_extraArgSchemas,
if (typeof eventName != 'string') if (typeof eventName != 'string')
throw new Error('chrome.WebRequestEvent requires an event name.'); throw new Error('chrome.WebRequestEvent requires an event name.');
if (bindingUtil)
bindingUtil.addCustomSignature(eventName, opt_extraArgSchemas);
this.eventName = eventName; this.eventName = eventName;
this.argSchemas = opt_argSchemas; this.argSchemas = opt_argSchemas;
this.extraArgSchemas = opt_extraArgSchemas; this.extraArgSchemas = opt_extraArgSchemas;
...@@ -77,7 +88,8 @@ WebRequestEventImpl.prototype.addListener = ...@@ -77,7 +88,8 @@ WebRequestEventImpl.prototype.addListener =
var subEventName = getUniqueSubEventName(this.eventName); var subEventName = getUniqueSubEventName(this.eventName);
// Note: this could fail to validate, in which case we would not add the // Note: this could fail to validate, in which case we would not add the
// subEvent listener. // subEvent listener.
validate($Array.slice(arguments, 1), this.extraArgSchemas); validateListenerArguments(this.eventName, this.extraArgSchemas,
$Array.slice(arguments, 1));
webRequestInternal.addEventListener( webRequestInternal.addEventListener(
cb, opt_filter, opt_extraInfo, this.eventName, subEventName, cb, opt_filter, opt_extraInfo, this.eventName, subEventName,
this.webViewInstanceId); this.webViewInstanceId);
......
...@@ -28,11 +28,15 @@ chrome.test.sendMessage('loaded', function(test) { ...@@ -28,11 +28,15 @@ chrome.test.sendMessage('loaded', function(test) {
} }
if (test == 'INVALID_VALUE') { if (test == 'INVALID_VALUE') {
chrome.test.assertThrows( var jsBindingsError =
callback, 'Invalid value for argument 1. Expected \'object\'' +
['XXX'], ' but got \'string\'.';
'Invalid value for argument 1. '+ var nativeBindingsError =
'Expected \'object\' but got \'string\'.'); 'Error at parameter \'capabilities\': ' +
'Invalid type: expected object, found string.';
var regexp =
new RegExp(nativeBindingsError + '|' + jsBindingsError);
chrome.test.assertThrows(callback, ['XXX'], regexp);
} else if (test == 'EMPTY') { } else if (test == 'EMPTY') {
callback({}); callback({});
} else { } else {
......
...@@ -45,12 +45,16 @@ chrome.test.sendMessage('loaded', function(test) { ...@@ -45,12 +45,16 @@ chrome.test.sendMessage('loaded', function(test) {
setTimeout(callback.bind(null, 'OK'), 0); setTimeout(callback.bind(null, 'OK'), 0);
break; break;
case 'INVALID_VALUE': case 'INVALID_VALUE':
chrome.test.assertThrows( var jsBindingsError =
callback,
['XXX'],
'Invalid value for argument 1. ' + 'Invalid value for argument 1. ' +
'Value must be one of: ' + 'Value must be one of: ' +
'[OK, FAILED, INVALID_TICKET, INVALID_DATA].'); '\\[OK, FAILED, INVALID_TICKET, INVALID_DATA\\].';
var nativeBindingsError =
'Error at parameter \'result\': Value must be one of ' +
'FAILED, INVALID_DATA, INVALID_TICKET, OK.';
chrome.test.assertThrows(
callback, ['XXX'],
new RegExp(jsBindingsError + '|' + nativeBindingsError));
break; break;
case 'FAILED': case 'FAILED':
case 'INVALID_TICKET': case 'INVALID_TICKET':
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
function eitherError(error1, error2) {
return new RegExp(error1 + '|' + error2);
}
chrome.test.sendMessage('loaded', function(test) { chrome.test.sendMessage('loaded', function(test) {
chrome.test.runTests([function printTest() { chrome.test.runTests([function printTest() {
if (test == 'NO_LISTENER') { if (test == 'NO_LISTENER') {
...@@ -31,12 +35,23 @@ chrome.test.sendMessage('loaded', function(test) { ...@@ -31,12 +35,23 @@ chrome.test.sendMessage('loaded', function(test) {
} }
if (test == 'NOT_ARRAY') { if (test == 'NOT_ARRAY') {
chrome.test.assertThrows( var jsBindingsError =
callback,
['XXX'],
'Invalid value for argument 1. ' + 'Invalid value for argument 1. ' +
'Expected \'array\' but got \'string\'.'); 'Expected \'array\' but got \'string\'.'
var nativeBindingsError =
'Error at parameter \'printerInfo\': ' +
'Invalid type: expected array, found string.'
chrome.test.assertThrows(
callback, ['XXX'],
eitherError(jsBindingsError, nativeBindingsError));
} else if (test == 'INVALID_PRINTER_TYPE') { } else if (test == 'INVALID_PRINTER_TYPE') {
var jsBindingsError =
'Invalid value for argument 1. ' +
'Property \'.1\': Expected \'object\' but got \'string\'.';
var nativeBindingsError =
'Error at parameter \'printerInfo\': Error at index 1: ' +
'Invalid type: expected printerProvider.PrinterInfo, ' +
'found string.';
chrome.test.assertThrows( chrome.test.assertThrows(
callback, callback,
[[{ [[{
...@@ -44,9 +59,14 @@ chrome.test.sendMessage('loaded', function(test) { ...@@ -44,9 +59,14 @@ chrome.test.sendMessage('loaded', function(test) {
name: 'Printer 1', name: 'Printer 1',
description: 'Test printer' description: 'Test printer'
}, 'printer2']], }, 'printer2']],
'Invalid value for argument 1. ' + eitherError(jsBindingsError, nativeBindingsError));
'Property \'.1\': Expected \'object\' but got \'string\'.');
} else if (test == 'INVALID_PRINTER') { } else if (test == 'INVALID_PRINTER') {
var jsBindingsError =
'Invalid value for argument 1. ' +
'Property \'.0.unsupported\': Unexpected property.';
var nativeBindingsError =
'Error at parameter \'printerInfo\': ' +
'Error at index 0: Unexpected property: \'unsupported\'.';
chrome.test.assertThrows( chrome.test.assertThrows(
callback, callback,
[[{ [[{
...@@ -55,8 +75,7 @@ chrome.test.sendMessage('loaded', function(test) { ...@@ -55,8 +75,7 @@ chrome.test.sendMessage('loaded', function(test) {
description: 'Test printer', description: 'Test printer',
unsupported: 'print' unsupported: 'print'
}]], }]],
'Invalid value for argument 1. ' + eitherError(jsBindingsError, nativeBindingsError));
'Property \'.0.unsupported\': Unexpected property.');
} else { } else {
chrome.test.assertEq('OK', test); chrome.test.assertEq('OK', test);
callback([{ callback([{
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment