JavaScript bindings for Mojo message validation

For each interface Foo, the Mojo JavaScript bindings now generate
a pair of functions: |validateFooRequest(validator)| and 
|validateFooResponse(messageValidator)|.

For each Mojo struct type, an additional |validate(validator)|
method is generated.

All of the validate methods return a validationError;
validationError.NONE if the message is valid or not recognized.

The JS validator class has been extended to validate structs, arrays,
and handles.

The generated methods are unit tested, in the same way as their C++
counterparts, by reading test messages and expected results from 
/mojo/public/interfaces/bindings/tests/data/validation/.

Currently the two pointer overflow test cases are skipped because
they depend on reading 64 uints, which the test message file parser
can't handle at the moment. JS doesn't support 64 bit integers. 53
bits is the limit for integer Numbers.

The message router still only checks the validity of incoming message
headers. I'd like to integrate the router with the new validation code
in a separate commit.


BUG=386808

Review URL: https://codereview.chromium.org/468713002

Cr-Commit-Position: refs/heads/master@{#290104}
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@290104 0039d316-1c4b-4281-b951-d872f2087c98
parent c90f0d8d
...@@ -31,6 +31,8 @@ define("mojo/public/js/bindings/codec", [ ...@@ -31,6 +31,8 @@ define("mojo/public/js/bindings/codec", [
var kStructHeaderNumBytesOffset = 0; var kStructHeaderNumBytesOffset = 0;
var kStructHeaderNumFieldsOffset = 4; var kStructHeaderNumFieldsOffset = 4;
var kEncodedInvalidHandleValue = 0xFFFFFFFF;
// Decoder ------------------------------------------------------------------ // Decoder ------------------------------------------------------------------
function Decoder(buffer, handles, base) { function Decoder(buffer, handles, base) {
...@@ -135,7 +137,7 @@ define("mojo/public/js/bindings/codec", [ ...@@ -135,7 +137,7 @@ define("mojo/public/js/bindings/codec", [
var numberOfBytes = this.readUint32(); var numberOfBytes = this.readUint32();
var numberOfElements = this.readUint32(); var numberOfElements = this.readUint32();
var val = new Array(numberOfElements); var val = new Array(numberOfElements);
if (cls.cls === PackedBool) { if (cls === PackedBool) {
var byte; var byte;
for (var i = 0; i < numberOfElements; ++i) { for (var i = 0; i < numberOfElements; ++i) {
if (i % 8 === 0) if (i % 8 === 0)
...@@ -293,7 +295,7 @@ define("mojo/public/js/bindings/codec", [ ...@@ -293,7 +295,7 @@ define("mojo/public/js/bindings/codec", [
this.writeUint32(encodedSize); this.writeUint32(encodedSize);
this.writeUint32(numberOfElements); this.writeUint32(numberOfElements);
if (cls.cls === PackedBool) { if (cls === PackedBool) {
var byte = 0; var byte = 0;
for (i = 0; i < numberOfElements; ++i) { for (i = 0; i < numberOfElements; ++i) {
if (val[i]) if (val[i])
...@@ -328,7 +330,7 @@ define("mojo/public/js/bindings/codec", [ ...@@ -328,7 +330,7 @@ define("mojo/public/js/bindings/codec", [
return; return;
} }
var numberOfElements = val.length; var numberOfElements = val.length;
var encodedSize = kArrayHeaderSize + ((cls.cls === PackedBool) ? var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ?
Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements); Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements);
var encoder = this.createAndEncodeEncoder(encodedSize); var encoder = this.createAndEncodeEncoder(encodedSize);
encoder.encodeArray(cls, val, numberOfElements, encodedSize); encoder.encodeArray(cls, val, numberOfElements, encodedSize);
...@@ -366,6 +368,10 @@ define("mojo/public/js/bindings/codec", [ ...@@ -366,6 +368,10 @@ define("mojo/public/js/bindings/codec", [
return this.buffer.getUint32(kStructHeaderNumFieldsOffset); return this.buffer.getUint32(kStructHeaderNumFieldsOffset);
}; };
Message.prototype.getName = function() {
return this.buffer.getUint32(kMessageNameOffset);
};
Message.prototype.getFlags = function() { Message.prototype.getFlags = function() {
return this.buffer.getUint32(kMessageFlagsOffset); return this.buffer.getUint32(kMessageFlagsOffset);
}; };
...@@ -673,6 +679,7 @@ define("mojo/public/js/bindings/codec", [ ...@@ -673,6 +679,7 @@ define("mojo/public/js/bindings/codec", [
exports.MessageReader = MessageReader; exports.MessageReader = MessageReader;
exports.kArrayHeaderSize = kArrayHeaderSize; exports.kArrayHeaderSize = kArrayHeaderSize;
exports.kStructHeaderSize = kStructHeaderSize; exports.kStructHeaderSize = kStructHeaderSize;
exports.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
exports.kMessageHeaderSize = kMessageHeaderSize; exports.kMessageHeaderSize = kMessageHeaderSize;
exports.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize; exports.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize;
exports.kMessageExpectsResponse = kMessageExpectsResponse; exports.kMessageExpectsResponse = kMessageExpectsResponse;
......
...@@ -62,7 +62,7 @@ define("mojo/public/js/bindings/router", [ ...@@ -62,7 +62,7 @@ define("mojo/public/js/bindings/router", [
Router.prototype.handleIncomingMessage_ = function(message) { Router.prototype.handleIncomingMessage_ = function(message) {
var v = new validator.Validator(message); var v = new validator.Validator(message);
if (v.validateMessage() !== validator.validationError.NONE) if (v.validateMessageHeader() !== validator.validationError.NONE)
this.close(); this.close();
if (message.expectsResponse()) { if (message.expectsResponse()) {
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
define([ define([
"console",
"file", "file",
"gin/test/expect", "gin/test/expect",
"mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom", "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom",
...@@ -10,7 +11,8 @@ define([ ...@@ -10,7 +11,8 @@ define([
"mojo/public/js/bindings/codec", "mojo/public/js/bindings/codec",
"mojo/public/js/bindings/tests/validation_test_input_parser", "mojo/public/js/bindings/tests/validation_test_input_parser",
"mojo/public/js/bindings/validator", "mojo/public/js/bindings/validator",
], function(file, expect, testInterface, buffer, codec, parser, validator) { ], function(
console, file, expect, testInterface, buffer, codec, parser, validator) {
function checkTestMessageParser() { function checkTestMessageParser() {
function TestMessageParserFailure(message, input) { function TestMessageParserFailure(message, input) {
...@@ -162,7 +164,7 @@ define([ ...@@ -162,7 +164,7 @@ define([
return null; return null;
} }
function getMessageTestFiles() { function getMessageTestFiles(key) {
var sourceRoot = file.getSourceRootDirectory(); var sourceRoot = file.getSourceRootDirectory();
expect(sourceRoot).not.toBeNull(); expect(sourceRoot).not.toBeNull();
...@@ -172,17 +174,14 @@ define([ ...@@ -172,17 +174,14 @@ define([
expect(testFiles).not.toBeNull(); expect(testFiles).not.toBeNull();
expect(testFiles.length).toBeGreaterThan(0); expect(testFiles.length).toBeGreaterThan(0);
// The ".data" pathnames with the extension removed. // The matching ".data" pathnames with the extension removed.
var testPathNames = testFiles.filter(function(s) { return testFiles.filter(function(s) {
return s.substr(-5) == ".data"; return s.substr(-5) == ".data";
}).map(function(s) { }).map(function(s) {
return testDir + s.slice(0, -5); return testDir + s.slice(0, -5);
}); }).filter(function(s) {
return s.indexOf(key) != -1;
// For now, just checking the message header tests. });
return testPathNames.filter(function(s) {
return s.indexOf("_msghdr_") != -1;
});
} }
function readTestMessage(filename) { function readTestMessage(filename) {
...@@ -197,21 +196,57 @@ define([ ...@@ -197,21 +196,57 @@ define([
return contents.trim(); return contents.trim();
} }
function testValidateMessageHeader() { function testMessageValidation(key, filters) {
var testFiles = getMessageTestFiles(); var testFiles = getMessageTestFiles(key);
expect(testFiles.length).toBeGreaterThan(0); expect(testFiles.length).toBeGreaterThan(0);
var noError = validator.validationError.NONE;
for (var i = 0; i < testFiles.length; i++) { for (var i = 0; i < testFiles.length; i++) {
// TODO(hansmuller): Temporarily skipping array pointer overflow tests.
if (testFiles[i].indexOf("overflow") != -1) {
console.log("[Skipping " + testFiles[i] + "]");
continue;
}
// TODO(hansmuller): Temporarily skipping array unexpected null tests.
if (testFiles[i].indexOf("unexpected_null") != -1 ||
testFiles[i].indexOf("unexpected_invalid") != -1) {
console.log("[Skipping " + testFiles[i] + "]");
continue;
}
var testMessage = readTestMessage(testFiles[i]); var testMessage = readTestMessage(testFiles[i]);
// TODO(hansmuller): add the message handles. var handles = new Array(testMessage.handleCount);
var message = new codec.Message(testMessage.buffer); var message = new codec.Message(testMessage.buffer, handles);
var actualResult = new validator.Validator(message).validateMessage(); var messageValidator = new validator.Validator(message);
var err = messageValidator.validateMessageHeader();
for (var j = 0; err === noError && j < filters.length; ++j)
err = filters[j](messageValidator);
var actualResult = (err === noError) ? "PASS" : err;
var expectedResult = readTestExpected(testFiles[i]); var expectedResult = readTestExpected(testFiles[i]);
if (actualResult != expectedResult)
console.log("[Test message validation failed: " + testFiles[i] + " ]");
expect(actualResult).toEqual(expectedResult); expect(actualResult).toEqual(expectedResult);
} }
} }
testValidateMessageHeader(); function testConformanceMessageValidation() {
testMessageValidation("conformance_", [
testInterface.validateConformanceTestInterfaceRequest,
]);
}
function testIntegrationMessageValidation() {
testMessageValidation("integration_", [
testInterface.validateIntegrationTestInterface1Request,
testInterface.validateIntegrationTestInterface2Response
]);
}
testConformanceMessageValidation();
testIntegrationMessageValidation();
expect(checkTestMessageParser()).toBeNull(); expect(checkTestMessageParser()).toBeNull();
this.result = "PASS"; this.result = "PASS";
}); });
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
define("mojo/public/js/bindings/validator", [ define("mojo/public/js/bindings/validator", [
"mojo/public/js/bindings/codec", "mojo/public/js/bindings/codec",
], function(codec) { ], function(codec) {
var validationError = { var validationError = {
NONE: 'VALIDATION_ERROR_NONE', NONE: 'VALIDATION_ERROR_NONE',
...@@ -20,15 +20,22 @@ define("mojo/public/js/bindings/validator", [ ...@@ -20,15 +20,22 @@ define("mojo/public/js/bindings/validator", [
'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID' 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID'
}; };
var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
function Validator(message) { function Validator(message) {
this.message = message; this.message = message;
this.offset = 0; this.offset = 0;
this.handleIndex = 0;
} }
Object.defineProperty(Validator.prototype, "offsetLimit", { Object.defineProperty(Validator.prototype, "offsetLimit", {
get: function() { return this.message.buffer.byteLength; } get: function() { return this.message.buffer.byteLength; }
}); });
Object.defineProperty(Validator.prototype, "handleIndexLimit", {
get: function() { return this.message.handles.length; }
});
// True if we can safely allocate a block of bytes from start to // True if we can safely allocate a block of bytes from start to
// to start + numBytes. // to start + numBytes.
Validator.prototype.isValidRange = function(start, numBytes) { Validator.prototype.isValidRange = function(start, numBytes) {
...@@ -54,6 +61,25 @@ define("mojo/public/js/bindings/validator", [ ...@@ -54,6 +61,25 @@ define("mojo/public/js/bindings/validator", [
return false; return false;
} }
Validator.prototype.claimHandle = function(index) {
if (index === codec.kEncodedInvalidHandleValue)
return true;
if (index < this.handleIndex || index >= this.handleIndexLimit)
return false;
// This is safe because handle indices are uint32.
this.handleIndex = index + 1;
return true;
}
Validator.prototype.validateHandle = function(offset) {
var index = this.message.buffer.getUint32(offset);
if (!this.claimHandle(index))
return validationError.ILLEGAL_HANDLE;
return validationError.NONE;
}
Validator.prototype.validateStructHeader = Validator.prototype.validateStructHeader =
function(offset, minNumBytes, minNumFields) { function(offset, minNumBytes, minNumFields) {
if (!codec.isAligned(offset)) if (!codec.isAligned(offset))
...@@ -75,6 +101,10 @@ define("mojo/public/js/bindings/validator", [ ...@@ -75,6 +101,10 @@ define("mojo/public/js/bindings/validator", [
} }
Validator.prototype.validateMessageHeader = function() { Validator.prototype.validateMessageHeader = function() {
var err = this.validateStructHeader(0, codec.kMessageHeaderSize, 2);
if (err != validationError.NONE)
return err;
var numBytes = this.message.getHeaderNumBytes(); var numBytes = this.message.getHeaderNumBytes();
var numFields = this.message.getHeaderNumFields(); var numFields = this.message.getHeaderNumFields();
...@@ -99,12 +129,126 @@ define("mojo/public/js/bindings/validator", [ ...@@ -99,12 +129,126 @@ define("mojo/public/js/bindings/validator", [
return validationError.NONE; return validationError.NONE;
} }
Validator.prototype.validateMessage = function() { // Returns the message.buffer relative offset this pointer "points to",
var err = this.validateStructHeader(0, codec.kStructHeaderSize, 2); // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
if (err != validationError.NONE) // pointer's value is not valid.
return err; Validator.prototype.decodePointer = function(offset) {
var pointerValue = this.message.buffer.getUint64(offset);
if (pointerValue === 0)
return NULL_MOJO_POINTER;
var bufferOffset = offset + pointerValue;
return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
}
Validator.prototype.validateArrayPointer =
function(offset, elementSize, expectedElementCount, elementType) {
var arrayOffset = this.decodePointer(offset);
if (arrayOffset === null)
return validationError.ILLEGAL_POINTER;
if (arrayOffset === NULL_MOJO_POINTER)
return validationError.NONE;
return this.validateArray(
arrayOffset, elementSize, expectedElementCount, elementType);
}
return this.validateMessageHeader(); Validator.prototype.validateStructPointer = function(offset, structClass) {
var structOffset = this.decodePointer(offset);
if (structOffset === null)
return validationError.ILLEGAL_POINTER;
if (structOffset === NULL_MOJO_POINTER)
return validationError.NONE;
return structClass.validate(this, structOffset);
}
Validator.prototype.validateStringPointer = function(offset) {
return this.validateArrayPointer(
offset, codec.Uint8.encodedSize, 0, codec.Uint8);
}
// Similar to Array_Data<T>::Validate()
// mojo/public/cpp/bindings/lib/array_internal.h
Validator.prototype.validateArray =
function (offset, elementSize, expectedElementCount, elementType) {
if (!codec.isAligned(offset))
return validationError.MISALIGNED_OBJECT;
if (!this.isValidRange(offset, codec.kArrayHeaderSize))
return validationError.ILLEGAL_MEMORY_RANGE;
var numBytes = this.message.buffer.getUint32(offset);
var numElements = this.message.buffer.getUint32(offset + 4);
// Note: this computation is "safe" because elementSize <= 8 and
// numElements is a uint32.
var elementsTotalSize = (elementType === codec.PackedBool) ?
Math.ceil(numElements / 8) : (elementSize * numElements);
if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
return validationError.UNEXPECTED_ARRAY_HEADER;
if (expectedElementCount != 0 && numElements != expectedElementCount)
return validationError.UNEXPECTED_ARRAY_HEADER;
if (!this.claimRange(offset, numBytes))
return validationError.ILLEGAL_MEMORY_RANGE;
// Validate the array's elements if they are pointers or handles.
var elementsOffset = offset + codec.kArrayHeaderSize;
if (elementType === codec.Handle)
return this.validateHandleElements(elementsOffset, numElements);
if (elementType instanceof codec.PointerTo)
return this.validateStructElements(
elementsOffset, numElements, elementType.cls);
if (elementType instanceof codec.String)
return this.validateArrayElements(
elementsOffset, numElements, codec.Uint8);
if (elementType instanceof codec.ArrayOf)
return this.validateArrayElements(
elementsOffset, numElements, elementType.cls);
return validationError.NONE;
}
// Note: the |offset + i * elementSize| computation in the validateFooElements
// methods below is "safe" because elementSize <= 8, offset and
// numElements are uint32, and 0 <= i < numElements.
Validator.prototype.validateHandleElements = function(offset, numElements) {
var elementSize = codec.Handle.encodedSize;
for (var i = 0; i < numElements; i++) {
var index = this.message.buffer.getUint32(offset + i * elementSize);
if (!this.claimHandle(index))
return validationError.ILLEGAL_HANDLE;
}
return validationError.NONE;
}
// The elementClass parameter is the element type of the element arrays.
Validator.prototype.validateArrayElements =
function(offset, numElements, elementClass) {
var elementSize = codec.PointerTo.prototype.encodedSize;
for (var i = 0; i < numElements; i++) {
var elementOffset = offset + i * elementSize;
var err = this.validateArrayPointer(
elementOffset, elementClass.encodedSize, 0, elementClass);
if (err != validationError.NONE)
return err;
}
return validationError.NONE;
}
Validator.prototype.validateStructElements =
function(offset, numElements, structClass) {
var elementSize = codec.PointerTo.prototype.encodedSize;
for (var i = 0; i < numElements; i++) {
var elementOffset = offset + i * elementSize;
var err = this.validateStructPointer(elementOffset, structClass);
if (err != validationError.NONE)
return err;
}
return validationError.NONE;
} }
var exports = {}; var exports = {};
......
...@@ -108,6 +108,55 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%} ...@@ -108,6 +108,55 @@ params.{{parameter.name}}{% if not loop.last %}, {% endif -%}
} }
}; };
{#--- Validation #}
function validate{{interface.name}}Request(messageValidator) {
{%- if not(interface.methods) %}
return validator.validationError.NONE;
{%- else %}
var message = messageValidator.message;
var paramsClass = null;
switch (message.getName()) {
{%- for method in interface.methods %}
case k{{interface.name}}_{{method.name}}_Name:
{%- if method.response_parameters == None %}
if (!message.expectsResponse() && !message.isResponse())
paramsClass = {{interface.name}}_{{method.name}}_Params;
{%- else %}
if (message.expectsResponse())
paramsClass = {{interface.name}}_{{method.name}}_Params;
{%- endif %}
break;
{%- endfor %}
}
if (paramsClass === null)
return validator.validationError.NONE;
return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
{%- endif %}
}
function validate{{interface.name}}Response(messageValidator) {
{%- if not(interface|has_callbacks) %}
return validator.validationError.NONE;
{%- else %}
var message = messageValidator.message;
var paramsClass = null;
switch (message.getName()) {
{%- for method in interface.methods %}
{%- if method.response_parameters != None %}
case k{{interface.name}}_{{method.name}}_Name:
if (message.isResponse())
paramsClass = {{interface.name}}_{{method.name}}_ResponseParams;
break;
{%- endif %}
{%- endfor %}
}
if (paramsClass === null)
return validator.validationError.NONE;
return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes());
{%- endif %}
}
{#--- Enums #} {#--- Enums #}
{% from "enum_definition.tmpl" import enum_def -%} {% from "enum_definition.tmpl" import enum_def -%}
{% for enum in interface.enums %} {% for enum in interface.enums %}
......
...@@ -4,10 +4,11 @@ ...@@ -4,10 +4,11 @@
define("{{module.path}}", [ define("{{module.path}}", [
"mojo/public/js/bindings/codec", "mojo/public/js/bindings/codec",
"mojo/public/js/bindings/validator",
{%- for import in imports %} {%- for import in imports %}
"{{import.module.path}}", "{{import.module.path}}",
{%- endfor %} {%- endfor %}
], function(codec ], function(codec, validator
{%- for import in imports -%} {%- for import in imports -%}
, {{import.unique_name}} , {{import.unique_name}}
{%- endfor -%} {%- endfor -%}
...@@ -47,6 +48,8 @@ define("{{module.path}}", [ ...@@ -47,6 +48,8 @@ define("{{module.path}}", [
{%- for interface in interfaces %} {%- for interface in interfaces %}
exports.{{interface.name}}Proxy = {{interface.name}}Proxy; exports.{{interface.name}}Proxy = {{interface.name}}Proxy;
exports.{{interface.name}}Stub = {{interface.name}}Stub; exports.{{interface.name}}Stub = {{interface.name}}Stub;
exports.validate{{interface.name}}Request = validate{{interface.name}}Request;
exports.validate{{interface.name}}Response = validate{{interface.name}}Response;
{%- endfor %} {%- endfor %}
return exports; return exports;
}); });
...@@ -21,6 +21,36 @@ ...@@ -21,6 +21,36 @@
{%- endfor %} {%- endfor %}
}; };
{#--- Validation #}
{{struct.name}}.validate = function(messageValidator, offset) {
var err;
err = messageValidator.validateStructHeader(offset, {{struct.name}}.encodedSize, {{struct.packed.packed_fields|length}});
if (err !== validator.validationError.NONE)
return err;
{%- for packed_field in struct.packed.packed_fields %}
{%- set field_name = packed_field.field.name %}
{%- if packed_field.field|is_string_pointer_field %}
// validate {{struct.name}}.{{field_name}}
err = messageValidator.validateStringPointer({{packed_field|field_offset}})
{%- elif packed_field.field|is_array_pointer_field %}
// validate {{struct.name}}.{{field_name}}
err = messageValidator.validateArrayPointer({{packed_field|validate_array_params}});
{%- elif packed_field.field|is_struct_pointer_field %}
// validate {{struct.name}}.{{field_name}}
err = messageValidator.validateStructPointer({{packed_field|validate_struct_params}});
{%- elif packed_field.field|is_handle_field %}
// validate {{struct.name}}.{{field_name}}
err = messageValidator.validateHandle({{packed_field|field_offset}})
{%- endif %}
if (err !== validator.validationError.NONE)
return err;
{%- endfor %}
return validator.validationError.NONE;
};
{#--- Encoding and decoding #} {#--- Encoding and decoding #}
{{struct.name}}.encodedSize = codec.kStructHeaderSize + {{struct.packed|payload_size}}; {{struct.name}}.encodedSize = codec.kStructHeaderSize + {{struct.packed|payload_size}};
......
...@@ -228,12 +228,6 @@ def TranslateConstants(token, kind): ...@@ -228,12 +228,6 @@ def TranslateConstants(token, kind):
def ExpressionToText(value, kind=None): def ExpressionToText(value, kind=None):
return TranslateConstants(value, kind) return TranslateConstants(value, kind)
def HasCallbacks(interface):
for method in interface.methods:
if method.response_parameters != None:
return True
return False
def ShouldInlineStruct(struct): def ShouldInlineStruct(struct):
# TODO(darin): Base this on the size of the wrapper class. # TODO(darin): Base this on the size of the wrapper class.
if len(struct.fields) > 4: if len(struct.fields) > 4:
...@@ -279,7 +273,7 @@ class Generator(generator.Generator): ...@@ -279,7 +273,7 @@ class Generator(generator.Generator):
"get_array_validate_params": GetArrayValidateParams, "get_array_validate_params": GetArrayValidateParams,
"get_name_for_kind": GetNameForKind, "get_name_for_kind": GetNameForKind,
"get_pad": pack.GetPad, "get_pad": pack.GetPad,
"has_callbacks": HasCallbacks, "has_callbacks": mojom.HasCallbacks,
"should_inline": ShouldInlineStruct, "should_inline": ShouldInlineStruct,
"is_any_array_kind": mojom.IsAnyArrayKind, "is_any_array_kind": mojom.IsAnyArrayKind,
"is_enum_kind": mojom.IsEnumKind, "is_enum_kind": mojom.IsEnumKind,
......
...@@ -46,7 +46,7 @@ def JavaScriptDefaultValue(field): ...@@ -46,7 +46,7 @@ def JavaScriptDefaultValue(field):
return _kind_to_javascript_default_value[field.kind] return _kind_to_javascript_default_value[field.kind]
if mojom.IsStructKind(field.kind): if mojom.IsStructKind(field.kind):
return "null" return "null"
if mojom.IsArrayKind(field.kind): if mojom.IsAnyArrayKind(field.kind):
return "null" return "null"
if mojom.IsInterfaceKind(field.kind) or \ if mojom.IsInterfaceKind(field.kind) or \
mojom.IsInterfaceRequestKind(field.kind): mojom.IsInterfaceRequestKind(field.kind):
...@@ -97,9 +97,9 @@ def CodecType(kind): ...@@ -97,9 +97,9 @@ def CodecType(kind):
return _kind_to_codec_type[kind] return _kind_to_codec_type[kind]
if mojom.IsStructKind(kind): if mojom.IsStructKind(kind):
return "new codec.PointerTo(%s)" % CodecType(kind.name) return "new codec.PointerTo(%s)" % CodecType(kind.name)
if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind): if mojom.IsAnyArrayKind(kind) and mojom.IsBoolKind(kind.kind):
return "new codec.ArrayOf(new codec.ArrayOf(codec.PackedBool))" return "new codec.ArrayOf(codec.PackedBool)"
if mojom.IsArrayKind(kind): if mojom.IsAnyArrayKind(kind):
return "new codec.ArrayOf(%s)" % CodecType(kind.kind) return "new codec.ArrayOf(%s)" % CodecType(kind.kind)
if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind): if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
return CodecType(mojom.MSGPIPE) return CodecType(mojom.MSGPIPE)
...@@ -113,9 +113,9 @@ def JavaScriptDecodeSnippet(kind): ...@@ -113,9 +113,9 @@ def JavaScriptDecodeSnippet(kind):
return "decodeStruct(%s)" % CodecType(kind) return "decodeStruct(%s)" % CodecType(kind)
if mojom.IsStructKind(kind): if mojom.IsStructKind(kind):
return "decodeStructPointer(%s)" % CodecType(kind.name) return "decodeStructPointer(%s)" % CodecType(kind.name)
if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind): if mojom.IsAnyArrayKind(kind) and mojom.IsBoolKind(kind.kind):
return "decodeArrayPointer(new codec.ArrayOf(codec.PackedBool))" return "decodeArrayPointer(codec.PackedBool)"
if mojom.IsArrayKind(kind): if mojom.IsAnyArrayKind(kind):
return "decodeArrayPointer(%s)" % CodecType(kind.kind) return "decodeArrayPointer(%s)" % CodecType(kind.kind)
if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind): if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
return JavaScriptDecodeSnippet(mojom.MSGPIPE) return JavaScriptDecodeSnippet(mojom.MSGPIPE)
...@@ -128,8 +128,8 @@ def JavaScriptEncodeSnippet(kind): ...@@ -128,8 +128,8 @@ def JavaScriptEncodeSnippet(kind):
return "encodeStruct(%s, " % CodecType(kind) return "encodeStruct(%s, " % CodecType(kind)
if mojom.IsStructKind(kind): if mojom.IsStructKind(kind):
return "encodeStructPointer(%s, " % CodecType(kind.name) return "encodeStructPointer(%s, " % CodecType(kind.name)
if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind): if mojom.IsAnyArrayKind(kind) and mojom.IsBoolKind(kind.kind):
return "encodeArrayPointer(new codec.ArrayOf(codec.PackedBool), "; return "encodeArrayPointer(codec.PackedBool, ";
if mojom.IsAnyArrayKind(kind): if mojom.IsAnyArrayKind(kind):
return "encodeArrayPointer(%s, " % CodecType(kind.kind) return "encodeArrayPointer(%s, " % CodecType(kind.kind)
if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind): if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
...@@ -138,6 +138,22 @@ def JavaScriptEncodeSnippet(kind): ...@@ -138,6 +138,22 @@ def JavaScriptEncodeSnippet(kind):
return JavaScriptEncodeSnippet(mojom.INT32) return JavaScriptEncodeSnippet(mojom.INT32)
def JavaScriptFieldOffset(packed_field):
return "offset + codec.kStructHeaderSize + %s" % packed_field.offset
def JavaScriptValidateArrayParams(pf):
elementKind = pf.field.kind.kind
elementSize = pack.PackedField.GetSizeForKind(elementKind)
elementCount = generator.ExpectedArraySize(pf.field.kind)
elementType = "codec.PackedBool" if mojom.IsBoolKind(elementKind) \
else CodecType(elementKind)
return "%s, %s, %s, %s" % \
(JavaScriptFieldOffset(pf), elementSize, elementCount, elementType)
def JavaScriptValidateStructParams(pf):
return "%s, %s" % (JavaScriptFieldOffset(pf), pf.field.kind.name)
def TranslateConstants(token): def TranslateConstants(token):
if isinstance(token, (mojom.EnumValue, mojom.NamedValue)): if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
# Both variable and enum constants are constructed like: # Both variable and enum constants are constructed like:
...@@ -163,6 +179,18 @@ def JavascriptType(kind): ...@@ -163,6 +179,18 @@ def JavascriptType(kind):
return kind.imported_from["unique_name"] + "." + kind.name return kind.imported_from["unique_name"] + "." + kind.name
return kind.name return kind.name
def IsArrayPointerField(field):
return mojom.IsAnyArrayKind(field.kind)
def IsStringPointerField(field):
return mojom.IsStringKind(field.kind)
def IsStructPointerField(field):
return mojom.IsStructKind(field.kind)
def IsHandleField(field):
return mojom.IsAnyHandleKind(field.kind)
class Generator(generator.Generator): class Generator(generator.Generator):
...@@ -172,8 +200,16 @@ class Generator(generator.Generator): ...@@ -172,8 +200,16 @@ class Generator(generator.Generator):
"decode_snippet": JavaScriptDecodeSnippet, "decode_snippet": JavaScriptDecodeSnippet,
"encode_snippet": JavaScriptEncodeSnippet, "encode_snippet": JavaScriptEncodeSnippet,
"expression_to_text": ExpressionToText, "expression_to_text": ExpressionToText,
"field_offset": JavaScriptFieldOffset,
"has_callbacks": mojom.HasCallbacks,
"is_array_pointer_field": IsArrayPointerField,
"is_struct_pointer_field": IsStructPointerField,
"is_string_pointer_field": IsStringPointerField,
"is_handle_field": IsHandleField,
"js_type": JavascriptType, "js_type": JavascriptType,
"stylize_method": generator.StudlyCapsToCamel, "stylize_method": generator.StudlyCapsToCamel,
"validate_array_params": JavaScriptValidateArrayParams,
"validate_struct_params": JavaScriptValidateStructParams,
} }
@UseJinja("js_templates/module.js.tmpl", filters=js_filters) @UseJinja("js_templates/module.js.tmpl", filters=js_filters)
......
...@@ -400,3 +400,11 @@ def IsAnyHandleKind(kind): ...@@ -400,3 +400,11 @@ def IsAnyHandleKind(kind):
def IsMoveOnlyKind(kind): def IsMoveOnlyKind(kind):
return IsObjectKind(kind) or IsAnyHandleKind(kind) return IsObjectKind(kind) or IsAnyHandleKind(kind)
def HasCallbacks(interface):
for method in interface.methods:
if method.response_parameters != None:
return True
return False
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