# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generates JavaScript source files from a mojom.Module."""
import mojom.generate.generator as generator
import mojom.generate.module as mojom
import mojom.generate.pack as pack
import os
import sys
import urllib.request
from mojom.generate.template_expander import UseJinja
_kind_to_javascript_default_value = {
mojom.BOOL: "false",
mojom.INT8: "0",
mojom.UINT8: "0",
mojom.INT16: "0",
mojom.UINT16: "0",
mojom.INT32: "0",
mojom.UINT32: "0",
mojom.FLOAT: "0",
mojom.HANDLE: "null",
mojom.DCPIPE: "null",
mojom.DPPIPE: "null",
mojom.MSGPIPE: "null",
mojom.SHAREDBUFFER: "null",
mojom.PLATFORMHANDLE: "null",
mojom.NULLABLE_HANDLE: "null",
mojom.NULLABLE_DCPIPE: "null",
mojom.NULLABLE_DPPIPE: "null",
mojom.NULLABLE_MSGPIPE: "null",
mojom.NULLABLE_SHAREDBUFFER: "null",
mojom.NULLABLE_PLATFORMHANDLE: "null",
mojom.INT64: "0",
mojom.UINT64: "0",
mojom.DOUBLE: "0",
mojom.STRING: "null",
mojom.NULLABLE_STRING: "null"
}
_kind_to_codec_type = {
mojom.BOOL: "codec.Uint8",
mojom.INT8: "codec.Int8",
mojom.UINT8: "codec.Uint8",
mojom.INT16: "codec.Int16",
mojom.UINT16: "codec.Uint16",
mojom.INT32: "codec.Int32",
mojom.UINT32: "codec.Uint32",
mojom.FLOAT: "codec.Float",
mojom.HANDLE: "codec.Handle",
mojom.DCPIPE: "codec.Handle",
mojom.DPPIPE: "codec.Handle",
mojom.MSGPIPE: "codec.Handle",
mojom.SHAREDBUFFER: "codec.Handle",
mojom.PLATFORMHANDLE: "codec.Handle",
mojom.NULLABLE_HANDLE: "codec.NullableHandle",
mojom.NULLABLE_DCPIPE: "codec.NullableHandle",
mojom.NULLABLE_DPPIPE: "codec.NullableHandle",
mojom.NULLABLE_MSGPIPE: "codec.NullableHandle",
mojom.NULLABLE_SHAREDBUFFER: "codec.NullableHandle",
mojom.NULLABLE_PLATFORMHANDLE: "codec.NullableHandle",
mojom.INT64: "codec.Int64",
mojom.UINT64: "codec.Uint64",
mojom.DOUBLE: "codec.Double",
mojom.STRING: "codec.String",
mojom.NULLABLE_STRING: "codec.NullableString",
}
_kind_to_closure_type = {
mojom.BOOL: "boolean",
mojom.INT8: "number",
mojom.UINT8: "number",
mojom.INT16: "number",
mojom.UINT16: "number",
mojom.INT32: "number",
mojom.UINT32: "number",
mojom.FLOAT: "number",
mojom.INT64: "bigint",
mojom.UINT64: "bigint",
mojom.DOUBLE: "number",
# The nullability annotation i.e. '?' is added by the code that needs it, so
# these have the same types as the above non-nullable kinds.
mojom.NULLABLE_BOOL: "boolean",
mojom.NULLABLE_INT8: "number",
mojom.NULLABLE_UINT8: "number",
mojom.NULLABLE_INT16: "number",
mojom.NULLABLE_UINT16: "number",
mojom.NULLABLE_INT32: "number",
mojom.NULLABLE_UINT32: "number",
mojom.NULLABLE_FLOAT: "number",
mojom.NULLABLE_INT64: "bigint",
mojom.NULLABLE_UINT64: "bigint",
mojom.NULLABLE_DOUBLE: "number",
mojom.STRING: "string",
mojom.NULLABLE_STRING: "string",
mojom.HANDLE: "MojoHandle",
mojom.DCPIPE: "MojoHandle",
mojom.DPPIPE: "MojoHandle",
mojom.MSGPIPE: "MojoHandle",
mojom.SHAREDBUFFER: "MojoHandle",
mojom.PLATFORMHANDLE: "MojoHandle",
mojom.NULLABLE_HANDLE: "MojoHandle",
mojom.NULLABLE_DCPIPE: "MojoHandle",
mojom.NULLABLE_DPPIPE: "MojoHandle",
mojom.NULLABLE_MSGPIPE: "MojoHandle",
mojom.NULLABLE_SHAREDBUFFER: "MojoHandle",
mojom.NULLABLE_PLATFORMHANDLE: "MojoHandle",
}
_kind_to_lite_js_type = {
mojom.BOOL: "mojo.internal.Bool",
mojom.INT8: "mojo.internal.Int8",
mojom.UINT8: "mojo.internal.Uint8",
mojom.INT16: "mojo.internal.Int16",
mojom.UINT16: "mojo.internal.Uint16",
mojom.INT32: "mojo.internal.Int32",
mojom.UINT32: "mojo.internal.Uint32",
mojom.FLOAT: "mojo.internal.Float",
mojom.HANDLE: "mojo.internal.Handle",
mojom.DCPIPE: "mojo.internal.Handle",
mojom.DPPIPE: "mojo.internal.Handle",
mojom.MSGPIPE: "mojo.internal.Handle",
mojom.SHAREDBUFFER: "mojo.internal.Handle",
mojom.PLATFORMHANDLE: "mojo.internal.Handle",
mojom.NULLABLE_HANDLE: "mojo.internal.Handle",
mojom.NULLABLE_DCPIPE: "mojo.internal.Handle",
mojom.NULLABLE_DPPIPE: "mojo.internal.Handle",
mojom.NULLABLE_MSGPIPE: "mojo.internal.Handle",
mojom.NULLABLE_SHAREDBUFFER: "mojo.internal.Handle",
mojom.NULLABLE_PLATFORMHANDLE: "mojo.internal.Handle",
mojom.INT64: "mojo.internal.Int64",
mojom.UINT64: "mojo.internal.Uint64",
mojom.DOUBLE: "mojo.internal.Double",
mojom.STRING: "mojo.internal.String",
mojom.NULLABLE_STRING: "mojo.internal.String",
}
_js_reserved_keywords = [
'arguments',
'await',
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'enum',
'export',
'extends',
'finally',
'for',
'function',
'if',
'implements',
'import',
'in',
'instanceof',
'interface',
'let',
'new',
'package',
'private',
'protected',
'public',
'return',
'static',
'super',
'switch',
'this',
'throw',
'try',
'typeof',
'var',
'void',
'while',
'with',
'yield',
]
_primitive_kind_to_fuzz_type = {
mojom.BOOL: "Bool",
mojom.NULLABLE_BOOL: "Bool",
mojom.INT8: "Int8",
mojom.NULLABLE_INT8: "Int8",
mojom.UINT8: "Uint8",
mojom.NULLABLE_UINT8: "Uint8",
mojom.INT16: "Int16",
mojom.NULLABLE_INT16: "Int16",
mojom.UINT16: "Uint16",
mojom.NULLABLE_UINT16: "Uint16",
mojom.INT32: "Int32",
mojom.NULLABLE_INT32: "Int32",
mojom.UINT32: "Uint32",
mojom.NULLABLE_UINT32: "Uint32",
mojom.FLOAT: "Float",
mojom.NULLABLE_FLOAT: "Float",
mojom.INT64: "Int64",
mojom.NULLABLE_INT64: "Int64",
mojom.UINT64: "Uint64",
mojom.NULLABLE_UINT64: "Uint64",
mojom.DOUBLE: "Double",
mojom.NULLABLE_DOUBLE: "Double",
mojom.STRING: "String",
mojom.NULLABLE_STRING: "String",
mojom.HANDLE: "Handle",
mojom.DCPIPE: "DataPipeConsumer",
mojom.DPPIPE: "DataPipeProducer",
mojom.MSGPIPE: "MessagePipe",
mojom.SHAREDBUFFER: "SharedBuffer",
mojom.PLATFORMHANDLE: "PlatformHandle",
mojom.NULLABLE_HANDLE: "Handle",
mojom.NULLABLE_DCPIPE: "DataPipeConsumer",
mojom.NULLABLE_DPPIPE: "DataPipeProducer",
mojom.NULLABLE_MSGPIPE: "MessagePipe",
mojom.NULLABLE_SHAREDBUFFER: "SharedBuffer",
mojom.NULLABLE_PLATFORMHANDLE: "PlatformHandle",
}
_CHROME_SCHEME_PREFIX = 'chrome:'
_SHARED_MODULE_PREFIX = '//resources/mojo'
def _IsSharedModulePath(path):
return path.startswith(_SHARED_MODULE_PREFIX) or \
path.startswith(_CHROME_SCHEME_PREFIX + _SHARED_MODULE_PREFIX)
def _IsAbsoluteChromeResourcesPath(path):
return path.startswith('chrome://resources/') or \
path.startswith('//resources/')
def _GetWebUiModulePath(module):
"""Returns the path to a WebUI module, from the perspective of a WebUI page
that makes it available. This is based on the corresponding mojom target's
webui_module_path value. Returns None if the target specifies no module
path. Otherwise, returned paths always end in a '/' and begin with either
`chrome://resources/` or a '/'."""
path = module.metadata.get('webui_module_path')
if path is None or path == '/':
return path
if _IsAbsoluteChromeResourcesPath(path):
return path.rstrip('/') + '/'
return '/{}/'.format(path.strip('/'))
def JavaScriptPayloadSize(packed):
packed_fields = packed.packed_fields
if not packed_fields:
return 0
last_field = packed_fields[-1]
offset = last_field.offset + last_field.size
pad = pack.GetPad(offset, 8)
return offset + pad
def JavaScriptFieldOffset(packed_field):
return "offset + codec.kStructHeaderSize + %s" % packed_field.offset
def GetArrayExpectedDimensionSizes(kind):
expected_dimension_sizes = []
while mojom.IsArrayKind(kind):
expected_dimension_sizes.append(generator.ExpectedArraySize(kind) or 0)
kind = kind.kind
# Strings are serialized as variable-length arrays.
if (mojom.IsStringKind(kind)):
expected_dimension_sizes.append(0)
return expected_dimension_sizes
def GetRelativeUrl(module, base_module):
return urllib.request.pathname2url(
os.path.relpath(module.path, os.path.dirname(base_module.path)))
class JavaScriptStylizer(generator.Stylizer):
def StylizeConstant(self, mojom_name):
return generator.ToUpperSnakeCase(mojom_name)
def StylizeField(self, mojom_name):
return generator.ToCamel(mojom_name, lower_initial=True)
def StylizeStruct(self, mojom_name):
return mojom_name
def StylizeUnion(self, mojom_name):
return mojom_name
def StylizeParameter(self, mojom_name):
return generator.ToCamel(mojom_name, lower_initial=True)
def StylizeMethod(self, mojom_name):
return generator.ToCamel(mojom_name, lower_initial=True)
def StylizeEnumField(self, mojom_name):
return mojom_name
def StylizeEnum(self, mojom_name):
return mojom_name
def StylizeModule(self, mojom_namespace):
return '.'.join(
generator.ToCamel(word, lower_initial=True)
for word in mojom_namespace.split('.'))
class Generator(generator.Generator):
def _GetParameters(self, for_compile=False, for_webui_module=False):
return {
"bindings_library_path":
self._GetBindingsLibraryPath(for_webui_module=for_webui_module),
"enums":
self.module.enums,
"for_bindings_internals":
self.disallow_native_types,
"imports":
self.module.imports,
"interfaces":
self.module.interfaces,
"js_module_imports":
self._GetJsModuleImports(for_webui_module=for_webui_module),
"kinds":
self.module.kinds,
"module":
self.module,
"mojom_namespace":
self.module.mojom_namespace,
"structs":
self.module.structs + self._GetStructsFromMethods(),
"unions":
self.module.unions,
"generate_fuzzing":
self.generate_fuzzing,
"generate_closure_exports":
for_compile,
"generate_struct_deserializers":
self.js_generate_struct_deserializers,
}
@staticmethod
def GetTemplatePrefix():
return "js_templates"
def GetFilters(self):
js_filters = {
"is_nullable_value_kind_packed_field":
pack.IsNullableValueKindPackedField,
"is_primary_nullable_value_kind_packed_field":
pack.IsPrimaryNullableValueKindPackedField,
"closure_type": self._ClosureType,
"constant_value": self._GetConstantValue,
"constant_value_in_js_module": self._GetConstantValueInJsModule,
"decode_snippet": self._JavaScriptDecodeSnippet,
"default_value": self._JavaScriptDefaultValue,
"default_value_in_js_module": self._DefaultValueInJsModule,
"encode_snippet": self._JavaScriptEncodeSnippet,
"expression_to_text": self._ExpressionToText,
"field_offset": JavaScriptFieldOffset,
"field_type_in_js_module": self._GetFieldTypeInJsModule,
"get_relative_url": GetRelativeUrl,
"has_callbacks": mojom.HasCallbacks,
"imports_for_kind": self._GetImportsForKind,
"is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind,
"is_array_kind": mojom.IsArrayKind,
"is_pending_associated_remote_kind":
mojom.IsPendingAssociatedRemoteKind,
"is_pending_associated_receiver_kind":
mojom.IsPendingAssociatedReceiverKind,
"is_bool_kind": mojom.IsBoolKind,
"is_enum_kind": mojom.IsEnumKind,
"is_any_handle_kind": mojom.IsAnyHandleKind,
"is_any_interface_kind": mojom.IsAnyInterfaceKind,
"is_interface_kind": mojom.IsInterfaceKind,
"is_pending_remote_kind": mojom.IsPendingRemoteKind,
"is_pending_receiver_kind": mojom.IsPendingReceiverKind,
"is_map_kind": mojom.IsMapKind,
"is_object_kind": mojom.IsObjectKind,
"is_reference_kind": mojom.IsReferenceKind,
"is_string_kind": mojom.IsStringKind,
"is_struct_kind": mojom.IsStructKind,
"is_union_kind": mojom.IsUnionKind,
"js_type": self._JavaScriptType,
"lite_default_value": self._LiteJavaScriptDefaultValue,
"lite_js_type": self._LiteJavaScriptType,
"lite_js_import_name": self._LiteJavaScriptImportName,
"method_passes_associated_kinds": mojom.MethodPassesAssociatedKinds,
"namespace_declarations": self._NamespaceDeclarations,
"closure_type_with_nullability": self._ClosureTypeWithNullability,
"lite_closure_type": self._LiteClosureType,
"lite_closure_type_with_nullability":
self._LiteClosureTypeWithNullability,
"lite_closure_field_type": self._LiteClosureFieldType,
"payload_size": JavaScriptPayloadSize,
"spec_type_in_js_module": self._GetSpecTypeInJsModule,
"to_camel": generator.ToCamel,
"type_in_js_module": self._GetTypeInJsModule,
"type_in_js_module_with_nullability":
self._GetTypeInJsModuleWithNullability,
"union_decode_snippet": self._JavaScriptUnionDecodeSnippet,
"union_encode_snippet": self._JavaScriptUnionEncodeSnippet,
"validate_array_params": self._JavaScriptValidateArrayParams,
"validate_enum_params": self._JavaScriptValidateEnumParams,
"validate_map_params": self._JavaScriptValidateMapParams,
"validate_nullable_params": self._JavaScriptNullableParam,
"validate_struct_params": self._JavaScriptValidateStructParams,
"validate_union_params": self._JavaScriptValidateUnionParams,
"sanitize_identifier": self._JavaScriptSanitizeIdentifier,
"contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces,
"fuzz_handle_name": self._FuzzHandleName,
"is_primitive_kind": self._IsPrimitiveKind,
"primitive_to_fuzz_type": self._PrimitiveToFuzzType,
"to_js_boolean": self._ToJsBoolean,
}
return js_filters
@UseJinja("module.amd.tmpl")
def _GenerateAMDModule(self):
return self._GetParameters()
@UseJinja("lite/mojom-lite.js.tmpl")
def _GenerateLiteBindings(self):
return self._GetParameters()
@UseJinja("lite/mojom-lite.js.tmpl")
def _GenerateLiteBindingsForCompile(self):
return self._GetParameters(for_compile=True)
@UseJinja("lite/mojom.m.js.tmpl")
def _GenerateJsModule(self):
return self._GetParameters()
@UseJinja("lite/mojom.m.js.tmpl")
def _GenerateWebUiModule(self):
return self._GetParameters(for_webui_module=True)
def GenerateFiles(self, args):
if self.variant:
raise Exception("Variants not supported in JavaScript bindings.")
self.module.Stylize(JavaScriptStylizer())
# TODO(crbug.com/41361453): Change the media router extension to not mess with
# the mojo namespace, so that namespaces such as "mojo.common.mojom" are not
# affected and we can remove this method.
self._SetUniqueNameForImports()
self.WriteWithComment(self._GenerateAMDModule(), "%s.js" % self.module.path)
self.WriteWithComment(self._GenerateLiteBindings(),
"%s-lite.js" % self.module.path)
self.WriteWithComment(self._GenerateLiteBindingsForCompile(),
"%s-lite-for-compile.js" % self.module.path)
self.WriteWithComment(self._GenerateJsModule(),
"%s.m.js" % self.module.path)
if self.module.metadata.get("generate_webui_js") is not None:
self.WriteWithComment(self._GenerateWebUiModule(),
"mojom-webui/%s-webui.js" % self.module.path)
def _GetRelativePath(self, path):
relpath = urllib.request.pathname2url(
os.path.relpath(path, os.path.dirname(self.module.path)))
if relpath.startswith('.') or relpath.startswith('/'):
return relpath
return './' + relpath
def _GetBindingsLibraryPath(self, for_webui_module=False):
if for_webui_module:
return "//resources/mojo/mojo/public/js/bindings.js"
return self._GetRelativePath('mojo/public/js/bindings.js')
def _SetUniqueNameForImports(self):
used_names = set()
for each_import in self.module.imports:
simple_name = os.path.basename(each_import.path).split(".")[0]
# Since each import is assigned a variable in JS, they need to have unique
# names.
unique_name = simple_name
counter = 0
while unique_name in used_names:
counter += 1
unique_name = simple_name + str(counter)
used_names.add(unique_name)
each_import.unique_name = unique_name + "$"
counter += 1
def _ClosureType(self, kind):
if kind in mojom.PRIMITIVES:
return _kind_to_closure_type[kind]
if mojom.IsInterfaceKind(kind):
return kind.module.namespace + "." + kind.name + "Ptr"
if mojom.IsPendingRemoteKind(kind):
return kind.kind.module.namespace + "." + kind.kind.name + "Ptr"
if (mojom.IsStructKind(kind) or mojom.IsEnumKind(kind)):
return kind.module.namespace + "." + kind.name
# TODO(calamity): Support unions properly.
if mojom.IsUnionKind(kind):
return "Object"
if mojom.IsArrayKind(kind):
return "Array<%s>" % self._ClosureType(kind.kind)
if mojom.IsMapKind(kind):
return "Map<%s, %s>" % (self._ClosureType(
kind.key_kind), self._ClosureType(kind.value_kind))
if mojom.IsPendingReceiverKind(kind):
return "mojo.InterfaceRequest"
# TODO(calamity): Support associated interfaces properly.
if mojom.IsPendingAssociatedRemoteKind(kind):
return "mojo.AssociatedInterfacePtrInfo"
# TODO(calamity): Support associated interface requests properly.
if mojom.IsPendingAssociatedReceiverKind(kind):
return "mojo.AssociatedInterfaceRequest"
# TODO(calamity): Support enums properly.
raise Exception("No valid closure type: %s" % kind)
def _IsStringableKind(self, kind):
# Indicates whether a kind of suitable to stringify and use as an Object
# property name. This is checked for map key types to allow most kinds of
# mojom maps to be represented as either a Map or an Object.
if kind == mojom.INT64 or kind == mojom.UINT64:
# JS BigInts are not stringable and cannot be used as Object property
# names.
return False
return (mojom.IsIntegralKind(kind) or mojom.IsFloatKind(kind)
or mojom.IsDoubleKind(kind) or mojom.IsStringKind(kind)
or mojom.IsEnumKind(kind))
def _GetTypeNameForNewBindings(self,
kind,
with_nullability=False,
for_module=False):
# If `with_nullability` is true, we'll include a nullable annotation which
# in the Closure case is `?`. Otherwise, the annotation will be omitted.
def recurse_with_nullability(kind):
return self._GetTypeNameForNewBindings(kind,
with_nullability=True,
for_module=for_module)
def get_type_name(kind):
if kind == mojom.INT64 or kind == mojom.UINT64:
return "bigint"
if kind in mojom.PRIMITIVES:
return _kind_to_closure_type[kind]
if mojom.IsArrayKind(kind):
return "Array<%s>" % recurse_with_nullability(kind.kind)
if mojom.IsMapKind(kind) and self._IsStringableKind(kind.key_kind):
return "Object<%s, %s>" % (recurse_with_nullability(
kind.key_kind), recurse_with_nullability(kind.value_kind))
if mojom.IsMapKind(kind):
return "Map<%s, %s>" % (recurse_with_nullability(
kind.key_kind), recurse_with_nullability(kind.value_kind))
if (mojom.IsAssociatedKind(kind) or mojom.IsPendingRemoteKind(kind)
or mojom.IsPendingReceiverKind(kind)
or mojom.IsPendingAssociatedRemoteKind(kind)
or mojom.IsPendingAssociatedReceiverKind(kind)):
named_kind = kind.kind
else:
named_kind = kind
name = []
qualified = (not for_module) or (self.module is not named_kind.module)
if qualified and named_kind.module:
name.append(named_kind.module.namespace)
if named_kind.parent_kind:
name.append(named_kind.parent_kind.name)
if mojom.IsEnumKind(kind) and named_kind.parent_kind:
name = ".".join(name)
name += "_" + named_kind.name
else:
name.append("" + named_kind.name)
name = ".".join(name)
if for_module:
name = name.replace(".", "_")
if (mojom.IsStructKind(kind) or mojom.IsUnionKind(kind)
or mojom.IsEnumKind(kind)):
return name
if mojom.IsInterfaceKind(kind) or mojom.IsPendingRemoteKind(kind):
return name + "Remote"
if mojom.IsPendingReceiverKind(kind):
return name + "PendingReceiver"
# TODO(calamity): Support associated interfaces properly.
if mojom.IsPendingAssociatedRemoteKind(kind):
return "Object"
# TODO(calamity): Support associated interface requests properly.
if mojom.IsPendingAssociatedReceiverKind(kind):
return "Object"
raise Exception("No valid closure type: %s" % kind)
# Prepend `?` for nullable kinds and `!` for non-nullable kinds. These are
# used by Closure.
if with_nullability:
return ('?' if mojom.IsNullableKind(kind) else '!') + get_type_name(kind)
return get_type_name(kind)
def _LiteClosureType(self, kind):
return self._GetTypeNameForNewBindings(kind,
with_nullability=False,
for_module=False)
def _GetTypeInJsModule(self, kind):
return self._GetTypeNameForNewBindings(kind,
with_nullability=False,
for_module=True)
def _ClosureTypeWithNullability(self, kind):
return ("" if mojom.IsNullableKind(kind) else "!") + self._ClosureType(kind)
def _LiteClosureTypeWithNullability(self, kind):
return self._GetTypeNameForNewBindings(kind,
with_nullability=True,
for_module=False)
def _GetTypeInJsModuleWithNullability(self, kind):
return self._GetTypeNameForNewBindings(kind,
with_nullability=True,
for_module=True)
def _GetFieldTypeForNewBindings(self, kind, for_module=False):
if mojom.IsNullableKind(kind):
return "({}|undefined)".format(
self._GetTypeNameForNewBindings(kind, for_module=for_module))
else:
return "!" + self._GetTypeNameForNewBindings(kind, for_module=for_module)
def _LiteClosureFieldType(self, kind):
return self._GetFieldTypeForNewBindings(kind, for_module=False)
def _GetFieldTypeInJsModule(self, kind):
return self._GetFieldTypeForNewBindings(kind, for_module=True)
def _NamespaceDeclarations(self, namespace):
pieces = namespace.split('.')
declarations = []
declaration = []
for p in pieces:
declaration.append(p)
declarations.append('.'.join(declaration))
return declarations
def _GetNameInJsModule(self, kind):
qualifier = ""
if kind.module is not self.module and kind.module.namespace:
qualifier = kind.module.namespace + '.'
if kind.parent_kind:
qualifier += kind.parent_kind.name + '.'
return (qualifier + kind.name).replace('.', '_')
def _GetImportsForKind(self, kind):
qualified_name = self._GetNameInJsModule(kind)
def make_import(name, suffix=''):
class ImportInfo(object):
def __init__(self, name, alias):
self.name = name
self.alias = alias
return ImportInfo(name + suffix, qualified_name + suffix)
if (mojom.IsEnumKind(kind) or mojom.IsStructKind(kind)
or mojom.IsUnionKind(kind)):
return [make_import(kind.name), make_import(kind.name, 'Spec')]
if mojom.IsInterfaceKind(kind):
return [
make_import(kind.name, 'Remote'),
make_import(kind.name, 'PendingReceiver')
]
assert False, kind.name
def _JavaScriptType(self, kind):
name = []
if kind.module and kind.module.path != self.module.path:
name.append(kind.module.unique_name)
if kind.parent_kind:
name.append(kind.parent_kind.name)
name.append(kind.name)
return ".".join(name)
def _GetSpecType(self, kind, for_module=False):
def get_spec(kind):
if self._IsPrimitiveKind(kind):
return _kind_to_lite_js_type[mojom.EnsureUnnullable(kind)]
if mojom.IsArrayKind(kind):
return "mojo.internal.Array(%s, %s)" % (get_spec(
kind.kind), "true" if mojom.IsNullableKind(kind.kind) else "false")
if mojom.IsMapKind(kind):
return "mojo.internal.Map(%s, %s, %s)" % (
get_spec(kind.key_kind), get_spec(kind.value_kind),
"true" if mojom.IsNullableKind(kind.value_kind) else "false")
if (mojom.IsAssociatedKind(kind) or mojom.IsPendingRemoteKind(kind)
or mojom.IsPendingReceiverKind(kind)
or mojom.IsPendingAssociatedRemoteKind(kind)
or mojom.IsPendingAssociatedReceiverKind(kind)):
named_kind = kind.kind
else:
named_kind = kind
name = []
qualified = (not for_module) or (self.module is not named_kind.module)
if qualified and named_kind.module:
name.append(named_kind.module.namespace)
if named_kind.parent_kind:
parent_name = named_kind.parent_kind.name
if mojom.IsStructKind(named_kind.parent_kind) and not for_module:
parent_name += "Spec"
name.append(parent_name)
name.append(named_kind.name)
name = ".".join(name)
if for_module:
name = name.replace(".", "_")
if (mojom.IsStructKind(kind) or mojom.IsUnionKind(kind)
or mojom.IsEnumKind(kind)):
return "%sSpec.$" % name
if mojom.IsInterfaceKind(kind) or mojom.IsPendingRemoteKind(kind):
return "mojo.internal.InterfaceProxy(%sRemote)" % name
if mojom.IsPendingReceiverKind(kind):
return "mojo.internal.InterfaceRequest(%sPendingReceiver)" % name
if mojom.IsPendingAssociatedRemoteKind(kind):
# TODO(rockot): Implement associated interfaces.
return "mojo.internal.AssociatedInterfaceProxy(%sRemote)" % (name)
if mojom.IsPendingAssociatedReceiverKind(kind):
return "mojo.internal.AssociatedInterfaceRequest(%sPendingReceiver)" % (
name)
return name
return get_spec(kind)
def _LiteJavaScriptType(self, kind):
return self._GetSpecType(kind, for_module=False)
def _GetSpecTypeInJsModule(self, kind):
return self._GetSpecType(kind, for_module=True)
def _LiteJavaScriptImportName(self, kind):
name = []
if kind.parent_kind:
name.append(self._LiteJavaScriptImportName(kind.parent_kind))
elif kind.module:
name.append(kind.module.namespace)
name.append(kind.name)
return '.'.join(name)
def _JavaScriptDefaultValue(self, field):
if field.default:
if mojom.IsStructKind(field.kind):
assert field.default == "default"
return "new %s()" % self._JavaScriptType(field.kind)
return self._ExpressionToText(field.default)
if field.kind in mojom.PRIMITIVES:
return _kind_to_javascript_default_value[field.kind]
if mojom.IsStructKind(field.kind):
return "null"
if mojom.IsUnionKind(field.kind):
return "null"
if mojom.IsArrayKind(field.kind):
return "null"
if mojom.IsMapKind(field.kind):
return "null"
if mojom.IsInterfaceKind(field.kind):
return "new %sPtr()" % self._JavaScriptType(field.kind)
if mojom.IsPendingRemoteKind(field.kind):
return "new %sPtr()" % self._JavaScriptType(field.kind.kind)
if mojom.IsPendingReceiverKind(field.kind):
return "new bindings.InterfaceRequest()"
if mojom.IsPendingAssociatedRemoteKind(field.kind):
return "new associatedBindings.AssociatedInterfacePtrInfo()"
if mojom.IsPendingAssociatedReceiverKind(field.kind):
return "new associatedBindings.AssociatedInterfaceRequest()"
if mojom.IsEnumKind(field.kind):
return "0"
raise Exception("No valid default: %s" % field)
def _GetDefaultValue(self, field, for_module=False):
if field.default:
if mojom.IsStructKind(field.kind):
assert field.default == "default"
return "null"
if ((field.kind == mojom.INT64 or field.kind == mojom.UINT64)
and not isinstance(
field.default,
(mojom.EnumValue, mojom.NamedValue, mojom.BuiltinValue))):
return "BigInt('{}')".format(int(field.default, 0))
return self._ExpressionToTextLite(field.default, for_module=for_module)
if field.kind == mojom.INT64 or field.kind == mojom.UINT64:
return "BigInt(0)"
if field.kind in mojom.PRIMITIVES:
return _kind_to_javascript_default_value[field.kind]
if mojom.IsEnumKind(field.kind):
if field.kind.min_value is not None:
return f'{field.kind.min_value}'
return "0"
return "null"
def _LiteJavaScriptDefaultValue(self, field):
return self._GetDefaultValue(field, for_module=False)
def _DefaultValueInJsModule(self, field):
return self._GetDefaultValue(field, for_module=True)
def _CodecType(self, kind):
if kind in mojom.PRIMITIVES:
return _kind_to_codec_type[mojom.EnsureUnnullable(kind)]
if mojom.IsStructKind(kind):
pointer_type = "NullablePointerTo" if mojom.IsNullableKind(kind) \
else "PointerTo"
return "new codec.%s(%s)" % (pointer_type, self._JavaScriptType(kind))
if mojom.IsUnionKind(kind):
return self._JavaScriptType(kind)
if mojom.IsArrayKind(kind):
array_type = ("NullableArrayOf"
if mojom.IsNullableKind(kind) else "ArrayOf")
array_length = "" if kind.length is None else ", %d" % kind.length
element_type = self._ElementCodecType(kind.kind)
return "new codec.%s(%s%s)" % (array_type, element_type, array_length)
if mojom.IsInterfaceKind(kind):
return "new codec.%s(%sPtr)" % (
"NullableInterface" if mojom.IsNullableKind(kind) else "Interface",
self._JavaScriptType(kind))
if mojom.IsPendingRemoteKind(kind):
return "new codec.%s(%sPtr)" % (
"NullableInterface" if mojom.IsNullableKind(kind) else "Interface",
self._JavaScriptType(kind.kind))
if mojom.IsPendingReceiverKind(kind):
return "codec.%s" % ("NullableInterfaceRequest" if
mojom.IsNullableKind(kind) else "InterfaceRequest")
if mojom.IsPendingAssociatedRemoteKind(kind):
return "codec.%s" % ("NullableAssociatedInterfacePtrInfo"
if mojom.IsNullableKind(kind) else
"AssociatedInterfacePtrInfo")
if mojom.IsPendingAssociatedReceiverKind(kind):
return "codec.%s" % ("NullableAssociatedInterfaceRequest"
if mojom.IsNullableKind(kind) else
"AssociatedInterfaceRequest")
if mojom.IsEnumKind(kind):
return "new codec.Enum(%s)" % self._JavaScriptType(kind)
if mojom.IsMapKind(kind):
map_type = "NullableMapOf" if mojom.IsNullableKind(kind) else "MapOf"
key_type = self._ElementCodecType(kind.key_kind)
value_type = self._ElementCodecType(kind.value_kind)
return "new codec.%s(%s, %s)" % (map_type, key_type, value_type)
raise Exception("No codec type for %s" % kind)
def _ElementCodecType(self, kind):
return ("codec.PackedBool"
if mojom.IsBoolKind(kind) else self._CodecType(kind))
def _JavaScriptDecodeSnippet(self, kind):
if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind)
or mojom.IsAnyInterfaceKind(kind)):
return "decodeStruct(%s)" % self._CodecType(kind)
if mojom.IsStructKind(kind):
return "decodeStructPointer(%s)" % self._JavaScriptType(kind)
if mojom.IsMapKind(kind):
return "decodeMapPointer(%s, %s)" % (self._ElementCodecType(
kind.key_kind), self._ElementCodecType(kind.value_kind))
if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
return "decodeArrayPointer(codec.PackedBool)"
if mojom.IsArrayKind(kind):
return "decodeArrayPointer(%s)" % self._CodecType(kind.kind)
if mojom.IsUnionKind(kind):
return "decodeUnion(%s)" % self._CodecType(kind)
if mojom.IsEnumKind(kind):
return "decodeStruct(%s)" % self._CodecType(kind)
raise Exception("No decode snippet for %s" % kind)
def _JavaScriptEncodeSnippet(self, kind):
if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind)
or mojom.IsAnyInterfaceKind(kind)):
return "encodeStruct(%s, " % self._CodecType(kind)
if mojom.IsUnionKind(kind):
return "encodeStruct(%s, " % self._JavaScriptType(kind)
if mojom.IsStructKind(kind):
return "encodeStructPointer(%s, " % self._JavaScriptType(kind)
if mojom.IsMapKind(kind):
return "encodeMapPointer(%s, %s, " % (self._ElementCodecType(
kind.key_kind), self._ElementCodecType(kind.value_kind))
if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
return "encodeArrayPointer(codec.PackedBool, "
if mojom.IsArrayKind(kind):
return "encodeArrayPointer(%s, " % self._CodecType(kind.kind)
if mojom.IsEnumKind(kind):
return self._JavaScriptEncodeSnippet(mojom.INT32)
raise Exception("No encode snippet for %s" % kind)
def _JavaScriptUnionDecodeSnippet(self, kind):
if mojom.IsUnionKind(kind):
return "decodeStructPointer(%s)" % self._JavaScriptType(kind)
return self._JavaScriptDecodeSnippet(kind)
def _JavaScriptUnionEncodeSnippet(self, kind):
if mojom.IsUnionKind(kind):
return "encodeStructPointer(%s, " % self._JavaScriptType(kind)
return self._JavaScriptEncodeSnippet(kind)
def _JavaScriptNullableParam(self, field):
return "true" if mojom.IsNullableKind(field.kind) else "false"
def _JavaScriptValidateArrayParams(self, field):
nullable = self._JavaScriptNullableParam(field)
element_kind = mojom.EnsureUnnullable(field.kind.kind)
element_size = pack.PackedField.GetSizeForKind(element_kind)
expected_dimension_sizes = GetArrayExpectedDimensionSizes(field.kind)
element_type = self._ElementCodecType(element_kind)
return "%s, %s, %s, %s, 0" % \
(element_size, element_type, nullable,
expected_dimension_sizes)
def _JavaScriptValidateEnumParams(self, field):
return self._JavaScriptType(field.kind)
def _JavaScriptValidateStructParams(self, field):
nullable = self._JavaScriptNullableParam(field)
struct_type = self._JavaScriptType(field.kind)
return "%s, %s" % (struct_type, nullable)
def _JavaScriptValidateUnionParams(self, field):
nullable = self._JavaScriptNullableParam(field)
union_type = self._JavaScriptType(field.kind)
return "%s, %s" % (union_type, nullable)
def _JavaScriptValidateMapParams(self, field):
nullable = self._JavaScriptNullableParam(field)
keys_type = self._ElementCodecType(field.kind.key_kind)
values_kind = field.kind.value_kind
values_type = self._ElementCodecType(values_kind)
values_nullable = "true" if mojom.IsNullableKind(values_kind) else "false"
return "%s, %s, %s, %s" % \
(nullable, keys_type, values_type, values_nullable)
def _JavaScriptSanitizeIdentifier(self, identifier):
if identifier in _js_reserved_keywords:
return identifier + '_'
return identifier
def _ExpressionToText(self, token):
if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
# Both variable and enum constants are constructed like:
# NamespaceUid.Struct[.Enum].CONSTANT_NAME
name = []
if token.module and token.module.path != self.module.path:
name.append(token.module.unique_name)
if token.parent_kind:
name.append(token.parent_kind.name)
if isinstance(token, mojom.EnumValue):
name.append(token.enum.name)
name.append(token.name)
return ".".join(name)
if isinstance(token, mojom.BuiltinValue):
if token.value == "double.INFINITY" or token.value == "float.INFINITY":
return "Infinity"
if token.value == "double.NEGATIVE_INFINITY" or \
token.value == "float.NEGATIVE_INFINITY":
return "-Infinity"
if token.value == "double.NAN" or token.value == "float.NAN":
return "NaN"
return token
def _ExpressionToTextLite(self, token, for_module=False):
if isinstance(token, (mojom.EnumValue, mojom.NamedValue)):
# Generate the following for:
# - Enums: NamespaceUid.Enum.CONSTANT_NAME
# - Struct: NamespaceUid.Struct_CONSTANT_NAME
namespace_components = []
qualified = (not for_module) or (token.module is not self.module)
if token.module and qualified:
namespace_components.append(token.module.namespace)
if token.parent_kind:
namespace_components.append(token.parent_kind.name)
name_prefix = '.'.join(namespace_components)
if for_module:
name_prefix = name_prefix.replace('.', '_')
name = []
if isinstance(token, mojom.EnumValue):
name.append(token.enum.name)
name.append(token.name)
separator = "."
if mojom.IsStructKind(token.parent_kind) or for_module:
separator = "_"
if len(name_prefix) > 0:
return name_prefix + separator + ".".join(name)
return ".".join(name)
return self._ExpressionToText(token)
def _GetConstantValue(self, constant, for_module=False):
assert isinstance(constant, mojom.Constant)
text = self._ExpressionToTextLite(constant.value, for_module=for_module)
if constant.kind == mojom.INT64 or constant.kind == mojom.UINT64:
return "BigInt('{}')".format(int(text, 0))
return text
def _GetConstantValueInJsModule(self, constant):
return self._GetConstantValue(constant, for_module=True)
def _GetJsModuleImports(self, for_webui_module=False):
this_module_path = _GetWebUiModulePath(self.module)
this_module_is_shared = bool(this_module_path
and _IsSharedModulePath(this_module_path))
imports = dict()
def strip_prefix(s, prefix):
if s.startswith(prefix):
return s[len(prefix):]
return s
for spec, kind in self.module.imported_kinds.items():
if for_webui_module:
assert this_module_path is not None
base_path = _GetWebUiModulePath(kind.module)
assert base_path is not None
import_path = '{}{}-webui.js'.format(base_path,
os.path.basename(kind.module.path))
import_module_is_shared = _IsSharedModulePath(import_path)
if this_module_is_shared:
assert import_module_is_shared, \
'Shared WebUI module "{}" cannot depend on non-shared WebUI ' \
'module "{}"'.format(self.module.path, kind.module.path)
# Some Mojo JS files are served from //resources/, but not from
# //resources/mojo/, for example from
# //resources/cr_components/. Need to use absolute paths when
# referring to such files from other modules, so that TypeScript can
# correctly resolve them since they belong to a different ts_library()
# target compared to |this_module_path|.
import_module_is_in_chrome_resources = _IsAbsoluteChromeResourcesPath(
import_path)
use_absolute_path = import_module_is_in_chrome_resources and not \
import_module_is_shared
# Either we're a non-shared resource importing another non-shared
# resource, or we're a shared resource importing another shared
# resource. In both cases, we assume a relative import path will
# suffice.
use_relative_path = not use_absolute_path and \
import_module_is_shared == this_module_is_shared
if use_relative_path:
import_path = urllib.request.pathname2url(
os.path.relpath(
strip_prefix(strip_prefix(import_path, _CHROME_SCHEME_PREFIX),
_SHARED_MODULE_PREFIX),
strip_prefix(
strip_prefix(this_module_path, _CHROME_SCHEME_PREFIX),
_SHARED_MODULE_PREFIX)))
if (not import_path.startswith('.')
and not import_path.startswith('/')):
import_path = './' + import_path
else:
# Import absolute imports from scheme-relative paths.
import_path = strip_prefix(import_path, _CHROME_SCHEME_PREFIX)
else:
import_path = self._GetRelativePath(kind.module.path) + '.m.js'
if import_path not in imports:
imports[import_path] = []
imports[import_path].append(kind)
return imports
def _GetStructsFromMethods(self):
result = []
for interface in self.module.interfaces:
for method in interface.methods:
result.append(method.param_struct)
if method.response_param_struct is not None:
result.append(method.response_param_struct)
return result
def _FuzzHandleName(self, kind):
if mojom.IsPendingReceiverKind(kind):
return '{0}.{1}Request'.format(kind.kind.module.namespace, kind.kind.name)
elif mojom.IsInterfaceKind(kind):
return '{0}.{1}Ptr'.format(kind.module.namespace, kind.name)
elif mojom.IsPendingRemoteKind(kind):
return '{0}.{1}Ptr'.format(kind.kind.module.namespace, kind.kind.name)
elif mojom.IsPendingAssociatedReceiverKind(kind):
return '{0}.{1}AssociatedRequest'.format(kind.kind.module.namespace,
kind.kind.name)
elif mojom.IsPendingAssociatedRemoteKind(kind):
return '{0}.{1}AssociatedPtr'.format(kind.kind.module.namespace,
kind.kind.name)
elif mojom.IsSharedBufferKind(kind):
return 'handle<shared_buffer>'
elif mojom.IsDataPipeConsumerKind(kind):
return 'handle<data_pipe_consumer>'
elif mojom.IsDataPipeProducerKind(kind):
return 'handle<data_pipe_producer>'
elif mojom.IsMessagePipeKind(kind):
return 'handle<message_pipe>'
def _ToJsBoolean(self, value):
if value:
return 'true'
return 'false'
def _IsPrimitiveKind(self, kind):
return kind in mojom.PRIMITIVES
def _PrimitiveToFuzzType(self, kind):
return _primitive_kind_to_fuzz_type[kind]