Commit 350b8a72 authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[JSON Schema Compiler] Add support for serializable functions

Add support for a new property on function types, serializableFunction.

IDL Example:
callback SerializableFunction = void();
dictionary SerializableFunctionObject {
  [serializableFunction]SerializableFunction func;
};

JSON Example:
{
  "type": "object",
  "id": "SerializableFunctionObject",
  "properties": {
    "func": {
      "name": "func",
      "serializableFunction": true,
      "type": "function",
      "parameters": []
    }
  }
}

If this trait is present, functions will be serialized and deserialized
as strings, rather than as empty base::DictionaryValues.

Add testing for the same.

Note: this only provides support in the JSON schema compiler (which
includes support in our auto-generated strong-typing code); a follow-up
will add support in our bindings code for parsing the values passed
from extensions.

Bug: 1144841
Change-Id: I430fb211166a5ccffaa6b478642e98115fd25565
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2518717
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#825727}
parent ede4486c
......@@ -772,7 +772,8 @@ class _Generator(object):
else:
return '(%s).ToValue()' % var
elif (underlying_type.property_type == PropertyType.ANY or
underlying_type.property_type == PropertyType.FUNCTION):
(underlying_type.property_type == PropertyType.FUNCTION and
not underlying_type.is_serializable_function)):
if is_ptr:
vardot = '(%s)->' % var
else:
......@@ -790,7 +791,8 @@ class _Generator(object):
return 'std::make_unique<base::Value>(%s)' % var
elif underlying_type.property_type == PropertyType.ARRAY:
return '%s' % self._util_cc_helper.CreateValueFromArray(var, is_ptr)
elif underlying_type.property_type.is_fundamental:
elif (underlying_type.property_type.is_fundamental or
underlying_type.is_serializable_function):
if is_ptr:
var = '*%s' % var
return 'std::make_unique<base::Value>(%s)' % var
......@@ -906,7 +908,8 @@ class _Generator(object):
underlying_type = self._type_helper.FollowRef(type_)
if underlying_type.property_type.is_fundamental:
if (underlying_type.property_type.is_fundamental or
underlying_type.is_serializable_function):
if is_ptr:
(c.Append('%(cpp_type)s temp;')
.Sblock('if (!%s) {' % cpp_util.GetAsFundamentalValue(
......@@ -970,7 +973,9 @@ class _Generator(object):
.Append('}')
)
elif underlying_type.property_type == PropertyType.FUNCTION:
if is_ptr:
assert not underlying_type.is_serializable_function, \
'Serializable functions should have been handled above.'
if is_ptr: # Non-serializable functions are just represented as dicts.
c.Append('%(dst_var)s = std::make_unique<base::DictionaryValue>();')
elif underlying_type.property_type == PropertyType.ANY:
c.Append('%(dst_var)s = %(src_var)s->CreateDeepCopy();')
......
......@@ -104,10 +104,14 @@ class CppTypeGenerator(object):
elif type_.property_type == PropertyType.ANY:
cpp_type = 'base::Value'
elif type_.property_type == PropertyType.FUNCTION:
# Functions come into the json schema compiler as empty objects. We can
# record these as empty DictionaryValues so that we know if the function
# was passed in or not.
cpp_type = 'base::DictionaryValue'
if type_.is_serializable_function:
# Serializable functions get transformed into strings.
cpp_type = 'std::string'
else:
# Non-serializable functions come into the json schema compiler as
# empty objects. We can record these as empty DictionaryValues so that
# we know if the function was passed in or not.
cpp_type = 'base::DictionaryValue'
elif type_.property_type == PropertyType.ARRAY:
item_cpp_type = self.GetCppType(type_.item_type, is_in_container=True)
cpp_type = 'std::vector<%s>' % item_cpp_type
......
......@@ -51,29 +51,47 @@ def GetAsFundamentalValue(type_, src, dst):
src: Value*
dst: Property*
"""
return {
PropertyType.BOOLEAN: '%s->GetAsBoolean(%s)',
PropertyType.DOUBLE: '%s->GetAsDouble(%s)',
PropertyType.INTEGER: '%s->GetAsInteger(%s)',
PropertyType.STRING: '%s->GetAsString(%s)',
}[type_.property_type] % (src, dst)
if type_.property_type == PropertyType.BOOLEAN:
s = '%s->GetAsBoolean(%s)'
elif type_.property_type == PropertyType.DOUBLE:
s = '%s->GetAsDouble(%s)'
elif type_.property_type == PropertyType.INTEGER:
s = '%s->GetAsInteger(%s)'
elif (type_.property_type == PropertyType.STRING or
(type_.property_type == PropertyType.FUNCTION and
type_.is_serializable_function)):
s = '%s->GetAsString(%s)'
else:
raise ValueError('Type %s is not a fundamental value' % type_.name)
return s % (src, dst)
def GetValueType(type_):
"""Returns the Value::Type corresponding to the model.Type.
"""
return {
PropertyType.ARRAY: 'base::Value::Type::LIST',
PropertyType.BINARY: 'base::Value::Type::BINARY',
PropertyType.BOOLEAN: 'base::Value::Type::BOOLEAN',
# PropertyType.CHOICES can be any combination of types.
PropertyType.DOUBLE: 'base::Value::Type::DOUBLE',
PropertyType.ENUM: 'base::Value::Type::STRING',
PropertyType.FUNCTION: 'base::Value::Type::DICTIONARY',
PropertyType.INTEGER: 'base::Value::Type::INTEGER',
PropertyType.OBJECT: 'base::Value::Type::DICTIONARY',
PropertyType.STRING: 'base::Value::Type::STRING',
}[type_.property_type]
if type_.property_type == PropertyType.ARRAY:
return 'base::Value::Type::LIST'
if type_.property_type == PropertyType.BINARY:
return 'base::Value::Type::BINARY'
if type_.property_type == PropertyType.BOOLEAN:
return 'base::Value::Type::BOOLEAN'
if type_.property_type == PropertyType.DOUBLE:
return 'base::Value::Type::DOUBLE'
if type_.property_type == PropertyType.ENUM:
return 'base::Value::Type::STRING'
if type_.property_type == PropertyType.FUNCTION:
if type_.is_serializable_function:
return 'base::Value::Type::STRING'
return 'base::Value::Type::DICTIONARY'
if type_.property_type == PropertyType.INTEGER:
return 'base::Value::Type::INTEGER'
if type_.property_type == PropertyType.OBJECT:
return 'base::Value::Type::DICTIONARY'
if type_.property_type == PropertyType.STRING:
return 'base::Value::Type::STRING'
raise ValueError('Invalid type: %s' % type_.name)
def GetParameterDeclaration(param, type_):
......
......@@ -189,7 +189,8 @@ class Member(object):
properties['deprecated'] = self.node.GetProperty('deprecated')
for property_name in ['allowAmbiguousOptionalArguments',
'nodoc', 'nocompile', 'nodart']:
'nodoc', 'nocompile', 'nodart',
'serializableFunction']:
if self.node.GetProperty(property_name):
properties[property_name] = True
......
......@@ -381,6 +381,23 @@ class IdlSchemaTest(unittest.TestCase):
self.assertEquals(expected, union_type)
def testSerializableFunctionType(self):
schema = idl_schema.Load('test/idl_object_types.idl')[0]
object_type = getType(schema, 'SerializableFunctionObject')
expected = {
'type': 'object',
'id': 'SerializableFunctionObject',
'properties': {
'func': {
'name': 'func',
'serializableFunction': True,
'type': 'function',
'parameters': []
}
}
}
self.assertEquals(expected, object_type)
def testUnionsWithFunctions(self):
schema = idl_schema.Load('test/idl_function_types.idl')[0]
......
......@@ -227,6 +227,7 @@ class Type(object):
self.parent = parent
self.instance_of = json.get('isInstanceOf', None)
self.is_serializable_function = json.get('serializableFunction', False)
# TODO(kalman): Only objects need functions/events/properties, but callers
# assume that all types have them. Fix this.
self.functions = _GetFunctions(self, json, namespace)
......
......@@ -9,7 +9,7 @@
"properties": {
"event_callback": {
"type": "function",
"parameters": { }
"parameters": []
}
}
},
......@@ -20,7 +20,30 @@
"event_callback": {
"type": "function",
"optional": true,
"parameters": { }
"parameters": []
}
}
},
{
"id": "SerializableFunctionType",
"type": "object",
"properties": {
"functionProperty": {
"type": "function",
"serializableFunction": true,
"parameters": []
}
}
},
{
"id": "OptionalSerializableFunctionType",
"type": "object",
"properties": {
"functionProperty": {
"type": "function",
"serializableFunction": true,
"optional": true,
"parameters": []
}
}
}
......
......@@ -11,6 +11,8 @@
using test::api::functions_as_parameters::FunctionType;
using test::api::functions_as_parameters::OptionalFunctionType;
using test::api::functions_as_parameters::OptionalSerializableFunctionType;
using test::api::functions_as_parameters::SerializableFunctionType;
TEST(JsonSchemaCompilerFunctionsAsParametersTest, PopulateRequiredFunction) {
// The expectation is that if any value is set for the function, then
......@@ -96,3 +98,45 @@ TEST(JsonSchemaCompilerFunctionsAsParametersTest, OptionalFunctionToValue) {
EXPECT_TRUE(value.Equals(out.ToValue().get()));
}
}
TEST(JsonSchemaCompilerFunctionsAsParametersTest, SerializableFunctionTypes) {
constexpr char kFunction[] = "function() {}";
SerializableFunctionType serializable_type;
serializable_type.function_property = kFunction;
std::unique_ptr<base::DictionaryValue> serialized =
serializable_type.ToValue();
ASSERT_TRUE(serialized);
SerializableFunctionType deserialized;
ASSERT_TRUE(SerializableFunctionType::Populate(*serialized, &deserialized));
EXPECT_EQ(kFunction, serializable_type.function_property);
}
TEST(JsonSchemaCompilerFunctionsAsParametersTest,
OptionalSerializableFunctionTypes) {
constexpr char kFunction[] = "function() {}";
{
// Test with the optional property set.
OptionalSerializableFunctionType serializable_type;
serializable_type.function_property =
std::make_unique<std::string>(kFunction);
std::unique_ptr<base::DictionaryValue> serialized =
serializable_type.ToValue();
ASSERT_TRUE(serialized);
OptionalSerializableFunctionType deserialized;
ASSERT_TRUE(
OptionalSerializableFunctionType::Populate(*serialized, &deserialized));
ASSERT_TRUE(serializable_type.function_property);
EXPECT_EQ(kFunction, *serializable_type.function_property);
}
{
// Test without the property set.
OptionalSerializableFunctionType serializable_type;
std::unique_ptr<base::DictionaryValue> serialized =
serializable_type.ToValue();
ASSERT_TRUE(serialized);
OptionalSerializableFunctionType deserialized;
ASSERT_TRUE(
OptionalSerializableFunctionType::Populate(*serialized, &deserialized));
EXPECT_FALSE(serializable_type.function_property);
}
}
......@@ -30,6 +30,12 @@ namespace idl_object_types {
[nodoc] (long or DOMString) x;
};
callback SerializableFunction = void();
dictionary SerializableFunctionObject {
[serializableFunction]SerializableFunction func;
};
interface Functions {
static void objectFunction1([instanceOf=ImageData]object icon);
static void objectFunction2(any some_arg);
......
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