[{"id":28390,"web_url":"https://patchwork.libcamera.org/comment/28390/","msgid":"<87a5pkszh6.fsf@redhat.com>","date":"2024-01-05T13:04:37","subject":"Re: [libcamera-devel] [PATCH 8/8] utils: ipc: Update mojo","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:\n\n> Update mojo from commit\n>\n> 9be4263648d7d1a04bb78be75df53f56449a5e3a \"Updating trunk VERSION from 6225.0 to 6226.0\"\n>\n> from the Chromium repository.\n\nI don't know which one but when fetching the required components from\nchromium.googlesource.com manually and applying update-mojo.sh, I get the same\ntree.  I also checked it compiles in my environment, where it previously failed\ndue to the Python incompatibilities.  So the change looks all right to me.\nThank you for fixing this.\n\n> The update-mojo.sh script was used for this update.\n>\n> Bug: https://bugs.libcamera.org/show_bug.cgi?id=206\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\nReviewed-by: Milan Zamazal <mzamazal@redhat.com>\n\n> ---\n>  utils/ipc/mojo/README                         |   2 +-\n>  utils/ipc/mojo/public/LICENSE                 |   2 +-\n>  utils/ipc/mojo/public/tools/BUILD.gn          |   8 +-\n>  utils/ipc/mojo/public/tools/bindings/BUILD.gn |  36 +-\n>  .../ipc/mojo/public/tools/bindings/README.md  | 239 +++--\n>  .../public/tools/bindings/checks/__init__.py  |   0\n>  .../bindings/checks/mojom_attributes_check.py | 170 ++++\n>  .../checks/mojom_attributes_check_unittest.py | 194 ++++\n>  .../checks/mojom_definitions_check.py         |  34 +\n>  .../checks/mojom_interface_feature_check.py   |  62 ++\n>  .../mojom_interface_feature_check_unittest.py | 173 ++++\n>  .../checks/mojom_restrictions_check.py        | 102 +++\n>  .../mojom_restrictions_checks_unittest.py     | 254 ++++++\n>  .../chromium_bindings_configuration.gni       |  51 --\n>  .../tools/bindings/compile_typescript.py      |  27 -\n>  .../tools/bindings/concatenate-files.py       |   5 +-\n>  ...concatenate_and_replace_closure_exports.py |  10 +-\n>  .../bindings/format_typemap_generator_args.py |  36 -\n>  .../tools/bindings/gen_data_files_list.py     |   2 +-\n>  .../tools/bindings/generate_type_mappings.py  |   4 +-\n>  .../tools/bindings/minify_with_terser.py      |  47 +\n>  .../ipc/mojo/public/tools/bindings/mojom.gni  | 853 ++++++++++--------\n>  .../bindings/mojom_bindings_generator.py      |  62 +-\n>  .../mojom_bindings_generator_unittest.py      |   6 +-\n>  .../tools/bindings/mojom_types_downgrader.py  | 119 ---\n>  .../tools/bindings/validate_typemap_config.py |   5 +-\n>  utils/ipc/mojo/public/tools/mojom/BUILD.gn    |  18 +\n>  .../mojom/check_stable_mojom_compatibility.py |  69 +-\n>  ...eck_stable_mojom_compatibility_unittest.py |  87 +-\n>  .../mojo/public/tools/mojom/const_unittest.py |   2 +-\n>  .../mojo/public/tools/mojom/enum_unittest.py  |  30 +-\n>  .../public/tools/mojom/feature_unittest.py    |  84 ++\n>  .../mojo/public/tools/mojom/mojom/BUILD.gn    |   3 +-\n>  .../mojo/public/tools/mojom/mojom/error.py    |   2 +-\n>  .../mojo/public/tools/mojom/mojom/fileutil.py |   3 +-\n>  .../tools/mojom/mojom/fileutil_unittest.py    |   7 +-\n>  .../tools/mojom/mojom/generate/check.py       |  26 +\n>  .../mojom/mojom/generate/constant_resolver.py |  93 --\n>  .../tools/mojom/mojom/generate/generator.py   |  11 +-\n>  .../mojom/generate/generator_unittest.py      |   9 +-\n>  .../tools/mojom/mojom/generate/module.py      | 783 +++++++++++-----\n>  .../mojom/mojom/generate/module_unittest.py   |   2 +-\n>  .../public/tools/mojom/mojom/generate/pack.py | 151 +++-\n>  .../mojom/mojom/generate/pack_unittest.py     |  30 +-\n>  .../mojom/mojom/generate/template_expander.py |   2 +-\n>  .../tools/mojom/mojom/generate/translate.py   | 464 +++++++++-\n>  .../mojom/generate/translate_unittest.py      |  82 +-\n>  .../public/tools/mojom/mojom/parse/ast.py     | 145 +--\n>  .../tools/mojom/mojom/parse/ast_unittest.py   |  12 +-\n>  .../mojom/mojom/parse/conditional_features.py |  21 +-\n>  .../parse/conditional_features_unittest.py    | 155 +++-\n>  .../public/tools/mojom/mojom/parse/lexer.py   |   8 +-\n>  .../tools/mojom/mojom/parse/lexer_unittest.py |  10 +-\n>  .../public/tools/mojom/mojom/parse/parser.py  | 108 ++-\n>  .../mojom/mojom/parse/parser_unittest.py      |  39 +-\n>  .../mojo/public/tools/mojom/mojom_parser.py   | 119 ++-\n>  .../tools/mojom/mojom_parser_test_case.py     |   6 +-\n>  .../tools/mojom/mojom_parser_unittest.py      |  31 +-\n>  .../tools/mojom/stable_attribute_unittest.py  |   2 +-\n>  .../mojo/public/tools/mojom/union_unittest.py |  44 +\n>  .../mojom/version_compatibility_unittest.py   |  73 +-\n>  .../public/tools/run_all_python_unittests.py  |   8 +-\n>  utils/ipc/tools/README                        |   2 +-\n>  utils/ipc/tools/diagnosis/crbug_1001171.py    |   2 +-\n>  64 files changed, 3830 insertions(+), 1416 deletions(-)\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/__init__.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py\n>  delete mode 100644 utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n>  delete mode 100644 utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n>  delete mode 100755 utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n>  create mode 100755 utils/ipc/mojo/public/tools/bindings/minify_with_terser.py\n>  delete mode 100755 utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/BUILD.gn\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/feature_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py\n>  delete mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/union_unittest.py\n>\n> diff --git a/utils/ipc/mojo/README b/utils/ipc/mojo/README\n> index d5c24fc3b5cb..961cabd222e3 100644\n> --- a/utils/ipc/mojo/README\n> +++ b/utils/ipc/mojo/README\n> @@ -1,4 +1,4 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>  \n> -Files in this directory are imported from 9c138d992bfc of Chromium. Do not\n> +Files in this directory are imported from 9be4263648d7 of Chromium. Do not\n>  modify them manually.\n> diff --git a/utils/ipc/mojo/public/LICENSE b/utils/ipc/mojo/public/LICENSE\n> index 972bb2edb099..513e8a6a0a1d 100644\n> --- a/utils/ipc/mojo/public/LICENSE\n> +++ b/utils/ipc/mojo/public/LICENSE\n> @@ -1,4 +1,4 @@\n> -// Copyright 2014 The Chromium Authors. All rights reserved.\n> +// Copyright 2014 The Chromium Authors\n>  //\n>  // Redistribution and use in source and binary forms, with or without\n>  // modification, are permitted provided that the following conditions are\n> diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/ipc/mojo/public/tools/BUILD.gn\n> index eb6391a65400..5328a34a3935 100644\n> --- a/utils/ipc/mojo/public/tools/BUILD.gn\n> +++ b/utils/ipc/mojo/public/tools/BUILD.gn\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -10,7 +10,11 @@ group(\"mojo_python_unittests\") {\n>      \"run_all_python_unittests.py\",\n>      \"//testing/scripts/run_isolated_script_test.py\",\n>    ]\n> -  deps = [ \"//mojo/public/tools/mojom/mojom:tests\" ]\n> +  deps = [\n> +    \"//mojo/public/tools/bindings:tests\",\n> +    \"//mojo/public/tools/mojom:tests\",\n> +    \"//mojo/public/tools/mojom/mojom:tests\",\n> +  ]\n>    data_deps = [\n>      \"//testing:test_scripts_shared\",\n>      \"//third_party/catapult/third_party/typ/\",\n> diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/ipc/mojo/public/tools/bindings/BUILD.gn\n> index 3e2425327490..eeca73ea3d56 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn\n> +++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn\n> @@ -1,24 +1,27 @@\n> -# Copyright 2016 The Chromium Authors. All rights reserved.\n> +# Copyright 2016 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import(\"//build/config/python.gni\")\n>  import(\"//mojo/public/tools/bindings/mojom.gni\")\n>  import(\"//third_party/jinja2/jinja2.gni\")\n>  \n> -# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -python2_action(\"precompile_templates\") {\n> +action(\"precompile_templates\") {\n>    sources = mojom_generator_sources\n>    sources += [\n> +    \"$mojom_generator_root/generators/cpp_templates/cpp_macros.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/feature_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/feature_definition.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/interface_feature_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-features.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl\",\n> @@ -26,7 +29,6 @@ python2_action(\"precompile_templates\") {\n>      \"$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl\",\n> -    \"$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module.h.tmpl\",\n> @@ -65,9 +67,6 @@ python2_action(\"precompile_templates\") {\n>      \"$mojom_generator_root/generators/java_templates/struct.java.tmpl\",\n>      \"$mojom_generator_root/generators/java_templates/union.java.tmpl\",\n>      \"$mojom_generator_root/generators/js_templates/enum_definition.tmpl\",\n> -    \"$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl\",\n> -    \"$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl\",\n> -    \"$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl\",\n>      \"$mojom_generator_root/generators/js_templates/fuzzing.tmpl\",\n>      \"$mojom_generator_root/generators/js_templates/interface_definition.tmpl\",\n>      \"$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl\",\n> @@ -93,8 +92,11 @@ python2_action(\"precompile_templates\") {\n>      \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl\",\n>      \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl\",\n>      \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/enum_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/interface_definition.tmpl\",\n>      \"$mojom_generator_root/generators/ts_templates/module_definition.tmpl\",\n> -    \"$mojom_generator_root/generators/ts_templates/mojom.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/struct_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/union_definition.tmpl\",\n>    ]\n>    script = mojom_generator_script\n>  \n> @@ -102,8 +104,8 @@ python2_action(\"precompile_templates\") {\n>    outputs = [\n>      \"$target_gen_dir/cpp_templates.zip\",\n>      \"$target_gen_dir/java_templates.zip\",\n> -    \"$target_gen_dir/mojolpm_templates.zip\",\n>      \"$target_gen_dir/js_templates.zip\",\n> +    \"$target_gen_dir/mojolpm_templates.zip\",\n>      \"$target_gen_dir/ts_templates.zip\",\n>    ]\n>    args = [\n> @@ -113,3 +115,17 @@ python2_action(\"precompile_templates\") {\n>      \"precompile\",\n>    ]\n>  }\n> +\n> +group(\"tests\") {\n> +  data = [\n> +    mojom_generator_script,\n> +    \"checks/mojom_attributes_check_unittest.py\",\n> +    \"checks/mojom_interface_feature_check_unittest.py\",\n> +    \"checks/mojom_restrictions_checks_unittest.py\",\n> +    \"mojom_bindings_generator_unittest.py\",\n> +    \"//tools/diagnosis/crbug_1001171.py\",\n> +    \"//third_party/markupsafe/\",\n> +  ]\n> +  data += mojom_generator_sources\n> +  data += jinja2_sources\n> +}\n> diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/ipc/mojo/public/tools/bindings/README.md\n> index 438824502280..b27b2d01b3fc 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/README.md\n> +++ b/utils/ipc/mojo/public/tools/bindings/README.md\n> @@ -96,7 +96,7 @@ for message parameters.\n>  | `string`                      | UTF-8 encoded string.\n>  | `array<T>`                    | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.\n>  | `array<T, N>`                 | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.\n> -| `map<S, T>`                   | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.\n> +| `map<S, T>`                   | Associated array mapping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.\n>  | `handle`                      | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.\n>  | `handle<message_pipe>`        | Generic message pipe handle.\n>  | `handle<shared_buffer>`       | Shared buffer handle.\n> @@ -188,8 +188,8 @@ struct StringPair {\n>  };\n>  \n>  enum AnEnum {\n> -  YES,\n> -  NO\n> +  kYes,\n> +  kNo\n>  };\n>  \n>  interface SampleInterface {\n> @@ -209,7 +209,7 @@ struct AllTheThings {\n>    uint64 unsigned_64bit_value;\n>    float float_value_32bit;\n>    double float_value_64bit;\n> -  AnEnum enum_value = AnEnum.YES;\n> +  AnEnum enum_value = AnEnum.kYes;\n>  \n>    // Strings may be nullable.\n>    string? maybe_a_string_maybe_not;\n> @@ -300,14 +300,14 @@ within a module or nested within the namespace of some struct or interface:\n>  module business.mojom;\n>  \n>  enum Department {\n> -  SALES = 0,\n> -  DEV,\n> +  kSales = 0,\n> +  kDev,\n>  };\n>  \n>  struct Employee {\n>    enum Type {\n> -    FULL_TIME,\n> -    PART_TIME,\n> +    kFullTime,\n> +    kPartTime,\n>    };\n>  \n>    Type type;\n> @@ -315,6 +315,9 @@ struct Employee {\n>  };\n>  ```\n>  \n> +C++ constant-style enum value names are preferred as specified in the\n> +[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names).\n> +\n>  Similar to C-style enums, individual values may be explicitly assigned within an\n>  enum definition. By default, values are based at zero and increment by\n>  1 sequentially.\n> @@ -336,8 +339,8 @@ struct Employee {\n>    const uint64 kInvalidId = 0;\n>  \n>    enum Type {\n> -    FULL_TIME,\n> -    PART_TIME,\n> +    kFullTime,\n> +    kPartTime,\n>    };\n>  \n>    uint64 id = kInvalidId;\n> @@ -348,6 +351,37 @@ struct Employee {\n>  The effect of nested definitions on generated bindings varies depending on the\n>  target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages).\n>  \n> +### Features\n> +\n> +Features can be declared with a `name` and `default_state` and can be attached\n> +in mojo to interfaces or methods using the `RuntimeFeature` attribute. If the\n> +feature is disabled at runtime, the method will crash and the interface will\n> +refuse to be bound / instantiated. Features cannot be serialized to be sent over\n> +IPC at this time.\n> +\n> +```\n> +module experimental.mojom;\n> +\n> +feature kUseElevators {\n> +  const string name = \"UseElevators\";\n> +  const bool default_state = false;\n> +}\n> +\n> +[RuntimeFeature=kUseElevators]\n> +interface Elevator {\n> +  // This interface cannot be bound or called if the feature is disabled.\n> +}\n> +\n> +interface Building {\n> +  // This method cannot be called if the feature is disabled.\n> +  [RuntimeFeature=kUseElevators]\n> +  CallElevator(int floor);\n> +\n> +  // This method can be called.\n> +  RingDoorbell(int volume);\n> +}\n> +```\n> +\n>  ### Interfaces\n>  \n>  An **interface** is a logical bundle of parameterized request messages. Each\n> @@ -396,20 +430,33 @@ interesting attributes supported today.\n>    extreme caution, because it can lead to deadlocks otherwise.\n>  \n>  * **`[Default]`**:\n> -  The `Default` attribute may be used to specify an enumerator value that\n> -  will be used if an `Extensible` enumeration does not deserialize to a known\n> -  value on the receiver side, i.e. the sender is using a newer version of the\n> -  enum. This allows unknown values to be mapped to a well-defined value that can\n> -  be appropriately handled.\n> +  The `Default` attribute may be used to specify an enumerator value or union\n> +  field that will be used if an `Extensible` enumeration or union does not\n> +  deserialize to a known value on the receiver side, i.e. the sender is using a\n> +  newer version of the enum or union. This allows unknown values to be mapped to\n> +  a well-defined value that can be appropriately handled.\n> +\n> +  Note: The `Default` field for a union must be of nullable or integral type.\n> +  When a union is defaulted to this field, the field takes on the default value\n> +  for its type: null for nullable types, and zero/false for integral types.\n>  \n>  * **`[Extensible]`**:\n> -  The `Extensible` attribute may be specified for any enum definition. This\n> -  essentially disables builtin range validation when receiving values of the\n> -  enum type in a message, allowing older bindings to tolerate unrecognized\n> -  values from newer versions of the enum.\n> +  The `Extensible` attribute may be specified for any enum or union definition.\n> +  For enums, this essentially disables builtin range validation when receiving\n> +  values of the enum type in a message, allowing older bindings to tolerate\n> +  unrecognized values from newer versions of the enum.\n>  \n> -  Note: in the future, an `Extensible` enumeration will require that a `Default`\n> -  enumerator value also be specified.\n> +  If an enum value within an extensible enum definition is affixed with the\n> +  `Default` attribute, out-of-range values for the enum will deserialize to that\n> +  default value. Only one enum value may be designated as the `Default`.\n> +\n> +  Similarly, a union marked `Extensible` will deserialize to its `Default` field\n> +  when an unrecognized field is received. Extensible unions MUST specify exactly\n> +  one `Default` field, and the field must be of nullable or integral type. When\n> +  defaulted to this field, the value is always null/zero/false as appropriate.\n> +\n> +  An `Extensible` enumeration REQUIRES that a `Default` value be specified,\n> +  so all new extensible enums should specify one.\n>  \n>  * **`[Native]`**:\n>    The `Native` attribute may be specified for an empty struct declaration to\n> @@ -422,7 +469,10 @@ interesting attributes supported today.\n>  * **`[MinVersion=N]`**:\n>    The `MinVersion` attribute is used to specify the version at which a given\n>    field, enum value, interface method, or method parameter was introduced.\n> -  See [Versioning](#Versioning) for more details.\n> +  See [Versioning](#Versioning) for more details. `MinVersion` does not apply\n> +  to interfaces, structs or enums, but to the fields of those types.\n> +  `MinVersion` is not a module-global value, but it is ok to pretend it is by\n> +  skipping versions when adding fields or parameters.\n>  \n>  * **`[Stable]`**:\n>    The `Stable` attribute specifies that a given mojom type or interface\n> @@ -442,13 +492,73 @@ interesting attributes supported today.\n>    string representation as specified by RFC 4122. New UUIDs can be generated\n>    with common tools such as `uuidgen`.\n>  \n> +* **`[RuntimeFeature=feature]`**\n> +  The `RuntimeFeature` attribute should reference a mojo `feature`. If this\n> +  feature is enabled (e.g. using `--enable-features={feature.name}`) then the\n> +  interface behaves entirely as expected. If the feature is not enabled the\n> +  interface cannot be bound to a concrete receiver or remote - attempting to do\n> +  so will result in the receiver or remote being reset() to an unbound state.\n> +  Note that this is a different concept to the build-time `EnableIf` directive.\n> +  `RuntimeFeature` is currently only supported for C++ bindings and has no\n> +  effect for, say, Java or TypeScript bindings (see https://crbug.com/1278253).\n> +\n>  * **`[EnableIf=value]`**:\n>    The `EnableIf` attribute is used to conditionally enable definitions when the\n>    mojom is parsed. If the `mojom` target in the GN file does not include the\n>    matching `value` in the list of `enabled_features`, the definition will be\n>    disabled. This is useful for mojom definitions that only make sense on one\n>    platform. Note that the `EnableIf` attribute can only be set once per\n> -  definition.\n> +  definition and cannot be set at the same time as `EnableIfNot`. Also be aware\n> +  that only one condition can be tested, `EnableIf=value,xyz` introduces a new\n> +  `xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends\n> +  only on the feature `value`. Complex conditions can be introduced via\n> +  enabled_features in `build.gn` files.\n> +\n> +* **`[EnableIfNot=value]`**:\n> +  The `EnableIfNot` attribute is used to conditionally enable definitions when\n> +  the mojom is parsed. If the `mojom` target in the GN file includes the\n> +  matching `value` in the list of `enabled_features`, the definition will be\n> +  disabled. This is useful for mojom definitions that only make sense on all but\n> +  one platform. Note that the `EnableIfNot` attribute can only be set once per\n> +  definition and cannot be set at the same time as `EnableIf`.\n> +\n> +* **`[ServiceSandbox=value]`**:\n> +  The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a\n> +  service hosting an implementation of interface will be launched in. This only\n> +  applies to `C++` bindings. `value` should match a constant defined in an\n> +  imported `sandbox.mojom.Sandbox` enum (for Chromium this is\n> +  `//sandbox/policy/mojom/sandbox.mojom`), such as `kService`.\n> +\n> +* **`[RequireContext=enum]`**:\n> +  The `RequireContext` attribute is used in Chromium to tag interfaces that\n> +  should be passed (as remotes or receivers) only to privileged process\n> +  contexts. The process context must be an enum that is imported into the\n> +  mojom that defines the tagged interface. `RequireContext` may be used in\n> +  future to DCHECK or CHECK if remotes are made available in contexts that\n> +  conflict with the one provided in the interface definition. Process contexts\n> +  are not the same as the sandbox a process is running in, but will reflect\n> +  the set of capabilities provided to the service.\n> +\n> +* **`[AllowedContext=enum]`**:\n> +  The `AllowedContext` attribute is used in Chromium to tag methods that pass\n> +  remotes or receivers of interfaces that are marked with a `RequireContext`\n> +  attribute. The enum provided on the method must be equal or better (lower\n> +  numerically) than the one required on the interface being passed. At present\n> +  failing to specify an adequate `AllowedContext` value will cause mojom\n> +  generation to fail at compile time. In future DCHECKs or CHECKs might be\n> +  added to enforce that method is only called from a process context that meets\n> +  the given `AllowedContext` value. The enum must of the same type as that\n> +  specified in the interface's `RequireContext` attribute. Adding an\n> +  `AllowedContext` attribute to a method is a strong indication that you need\n> +   a detailed security review of your design - please reach out to the security\n> +   team.\n> +\n> +* **`[SupportsUrgent]`**:\n> +  The `SupportsUrgent` attribute is used in conjunction with\n> +  `mojo::UrgentMessageScope` in Chromium to tag messages as having high\n> +  priority. The IPC layer notifies the underlying scheduler upon both receiving\n> +  and processing an urgent message. At present, this attribute only affects\n> +  channel associated messages in the renderer process.\n>  \n>  ## Generated Code For Target Languages\n>  \n> @@ -495,9 +605,9 @@ values. For example if a Mojom declares the enum:\n>  \n>  ``` cpp\n>  enum AdvancedBoolean {\n> -  TRUE = 0,\n> -  FALSE = 1,\n> -  FILE_NOT_FOUND = 2,\n> +  kTrue = 0,\n> +  kFalse = 1,\n> +  kFileNotFound = 2,\n>  };\n>  ```\n>  \n> @@ -550,10 +660,16 @@ See the documentation for\n>  \n>  *** note\n>  **NOTE:** You don't need to worry about versioning if you don't care about\n> -backwards compatibility. Specifically, all parts of Chrome are updated\n> -atomically today and there is not yet any possibility of any two Chrome\n> -processes communicating with two different versions of any given Mojom\n> -interface.\n> +backwards compatibility. Today, all parts of the Chrome browser are\n> +updated atomically and there is not yet any possibility of any two\n> +Chrome processes communicating with two different versions of any given Mojom\n> +interface. On Chrome OS, there are several places where versioning is required.\n> +For example,\n> +[ARC++](https://developer.android.com/chrome-os/intro)\n> +uses versioned mojo to send IPC to the Android container.\n> +Likewise, the\n> +[Lacros](/docs/lacros.md)\n> +browser uses versioned mojo to talk to the ash system UI.\n>  ***\n>  \n>  Services extend their interfaces to support new features over time, and clients\n> @@ -593,8 +709,8 @@ struct Employee {\n>  \n>  *** note\n>  **NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be\n> -optional (nullable). See [Primitive Types](#Primitive-Types) for details on\n> -nullable values.\n> +optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for\n> +details on nullable values.\n>  ***\n>  \n>  By default, fields belong to version 0. New fields must be appended to the\n> @@ -624,10 +740,10 @@ the following hard constraints:\n>  * For any given struct or interface, if any field or method explicitly specifies\n>      an ordinal value, all fields or methods must explicitly specify an ordinal\n>      value.\n> -* For an *N*-field struct or *N*-method interface, the set of explicitly\n> -    assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces\n> -    should include placeholder methods to fill the ordinal positions of removed\n> -    methods (for example \"Unused_Message_7@7()\" or \"RemovedMessage@42()\", etc).\n> +* For an *N*-field struct, the set of explicitly assigned ordinal values must be\n> +    limited to the range *[0, N-1]*. Structs should include placeholder fields\n> +    to fill the ordinal positions of removed fields (for example \"Unused_Field\"\n> +    or \"RemovedField\", etc).\n>  \n>  You may reorder fields, but you must ensure that the ordinal values of existing\n>  fields remain unchanged. For example, the following struct remains\n> @@ -652,6 +768,24 @@ There are two dimensions on which an interface can be extended\n>      that the version number is scoped to the whole interface rather than to any\n>      individual parameter list.\n>  \n> +``` cpp\n> +// Old version:\n> +interface HumanResourceDatabase {\n> +  QueryEmployee(uint64 id) => (Employee? employee);\n> +};\n> +\n> +// New version:\n> +interface HumanResourceDatabase {\n> +  QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)\n> +      => (Employee? employee,\n> +          [MinVersion=1] array<uint8>? finger_print);\n> +};\n> +```\n> +\n> +Similar to [versioned structs](#Versioned-Structs), when you pass the parameter\n> +list of a request or response method to a destination using an older version of\n> +an interface, unrecognized fields are silently discarded.\n> +\n>      Please note that adding a response to a message which did not previously\n>      expect a response is a not a backwards-compatible change.\n>  \n> @@ -664,17 +798,12 @@ For example:\n>  ``` cpp\n>  // Old version:\n>  interface HumanResourceDatabase {\n> -  AddEmployee(Employee employee) => (bool success);\n>    QueryEmployee(uint64 id) => (Employee? employee);\n>  };\n>  \n>  // New version:\n>  interface HumanResourceDatabase {\n> -  AddEmployee(Employee employee) => (bool success);\n> -\n> -  QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)\n> -      => (Employee? employee,\n> -          [MinVersion=1] array<uint8>? finger_print);\n> +  QueryEmployee(uint64 id) => (Employee? employee);\n>  \n>    [MinVersion=1]\n>    AttachFingerPrint(uint64 id, array<uint8> finger_print)\n> @@ -682,10 +811,7 @@ interface HumanResourceDatabase {\n>  };\n>  ```\n>  \n> -Similar to [versioned structs](#Versioned-Structs), when you pass the parameter\n> -list of a request or response method to a destination using an older version of\n> -an interface, unrecognized fields are silently discarded. However, if the method\n> -call itself is not recognized, it is considered a validation error and the\n> +If a method call is not recognized, it is considered a validation error and the\n>  receiver will close its end of the interface pipe. For example, if a client on\n>  version 1 of the above interface sends an `AttachFingerPrint` request to an\n>  implementation of version 0, the client will be disconnected.\n> @@ -712,8 +838,8 @@ If you want an enum to be extensible in the future, you can apply the\n>  ``` cpp\n>  [Extensible]\n>  enum Department {\n> -  SALES,\n> -  DEV,\n> +  kSales,\n> +  kDev,\n>  };\n>  ```\n>  \n> @@ -722,9 +848,9 @@ And later you can extend this enum without breaking backwards compatibility:\n>  ``` cpp\n>  [Extensible]\n>  enum Department {\n> -  SALES,\n> -  DEV,\n> -  [MinVersion=1] RESEARCH,\n> +  kSales,\n> +  kDev,\n> +  [MinVersion=1] kResearch,\n>  };\n>  ```\n>  \n> @@ -782,7 +908,7 @@ Statement = ModuleStatement | ImportStatement | Definition\n>  \n>  ModuleStatement = AttributeSection \"module\" Identifier \";\"\n>  ImportStatement = \"import\" StringLiteral \";\"\n> -Definition = Struct Union Interface Enum Const\n> +Definition = Struct Union Interface Enum Feature Const\n>  \n>  AttributeSection = <empty> | \"[\" AttributeList \"]\"\n>  AttributeList = <empty> | NonEmptyAttributeList\n> @@ -809,7 +935,7 @@ InterfaceBody = <empty>\n>                | InterfaceBody Const\n>                | InterfaceBody Enum\n>                | InterfaceBody Method\n> -Method = AttributeSection Name Ordinal \"(\" ParamterList \")\" Response \";\"\n> +Method = AttributeSection Name Ordinal \"(\" ParameterList \")\" Response \";\"\n>  ParameterList = <empty> | NonEmptyParameterList\n>  NonEmptyParameterList = Parameter\n>                        | Parameter \",\" NonEmptyParameterList\n> @@ -847,6 +973,13 @@ EnumValue = AttributeSection Name\n>            | AttributeSection Name \"=\" Integer\n>            | AttributeSection Name \"=\" Identifier\n>  \n> +; Note: `feature` is a weak keyword and can appear as, say, a struct field name.\n> +Feature = AttributeSection \"feature\" Name \"{\" FeatureBody \"}\" \";\"\n> +       | AttributeSection \"feature\" Name \";\"\n> +FeatureBody = <empty>\n> +           | FeatureBody FeatureField\n> +FeatureField = AttributeSection TypeSpec Name Default \";\"\n> +\n>  Const = \"const\" TypeSpec Name \"=\" Constant \";\"\n>  \n>  Constant = Literal | Identifier \";\"\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py b/utils/ipc/mojo/public/tools/bindings/checks/__init__.py\n> new file mode 100644\n> index 000000000000..e69de29bb2d1\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py\n> new file mode 100644\n> index 000000000000..e6e4f2c9392b\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py\n> @@ -0,0 +1,170 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Validate mojo attributes are allowed in Chrome before generation.\"\"\"\n> +\n> +import mojom.generate.check as check\n> +import mojom.generate.module as module\n> +\n> +_COMMON_ATTRIBUTES = {\n> +    'EnableIf',\n> +    'EnableIfNot',\n> +}\n> +\n> +# For struct, union & parameter lists.\n> +_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'MinVersion',\n> +    'RenamedFrom',\n> +}\n> +\n> +# Note: `Default`` goes on the default _value_, not on the enum.\n> +# Note: [Stable] without [Extensible] is not allowed.\n> +_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'Extensible',\n> +    'Native',\n> +    'Stable',\n> +    'RenamedFrom',\n> +    'Uuid',\n> +}\n> +\n> +# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal.\n> +_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'Default',\n> +    'MinVersion',\n> +}\n> +\n> +_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'RenamedFrom',\n> +    'RequireContext',\n> +    'RuntimeFeature',\n> +    'ServiceSandbox',\n> +    'Stable',\n> +    'Uuid',\n> +}\n> +\n> +_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'AllowedContext',\n> +    'MinVersion',\n> +    'NoInterrupt',\n> +    'RuntimeFeature',\n> +    'SupportsUrgent',\n> +    'Sync',\n> +    'UnlimitedSize',\n> +}\n> +\n> +_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'JavaConstantsClassName',\n> +    'JavaPackage',\n> +}\n> +\n> +_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES\n> +\n> +_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'CustomSerializer',\n> +    'JavaClassName',\n> +    'Native',\n> +    'Stable',\n> +    'RenamedFrom',\n> +    'Uuid',\n> +}\n> +\n> +_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES\n> +\n> +_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'Extensible',\n> +    'Stable',\n> +    'RenamedFrom',\n> +    'Uuid',\n> +}\n> +\n> +_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | {\n> +    'Default',\n> +}\n> +\n> +# TODO(https://crbug.com/1193875) empty this set and remove the allowlist.\n> +_STABLE_ONLY_ALLOWLISTED_ENUMS = {\n> +    'crosapi.mojom.OptionalBool',\n> +    'crosapi.mojom.TriState',\n> +}\n> +\n> +\n> +class Check(check.Check):\n> +  def __init__(self, *args, **kwargs):\n> +    super(Check, self).__init__(*args, **kwargs)\n> +\n> +  def _Respell(self, allowed, attribute):\n> +    for a in allowed:\n> +      if a.lower() == attribute.lower():\n> +        return f\" - Did you mean: {a}?\"\n> +    return \"\"\n> +\n> +  def _CheckAttributes(self, context, allowed, attributes):\n> +    if not attributes:\n> +      return\n> +    for attribute in attributes:\n> +      if not attribute in allowed:\n> +        # Is there a close misspelling?\n> +        hint = self._Respell(allowed, attribute)\n> +        raise check.CheckException(\n> +            self.module,\n> +            f\"attribute {attribute} not allowed on {context}{hint}\")\n> +\n> +  def _CheckEnumAttributes(self, enum):\n> +    if enum.attributes:\n> +      self._CheckAttributes(\"enum\", _ENUM_ATTRIBUTES, enum.attributes)\n> +      if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes:\n> +        full_name = f\"{self.module.mojom_namespace}.{enum.mojom_name}\"\n> +        if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS:\n> +          raise check.CheckException(\n> +              self.module,\n> +              f\"[Extensible] required on [Stable] enum {full_name}\")\n> +    for enumval in enum.fields:\n> +      self._CheckAttributes(\"enum value\", _ENUMVAL_ATTRIBUTES,\n> +                            enumval.attributes)\n> +\n> +  def _CheckInterfaceAttributes(self, interface):\n> +    self._CheckAttributes(\"interface\", _INTERFACE_ATTRIBUTES,\n> +                          interface.attributes)\n> +    for method in interface.methods:\n> +      self._CheckAttributes(\"method\", _METHOD_ATTRIBUTES, method.attributes)\n> +      for param in method.parameters:\n> +        self._CheckAttributes(\"parameter\", _PARAMETER_ATTRIBUTES,\n> +                              param.attributes)\n> +      if method.response_parameters:\n> +        for param in method.response_parameters:\n> +          self._CheckAttributes(\"parameter\", _PARAMETER_ATTRIBUTES,\n> +                                param.attributes)\n> +    for enum in interface.enums:\n> +      self._CheckEnumAttributes(enum)\n> +\n> +  def _CheckModuleAttributes(self):\n> +    self._CheckAttributes(\"module\", _MODULE_ATTRIBUTES, self.module.attributes)\n> +\n> +  def _CheckStructAttributes(self, struct):\n> +    self._CheckAttributes(\"struct\", _STRUCT_ATTRIBUTES, struct.attributes)\n> +    for field in struct.fields:\n> +      self._CheckAttributes(\"struct field\", _STRUCT_FIELD_ATTRIBUTES,\n> +                            field.attributes)\n> +    for enum in struct.enums:\n> +      self._CheckEnumAttributes(enum)\n> +\n> +  def _CheckUnionAttributes(self, union):\n> +    self._CheckAttributes(\"union\", _UNION_ATTRIBUTES, union.attributes)\n> +    for field in union.fields:\n> +      self._CheckAttributes(\"union field\", _UNION_FIELD_ATTRIBUTES,\n> +                            field.attributes)\n> +\n> +  def CheckModule(self):\n> +    \"\"\"Note that duplicate attributes are forbidden at the parse phase.\n> +    We also do not need to look at the types of any parameters, as they will be\n> +    checked where they are defined. Consts do not have attributes so can be\n> +    skipped.\"\"\"\n> +    self._CheckModuleAttributes()\n> +    for interface in self.module.interfaces:\n> +      self._CheckInterfaceAttributes(interface)\n> +    for enum in self.module.enums:\n> +      self._CheckEnumAttributes(enum)\n> +    for struct in self.module.structs:\n> +      self._CheckStructAttributes(struct)\n> +    for union in self.module.unions:\n> +      self._CheckUnionAttributes(union)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py\n> new file mode 100644\n> index 000000000000..f1a50a4ab508\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py\n> @@ -0,0 +1,194 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import unittest\n> +\n> +import mojom.generate.check as check\n> +from mojom_bindings_generator import LoadChecks, _Generate\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class FakeArgs:\n> +  \"\"\"Fakes args to _Generate - intention is to do just enough to run checks\"\"\"\n> +\n> +  def __init__(self, tester, files=None):\n> +    \"\"\" `tester` is MojomParserTestCase for paths.\n> +        `files` will have tester path added.\"\"\"\n> +    self.checks_string = 'attributes'\n> +    self.depth = tester.GetPath('')\n> +    self.filelist = None\n> +    self.filename = [tester.GetPath(x) for x in files]\n> +    self.gen_directories = tester.GetPath('gen')\n> +    self.generators_string = ''\n> +    self.import_directories = []\n> +    self.output_dir = tester.GetPath('out')\n> +    self.scrambled_message_id_salt_paths = None\n> +    self.typemaps = []\n> +    self.variant = 'none'\n> +\n> +\n> +class MojoBindingsCheckTest(MojomParserTestCase):\n> +  def _ParseAndGenerate(self, mojoms):\n> +    self.ParseMojoms(mojoms)\n> +    args = FakeArgs(self, files=mojoms)\n> +    _Generate(args, {})\n> +\n> +  def _testValid(self, filename, content):\n> +    self.WriteFile(filename, content)\n> +    self._ParseAndGenerate([filename])\n> +\n> +  def _testThrows(self, filename, content, regexp):\n> +    mojoms = []\n> +    self.WriteFile(filename, content)\n> +    mojoms.append(filename)\n> +    with self.assertRaisesRegexp(check.CheckException, regexp):\n> +      self._ParseAndGenerate(mojoms)\n> +\n> +  def testLoads(self):\n> +    \"\"\"Validate that the check is registered under the expected name.\"\"\"\n> +    check_modules = LoadChecks('attributes')\n> +    self.assertTrue(check_modules['attributes'])\n> +\n> +  def testNoAnnotations(self):\n> +    # Undecorated mojom should be fine.\n> +    self._testValid(\n> +        \"a.mojom\", \"\"\"\n> +      module a;\n> +      struct Bar { int32 a; };\n> +      enum Hello { kValue };\n> +      union Thingy { Bar b; Hello hi; };\n> +      interface Foo {\n> +        Foo(int32 a, Hello hi, Thingy t) => (Bar b);\n> +      };\n> +    \"\"\")\n> +\n> +  def testValidAnnotations(self):\n> +    # Obviously this is meaningless and won't generate, but it should pass\n> +    # the attribute check's validation.\n> +    self._testValid(\n> +        \"a.mojom\", \"\"\"\n> +      [JavaConstantsClassName=\"FakeClass\",JavaPackage=\"org.chromium.Fake\"]\n> +      module a;\n> +      [Stable, Extensible]\n> +      enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 };\n> +      [Native]\n> +      enum NativeEnum {};\n> +      [Stable,Extensible]\n> +      union Thingy { Bar b; [Default]int32 c; Hello hi; };\n> +\n> +      [Stable,RenamedFrom=\"module.other.Foo\",\n> +       Uuid=\"4C178401-4B07-4C2E-9255-5401A943D0C7\"]\n> +      struct Structure { Hello hi; };\n> +\n> +      [ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable,\n> +       Uuid=\"2F17D7DD-865A-4B1C-9394-9C94E035E82F\"]\n> +      interface Foo {\n> +        [AllowedContext=Hello.kValue]\n> +        Foo@0(int32 a) => (int32 b);\n> +        [MinVersion=2,Sync,UnlimitedSize,NoInterrupt]\n> +        Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c);\n> +      };\n> +\n> +      [RuntimeFeature=test.mojom.FeatureName]\n> +      interface FooFeatureControlled {};\n> +\n> +      interface FooMethodFeatureControlled {\n> +        [RuntimeFeature=test.mojom.FeatureName]\n> +        MethodWithFeature() => (bool c);\n> +      };\n> +    \"\"\")\n> +\n> +  def testWrongModuleStable(self):\n> +    contents = \"\"\"\n> +      // err: module cannot be Stable\n> +      [Stable]\n> +      module a;\n> +      enum Hello { kValue, kValue2, kValue3 };\n> +      enum NativeEnum {};\n> +      struct Structure { Hello hi; };\n> +\n> +      interface Foo {\n> +        Foo(int32 a) => (int32 b);\n> +        Bar(int32 b, Structure? s) => (bool c);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'attribute Stable not allowed on module')\n> +\n> +  def testWrongEnumDefault(self):\n> +    contents = \"\"\"\n> +      module a;\n> +      // err: default should go on EnumValue not Enum.\n> +      [Default=kValue]\n> +      enum Hello { kValue, kValue2, kValue3 };\n> +      enum NativeEnum {};\n> +      struct Structure { Hello hi; };\n> +\n> +      interface Foo {\n> +        Foo(int32 a) => (int32 b);\n> +        Bar(int32 b, Structure? s) => (bool c);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'attribute Default not allowed on enum')\n> +\n> +  def testWrongStructMinVersion(self):\n> +    contents = \"\"\"\n> +      module a;\n> +      enum Hello { kValue, kValue2, kValue3 };\n> +      enum NativeEnum {};\n> +      // err: struct cannot have MinVersion.\n> +      [MinVersion=2]\n> +      struct Structure { Hello hi; };\n> +\n> +      interface Foo {\n> +        Foo(int32 a) => (int32 b);\n> +        Bar(int32 b, Structure? s) => (bool c);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'attribute MinVersion not allowed on struct')\n> +\n> +  def testWrongMethodRequireContext(self):\n> +    contents = \"\"\"\n> +      module a;\n> +      enum Hello { kValue, kValue2, kValue3 };\n> +      enum NativeEnum {};\n> +      struct Structure { Hello hi; };\n> +\n> +      interface Foo {\n> +        // err: RequireContext is for interfaces.\n> +        [RequireContext=Hello.kValue]\n> +        Foo(int32 a) => (int32 b);\n> +        Bar(int32 b, Structure? s) => (bool c);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'RequireContext not allowed on method')\n> +\n> +  def testWrongMethodRequireContext(self):\n> +    # crbug.com/1230122\n> +    contents = \"\"\"\n> +      module a;\n> +      interface Foo {\n> +        // err: sync not Sync.\n> +        [sync]\n> +        Foo(int32 a) => (int32 b);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'attribute sync not allowed.*Did you mean: Sync')\n> +\n> +  def testStableExtensibleEnum(self):\n> +    # crbug.com/1193875\n> +    contents = \"\"\"\n> +      module a;\n> +      [Stable]\n> +      enum Foo {\n> +        kDefaultVal,\n> +        kOtherVal = 2,\n> +      };\n> +    \"\"\"\n> +    self._testThrows('a.mojom', contents,\n> +                     'Extensible.*?required.*?Stable.*?enum')\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py\n> new file mode 100644\n> index 000000000000..702d41c30fc1\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py\n> @@ -0,0 +1,34 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Ensure no duplicate type definitions before generation.\"\"\"\n> +\n> +import mojom.generate.check as check\n> +import mojom.generate.module as module\n> +\n> +\n> +class Check(check.Check):\n> +  def __init__(self, *args, **kwargs):\n> +    super(Check, self).__init__(*args, **kwargs)\n> +\n> +  def CheckModule(self):\n> +    kinds = dict()\n> +    for module in self.module.imports:\n> +      for kind in module.enums + module.structs + module.unions:\n> +        kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'\n> +        if kind_name in kinds:\n> +          previous_module = kinds[kind_name]\n> +          if previous_module.path != module.path:\n> +            raise check.CheckException(\n> +                self.module, f\"multiple-definition for type {kind_name}\" +\n> +                f\"(defined in both {previous_module} and {module})\")\n> +        kinds[kind_name] = kind.module\n> +\n> +    for kind in self.module.enums + self.module.structs + self.module.unions:\n> +      kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'\n> +      if kind_name in kinds:\n> +        previous_module = kinds[kind_name]\n> +        raise check.CheckException(\n> +            self.module, f\"multiple-definition for type {kind_name}\" +\n> +            f\"(previous definition in {previous_module})\")\n> +    return True\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py\n> new file mode 100644\n> index 000000000000..07f51a64feaf\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py\n> @@ -0,0 +1,62 @@\n> +# Copyright 2023 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Validate mojo runtime feature guarded interfaces are nullable.\"\"\"\n> +\n> +import mojom.generate.check as check\n> +import mojom.generate.module as module\n> +\n> +\n> +class Check(check.Check):\n> +  def __init__(self, *args, **kwargs):\n> +    super(Check, self).__init__(*args, **kwargs)\n> +\n> +  # `param` is an Interface of some sort.\n> +  def _CheckNonNullableFeatureGuardedInterface(self, kind):\n> +    # Only need to validate interface if it has a RuntimeFeature\n> +    if not kind.kind.runtime_feature:\n> +      return\n> +    # Nullable (optional) is ok as the interface expects they might not be sent.\n> +    if kind.is_nullable:\n> +      return\n> +    interface = kind.kind.mojom_name\n> +    raise check.CheckException(\n> +        self.module,\n> +        f\"interface {interface} has a RuntimeFeature but is not nullable\")\n> +\n> +  # `param` can be a lot of things so check if it is a remote/receiver.\n> +  # Array/Map must be recursed into.\n> +  def _CheckFieldOrParam(self, kind):\n> +    if module.IsAnyInterfaceKind(kind):\n> +      self._CheckNonNullableFeatureGuardedInterface(kind)\n> +    if module.IsArrayKind(kind):\n> +      self._CheckFieldOrParam(kind.kind)\n> +    if module.IsMapKind(kind):\n> +      self._CheckFieldOrParam(kind.key_kind)\n> +      self._CheckFieldOrParam(kind.value_kind)\n> +\n> +  def _CheckInterfaceFeatures(self, interface):\n> +    for method in interface.methods:\n> +      for param in method.parameters:\n> +        self._CheckFieldOrParam(param.kind)\n> +      if method.response_parameters:\n> +        for param in method.response_parameters:\n> +          self._CheckFieldOrParam(param.kind)\n> +\n> +  def _CheckStructFeatures(self, struct):\n> +    for field in struct.fields:\n> +      self._CheckFieldOrParam(field.kind)\n> +\n> +  def _CheckUnionFeatures(self, union):\n> +    for field in union.fields:\n> +      self._CheckFieldOrParam(field.kind)\n> +\n> +  def CheckModule(self):\n> +    \"\"\"Validate that any runtime feature guarded interfaces that might be passed\n> +    over mojo are nullable.\"\"\"\n> +    for interface in self.module.interfaces:\n> +      self._CheckInterfaceFeatures(interface)\n> +    for struct in self.module.structs:\n> +      self._CheckStructFeatures(struct)\n> +    for union in self.module.unions:\n> +      self._CheckUnionFeatures(union)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py\n> new file mode 100644\n> index 000000000000..e96152fdd0ef\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py\n> @@ -0,0 +1,173 @@\n> +# Copyright 2023 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import unittest\n> +\n> +import mojom.generate.check as check\n> +from mojom_bindings_generator import LoadChecks, _Generate\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class FakeArgs:\n> +  \"\"\"Fakes args to _Generate - intention is to do just enough to run checks\"\"\"\n> +  def __init__(self, tester, files=None):\n> +    \"\"\" `tester` is MojomParserTestCase for paths.\n> +        `files` will have tester path added.\"\"\"\n> +    self.checks_string = 'features'\n> +    self.depth = tester.GetPath('')\n> +    self.filelist = None\n> +    self.filename = [tester.GetPath(x) for x in files]\n> +    self.gen_directories = tester.GetPath('gen')\n> +    self.generators_string = ''\n> +    self.import_directories = []\n> +    self.output_dir = tester.GetPath('out')\n> +    self.scrambled_message_id_salt_paths = None\n> +    self.typemaps = []\n> +    self.variant = 'none'\n> +\n> +\n> +class MojoBindingsCheckTest(MojomParserTestCase):\n> +  def _ParseAndGenerate(self, mojoms):\n> +    self.ParseMojoms(mojoms)\n> +    args = FakeArgs(self, files=mojoms)\n> +    _Generate(args, {})\n> +\n> +  def assertValid(self, filename, content):\n> +    self.WriteFile(filename, content)\n> +    self._ParseAndGenerate([filename])\n> +\n> +  def assertThrows(self, filename, content, regexp):\n> +    mojoms = []\n> +    self.WriteFile(filename, content)\n> +    mojoms.append(filename)\n> +    with self.assertRaisesRegexp(check.CheckException, regexp):\n> +      self._ParseAndGenerate(mojoms)\n> +\n> +  def testLoads(self):\n> +    \"\"\"Validate that the check is registered under the expected name.\"\"\"\n> +    check_modules = LoadChecks('features')\n> +    self.assertTrue(check_modules['features'])\n> +\n> +  def testNullableOk(self):\n> +    self.assertValid(\n> +        \"a.mojom\", \"\"\"\n> +          module a;\n> +          // Scaffolding.\n> +          feature kFeature {\n> +            const string name = \"Hello\";\n> +            const bool enabled_state = false;\n> +          };\n> +          [RuntimeFeature=kFeature]\n> +          interface Guarded {\n> +          };\n> +\n> +          // Unguarded interfaces should be ok everywhere.\n> +          interface NotGuarded { };\n> +\n> +          // Optional (nullable) interfaces should be ok everywhere:\n> +          struct Bar {\n> +            pending_remote<Guarded>? remote;\n> +            pending_receiver<Guarded>? receiver;\n> +          };\n> +          union Thingy {\n> +            pending_remote<Guarded>? remote;\n> +            pending_receiver<Guarded>? receiver;\n> +          };\n> +          interface Foo {\n> +            Foo(\n> +              pending_remote<Guarded>? remote,\n> +              pending_receiver<Guarded>? receiver,\n> +              pending_associated_remote<Guarded>? a_remote,\n> +              pending_associated_receiver<Guarded>? a_receiver,\n> +              // Unguarded interfaces do not have to be nullable.\n> +              pending_remote<NotGuarded> remote,\n> +              pending_receiver<NotGuarded> receiver,\n> +              pending_associated_remote<NotGuarded> a_remote,\n> +              pending_associated_receiver<NotGuarded> a_receiver\n> +            ) => (\n> +              pending_remote<Guarded>? remote,\n> +              pending_receiver<Guarded>? receiver\n> +            );\n> +            Bar(array<pending_remote<Guarded>?> remote)\n> +              => (map<string, pending_receiver<Guarded>?> a);\n> +          };\n> +    \"\"\")\n> +\n> +  def testMethodParamsMustBeNullable(self):\n> +    prelude = \"\"\"\n> +      module a;\n> +      // Scaffolding.\n> +      feature kFeature {\n> +        const string name = \"Hello\";\n> +        const bool enabled_state = false;\n> +      };\n> +      [RuntimeFeature=kFeature]\n> +      interface Guarded { };\n> +    \"\"\"\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(pending_remote<Guarded> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(bool foo) => (pending_receiver<Guarded> a);\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(pending_receiver<Guarded> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(pending_associated_remote<Guarded> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(pending_associated_receiver<Guarded> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(array<pending_associated_receiver<Guarded>> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(map<string, pending_associated_receiver<Guarded>> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +\n> +  def testStructUnionMembersMustBeNullable(self):\n> +    prelude = \"\"\"\n> +      module a;\n> +      // Scaffolding.\n> +      feature kFeature {\n> +        const string name = \"Hello\";\n> +        const bool enabled_state = false;\n> +      };\n> +      [RuntimeFeature=kFeature]\n> +      interface Guarded { };\n> +    \"\"\"\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          struct Trial {\n> +            pending_remote<Guarded> a;\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          union Trial {\n> +            pending_remote<Guarded> a;\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py\n> new file mode 100644\n> index 000000000000..d570e26cea68\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py\n> @@ -0,0 +1,102 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Validate RequireContext and AllowedContext annotations before generation.\"\"\"\n> +\n> +import mojom.generate.check as check\n> +import mojom.generate.module as module\n> +\n> +\n> +class Check(check.Check):\n> +  def __init__(self, *args, **kwargs):\n> +    self.kind_to_interfaces = dict()\n> +    super(Check, self).__init__(*args, **kwargs)\n> +\n> +  def _IsPassedInterface(self, candidate):\n> +    if isinstance(\n> +        candidate.kind,\n> +        (module.PendingReceiver, module.PendingRemote,\n> +         module.PendingAssociatedReceiver, module.PendingAssociatedRemote)):\n> +      return True\n> +    return False\n> +\n> +  def _CheckInterface(self, method, param):\n> +    # |param| is a pending_x<Interface> so need .kind.kind to get Interface.\n> +    interface = param.kind.kind\n> +    if interface.require_context:\n> +      if method.allowed_context is None:\n> +        raise check.CheckException(\n> +            self.module, \"method `{}` has parameter `{}` which passes interface\"\n> +            \" `{}` that requires an AllowedContext annotation but none exists.\".\n> +            format(\n> +                method.mojom_name,\n> +                param.mojom_name,\n> +                interface.mojom_name,\n> +            ))\n> +      # If a string was provided, or if an enum was not imported, this will\n> +      # be a string and we cannot validate that it is in range.\n> +      if not isinstance(method.allowed_context, module.EnumValue):\n> +        raise check.CheckException(\n> +            self.module,\n> +            \"method `{}` has AllowedContext={} which is not a valid enum value.\"\n> +            .format(method.mojom_name, method.allowed_context))\n> +      # EnumValue must be from the same enum to be compared.\n> +      if interface.require_context.enum != method.allowed_context.enum:\n> +        raise check.CheckException(\n> +            self.module, \"method `{}` has parameter `{}` which passes interface\"\n> +            \" `{}` that requires AllowedContext={} but one of kind `{}` was \"\n> +            \"provided.\".format(\n> +                method.mojom_name,\n> +                param.mojom_name,\n> +                interface.mojom_name,\n> +                interface.require_context.enum,\n> +                method.allowed_context.enum,\n> +            ))\n> +      # RestrictContext enums have most privileged field first (lowest value).\n> +      interface_value = interface.require_context.field.numeric_value\n> +      method_value = method.allowed_context.field.numeric_value\n> +      if interface_value < method_value:\n> +        raise check.CheckException(\n> +            self.module, \"RequireContext={} > AllowedContext={} for method \"\n> +            \"`{}` which passes interface `{}`.\".format(\n> +                interface.require_context.GetSpec(),\n> +                method.allowed_context.GetSpec(), method.mojom_name,\n> +                interface.mojom_name))\n> +      return True\n> +\n> +  def _GatherReferencedInterfaces(self, field):\n> +    key = field.kind.spec\n> +    # structs/unions can nest themselves so we need to bookkeep.\n> +    if not key in self.kind_to_interfaces:\n> +      # Might reference ourselves so have to create the list first.\n> +      self.kind_to_interfaces[key] = set()\n> +      for param in field.kind.fields:\n> +        if self._IsPassedInterface(param):\n> +          self.kind_to_interfaces[key].add(param)\n> +        elif isinstance(param.kind, (module.Struct, module.Union)):\n> +          for iface in self._GatherReferencedInterfaces(param):\n> +            self.kind_to_interfaces[key].add(iface)\n> +    return self.kind_to_interfaces[key]\n> +\n> +  def _CheckParams(self, method, params):\n> +    # Note: we have to repeat _CheckParams for each method as each might have\n> +    # different AllowedContext= attributes. We cannot memoize this function,\n> +    # but can do so for gathering referenced interfaces as their RequireContext\n> +    # attributes do not change.\n> +    for param in params:\n> +      if self._IsPassedInterface(param):\n> +        self._CheckInterface(method, param)\n> +      elif isinstance(param.kind, (module.Struct, module.Union)):\n> +        for interface in self._GatherReferencedInterfaces(param):\n> +          self._CheckInterface(method, interface)\n> +\n> +  def _CheckMethod(self, method):\n> +    if method.parameters:\n> +      self._CheckParams(method, method.parameters)\n> +    if method.response_parameters:\n> +      self._CheckParams(method, method.response_parameters)\n> +\n> +  def CheckModule(self):\n> +    for interface in self.module.interfaces:\n> +      for method in interface.methods:\n> +        self._CheckMethod(method)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py\n> new file mode 100644\n> index 000000000000..a6cd71e2f1f1\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py\n> @@ -0,0 +1,254 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import unittest\n> +\n> +import mojom.generate.check as check\n> +from mojom_bindings_generator import LoadChecks, _Generate\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +# Mojoms that we will use in multiple tests.\n> +basic_mojoms = {\n> +    'level.mojom':\n> +    \"\"\"\n> +  module level;\n> +  enum Level {\n> +    kHighest,\n> +    kMiddle,\n> +    kLowest,\n> +  };\n> +  \"\"\",\n> +    'interfaces.mojom':\n> +    \"\"\"\n> +  module interfaces;\n> +  import \"level.mojom\";\n> +  struct Foo {int32 bar;};\n> +  [RequireContext=level.Level.kHighest]\n> +  interface High {\n> +    DoFoo(Foo foo);\n> +  };\n> +  [RequireContext=level.Level.kMiddle]\n> +  interface Mid {\n> +    DoFoo(Foo foo);\n> +  };\n> +  [RequireContext=level.Level.kLowest]\n> +  interface Low {\n> +    DoFoo(Foo foo);\n> +  };\n> +  \"\"\"\n> +}\n> +\n> +\n> +class FakeArgs:\n> +  \"\"\"Fakes args to _Generate - intention is to do just enough to run checks\"\"\"\n> +\n> +  def __init__(self, tester, files=None):\n> +    \"\"\" `tester` is MojomParserTestCase for paths.\n> +        `files` will have tester path added.\"\"\"\n> +    self.checks_string = 'restrictions'\n> +    self.depth = tester.GetPath('')\n> +    self.filelist = None\n> +    self.filename = [tester.GetPath(x) for x in files]\n> +    self.gen_directories = tester.GetPath('gen')\n> +    self.generators_string = ''\n> +    self.import_directories = []\n> +    self.output_dir = tester.GetPath('out')\n> +    self.scrambled_message_id_salt_paths = None\n> +    self.typemaps = []\n> +    self.variant = 'none'\n> +\n> +\n> +class MojoBindingsCheckTest(MojomParserTestCase):\n> +  def _WriteBasicMojoms(self):\n> +    for filename, contents in basic_mojoms.items():\n> +      self.WriteFile(filename, contents)\n> +    return list(basic_mojoms.keys())\n> +\n> +  def _ParseAndGenerate(self, mojoms):\n> +    self.ParseMojoms(mojoms)\n> +    args = FakeArgs(self, files=mojoms)\n> +    _Generate(args, {})\n> +\n> +  def testLoads(self):\n> +    \"\"\"Validate that the check is registered under the expected name.\"\"\"\n> +    check_modules = LoadChecks('restrictions')\n> +    self.assertTrue(check_modules['restrictions'])\n> +\n> +  def testValidAnnotations(self):\n> +    mojoms = self._WriteBasicMojoms()\n> +\n> +    a = 'a.mojom'\n> +    self.WriteFile(\n> +        a, \"\"\"\n> +      module a;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +\n> +      interface PassesHigh {\n> +        [AllowedContext=level.Level.kHighest]\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +      interface PassesMedium {\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMedium(pending_receiver<interfaces.Mid> hi);\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMediumRem(pending_remote<interfaces.Mid> hi);\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);\n> +      };\n> +      interface PassesLow {\n> +        [AllowedContext=level.Level.kLowest]\n> +        DoLow(pending_receiver<interfaces.Low> hi);\n> +      };\n> +\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      struct Two { One one; };\n> +      interface PassesNestedHigh {\n> +        [AllowedContext=level.Level.kHighest]\n> +        DoNestedHigh(Two two);\n> +      };\n> +\n> +      // Allowed as PassesHigh is not itself restricted.\n> +      interface PassesPassesHigh {\n> +        DoPass(pending_receiver<PassesHigh> hiho);\n> +      };\n> +    \"\"\")\n> +    mojoms.append(a)\n> +    self._ParseAndGenerate(mojoms)\n> +\n> +  def _testThrows(self, filename, content, regexp):\n> +    mojoms = self._WriteBasicMojoms()\n> +    self.WriteFile(filename, content)\n> +    mojoms.append(filename)\n> +    with self.assertRaisesRegexp(check.CheckException, regexp):\n> +      self._ParseAndGenerate(mojoms)\n> +\n> +  def testMissingAnnotation(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +\n> +      interface PassesHigh {\n> +        // err: missing annotation.\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')\n> +\n> +  def testAllowTooLow(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +\n> +      interface PassesHigh {\n> +        // err: level is worse than required.\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')\n> +\n> +  def testWrongEnumInAllow(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      enum Blah {\n> +        kZero,\n> +      };\n> +      interface PassesHigh {\n> +        // err: different enums.\n> +        [AllowedContext=Blah.kZero]\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'but one of kind')\n> +\n> +  def testNotAnEnumInAllow(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      interface PassesHigh {\n> +        // err: not an enum.\n> +        [AllowedContext=doopdedoo.mojom.kWhatever]\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'not a valid enum value')\n> +\n> +  def testMissingAllowedForNestedStructs(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      struct Two { One one; };\n> +      interface PassesNestedHigh {\n> +        // err: missing annotation.\n> +        DoNestedHigh(Two two);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')\n> +\n> +  def testMissingAllowedForNestedUnions(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      struct Two { One one; };\n> +      union Three {One one; Two two; };\n> +      interface PassesNestedHigh {\n> +        // err: missing annotation.\n> +        DoNestedHigh(Three three);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')\n> +\n> +  def testMultipleInterfacesThrows(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      interface PassesMultipleInterfaces {\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMultiple(\n> +          pending_remote<interfaces.Mid> mid,\n> +          pending_receiver<interfaces.High> hi,\n> +          One one\n> +        );\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')\n> +\n> +  def testMultipleInterfacesAllowed(self):\n> +    \"\"\"Multiple interfaces can be passed, all satisfy the level.\"\"\"\n> +    mojoms = self._WriteBasicMojoms()\n> +\n> +    b = \"b.mojom\"\n> +    self.WriteFile(\n> +        b, \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      interface PassesMultipleInterfaces {\n> +        [AllowedContext=level.Level.kHighest]\n> +        DoMultiple(\n> +          pending_receiver<interfaces.High> hi,\n> +          pending_remote<interfaces.Mid> mid,\n> +          One one\n> +        );\n> +      };\n> +    \"\"\")\n> +    mojoms.append(b)\n> +    self._ParseAndGenerate(mojoms)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n> deleted file mode 100644\n> index d8a138744856..000000000000\n> --- a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n> +++ /dev/null\n> @@ -1,51 +0,0 @@\n> -# Copyright 2016 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\n> -_typemap_imports = [\n> -  \"//chrome/chrome_cleaner/mojom/typemaps/typemaps.gni\",\n> -  \"//chrome/common/importer/typemaps.gni\",\n> -  \"//chrome/common/media_router/mojom/typemaps.gni\",\n> -  \"//chrome/typemaps.gni\",\n> -  \"//chromecast/typemaps.gni\",\n> -  \"//chromeos/typemaps.gni\",\n> -  \"//chromeos/components/multidevice/mojom/typemaps.gni\",\n> -  \"//chromeos/services/cros_healthd/public/mojom/typemaps.gni\",\n> -  \"//chromeos/services/device_sync/public/mojom/typemaps.gni\",\n> -  \"//chromeos/services/network_config/public/mojom/typemaps.gni\",\n> -  \"//chromeos/services/secure_channel/public/mojom/typemaps.gni\",\n> -  \"//components/arc/mojom/typemaps.gni\",\n> -  \"//components/chromeos_camera/common/typemaps.gni\",\n> -  \"//components/services/storage/public/cpp/filesystem/typemaps.gni\",\n> -  \"//components/sync/mojom/typemaps.gni\",\n> -  \"//components/typemaps.gni\",\n> -  \"//content/browser/typemaps.gni\",\n> -  \"//content/public/common/typemaps.gni\",\n> -  \"//sandbox/mac/mojom/typemaps.gni\",\n> -  \"//services/media_session/public/cpp/typemaps.gni\",\n> -  \"//services/proxy_resolver/public/cpp/typemaps.gni\",\n> -  \"//services/resource_coordinator/public/cpp/typemaps.gni\",\n> -  \"//services/service_manager/public/cpp/typemaps.gni\",\n> -  \"//services/tracing/public/mojom/typemaps.gni\",\n> -]\n> -\n> -_typemaps = []\n> -foreach(typemap_import, _typemap_imports) {\n> -  # Avoid reassignment error by assigning to empty scope first.\n> -  _imported = {\n> -  }\n> -  _imported = read_file(typemap_import, \"scope\")\n> -  _typemaps += _imported.typemaps\n> -}\n> -\n> -typemaps = []\n> -foreach(typemap, _typemaps) {\n> -  typemaps += [\n> -    {\n> -      filename = typemap\n> -      config = read_file(typemap, \"scope\")\n> -    },\n> -  ]\n> -}\n> -\n> -component_macro_suffix = \"\"\n> diff --git a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py b/utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n> deleted file mode 100644\n> index a978901bc033..000000000000\n> --- a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n> +++ /dev/null\n> @@ -1,27 +0,0 @@\n> -# Copyright 2019 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\n> -import os\n> -import sys\n> -import argparse\n> -\n> -_HERE_PATH = os.path.dirname(__file__)\n> -_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))\n> -\n> -sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))\n> -import node\n> -import node_modules\n> -\n> -def main(argv):\n> -  parser = argparse.ArgumentParser()\n> -  parser.add_argument('--tsconfig_path', required=True)\n> -  args = parser.parse_args(argv)\n> -\n> -  result = node.RunNode([node_modules.PathToTypescript()] +\n> -                        ['--project', args.tsconfig_path])\n> -  if len(result) != 0:\n> -    raise RuntimeError('Failed to compile Typescript: \\n%s' % result)\n> -\n> -if __name__ == '__main__':\n> -  main(sys.argv[1:])\n> diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n> index 48bc66fd0f6e..4dd26d4aea8b 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2019 The Chromium Authors. All rights reserved.\n> +# Copyright 2019 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  #\n> @@ -15,6 +15,7 @@\n>  from __future__ import print_function\n>  \n>  import optparse\n> +import sys\n>  \n>  \n>  def Concatenate(filenames):\n> @@ -47,7 +48,7 @@ def main():\n>    parser.set_usage(\"\"\"Concatenate several files into one.\n>        Equivalent to: cat file1 ... > target.\"\"\")\n>    (_options, args) = parser.parse_args()\n> -  exit(0 if Concatenate(args) else 1)\n> +  sys.exit(0 if Concatenate(args) else 1)\n>  \n>  \n>  if __name__ == \"__main__\":\n> diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n> index be8985cedc99..7d56c9f962c3 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2018 The Chromium Authors. All rights reserved.\n> +# Copyright 2018 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -20,6 +20,7 @@ from __future__ import print_function\n>  \n>  import optparse\n>  import re\n> +import sys\n>  \n>  \n>  _MOJO_INTERNAL_MODULE_NAME = \"mojo.internal\"\n> @@ -31,10 +32,10 @@ def FilterLine(filename, line, output):\n>      return\n>  \n>    if line.startswith(\"goog.provide\"):\n> -    match = re.match(\"goog.provide\\('([^']+)'\\);\", line)\n> +    match = re.match(r\"goog.provide\\('([^']+)'\\);\", line)\n>      if not match:\n>        print(\"Invalid goog.provide line in %s:\\n%s\" % (filename, line))\n> -      exit(1)\n> +      sys.exit(1)\n>  \n>      module_name = match.group(1)\n>      if module_name == _MOJO_INTERNAL_MODULE_NAME:\n> @@ -67,7 +68,8 @@ def main():\n>      Concatenate several files into one, stripping Closure provide and\n>      require directives along the way.\"\"\")\n>    (_, args) = parser.parse_args()\n> -  exit(0 if ConcatenateAndReplaceExports(args) else 1)\n> +  sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)\n> +\n>  \n>  if __name__ == \"__main__\":\n>    main()\n> diff --git a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py b/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n> deleted file mode 100755\n> index 7ac4af5faef1..000000000000\n> --- a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n> +++ /dev/null\n> @@ -1,36 +0,0 @@\n> -#!/usr/bin/env python\n> -# Copyright 2016 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\n> -from __future__ import print_function\n> -\n> -import sys\n> -\n> -# This utility converts mojom dependencies into their corresponding typemap\n> -# paths and formats them to be consumed by generate_type_mappings.py.\n> -\n> -\n> -def FormatTypemap(typemap_filename):\n> -  # A simple typemap is valid Python with a minor alteration.\n> -  with open(typemap_filename) as f:\n> -    typemap_content = f.read().replace('=\\n', '=')\n> -  typemap = {}\n> -  exec typemap_content in typemap\n> -\n> -  for header in typemap.get('public_headers', []):\n> -    yield 'public_headers=%s' % header\n> -  for header in typemap.get('traits_headers', []):\n> -    yield 'traits_headers=%s' % header\n> -  for header in typemap.get('type_mappings', []):\n> -    yield 'type_mappings=%s' % header\n> -\n> -\n> -def main():\n> -  typemaps = sys.argv[1:]\n> -  print(' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap))\n> -                 for typemap in typemaps))\n> -\n> -\n> -if __name__ == '__main__':\n> -  sys.exit(main())\n> diff --git a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n> index 8b78d0924185..c6daff034f7c 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2017 The Chromium Authors. All rights reserved.\n> +# Copyright 2017 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Generates a list of all files in a directory.\n> diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n> index a009664945fe..4a53e2bffe1e 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2016 The Chromium Authors. All rights reserved.\n> +# Copyright 2016 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Generates a JSON typemap from its command-line arguments and dependencies.\n> @@ -82,10 +82,12 @@ def LoadCppTypemapConfig(path):\n>        for entry in config['types']:\n>          configs[entry['mojom']] = {\n>              'typename': entry['cpp'],\n> +            'forward_declaration': entry.get('forward_declaration', None),\n>              'public_headers': config.get('traits_headers', []),\n>              'traits_headers': config.get('traits_private_headers', []),\n>              'copyable_pass_by_value': entry.get('copyable_pass_by_value',\n>                                                  False),\n> +            'default_constructible': entry.get('default_constructible', True),\n>              'force_serialize': entry.get('force_serialize', False),\n>              'hashable': entry.get('hashable', False),\n>              'move_only': entry.get('move_only', False),\n> diff --git a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py\n> new file mode 100755\n> index 000000000000..cefee7a401a6\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py\n> @@ -0,0 +1,47 @@\n> +#!/usr/bin/env python3\n> +# Copyright 2023 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +#\n> +# This utility minifies JS files with terser.\n> +#\n> +# Instance of 'node' has no 'RunNode' member (no-member)\n> +# pylint: disable=no-member\n> +\n> +import argparse\n> +import os\n> +import sys\n> +\n> +_HERE_PATH = os.path.dirname(__file__)\n> +_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))\n> +_CWD = os.getcwd()\n> +sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))\n> +import node\n> +import node_modules\n> +\n> +\n> +def MinifyFile(input_file, output_file):\n> +  node.RunNode([\n> +      node_modules.PathToTerser(), input_file, '--mangle', '--compress',\n> +      '--comments', 'false', '--output', output_file\n> +  ])\n> +\n> +\n> +def main(argv):\n> +  parser = argparse.ArgumentParser()\n> +  parser.add_argument('--input', required=True)\n> +  parser.add_argument('--output', required=True)\n> +  args = parser.parse_args(argv)\n> +\n> +  # Delete the output file if it already exists. It may be a sym link to the\n> +  # input, because in non-optimized/pre-Terser builds the input file is copied\n> +  # to the output location with gn copy().\n> +  out_path = os.path.join(_CWD, args.output)\n> +  if (os.path.exists(out_path)):\n> +    os.remove(out_path)\n> +\n> +  MinifyFile(os.path.join(_CWD, args.input), out_path)\n> +\n> +\n> +if __name__ == '__main__':\n> +  main(sys.argv[1:])\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/ipc/mojo/public/tools/bindings/mojom.gni\n> index fe2a1da3750a..3f6e54e0a315 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/mojom.gni\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni\n> @@ -1,25 +1,28 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import(\"//build/config/python.gni\")\n>  import(\"//third_party/closure_compiler/closure_args.gni\")\n>  import(\"//third_party/closure_compiler/compile_js.gni\")\n>  import(\"//third_party/protobuf/proto_library.gni\")\n> +import(\"//ui/webui/resources/tools/generate_grd.gni\")\n>  import(\"//ui/webui/webui_features.gni\")\n>  \n> +import(\"//build/config/cast.gni\")\n> +\n>  # TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're\n>  # used to conditionally enable message ID scrambling in a way which is\n>  # consistent across toolchains and which is affected by branded vs non-branded\n>  # Chrome builds. Ideally we could create some generic knobs here that could be\n>  # flipped elsewhere though.\n>  import(\"//build/config/chrome_build.gni\")\n> -import(\"//build/config/chromecast_build.gni\")\n>  import(\"//build/config/chromeos/ui_mode.gni\")\n> +import(\"//build/config/features.gni\")\n>  import(\"//build/config/nacl/config.gni\")\n>  import(\"//build/toolchain/kythe.gni\")\n>  import(\"//components/nacl/features.gni\")\n>  import(\"//third_party/jinja2/jinja2.gni\")\n> +import(\"//third_party/ply/ply.gni\")\n>  import(\"//tools/ipc_fuzzer/ipc_fuzzer.gni\")\n>  declare_args() {\n>    # Indicates whether typemapping should be supported in this build\n> @@ -34,21 +37,30 @@ declare_args() {\n>  \n>    # Controls message ID scrambling behavior. If |true|, message IDs are\n>    # scrambled (i.e. randomized based on the contents of //chrome/VERSION) on\n> -  # non-Chrome OS desktop platforms. Set to |false| to disable message ID\n> -  # scrambling on all platforms.\n> -  enable_mojom_message_id_scrambling = true\n> -\n> -  # Enables Closure compilation of generated JS lite bindings. In environments\n> -  # where compilation is supported, any mojom target \"foo\" will also have a\n> -  # corresponding \"foo_js_library_for_compile\" target generated.\n> -  enable_mojom_closure_compile = enable_js_type_check && optimize_webui\n> -\n> -  # Enables generating Typescript bindings and compiling them to JS bindings.\n> -  enable_typescript_bindings = false\n> +  # non-Chrome OS desktop platforms. Enabled on official builds by default.\n> +  # Set to |true| to enable message ID scrambling on a specific build.\n> +  # See also `enable_scrambled_message_ids` below for more details.\n> +  enable_mojom_message_id_scrambling = is_official_build\n>  \n>    # Enables generating javascript fuzzing-related code and the bindings for the\n>    # MojoLPM fuzzer targets. Off by default.\n>    enable_mojom_fuzzer = false\n> +\n> +  # Enables Closure compilation of generated JS lite bindings. In environments\n> +  # where compilation is supported, any mojom target \"foo\" will also have a\n> +  # corresponding \"foo_js_library_for_compile\" target generated.\n> +  if (is_chromeos_ash) {\n> +    enable_mojom_closure_compile = enable_js_type_check && optimize_webui\n> +  }\n> +}\n> +\n> +# Closure libraries are needed for mojom_closure_compile, and when\n> +# js_type_check is enabled on Ash.\n> +if (is_chromeos_ash) {\n> +  generate_mojom_closure_libraries =\n> +      enable_mojom_closure_compile || enable_js_type_check\n> +} else {\n> +  generate_mojom_closure_libraries = false\n>  }\n>  \n>  # NOTE: We would like to avoid scrambling message IDs where it doesn't add\n> @@ -69,9 +81,8 @@ declare_args() {\n>  # lacros-chrome switches to target_os=\"chromeos\"\n>  enable_scrambled_message_ids =\n>      enable_mojom_message_id_scrambling &&\n> -    (is_mac || is_win ||\n> -     (is_linux && !is_chromeos_ash && !is_chromecast && !is_chromeos_lacros) ||\n> -     ((enable_nacl || is_nacl || is_nacl_nonsfi) &&\n> +    (is_mac || is_win || (is_linux && !is_castos) ||\n> +     ((enable_nacl || is_nacl) &&\n>        (target_os != \"chromeos\" && !chromeos_is_browser_only)))\n>  \n>  _mojom_tools_root = \"//mojo/public/tools\"\n> @@ -80,7 +91,9 @@ mojom_parser_script = \"$_mojom_tools_root/mojom/mojom_parser.py\"\n>  mojom_parser_sources = [\n>    \"$_mojom_library_root/__init__.py\",\n>    \"$_mojom_library_root/error.py\",\n> +  \"$_mojom_library_root/fileutil.py\",\n>    \"$_mojom_library_root/generate/__init__.py\",\n> +  \"$_mojom_library_root/generate/check.py\",\n>    \"$_mojom_library_root/generate/generator.py\",\n>    \"$_mojom_library_root/generate/module.py\",\n>    \"$_mojom_library_root/generate/pack.py\",\n> @@ -88,21 +101,32 @@ mojom_parser_sources = [\n>    \"$_mojom_library_root/generate/translate.py\",\n>    \"$_mojom_library_root/parse/__init__.py\",\n>    \"$_mojom_library_root/parse/ast.py\",\n> +  \"$_mojom_library_root/parse/conditional_features.py\",\n>    \"$_mojom_library_root/parse/lexer.py\",\n>    \"$_mojom_library_root/parse/parser.py\",\n> +  \"//tools/diagnosis/crbug_1001171.py\",\n>  ]\n>  \n>  mojom_generator_root = \"$_mojom_tools_root/bindings\"\n>  mojom_generator_script = \"$mojom_generator_root/mojom_bindings_generator.py\"\n>  mojom_generator_sources =\n>      mojom_parser_sources + [\n> +      \"$mojom_generator_root/checks/__init__.py\",\n> +      \"$mojom_generator_root/checks/mojom_attributes_check.py\",\n> +      \"$mojom_generator_root/checks/mojom_definitions_check.py\",\n> +      \"$mojom_generator_root/checks/mojom_interface_feature_check.py\",\n> +      \"$mojom_generator_root/checks/mojom_restrictions_check.py\",\n> +      \"$mojom_generator_root/generators/__init__.py\",\n>        \"$mojom_generator_root/generators/cpp_util.py\",\n>        \"$mojom_generator_root/generators/mojom_cpp_generator.py\",\n>        \"$mojom_generator_root/generators/mojom_java_generator.py\",\n> -      \"$mojom_generator_root/generators/mojom_mojolpm_generator.py\",\n>        \"$mojom_generator_root/generators/mojom_js_generator.py\",\n> +      \"$mojom_generator_root/generators/mojom_mojolpm_generator.py\",\n>        \"$mojom_generator_root/generators/mojom_ts_generator.py\",\n>        \"$mojom_generator_script\",\n> +      \"//build/action_helpers.py\",\n> +      \"//build/gn_helpers.py\",\n> +      \"//build/zip_helpers.py\",\n>      ]\n>  \n>  if (enable_scrambled_message_ids) {\n> @@ -243,12 +267,16 @@ if (enable_scrambled_message_ids) {\n>  #       |cpp_only| is set to true, it overrides this to prevent generation of\n>  #       Java bindings.\n>  #\n> -#   enable_fuzzing (optional)\n> +#   enable_js_fuzzing (optional)\n> +#       Enables generation of javascript fuzzing sources for the target if the\n> +#       global build arg |enable_mojom_fuzzer| is also set to |true|.\n> +#       Defaults to |true|. If JS fuzzing generation is enabled for a target,\n> +#       the target will always generate JS bindings even if |cpp_only| is set to\n> +#       |true|. See note above.\n> +#\n> +#   enable_mojolpm_fuzzing (optional)\n>  #       Enables generation of fuzzing sources for the target if the global build\n> -#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If\n> -#       fuzzing generation is enabled for a target, the target will always\n> -#       generate JS bindings even if |cpp_only| is set to |true|. See note\n> -#       above.\n> +#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|.\n>  #\n>  #   support_lazy_serialization (optional)\n>  #       If set to |true|, generated C++ bindings will effectively prefer to\n> @@ -310,8 +338,15 @@ if (enable_scrambled_message_ids) {\n>  #       correct dependency order. Note that this only has an effect if\n>  #       the |enable_mojom_closure_compile| global arg is set to |true| as well.\n>  #\n> -#   use_typescript_sources (optional)\n> -#       Uses the Typescript generator to generate JavaScript bindings.\n> +#   generate_webui_js_bindings (optional)\n> +#       Generate WebUI bindings in JavaScript rather than TypeScript. Defaults\n> +#       to false. ChromeOS only parameter.\n> +#\n> +#   generate_legacy_js_bindings (optional)\n> +#       Generate js_data_deps target containing legacy JavaScript bindings files\n> +#       for Blink tests and other non-WebUI users when generating TypeScript\n> +#       bindings for WebUI. Ignored if generate_webui_js_bindings is set to\n> +#       true.\n>  #\n>  #   js_generate_struct_deserializers (optional)\n>  #       Generates JS deerialize methods for structs.\n> @@ -323,17 +358,23 @@ if (enable_scrambled_message_ids) {\n>  #   webui_module_path (optional)\n>  #       The path or URL at which modules generated by this target will be\n>  #       accessible to WebUI pages. This may either be an absolute path or\n> -#       a full URL path starting with \"chrome://resources/mojo\".\n> +#       a full URL path starting with \"chrome://resources/mojo\". If this path\n> +#       is not specified, WebUI bindings will not be generated.\n>  #\n>  #       If an absolute path, a WebUI page may only import these modules if\n> -#       they are manually packaged and mapped independently by that page's\n> -#       WebUIDataSource. The mapped path must match the path given here.\n> +#       they are added to that page's data source (usually by adding the\n> +#       modules to the mojo_files list for build_webui(), or by listing the\n> +#       files as inputs to the page's ts_library() and/or generate_grd() build\n> +#       steps.\n>  #\n>  #       If this is is instead a URL string starting with\n> -#       \"chrome://resources/mojo\", the generated resources must be added to\n> -#       content_resources.grd and registered with\n> -#       content::SharedResourcesDataSource with a corresponding path, at which\n> -#       point they will be made available to all WebUI pages at the given URL.\n> +#       \"chrome://resources/mojo\", the resulting bindings files should\n> +#       be added to one of the lists in ui/webui/resources/mojo/BUILD.gn,\n> +#       at which point they will be made available to all WebUI pages at the\n> +#       given URL.\n> +#\n> +#       Note: WebUI module bindings are generated in TypeScript by default,\n> +#       unless |generate_webui_js_bindings| is specified as true.\n>  #\n>  # The following parameters are used to support the component build. They are\n>  # needed so that bindings which are linked with a component can use the same\n> @@ -402,16 +443,41 @@ if (enable_scrambled_message_ids) {\n>  #             should be mapped in generated bindings. This is a string like\n>  #             \"::base::Value\" or \"std::vector<::base::Value>\".\n>  #\n> -#         move_only (optional)\n> -#             A boolean value (default false) which indicates whether the C++\n> -#             type is move-only. If true, generated bindings will pass the type\n> -#             by value and use std::move() at call sites.\n> -#\n>  #         copyable_pass_by_value (optional)\n>  #             A boolean value (default false) which effectively indicates\n>  #             whether the C++ type is very cheap to copy. If so, generated\n>  #             bindings will pass by value but not use std::move() at call sites.\n>  #\n> +#         default_constructible (optional)\n> +#             A boolean value (default true) which indicates whether the C++\n> +#             type is default constructible. If a C++ type is not default\n> +#             constructible (e.g. the implementor of the type prefers not to\n> +#             publicly expose a default constructor that creates an object in an\n> +#             invalid state), Mojo will instead construct C++ type with an\n> +#             argument of the type `mojo::DefaultConstruct::Tag` (essentially a\n> +#             passkey-like type specifically for this use case).\n> +#\n> +#         force_serialize (optional)\n> +#             A boolean value (default false) which disables lazy serialization\n> +#             of the typemapped type if lazy serialization is enabled for the\n> +#             mojom target applying this typemap.\n> +#\n> +#         forward_declaration (optional)\n> +#             A forward declaration of the C++ type, which bindings that don't\n> +#             need the full type definition can use to reduce the size of\n> +#             the generated code. This is a string like\n> +#             \"namespace base { class Value; }\".\n> +#\n> +#         hashable (optional)\n> +#             A boolean value (default false) indicating whether the C++ type is\n> +#             hashable. Set to true if true AND needed (i.e. you need to use the\n> +#             type as the key of a mojom map).\n> +#\n> +#         move_only (optional)\n> +#             A boolean value (default false) which indicates whether the C++\n> +#             type is move-only. If true, generated bindings will pass the type\n> +#             by value and use std::move() at call sites.\n> +#\n>  #         nullable_is_same_type (optional)\n>  #             A boolean value (default false) which indicates that the C++ type\n>  #             has some baked-in semantic notion of a \"null\" state. If true, the\n> @@ -421,16 +487,6 @@ if (enable_scrambled_message_ids) {\n>  #             type with absl::optional, and null values are simply\n>  #             absl::nullopt.\n>  #\n> -#         hashable (optional)\n> -#             A boolean value (default false) indicating whether the C++ type is\n> -#             hashable. Set to true if true AND needed (i.e. you need to use the\n> -#             type as the key of a mojom map).\n> -#\n> -#         force_serialize (optional)\n> -#             A boolean value (default false) which disables lazy serialization\n> -#             of the typemapped type if lazy serialization is enabled for the\n> -#             mojom target applying this typemap.\n> -#\n>  # Additional typemap scope parameters:\n>  #\n>  #   traits_headers (optional)\n> @@ -621,20 +677,26 @@ template(\"mojom\") {\n>    build_metadata_filename = \"$target_gen_dir/$target_name.build_metadata\"\n>    build_metadata = {\n>    }\n> -  build_metadata.sources = rebase_path(sources_list)\n> +  build_metadata.sources = rebase_path(sources_list, target_gen_dir)\n>    build_metadata.deps = []\n>    foreach(dep, all_deps) {\n>      dep_target_gen_dir = get_label_info(dep, \"target_gen_dir\")\n>      dep_name = get_label_info(dep, \"name\")\n>      build_metadata.deps +=\n> -        [ rebase_path(\"$dep_target_gen_dir/$dep_name.build_metadata\") ]\n> +        [ rebase_path(\"$dep_target_gen_dir/$dep_name.build_metadata\",\n> +                      target_gen_dir) ]\n>    }\n>    write_file(build_metadata_filename, build_metadata, \"json\")\n>  \n> -  generate_fuzzing =\n> -      (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) &&\n> +  generate_js_fuzzing =\n> +      (!defined(invoker.enable_js_fuzzing) || invoker.enable_js_fuzzing) &&\n>        enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly)\n>  \n> +  generate_mojolpm_fuzzing =\n> +      (!defined(invoker.enable_mojolpm_fuzzing) ||\n> +       invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer &&\n> +      (!defined(invoker.testonly) || !invoker.testonly)\n> +\n>    parser_target_name = \"${target_name}__parser\"\n>    parser_deps = []\n>    foreach(dep, all_deps) {\n> @@ -665,30 +727,34 @@ template(\"mojom\") {\n>          \"is_chromeos\",\n>          \"is_chromeos_ash\",\n>        ]\n> +    } else if (is_chromeos_lacros) {\n> +      enabled_features += [\n> +        \"is_chromeos\",\n> +        \"is_chromeos_lacros\",\n> +      ]\n>      } else if (is_fuchsia) {\n>        enabled_features += [ \"is_fuchsia\" ]\n>      } else if (is_ios) {\n>        enabled_features += [ \"is_ios\" ]\n> -    } else if (is_linux || is_chromeos_lacros) {\n> +    } else if (is_linux) {\n>        enabled_features += [ \"is_linux\" ]\n> -      if (is_chromeos_lacros) {\n> -        enabled_features += [\n> -          \"is_chromeos\",\n> -          \"is_chromeos_lacros\",\n> -        ]\n> -      }\n>      } else if (is_mac) {\n>        enabled_features += [ \"is_mac\" ]\n>      } else if (is_win) {\n>        enabled_features += [ \"is_win\" ]\n>      }\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(parser_target_name) {\n> +    if (is_apple) {\n> +      enabled_features += [ \"is_apple\" ]\n> +    }\n> +\n> +    action(parser_target_name) {\n> +      allow_remote = true\n> +      custom_processor = \"mojom_parser\"\n>        script = mojom_parser_script\n> -      inputs = mojom_parser_sources + [ build_metadata_filename ]\n> +      inputs = mojom_parser_sources + ply_sources + [ build_metadata_filename ]\n>        sources = sources_list\n> -      deps = parser_deps\n> +      public_deps = parser_deps\n>        outputs = []\n>        foreach(base_path, output_file_base_paths) {\n>          filename = get_path_info(base_path, \"file\")\n> @@ -698,31 +764,35 @@ template(\"mojom\") {\n>  \n>        filelist = []\n>        foreach(source, sources_list) {\n> -        filelist += [ rebase_path(source) ]\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n>        }\n> -      response_file_contents = filelist\n> +\n> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +      rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +      write_file(rsp_file, filelist)\n> +      inputs += [ rsp_file ]\n>  \n>        args = [\n>          # Resolve relative input mojom paths against both the root src dir and\n>          # the root gen dir.\n>          \"--input-root\",\n> -        rebase_path(\"//.\"),\n> +        rebase_path(\"//.\", root_build_dir),\n>          \"--input-root\",\n> -        rebase_path(root_gen_dir),\n> +        rebase_path(root_gen_dir, root_build_dir),\n>  \n>          \"--output-root\",\n> -        rebase_path(root_gen_dir),\n> +        rebase_path(root_gen_dir, root_build_dir),\n>  \n> -        \"--mojom-file-list={{response_file_name}}\",\n> +        \"--mojom-file-list=\" + rebase_path(rsp_file, root_build_dir),\n>  \n>          \"--check-imports\",\n> -        rebase_path(build_metadata_filename),\n> +        rebase_path(build_metadata_filename, root_build_dir),\n>        ]\n>  \n>        if (defined(invoker.input_root_override)) {\n>          args += [\n>            \"--input-root\",\n> -          rebase_path(invoker.input_root_override),\n> +          rebase_path(invoker.input_root_override, root_build_dir),\n>          ]\n>        }\n>  \n> @@ -738,6 +808,13 @@ template(\"mojom\") {\n>            \"--add-module-metadata\",\n>            \"webui_module_path=${invoker.webui_module_path}\",\n>          ]\n> +        if (defined(invoker.generate_webui_js_bindings) &&\n> +            invoker.generate_webui_js_bindings) {\n> +          args += [\n> +            \"--add-module-metadata\",\n> +            \"generate_webui_js=True\",\n> +          ]\n> +        }\n>        }\n>      }\n>    }\n> @@ -819,11 +896,12 @@ template(\"mojom\") {\n>        }\n>      }\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(generator_cpp_message_ids_target_name) {\n> +    action(generator_cpp_message_ids_target_name) {\n> +      allow_remote = true\n>        script = mojom_generator_script\n>        inputs = mojom_generator_sources + jinja2_sources\n> -      sources = sources_list\n> +      sources = sources_list +\n> +                [ \"$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip\" ]\n>        deps = [\n>          \":$parser_target_name\",\n>          \"//mojo/public/tools/bindings:precompile_templates\",\n> @@ -835,16 +913,22 @@ template(\"mojom\") {\n>        args = common_generator_args\n>        filelist = []\n>        foreach(source, sources_list) {\n> -        filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n>        }\n>        foreach(base_path, output_file_base_paths) {\n> +        filename = get_path_info(base_path, \"file\")\n> +        dirname = get_path_info(base_path, \"dir\")\n> +        inputs += [ \"$root_gen_dir/$dirname/${filename}-module\" ]\n>          outputs += [ \"$root_gen_dir/$base_path-shared-message-ids.h\" ]\n>        }\n>  \n> -      response_file_contents = filelist\n> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +      rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +      write_file(rsp_file, filelist)\n> +      inputs += [ rsp_file ]\n>  \n>        args += [\n> -        \"--filelist={{response_file_name}}\",\n> +        \"--filelist=\" + rebase_path(rsp_file, root_build_dir),\n>          \"--generate_non_variant_code\",\n>          \"--generate_message_ids\",\n>          \"-g\",\n> @@ -860,12 +944,13 @@ template(\"mojom\") {\n>  \n>      generator_shared_target_name = \"${target_name}_shared__generator\"\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(generator_shared_target_name) {\n> +    action(generator_shared_target_name) {\n> +      allow_remote = true\n>        visibility = [ \":*\" ]\n>        script = mojom_generator_script\n>        inputs = mojom_generator_sources + jinja2_sources\n> -      sources = sources_list\n> +      sources = sources_list +\n> +                [ \"$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip\" ]\n>        deps = [\n>          \":$parser_target_name\",\n>          \"//mojo/public/tools/bindings:precompile_templates\",\n> @@ -878,10 +963,16 @@ template(\"mojom\") {\n>        args = common_generator_args\n>        filelist = []\n>        foreach(source, sources_list) {\n> -        filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n>        }\n>        foreach(base_path, output_file_base_paths) {\n> +        # Need the mojom-module as an input to this action.\n> +        filename = get_path_info(base_path, \"file\")\n> +        dirname = get_path_info(base_path, \"dir\")\n> +        inputs += [ \"$root_gen_dir/$dirname/${filename}-module\" ]\n> +\n>          outputs += [\n> +          \"$root_gen_dir/$base_path-features.h\",\n>            \"$root_gen_dir/$base_path-params-data.h\",\n>            \"$root_gen_dir/$base_path-shared-internal.h\",\n>            \"$root_gen_dir/$base_path-shared.cc\",\n> @@ -889,10 +980,13 @@ template(\"mojom\") {\n>          ]\n>        }\n>  \n> -      response_file_contents = filelist\n> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +      rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +      write_file(rsp_file, filelist)\n> +      inputs += [ rsp_file ]\n>  \n>        args += [\n> -        \"--filelist={{response_file_name}}\",\n> +        \"--filelist=\" + rebase_path(rsp_file, root_build_dir),\n>          \"--generate_non_variant_code\",\n>          \"-g\",\n>          \"c++\",\n> @@ -923,12 +1017,14 @@ template(\"mojom\") {\n>      if (defined(invoker.testonly)) {\n>        testonly = invoker.testonly\n>      }\n> +    configs += [ \"//build/config/compiler:wexit_time_destructors\" ]\n>      deps = []\n>      public_deps = []\n>      if (output_file_base_paths != []) {\n>        sources = []\n>        foreach(base_path, output_file_base_paths) {\n>          sources += [\n> +          \"$root_gen_dir/$base_path-features.h\",\n>            \"$root_gen_dir/$base_path-params-data.h\",\n>            \"$root_gen_dir/$base_path-shared-internal.h\",\n>            \"$root_gen_dir/$base_path-shared.cc\",\n> @@ -972,7 +1068,7 @@ template(\"mojom\") {\n>      }\n>    }\n>  \n> -  if (generate_fuzzing) {\n> +  if (generate_mojolpm_fuzzing) {\n>      # This block generates the proto files used for the MojoLPM fuzzer,\n>      # and the corresponding proto targets that will be linked in the fuzzer\n>      # targets. These are independent of the typemappings, and can be done\n> @@ -981,11 +1077,15 @@ template(\"mojom\") {\n>      generator_mojolpm_proto_target_name =\n>          \"${target_name}_mojolpm_proto_generator\"\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(generator_mojolpm_proto_target_name) {\n> +    action(generator_mojolpm_proto_target_name) {\n> +      allow_remote = true\n>        script = mojom_generator_script\n>        inputs = mojom_generator_sources + jinja2_sources\n> -      sources = invoker.sources\n> +      sources =\n> +          invoker.sources + [\n> +            \"$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip\",\n> +            \"$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip\",\n> +          ]\n>        deps = [\n>          \":$parser_target_name\",\n>          \"//mojo/public/tools/bindings:precompile_templates\",\n> @@ -994,15 +1094,37 @@ template(\"mojom\") {\n>        outputs = []\n>        args = common_generator_args\n>        filelist = []\n> -      foreach(source, invoker.sources) {\n> -        filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +\n> +      # Split the input into generated and non-generated source files. They\n> +      # need to be processed separately.\n> +      gen_dir_path_wildcard = get_path_info(\"//\", \"gen_dir\") + \"/*\"\n> +      non_gen_sources =\n> +          filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])\n> +      gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])\n> +\n> +      foreach(source, non_gen_sources) {\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n> +        inputs += [ \"$target_gen_dir/$source-module\" ]\n>          outputs += [ \"$target_gen_dir/$source.mojolpm.proto\" ]\n>        }\n>  \n> -      response_file_contents = filelist\n> +      foreach(source, gen_sources) {\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n> +\n> +        # For generated files, we assume they're in the target_gen_dir or a\n> +        # sub-folder of it. Rebase the path so we can get the relative location.\n> +        source_file = rebase_path(source, target_gen_dir)\n> +        inputs += [ \"$target_gen_dir/$source_file-module\" ]\n> +        outputs += [ \"$target_gen_dir/$source_file.mojolpm.proto\" ]\n> +      }\n> +\n> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +      rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +      write_file(rsp_file, filelist)\n> +      inputs += [ rsp_file ]\n>  \n>        args += [\n> -        \"--filelist={{response_file_name}}\",\n> +        \"--filelist=\" + rebase_path(rsp_file, root_build_dir),\n>          \"--generate_non_variant_code\",\n>          \"-g\",\n>          \"mojolpm\",\n> @@ -1014,9 +1136,20 @@ template(\"mojom\") {\n>        proto_library(mojolpm_proto_target_name) {\n>          testonly = true\n>          generate_python = false\n> +\n> +        # Split the input into generated and non-generated source files. They\n> +        # need to be processed separately.\n> +        gen_dir_path_wildcard = get_path_info(\"//\", \"gen_dir\") + \"/*\"\n> +        non_gen_sources =\n> +            filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])\n> +        gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])\n>          sources = process_file_template(\n> -                invoker.sources,\n> +                non_gen_sources,\n>                  [ \"{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto\" ])\n> +        sources += process_file_template(\n> +                gen_sources,\n> +                [ \"{{source_dir}}/{{source_file_part}}.mojolpm.proto\" ])\n> +\n>          import_dirs = [ \"//\" ]\n>          proto_in_dir = \"${root_gen_dir}\"\n>          proto_out_dir = \".\"\n> @@ -1055,7 +1188,7 @@ template(\"mojom\") {\n>      component_macro_suffix = \"\"\n>    }\n>    if ((!defined(invoker.disable_variants) || !invoker.disable_variants) &&\n> -      !is_ios) {\n> +      use_blink) {\n>      blink_variant = {\n>        variant = \"blink\"\n>        component_macro_suffix = \"_BLINK\"\n> @@ -1149,39 +1282,6 @@ template(\"mojom\") {\n>              \"${bindings_configuration.component_macro_suffix}_IMPL\" ]\n>      }\n>  \n> -    export_args = []\n> -    export_args_overridden = false\n> -    if (defined(bindings_configuration.for_blink) &&\n> -        bindings_configuration.for_blink) {\n> -      if (defined(invoker.export_class_attribute_blink)) {\n> -        export_args_overridden = true\n> -        export_args += [\n> -          \"--export_attribute\",\n> -          invoker.export_class_attribute_blink,\n> -          \"--export_header\",\n> -          invoker.export_header_blink,\n> -        ]\n> -      }\n> -    } else if (defined(invoker.export_class_attribute)) {\n> -      export_args_overridden = true\n> -      export_args += [\n> -        \"--export_attribute\",\n> -        invoker.export_class_attribute,\n> -        \"--export_header\",\n> -        invoker.export_header,\n> -      ]\n> -    }\n> -\n> -    if (!export_args_overridden && defined(invoker.component_macro_prefix)) {\n> -      export_args += [\n> -        \"--export_attribute\",\n> -        \"COMPONENT_EXPORT(${invoker.component_macro_prefix}\" +\n> -            \"${bindings_configuration.component_macro_suffix})\",\n> -        \"--export_header\",\n> -        \"base/component_export.h\",\n> -      ]\n> -    }\n> -\n>      generate_java = false\n>      if (!cpp_only && defined(invoker.generate_java)) {\n>        generate_java = invoker.generate_java\n> @@ -1190,6 +1290,38 @@ template(\"mojom\") {\n>      type_mappings_path =\n>          \"$target_gen_dir/${target_name}${variant_suffix}__type_mappings\"\n>      if (sources_list != []) {\n> +      export_args = []\n> +      export_args_overridden = false\n> +      if (defined(bindings_configuration.for_blink) &&\n> +          bindings_configuration.for_blink) {\n> +        if (defined(invoker.export_class_attribute_blink)) {\n> +          export_args_overridden = true\n> +          export_args += [\n> +            \"--export_attribute\",\n> +            invoker.export_class_attribute_blink,\n> +            \"--export_header\",\n> +            invoker.export_header_blink,\n> +          ]\n> +        }\n> +      } else if (defined(invoker.export_class_attribute)) {\n> +        export_args_overridden = true\n> +        export_args += [\n> +          \"--export_attribute\",\n> +          invoker.export_class_attribute,\n> +          \"--export_header\",\n> +          invoker.export_header,\n> +        ]\n> +      }\n> +      if (!export_args_overridden && defined(invoker.component_macro_prefix)) {\n> +        export_args += [\n> +          \"--export_attribute\",\n> +          \"COMPONENT_EXPORT(${invoker.component_macro_prefix}\" +\n> +              \"${bindings_configuration.component_macro_suffix})\",\n> +          \"--export_header\",\n> +          \"base/component_export.h\",\n> +        ]\n> +      }\n> +\n>        generator_cpp_output_suffixes = []\n>        variant_dash_suffix = \"\"\n>        if (defined(variant)) {\n> @@ -1198,7 +1330,6 @@ template(\"mojom\") {\n>        generator_cpp_output_suffixes += [\n>          \"${variant_dash_suffix}-forward.h\",\n>          \"${variant_dash_suffix}-import-headers.h\",\n> -        \"${variant_dash_suffix}-test-utils.cc\",\n>          \"${variant_dash_suffix}-test-utils.h\",\n>          \"${variant_dash_suffix}.cc\",\n>          \"${variant_dash_suffix}.h\",\n> @@ -1207,16 +1338,28 @@ template(\"mojom\") {\n>        generator_target_name = \"${target_name}${variant_suffix}__generator\"\n>  \n>        # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(generator_target_name) {\n> +      action(generator_target_name) {\n> +        allow_remote = true\n>          visibility = [ \":*\" ]\n>          script = mojom_generator_script\n>          inputs = mojom_generator_sources + jinja2_sources\n> -        sources = sources_list\n> +        sources =\n> +            sources_list + [\n> +              \"$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip\",\n> +              type_mappings_path,\n> +            ]\n> +        if (generate_mojolpm_fuzzing &&\n> +            !defined(bindings_configuration.variant)) {\n> +          sources += [\n> +            \"$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip\",\n> +          ]\n> +        }\n>          deps = [\n>            \":$parser_target_name\",\n>            \":$type_mappings_target_name\",\n>            \"//mojo/public/tools/bindings:precompile_templates\",\n>          ]\n> +\n>          if (defined(invoker.parser_deps)) {\n>            deps += invoker.parser_deps\n>          }\n> @@ -1224,18 +1367,22 @@ template(\"mojom\") {\n>          args = common_generator_args + export_args\n>          filelist = []\n>          foreach(source, sources_list) {\n> -          filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +          filelist += [ rebase_path(source, root_build_dir) ]\n>          }\n>          foreach(base_path, output_file_base_paths) {\n> +          filename = get_path_info(base_path, \"file\")\n> +          dirname = get_path_info(base_path, \"dir\")\n> +          inputs += [ \"$root_gen_dir/$dirname/${filename}-module\" ]\n> +\n>            outputs += [\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h\",\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h\",\n> -            \"$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc\",\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h\",\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}.cc\",\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}.h\",\n>            ]\n> -          if (generate_fuzzing && !defined(bindings_configuration.variant)) {\n> +          if (generate_mojolpm_fuzzing &&\n> +              !defined(bindings_configuration.variant)) {\n>              outputs += [\n>                \"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc\",\n>                \"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h\",\n> @@ -1243,14 +1390,17 @@ template(\"mojom\") {\n>            }\n>          }\n>  \n> -        response_file_contents = filelist\n> -\n> +        # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +        rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +        write_file(rsp_file, filelist)\n> +        inputs += [ rsp_file ]\n>          args += [\n> -          \"--filelist={{response_file_name}}\",\n> +          \"--filelist=\" + rebase_path(\"$rsp_file\", root_build_dir),\n>            \"-g\",\n>          ]\n>  \n> -        if (generate_fuzzing && !defined(bindings_configuration.variant)) {\n> +        if (generate_mojolpm_fuzzing &&\n> +            !defined(bindings_configuration.variant)) {\n>            args += [ \"c++,mojolpm\" ]\n>          } else {\n>            args += [ \"c++\" ]\n> @@ -1294,6 +1444,8 @@ template(\"mojom\") {\n>                \"--extra_cpp_template_paths\",\n>                rebase_path(extra_cpp_template, root_build_dir),\n>              ]\n> +            inputs += [ extra_cpp_template ]\n> +\n>              assert(\n>                  get_path_info(extra_cpp_template, \"extension\") == \"tmpl\",\n>                  \"--extra_cpp_template_paths only accepts template files ending in extension .tmpl\")\n> @@ -1306,62 +1458,6 @@ template(\"mojom\") {\n>        }\n>      }\n>  \n> -    if (generate_fuzzing && !defined(variant)) {\n> -      # This block contains the C++ targets for the MojoLPM fuzzer, we need to\n> -      # do this here so that we can use the typemap configuration for the\n> -      # empty-variant Mojo target.\n> -\n> -      mojolpm_target_name = \"${target_name}_mojolpm\"\n> -      mojolpm_generator_target_name = \"${target_name}__generator\"\n> -      source_set(mojolpm_target_name) {\n> -        # There are still a few missing header dependencies between mojo targets\n> -        # with typemaps and the dependencies of their typemap headers. It would\n> -        # be good to enable include checking for these in the future though.\n> -        check_includes = false\n> -        testonly = true\n> -        if (defined(invoker.sources)) {\n> -          sources = process_file_template(\n> -                  invoker.sources,\n> -                  [\n> -                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc\",\n> -                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.h\",\n> -                  ])\n> -          deps = []\n> -        } else {\n> -          sources = []\n> -          deps = []\n> -        }\n> -\n> -        public_deps = [\n> -          \":$generator_shared_target_name\",\n> -\n> -          # NB: hardcoded dependency on the no-variant variant generator, since\n> -          # mojolpm only uses the no-variant type.\n> -          \":$mojolpm_generator_target_name\",\n> -          \":$mojolpm_proto_target_name\",\n> -          \"//base\",\n> -          \"//mojo/public/tools/fuzzers:mojolpm\",\n> -        ]\n> -\n> -        foreach(d, all_deps) {\n> -          # Resolve the name, so that a target //mojo/something becomes\n> -          # //mojo/something:something and we can append variant_suffix to\n> -          # get the cpp dependency name.\n> -          full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> -          public_deps += [ \"${full_name}_mojolpm\" ]\n> -        }\n> -\n> -        foreach(config, cpp_typemap_configs) {\n> -          if (defined(config.traits_deps)) {\n> -            deps += config.traits_deps\n> -          }\n> -          if (defined(config.traits_public_deps)) {\n> -            public_deps += config.traits_public_deps\n> -          }\n> -        }\n> -      }\n> -    }\n> -\n>      # Write the typemapping configuration for this target out to a file to be\n>      # validated by a Python script. This helps catch mistakes that can't\n>      # be caught by logic in GN.\n> @@ -1389,20 +1485,20 @@ template(\"mojom\") {\n>      write_file(_typemap_config_filename, _rebased_typemap_configs, \"json\")\n>      _mojom_target_name = target_name\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(_typemap_validator_target_name) {\n> +    action(_typemap_validator_target_name) {\n> +      allow_remote = true\n>        script = \"$mojom_generator_root/validate_typemap_config.py\"\n>        inputs = [ _typemap_config_filename ]\n>        outputs = [ _typemap_stamp_filename ]\n>        args = [\n>          get_label_info(_mojom_target_name, \"label_no_toolchain\"),\n> -        rebase_path(_typemap_config_filename),\n> -        rebase_path(_typemap_stamp_filename),\n> +        rebase_path(_typemap_config_filename, root_build_dir),\n> +        rebase_path(_typemap_stamp_filename, root_build_dir),\n>        ]\n>      }\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(type_mappings_target_name) {\n> +    action(type_mappings_target_name) {\n> +      allow_remote = true\n>        inputs =\n>            mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ]\n>        outputs = [ type_mappings_path ]\n> @@ -1413,6 +1509,7 @@ template(\"mojom\") {\n>          rebase_path(type_mappings_path, root_build_dir),\n>        ]\n>  \n> +      sources = []\n>        foreach(d, all_deps) {\n>          name = get_label_info(d, \"label_no_toolchain\")\n>          toolchain = get_label_info(d, \"toolchain\")\n> @@ -1422,12 +1519,11 @@ template(\"mojom\") {\n>          dependency_output_dir =\n>              get_label_info(dependency_output, \"target_gen_dir\")\n>          dependency_name = get_label_info(dependency_output, \"name\")\n> -        dependency_path =\n> -            rebase_path(\"$dependency_output_dir/${dependency_name}\",\n> -                        root_build_dir)\n> +        dependency_path = \"$dependency_output_dir/${dependency_name}\"\n> +        sources += [ dependency_path ]\n>          args += [\n>            \"--dependency\",\n> -          dependency_path,\n> +          rebase_path(dependency_path, root_build_dir),\n>          ]\n>        }\n>  \n> @@ -1485,11 +1581,15 @@ template(\"mojom\") {\n>        if (defined(output_name_override)) {\n>          output_name = output_name_override\n>        }\n> -      visibility = output_visibility + [ \":$output_target_name\" ]\n> +      visibility = output_visibility + [\n> +                     \":$output_target_name\",\n> +                     \":${target_name}_mojolpm\",\n> +                   ]\n>        if (defined(invoker.testonly)) {\n>          testonly = invoker.testonly\n>        }\n>        defines = export_defines\n> +      configs += [ \"//build/config/compiler:wexit_time_destructors\" ]\n>        configs += extra_configs\n>        if (output_file_base_paths != []) {\n>          sources = []\n> @@ -1578,13 +1678,81 @@ template(\"mojom\") {\n>        }\n>      }\n>  \n> +    if (generate_mojolpm_fuzzing && !defined(variant)) {\n> +      # This block contains the C++ targets for the MojoLPM fuzzer, we need to\n> +      # do this here so that we can use the typemap configuration for the\n> +      # empty-variant Mojo target.\n> +\n> +      mojolpm_target_name = \"${target_name}_mojolpm\"\n> +      mojolpm_generator_target_name = \"${target_name}__generator\"\n> +      source_set(mojolpm_target_name) {\n> +        # There are still a few missing header dependencies between mojo targets\n> +        # with typemaps and the dependencies of their typemap headers. It would\n> +        # be good to enable include checking for these in the future though.\n> +        check_includes = false\n> +        testonly = true\n> +        if (defined(invoker.sources)) {\n> +          # Split the input into generated and non-generated source files. They\n> +          # need to be processed separately.\n> +          gen_dir_path_wildcard = get_path_info(\"//\", \"gen_dir\") + \"/*\"\n> +          non_gen_sources =\n> +              filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])\n> +          gen_sources =\n> +              filter_include(invoker.sources, [ gen_dir_path_wildcard ])\n> +          sources = process_file_template(\n> +                  non_gen_sources,\n> +                  [\n> +                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc\",\n> +                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.h\",\n> +                  ])\n> +          sources += process_file_template(\n> +                  gen_sources,\n> +                  [\n> +                    \"{{source_dir}}/{{source_file_part}}-mojolpm.cc\",\n> +                    \"{{source_dir}}/{{source_file_part}}-mojolpm.h\",\n> +                  ])\n> +          deps = [ \":$output_target_name\" ]\n> +        } else {\n> +          sources = []\n> +          deps = []\n> +        }\n> +\n> +        public_deps = [\n> +          \":$generator_shared_target_name\",\n> +\n> +          # NB: hardcoded dependency on the no-variant variant generator, since\n> +          # mojolpm only uses the no-variant type.\n> +          \":$mojolpm_generator_target_name\",\n> +          \":$mojolpm_proto_target_name\",\n> +          \"//base\",\n> +          \"//mojo/public/tools/fuzzers:mojolpm\",\n> +        ]\n> +\n> +        foreach(d, all_deps) {\n> +          # Resolve the name, so that a target //mojo/something becomes\n> +          # //mojo/something:something and we can append variant_suffix to\n> +          # get the cpp dependency name.\n> +          full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +          public_deps += [ \"${full_name}_mojolpm\" ]\n> +        }\n> +\n> +        foreach(config, cpp_typemap_configs) {\n> +          if (defined(config.traits_deps)) {\n> +            deps += config.traits_deps\n> +          }\n> +          if (defined(config.traits_public_deps)) {\n> +            public_deps += config.traits_public_deps\n> +          }\n> +        }\n> +      }\n> +    }\n> +\n>      if (generate_java && is_android) {\n>        import(\"//build/config/android/rules.gni\")\n>  \n>        java_generator_target_name = target_name + \"_java__generator\"\n>        if (sources_list != []) {\n> -        # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -        python2_action(java_generator_target_name) {\n> +        action(java_generator_target_name) {\n>            script = mojom_generator_script\n>            inputs = mojom_generator_sources + jinja2_sources\n>            sources = sources_list\n> @@ -1597,7 +1765,7 @@ template(\"mojom\") {\n>            args = common_generator_args\n>            filelist = []\n>            foreach(source, sources_list) {\n> -            filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +            filelist += [ rebase_path(source, root_build_dir) ]\n>            }\n>            foreach(base_path, output_file_base_paths) {\n>              outputs += [ \"$root_gen_dir/$base_path.srcjar\" ]\n> @@ -1624,8 +1792,7 @@ template(\"mojom\") {\n>  \n>        java_srcjar_target_name = target_name + \"_java_sources\"\n>  \n> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(java_srcjar_target_name) {\n> +      action(java_srcjar_target_name) {\n>          script = \"//build/android/gyp/zip.py\"\n>          inputs = []\n>          if (output_file_base_paths != []) {\n> @@ -1651,7 +1818,6 @@ template(\"mojom\") {\n>        android_library(java_target_name) {\n>          forward_variables_from(invoker, [ \"enable_bytecode_checks\" ])\n>          deps = [\n> -          \"//base:base_java\",\n>            \"//mojo/public/java:bindings_java\",\n>            \"//mojo/public/java:system_java\",\n>            \"//third_party/androidx:androidx_annotation_annotation_java\",\n> @@ -1673,21 +1839,36 @@ template(\"mojom\") {\n>      }\n>    }\n>  \n> -  use_typescript_for_target =\n> -      enable_typescript_bindings && defined(invoker.use_typescript_sources) &&\n> -      invoker.use_typescript_sources\n> -\n> -  if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) {\n> -    not_needed(invoker, [ \"use_typescript_sources\" ])\n> +  if (defined(invoker.generate_webui_js_bindings)) {\n> +    assert(is_chromeos_ash,\n> +           \"generate_webui_js_bindings can only be used on ChromeOS Ash\")\n> +    assert(invoker.generate_webui_js_bindings,\n> +           \"generate_webui_js_bindings should be set to true or removed\")\n>    }\n>  \n> -  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&\n> -      !use_typescript_for_target) {\n> +  use_typescript_for_target = defined(invoker.webui_module_path) &&\n> +                              !defined(invoker.generate_webui_js_bindings)\n> +\n> +  generate_legacy_js = !use_typescript_for_target ||\n> +                       (defined(invoker.generate_legacy_js_bindings) &&\n> +                        invoker.generate_legacy_js_bindings)\n> +\n> +  if (!use_typescript_for_target &&\n> +      defined(invoker.generate_legacy_js_bindings)) {\n> +    not_needed(invoker, [ \"generate_legacy_js_bindings\" ])\n> +  }\n> +\n> +  # Targets needed by both TS and JS bindings targets. These are needed\n> +  # unconditionally for JS bindings targets, and are needed for TS bindings\n> +  # targets when generate_legacy_js_bindings is true. This option is provided\n> +  # since the legacy bindings are needed by Blink tests and non-Chromium users,\n> +  # which are not expected to migrate to modules or TypeScript.\n> +  if (generate_legacy_js && (generate_js_fuzzing ||\n> +                             !defined(invoker.cpp_only) || !invoker.cpp_only)) {\n>      if (sources_list != []) {\n>        generator_js_target_name = \"${target_name}_js__generator\"\n>  \n> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(generator_js_target_name) {\n> +      action(generator_js_target_name) {\n>          script = mojom_generator_script\n>          inputs = mojom_generator_sources + jinja2_sources\n>          sources = sources_list\n> @@ -1702,19 +1883,18 @@ template(\"mojom\") {\n>          args = common_generator_args\n>          filelist = []\n>          foreach(source, sources_list) {\n> -          filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +          filelist += [ rebase_path(source, root_build_dir) ]\n>          }\n>          foreach(base_path, output_file_base_paths) {\n>            outputs += [\n>              \"$root_gen_dir/$base_path.js\",\n> -            \"$root_gen_dir/$base_path.externs.js\",\n>              \"$root_gen_dir/$base_path.m.js\",\n>              \"$root_gen_dir/$base_path-lite.js\",\n> -            \"$root_gen_dir/$base_path.html\",\n>              \"$root_gen_dir/$base_path-lite-for-compile.js\",\n>            ]\n>  \n> -          if (defined(invoker.webui_module_path)) {\n> +          if (defined(invoker.webui_module_path) &&\n> +              !use_typescript_for_target) {\n>              outputs += [ \"$root_gen_dir/mojom-webui/$base_path-webui.js\" ]\n>            }\n>          }\n> @@ -1725,7 +1905,6 @@ template(\"mojom\") {\n>            \"--filelist={{response_file_name}}\",\n>            \"-g\",\n>            \"javascript\",\n> -          \"--js_bindings_mode=new\",\n>          ]\n>  \n>          if (defined(invoker.js_generate_struct_deserializers) &&\n> @@ -1739,7 +1918,7 @@ template(\"mojom\") {\n>            args += message_scrambling_args\n>          }\n>  \n> -        if (generate_fuzzing) {\n> +        if (generate_js_fuzzing) {\n>            args += [ \"--generate_fuzzing\" ]\n>          }\n>        }\n> @@ -1783,31 +1962,13 @@ template(\"mojom\") {\n>          data_deps += [ \"${full_name}_js_data_deps\" ]\n>        }\n>      }\n> +  }\n>  \n> -    js_library_target_name = \"${target_name}_js_library\"\n> -    if (sources_list != []) {\n> -      js_library(js_library_target_name) {\n> -        extra_public_deps = [ \":$generator_js_target_name\" ]\n> -        sources = []\n> -        foreach(base_path, output_file_base_paths) {\n> -          sources += [ \"$root_gen_dir/${base_path}-lite.js\" ]\n> -        }\n> -        externs_list = [\n> -          \"${externs_path}/mojo_core.js\",\n> -          \"${externs_path}/pending.js\",\n> -        ]\n> -\n> -        deps = []\n> -        foreach(d, all_deps) {\n> -          full_name = get_label_info(d, \"label_no_toolchain\")\n> -          deps += [ \"${full_name}_js_library\" ]\n> -        }\n> -      }\n> -    } else {\n> -      group(js_library_target_name) {\n> -      }\n> -    }\n> -\n> +  # js_library() closure compiler targets, primarily used on ChromeOS. Only\n> +  # generate these targets if the mojom target is not C++ only and is not using\n> +  # TypeScript.\n> +  if (generate_mojom_closure_libraries &&\n> +      (!defined(invoker.cpp_only) || !invoker.cpp_only) && generate_legacy_js) {\n>      js_library_for_compile_target_name = \"${target_name}_js_library_for_compile\"\n>      if (sources_list != []) {\n>        js_library(js_library_for_compile_target_name) {\n> @@ -1834,35 +1995,9 @@ template(\"mojom\") {\n>        }\n>      }\n>  \n> -    js_modules_target_name = \"${target_name}_js_modules\"\n> -    if (sources_list != []) {\n> -      js_library(js_modules_target_name) {\n> -        extra_public_deps = [ \":$generator_js_target_name\" ]\n> -        sources = []\n> -        foreach(base_path, output_file_base_paths) {\n> -          sources += [ \"$root_gen_dir/${base_path}.m.js\" ]\n> -        }\n> -        externs_list = [\n> -          \"${externs_path}/mojo_core.js\",\n> -          \"${externs_path}/pending.js\",\n> -        ]\n> -        if (defined(invoker.disallow_native_types) &&\n> -            invoker.disallow_native_types) {\n> -          deps = []\n> -        } else {\n> -          deps = [ \"//mojo/public/js:bindings_uncompiled\" ]\n> -        }\n> -        foreach(d, all_deps) {\n> -          full_name = get_label_info(d, \"label_no_toolchain\")\n> -          deps += [ \"${full_name}_js_modules\" ]\n> -        }\n> -      }\n> -    } else {\n> -      group(js_modules_target_name) {\n> -      }\n> -    }\n> -\n> -    if (defined(invoker.webui_module_path)) {\n> +    # WebUI specific closure targets, not needed by targets that are generating\n> +    # TypeScript WebUI bindings or by legacy-only targets.\n> +    if (defined(invoker.webui_module_path) && !use_typescript_for_target) {\n>        webui_js_target_name = \"${target_name}_webui_js\"\n>        if (sources_list != []) {\n>          js_library(webui_js_target_name) {\n> @@ -1890,46 +2025,38 @@ template(\"mojom\") {\n>          group(webui_js_target_name) {\n>          }\n>        }\n> +\n> +      webui_grdp_target_name = \"${target_name}_webui_grdp\"\n> +      out_grd = \"$target_gen_dir/${target_name}_webui_resources.grdp\"\n> +      grd_prefix = \"${target_name}_webui\"\n> +      generate_grd(webui_grdp_target_name) {\n> +        grd_prefix = grd_prefix\n> +        out_grd = out_grd\n> +\n> +        deps = [ \":$webui_js_target_name\" ]\n> +\n> +        input_files = []\n> +        foreach(base_path, output_file_base_paths) {\n> +          input_files += [ \"${base_path}-webui.js\" ]\n> +        }\n> +\n> +        input_files_base_dir =\n> +            rebase_path(\"$root_gen_dir/mojom-webui\", \"$root_build_dir\")\n> +      }\n>      }\n>    }\n> -  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&\n> -      use_typescript_for_target) {\n> -    generator_js_target_names = []\n> -    source_filelist = []\n> -    foreach(source, sources_list) {\n> -      source_filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> -    }\n> -\n> -    dependency_types = [\n> -      {\n> -        name = \"regular\"\n> -        ts_extension = \".ts\"\n> -        js_extension = \".js\"\n> -      },\n> -      {\n> -        name = \"es_modules\"\n> -        ts_extension = \".m.ts\"\n> -        js_extension = \".m.js\"\n> -      },\n> -    ]\n> -\n> -    foreach(dependency_type, dependency_types) {\n> -      ts_outputs = []\n> -      js_outputs = []\n> -\n> -      foreach(base_path, output_file_base_paths) {\n> -        ts_outputs +=\n> -            [ \"$root_gen_dir/$base_path-lite${dependency_type.ts_extension}\" ]\n> -        js_outputs +=\n> -            [ \"$root_gen_dir/$base_path-lite${dependency_type.js_extension}\" ]\n> +  if ((generate_js_fuzzing || !defined(invoker.cpp_only) ||\n> +       !invoker.cpp_only) && use_typescript_for_target) {\n> +    if (sources_list != []) {\n> +      source_filelist = []\n> +      foreach(source, sources_list) {\n> +        source_filelist += [ rebase_path(source, root_build_dir) ]\n>        }\n>  \n>        # Generate Typescript bindings.\n> -      generator_ts_target_name =\n> -          \"${target_name}_${dependency_type.name}__ts__generator\"\n> +      generator_ts_target_name = \"${target_name}_ts__generator\"\n>  \n> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(generator_ts_target_name) {\n> +      action(generator_ts_target_name) {\n>          script = mojom_generator_script\n>          inputs = mojom_generator_sources + jinja2_sources\n>          sources = sources_list\n> @@ -1938,7 +2065,10 @@ template(\"mojom\") {\n>            \"//mojo/public/tools/bindings:precompile_templates\",\n>          ]\n>  \n> -        outputs = ts_outputs\n> +        outputs = []\n> +        foreach(base_path, output_file_base_paths) {\n> +          outputs += [ \"$root_gen_dir/$base_path-webui.ts\" ]\n> +        }\n>          args = common_generator_args\n>          response_file_contents = source_filelist\n>  \n> @@ -1948,98 +2078,21 @@ template(\"mojom\") {\n>            \"typescript\",\n>          ]\n>  \n> -        if (dependency_type.name == \"es_modules\") {\n> -          args += [ \"--ts_use_es_modules\" ]\n> +        if (!defined(invoker.scramble_message_ids) ||\n> +            invoker.scramble_message_ids) {\n> +          inputs += message_scrambling_inputs\n> +          args += message_scrambling_args\n>          }\n>  \n> -        # TODO(crbug.com/1007587): Support scramble_message_ids.\n> +        if (defined(invoker.js_generate_struct_deserializers) &&\n> +            invoker.js_generate_struct_deserializers) {\n> +          args += [ \"--js_generate_struct_deserializers\" ]\n> +        }\n> +\n> +        # TODO(crbug.com/1007587): Support scramble_message_ids if above is\n> +        # insufficient.\n>          # TODO(crbug.com/1007591): Support generate_fuzzing.\n>        }\n> -\n> -      # Create tsconfig.json for the generated Typescript.\n> -      tsconfig_filename =\n> -          \"$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json\"\n> -      tsconfig = {\n> -      }\n> -      tsconfig.compilerOptions = {\n> -        composite = true\n> -        target = \"es6\"\n> -        module = \"es6\"\n> -        lib = [\n> -          \"es6\",\n> -          \"esnext.bigint\",\n> -        ]\n> -        strict = true\n> -      }\n> -      tsconfig.files = []\n> -      foreach(base_path, output_file_base_paths) {\n> -        tsconfig.files += [ rebase_path(\n> -                \"$root_gen_dir/$base_path-lite${dependency_type.ts_extension}\",\n> -                target_gen_dir,\n> -                root_gen_dir) ]\n> -      }\n> -      tsconfig.references = []\n> -\n> -      # Get tsconfigs for deps.\n> -      foreach(d, all_deps) {\n> -        dep_target_gen_dir = rebase_path(get_label_info(d, \"target_gen_dir\"))\n> -        dep_name = get_label_info(d, \"name\")\n> -        reference = {\n> -        }\n> -        reference.path = \"$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json\"\n> -        tsconfig.references += [ reference ]\n> -      }\n> -      write_file(tsconfig_filename, tsconfig, \"json\")\n> -\n> -      # Compile previously generated Typescript to Javascript.\n> -      generator_js_target_name =\n> -          \"${target_name}_${dependency_type.name}__js__generator\"\n> -      generator_js_target_names += [ generator_js_target_name ]\n> -\n> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(generator_js_target_name) {\n> -        script = \"$mojom_generator_root/compile_typescript.py\"\n> -        sources = ts_outputs\n> -        outputs = js_outputs\n> -        public_deps = [ \":$generator_ts_target_name\" ]\n> -        foreach(d, all_deps) {\n> -          full_name = get_label_info(d, \"label_no_toolchain\")\n> -          public_deps +=\n> -              [ \"${full_name}_${dependency_type.name}__js__generator\" ]\n> -        }\n> -\n> -        absolute_tsconfig_path =\n> -            rebase_path(tsconfig_filename, \"\", target_gen_dir)\n> -        args = [ \"--tsconfig_path=$absolute_tsconfig_path\" ]\n> -      }\n> -    }\n> -\n> -    js_target_name = target_name + \"_js\"\n> -    group(js_target_name) {\n> -      public_deps = []\n> -      if (sources_list != []) {\n> -        foreach(generator_js_target_name, generator_js_target_names) {\n> -          public_deps += [ \":$generator_js_target_name\" ]\n> -        }\n> -      }\n> -\n> -      foreach(d, all_deps) {\n> -        full_name = get_label_info(d, \"label_no_toolchain\")\n> -        public_deps += [ \"${full_name}_js\" ]\n> -      }\n> -    }\n> -\n> -    group(js_data_deps_target_name) {\n> -      data = js_outputs\n> -      deps = []\n> -      foreach(generator_js_target_name, generator_js_target_names) {\n> -        deps += [ \":$generator_js_target_name\" ]\n> -      }\n> -      data_deps = []\n> -      foreach(d, all_deps) {\n> -        full_name = get_label_info(d, \"label_no_toolchain\")\n> -        data_deps += [ \"${full_name}_js_data_deps\" ]\n> -      }\n>      }\n>    }\n>  }\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n> index da9efc71cefd..8c641c2aa8a6 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -57,10 +57,17 @@ _BUILTIN_GENERATORS = {\n>      \"typescript\": \"mojom_ts_generator\",\n>  }\n>  \n> +_BUILTIN_CHECKS = {\n> +    \"attributes\": \"mojom_attributes_check\",\n> +    \"definitions\": \"mojom_definitions_check\",\n> +    \"features\": \"mojom_interface_feature_check\",\n> +    \"restrictions\": \"mojom_restrictions_check\",\n> +}\n> +\n>  \n>  def LoadGenerators(generators_string):\n>    if not generators_string:\n> -    return []  # No generators.\n> +    return {}  # No generators.\n>  \n>    generators = {}\n>    for generator_name in [s.strip() for s in generators_string.split(\",\")]:\n> @@ -74,6 +81,21 @@ def LoadGenerators(generators_string):\n>    return generators\n>  \n>  \n> +def LoadChecks(checks_string):\n> +  if not checks_string:\n> +    return {}  # No checks.\n> +\n> +  checks = {}\n> +  for check_name in [s.strip() for s in checks_string.split(\",\")]:\n> +    check = check_name.lower()\n> +    if check not in _BUILTIN_CHECKS:\n> +      print(\"Unknown check name %s\" % check_name)\n> +      sys.exit(1)\n> +    check_module = importlib.import_module(\"checks.%s\" % _BUILTIN_CHECKS[check])\n> +    checks[check] = check_module\n> +  return checks\n> +\n> +\n>  def MakeImportStackMessage(imported_filename_stack):\n>    \"\"\"Make a (human-readable) message listing a chain of imports. (Returned\n>    string begins with a newline (if nonempty) and does not end with one.)\"\"\"\n> @@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack):\n>                      zip(imported_filename_stack[1:], imported_filename_stack)]))\n>  \n>  \n> -class RelativePath(object):\n> +class RelativePath:\n>    \"\"\"Represents a path relative to the source tree or generated output dir.\"\"\"\n>  \n>    def __init__(self, path, source_root, output_dir):\n> @@ -142,7 +164,7 @@ def ReadFileContents(filename):\n>      return f.read()\n>  \n>  \n> -class MojomProcessor(object):\n> +class MojomProcessor:\n>    \"\"\"Takes parsed mojom modules and generates language bindings from them.\n>  \n>    Attributes:\n> @@ -169,8 +191,8 @@ class MojomProcessor(object):\n>      if 'c++' in self._typemap:\n>        self._typemap['mojolpm'] = self._typemap['c++']\n>  \n> -  def _GenerateModule(self, args, remaining_args, generator_modules,\n> -                      rel_filename, imported_filename_stack):\n> +  def _GenerateModule(self, args, remaining_args, check_modules,\n> +                      generator_modules, rel_filename, imported_filename_stack):\n>      # Return the already-generated module.\n>      if rel_filename.path in self._processed_files:\n>        return self._processed_files[rel_filename.path]\n> @@ -190,12 +212,16 @@ class MojomProcessor(object):\n>        ScrambleMethodOrdinals(module.interfaces, salt)\n>  \n>      if self._should_generate(rel_filename.path):\n> +      # Run checks on module first.\n> +      for check_module in check_modules.values():\n> +        checker = check_module.Check(module)\n> +        checker.CheckModule()\n> +      # Then run generation.\n>        for language, generator_module in generator_modules.items():\n>          generator = generator_module.Generator(\n>              module, args.output_dir, typemap=self._typemap.get(language, {}),\n>              variant=args.variant, bytecode_path=args.bytecode_path,\n>              for_blink=args.for_blink,\n> -            js_bindings_mode=args.js_bindings_mode,\n>              js_generate_struct_deserializers=\\\n>                      args.js_generate_struct_deserializers,\n>              export_attribute=args.export_attribute,\n> @@ -234,6 +260,7 @@ def _Generate(args, remaining_args):\n>        args.import_directories[idx] = RelativePath(tokens[0], args.depth,\n>                                                    args.output_dir)\n>    generator_modules = LoadGenerators(args.generators_string)\n> +  check_modules = LoadChecks(args.checks_string)\n>  \n>    fileutil.EnsureDirectoryExists(args.output_dir)\n>  \n> @@ -246,7 +273,7 @@ def _Generate(args, remaining_args):\n>  \n>    for filename in args.filename:\n>      processor._GenerateModule(\n> -        args, remaining_args, generator_modules,\n> +        args, remaining_args, check_modules, generator_modules,\n>          RelativePath(filename, args.depth, args.output_dir), [])\n>  \n>    return 0\n> @@ -286,6 +313,12 @@ def main():\n>                                 metavar=\"GENERATORS\",\n>                                 default=\"c++,javascript,java,mojolpm\",\n>                                 help=\"comma-separated list of generators\")\n> +  generate_parser.add_argument(\"-c\",\n> +                               \"--checks\",\n> +                               dest=\"checks_string\",\n> +                               metavar=\"CHECKS\",\n> +                               default=\",\".join(_BUILTIN_CHECKS.keys()),\n> +                               help=\"comma-separated list of checks\")\n>    generate_parser.add_argument(\n>        \"--gen_dir\", dest=\"gen_directories\", action=\"append\", metavar=\"directory\",\n>        default=[], help=\"add a directory to be searched for the syntax trees.\")\n> @@ -308,11 +341,6 @@ def main():\n>    generate_parser.add_argument(\"--for_blink\", action=\"store_true\",\n>                                 help=\"Use WTF types as generated types for mojo \"\n>                                 \"string/array/map.\")\n> -  generate_parser.add_argument(\n> -      \"--js_bindings_mode\", choices=[\"new\", \"old\"], default=\"old\",\n> -      help=\"This option only affects the JavaScript bindings. The value could \"\n> -      \"be \\\"new\\\" to generate new-style lite JS bindings in addition to the \"\n> -      \"old, or \\\"old\\\" to only generate old bindings.\")\n>    generate_parser.add_argument(\n>        \"--js_generate_struct_deserializers\", action=\"store_true\",\n>        help=\"Generate javascript deserialize methods for structs in \"\n> @@ -387,4 +415,10 @@ def main():\n>  \n>  if __name__ == \"__main__\":\n>    with crbug_1001171.DumpStateOnLookupError():\n> -    sys.exit(main())\n> +    ret = main()\n> +    # Exit without running GC, which can save multiple seconds due to the large\n> +    # number of object created. But flush is necessary as os._exit doesn't do\n> +    # that.\n> +    sys.stdout.flush()\n> +    sys.stderr.flush()\n> +    os._exit(ret)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n> index bddbe3f4c580..761922b6777e 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage\n>  from mojom_bindings_generator import ScrambleMethodOrdinals\n>  \n>  \n> -class FakeIface(object):\n> +class FakeIface:\n>    def __init__(self):\n>      self.mojom_name = None\n>      self.methods = None\n>  \n>  \n> -class FakeMethod(object):\n> +class FakeMethod:\n>    def __init__(self, explicit_ordinal=None):\n>      self.explicit_ordinal = explicit_ordinal\n>      self.ordinal = explicit_ordinal\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py b/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n> deleted file mode 100755\n> index 15f0e3bac1f7..000000000000\n> --- a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n> +++ /dev/null\n> @@ -1,119 +0,0 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\"\"\"Downgrades *.mojom files to the old mojo types for remotes and receivers.\"\"\"\n> -\n> -import argparse\n> -import fnmatch\n> -import os\n> -import re\n> -import shutil\n> -import sys\n> -import tempfile\n> -\n> -# List of patterns and replacements to match and use against the contents of a\n> -# mojo file. Each replacement string will be used with Python string's format()\n> -# function, so the '{}' substring is used to mark where the mojo type should go.\n> -_MOJO_REPLACEMENTS = {\n> -    r'pending_remote': r'{}',\n> -    r'pending_receiver': r'{}&',\n> -    r'pending_associated_remote': r'associated {}',\n> -    r'pending_associated_receiver': r'associated {}&',\n> -}\n> -\n> -# Pre-compiled regular expression that matches against any of the replacements.\n> -_REGEXP_PATTERN = re.compile(\n> -    r'|'.join(\n> -        ['{}\\s*<\\s*(.*?)\\s*>'.format(k) for k in _MOJO_REPLACEMENTS.keys()]),\n> -    flags=re.DOTALL)\n> -\n> -\n> -def ReplaceFunction(match_object):\n> -  \"\"\"Returns the right replacement for the string matched against the regexp.\"\"\"\n> -  for index, (match, repl) in enumerate(_MOJO_REPLACEMENTS.items(), 1):\n> -    if match_object.group(0).startswith(match):\n> -      return repl.format(match_object.group(index))\n> -\n> -\n> -def DowngradeFile(path, output_dir=None):\n> -  \"\"\"Downgrades the mojom file specified by |path| to the old mojo types.\n> -\n> -  Optionally pass |output_dir| to place the result under a separate output\n> -  directory, preserving the relative path to the file included in |path|.\n> -  \"\"\"\n> -  # Use a temporary file to dump the new contents after replacing the patterns.\n> -  with open(path) as src_mojo_file:\n> -    with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_mojo_file:\n> -      tmp_contents = _REGEXP_PATTERN.sub(ReplaceFunction, src_mojo_file.read())\n> -      tmp_mojo_file.write(tmp_contents)\n> -\n> -  # Files should be placed in the desired output directory\n> -  if output_dir:\n> -    output_filepath = os.path.join(output_dir, os.path.basename(path))\n> -    if not os.path.exists(output_dir):\n> -      os.makedirs(output_dir)\n> -  else:\n> -    output_filepath = path\n> -\n> -  # Write the new contents preserving the original file's attributes.\n> -  shutil.copystat(path, tmp_mojo_file.name)\n> -  shutil.move(tmp_mojo_file.name, output_filepath)\n> -\n> -  # Make sure to \"touch\" the new file so that access, modify and change times\n> -  # are always newer than the source file's, otherwise Modify time will be kept\n> -  # as per the call to shutil.copystat(), causing unnecessary generations of the\n> -  # output file in subsequent builds due to ninja considering it dirty.\n> -  os.utime(output_filepath, None)\n> -\n> -\n> -def DowngradeDirectory(path, output_dir=None):\n> -  \"\"\"Downgrades mojom files inside directory |path| to the old mojo types.\n> -\n> -  Optionally pass |output_dir| to place the result under a separate output\n> -  directory, preserving the relative path to the file included in |path|.\n> -  \"\"\"\n> -  # We don't have recursive glob.glob() nor pathlib.Path.rglob() in Python 2.7\n> -  mojom_filepaths = []\n> -  for dir_path, _, filenames in os.walk(path):\n> -    for filename in fnmatch.filter(filenames, \"*mojom\"):\n> -      mojom_filepaths.append(os.path.join(dir_path, filename))\n> -\n> -  for path in mojom_filepaths:\n> -    absolute_dirpath = os.path.dirname(os.path.abspath(path))\n> -    if output_dir:\n> -      dest_dirpath = output_dir + absolute_dirpath\n> -    else:\n> -      dest_dirpath = absolute_dirpath\n> -    DowngradeFile(path, dest_dirpath)\n> -\n> -\n> -def DowngradePath(src_path, output_dir=None):\n> -  \"\"\"Downgrades the mojom files pointed by |src_path| to the old mojo types.\n> -\n> -  Optionally pass |output_dir| to place the result under a separate output\n> -  directory, preserving the relative path to the file included in |path|.\n> -  \"\"\"\n> -  if os.path.isdir(src_path):\n> -    DowngradeDirectory(src_path, output_dir)\n> -  elif os.path.isfile(src_path):\n> -    DowngradeFile(src_path, output_dir)\n> -  else:\n> -    print(\">>> {} not pointing to a valid file or directory\".format(src_path))\n> -    sys.exit(1)\n> -\n> -\n> -def main():\n> -  parser = argparse.ArgumentParser(\n> -      description=\"Downgrade *.mojom files to use the old mojo types.\")\n> -  parser.add_argument(\n> -      \"srcpath\", help=\"path to the file or directory to apply the conversion\")\n> -  parser.add_argument(\n> -      \"--outdir\", help=\"the directory to place the converted file(s) under\")\n> -  args = parser.parse_args()\n> -\n> -  DowngradePath(args.srcpath, args.outdir)\n> -\n> -\n> -if __name__ == \"__main__\":\n> -  sys.exit(main())\n> diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n> index f1783d59b951..6bb7a209310e 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -17,7 +17,8 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename):\n>    ])\n>    _SUPPORTED_TYPE_KEYS = set([\n>        'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',\n> -      'move_only', 'nullable_is_same_type'\n> +      'move_only', 'nullable_is_same_type', 'forward_declaration',\n> +      'default_constructible'\n>    ])\n>    with open(config_filename, 'r') as f:\n>      for config in json.load(f):\n> diff --git a/utils/ipc/mojo/public/tools/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/BUILD.gn\n> new file mode 100644\n> index 000000000000..eafb95a18ad1\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/BUILD.gn\n> @@ -0,0 +1,18 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +group(\"tests\") {\n> +  data = [\n> +    \"check_stable_mojom_compatibility_unittest.py\",\n> +    \"check_stable_mojom_compatibility.py\",\n> +    \"const_unittest.py\",\n> +    \"enum_unittest.py\",\n> +    \"feature_unittest.py\",\n> +    \"mojom_parser_test_case.py\",\n> +    \"mojom_parser_unittest.py\",\n> +    \"mojom_parser.py\",\n> +    \"stable_attribute_unittest.py\",\n> +    \"version_compatibility_unittest.py\",\n> +  ]\n> +}\n> diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n> index 08bd672f5ba5..35cd1cfde155 100755\n> --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n> @@ -1,5 +1,5 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +#!/usr/bin/env python3\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Verifies backward-compatibility of mojom type changes.\n> @@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to prevent developers from making\n>  breaking changes to stable mojoms.\"\"\"\n>  \n>  import argparse\n> -import errno\n>  import io\n>  import json\n>  import os\n>  import os.path\n> -import shutil\n> -import six\n>  import sys\n> -import tempfile\n>  \n>  from mojom.generate import module\n>  from mojom.generate import translate\n>  from mojom.parse import parser\n>  \n> +# pylint: disable=raise-missing-from\n> +\n>  \n>  class ParseError(Exception):\n>    pass\n> @@ -41,6 +39,8 @@ def _ValidateDelta(root, delta):\n>    transitive closure of a mojom's input dependencies all at once.\n>    \"\"\"\n>  \n> +  translate.is_running_backwards_compatibility_check_hack = True\n> +\n>    # First build a map of all files covered by the delta\n>    affected_files = set()\n>    old_files = {}\n> @@ -73,11 +73,35 @@ def _ValidateDelta(root, delta):\n>      try:\n>        ast = parser.Parse(contents, mojom)\n>      except Exception as e:\n> -      six.reraise(\n> -          ParseError,\n> -          'encountered exception {0} while parsing {1}'.format(e, mojom),\n> -          sys.exc_info()[2])\n> +      raise ParseError('encountered exception {0} while parsing {1}'.format(\n> +          e, mojom))\n> +\n> +    # Files which are generated at compile time can't be checked by this script\n> +    # (at the moment) since they may not exist in the output directory.\n> +    generated_files_to_skip = {\n> +        ('third_party/blink/public/mojom/runtime_feature_state/'\n> +         'runtime_feature.mojom'),\n> +        ('third_party/blink/public/mojom/origin_trial_feature/'\n> +         'origin_trial_feature.mojom'),\n> +    }\n> +\n> +    ast.import_list.items = [\n> +        x for x in ast.import_list.items\n> +        if x.import_filename not in generated_files_to_skip\n> +    ]\n> +\n>      for imp in ast.import_list:\n> +      if (not file_overrides.get(imp.import_filename)\n> +          and not os.path.exists(os.path.join(root, imp.import_filename))):\n> +        # Speculatively construct a path prefix to locate the import_filename\n> +        mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep)\n> +        test_prefix = ''\n> +        for path_component in mojom_path:\n> +          test_prefix = os.path.join(test_prefix, path_component)\n> +          test_import_filename = os.path.join(test_prefix, imp.import_filename)\n> +          if os.path.exists(os.path.join(root, test_import_filename)):\n> +            imp.import_filename = test_import_filename\n> +            break\n>        parseMojom(imp.import_filename, file_overrides, override_modules)\n>  \n>      # Now that the transitive set of dependencies has been imported and parsed\n> @@ -89,10 +113,10 @@ def _ValidateDelta(root, delta):\n>      modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)\n>  \n>    old_modules = {}\n> -  for mojom in old_files.keys():\n> +  for mojom in old_files:\n>      parseMojom(mojom, old_files, old_modules)\n>    new_modules = {}\n> -  for mojom in new_files.keys():\n> +  for mojom in new_files:\n>      parseMojom(mojom, new_files, new_modules)\n>  \n>    # At this point we have a complete set of translated Modules from both the\n> @@ -132,12 +156,21 @@ def _ValidateDelta(root, delta):\n>            'can be deleted by a subsequent change.' % qualified_name)\n>  \n>      checker = module.BackwardCompatibilityChecker()\n> -    if not checker.IsBackwardCompatible(new_types[new_name], kind):\n> -      raise Exception('Stable type %s appears to have changed in a way which '\n> -                      'breaks backward-compatibility. Please fix!\\n\\nIf you '\n> -                      'believe this assessment to be incorrect, please file a '\n> -                      'Chromium bug against the \"Internals>Mojo>Bindings\" '\n> -                      'component.' % qualified_name)\n> +    try:\n> +      if not checker.IsBackwardCompatible(new_types[new_name], kind):\n> +        raise Exception(\n> +            'Stable type %s appears to have changed in a way which '\n> +            'breaks backward-compatibility. Please fix!\\n\\nIf you '\n> +            'believe this assessment to be incorrect, please file a '\n> +            'Chromium bug against the \"Internals>Mojo>Bindings\" '\n> +            'component.' % qualified_name)\n> +    except Exception as e:\n> +      raise Exception(\n> +          'Stable type %s appears to have changed in a way which '\n> +          'breaks backward-compatibility: \\n\\n%s.\\nPlease fix!\\n\\nIf you '\n> +          'believe this assessment to be incorrect, please file a '\n> +          'Chromium bug against the \"Internals>Mojo>Bindings\" '\n> +          'component.' % (qualified_name, e))\n>  \n>  \n>  def Run(command_line, delta=None):\n> diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n> index 9f51ea7751fb..06769c9533c3 100755\n> --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n> @@ -1,5 +1,5 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +#!/usr/bin/env python3\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -15,7 +15,7 @@ import check_stable_mojom_compatibility\n>  from mojom.generate import module\n>  \n>  \n> -class Change(object):\n> +class Change:\n>    \"\"\"Helper to clearly define a mojom file delta to be analyzed.\"\"\"\n>  \n>    def __init__(self, filename, old=None, new=None):\n> @@ -28,7 +28,7 @@ class Change(object):\n>  \n>  class UnchangedFile(Change):\n>    def __init__(self, filename, contents):\n> -    super(UnchangedFile, self).__init__(filename, old=contents, new=contents)\n> +    super().__init__(filename, old=contents, new=contents)\n>  \n>  \n>  class CheckStableMojomCompatibilityTest(unittest.TestCase):\n> @@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase):\n>                 [Stable] struct T { foo.S s; int32 x; };\n>                 \"\"\")\n>      ])\n> +\n> +  def testWithPartialImport(self):\n> +    \"\"\"The compatibility checking tool correctly parses imports with partial\n> +    paths.\"\"\"\n> +    self.assertBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('foo/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\")\n> +    ])\n> +\n> +    self.assertBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('foo/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\")\n> +    ])\n> +\n> +    self.assertNotBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('bar/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\")\n> +    ])\n> +\n> +    self.assertNotBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('bar/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\")\n> +    ])\n> +\n> +  def testNewEnumDefault(self):\n> +    # Should be backwards compatible since it does not affect the wire format.\n> +    # This specific case also checks that the backwards compatibility checker\n> +    # does not throw an error due to the older version of the enum not\n> +    # specifying [Default].\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='[Extensible] enum E { One };',\n> +               new='[Extensible] enum E { [Default] One };')\n> +    ])\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='[Extensible] enum E { [Default] One, Two, };',\n> +               new='[Extensible] enum E { One, [Default] Two, };')\n> +    ])\n> diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/ipc/mojo/public/tools/mojom/const_unittest.py\n> index cb42dfac3556..e8ed36a7a5bf 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n> index d9005078671e..9269cde526b4 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase):\n>      self.assertEqual('F', b.enums[0].mojom_name)\n>      self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)\n>      self.assertEqual(37, b.enums[0].fields[0].numeric_value)\n> +\n> +  def testEnumAttributesAreEnums(self):\n> +    \"\"\"Verifies that enum values in attributes are really enum types.\"\"\"\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };')\n> +    b_mojom = 'b.mojom'\n> +    self.WriteFile(\n> +        b_mojom, 'module b;'\n> +        'import \"a.mojom\";'\n> +        '[MooCow=a.E.kFoo]'\n> +        'interface Foo { Foo(); };')\n> +    self.ParseMojoms([a_mojom, b_mojom])\n> +    b = self.LoadModule(b_mojom)\n> +    self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo')\n> +\n> +  def testConstantAttributes(self):\n> +    \"\"\"Verifies that constants as attributes are translated to the constant.\"\"\"\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(\n> +        a_mojom, 'module a;'\n> +        'enum E { kFoo, kBar };'\n> +        'const E kB = E.kFoo;'\n> +        '[Attr=kB] interface Hello { Foo(); };')\n> +    self.ParseMojoms([a_mojom])\n> +    a = self.LoadModule(a_mojom)\n> +    self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB')\n> +    self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name,\n> +                      'kFoo')\n> diff --git a/utils/ipc/mojo/public/tools/mojom/feature_unittest.py b/utils/ipc/mojo/public/tools/mojom/feature_unittest.py\n> new file mode 100644\n> index 000000000000..5f014e1cfe9a\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/feature_unittest.py\n> @@ -0,0 +1,84 @@\n> +# Copyright 2023 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class FeatureTest(MojomParserTestCase):\n> +  \"\"\"Tests feature parsing behavior.\"\"\"\n> +  def testFeatureOff(self):\n> +    \"\"\"Verifies basic parsing of feature types.\"\"\"\n> +    types = self.ExtractTypes(\"\"\"\n> +      // e.g. BASE_DECLARE_FEATURE(kFeature);\n> +      [AttributeOne=ValueOne]\n> +      feature kFeature {\n> +        // BASE_FEATURE(kFeature,\"MyFeature\",\n> +        //     base::FEATURE_DISABLED_BY_DEFAULT);\n> +        const string name = \"MyFeature\";\n> +        const bool default_state = false;\n> +      };\n> +    \"\"\")\n> +    self.assertEqual('name', types['kFeature'].constants[0].mojom_name)\n> +    self.assertEqual('\"MyFeature\"', types['kFeature'].constants[0].value)\n> +    self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)\n> +    self.assertEqual('false', types['kFeature'].constants[1].value)\n> +\n> +  def testFeatureOn(self):\n> +    \"\"\"Verifies basic parsing of feature types.\"\"\"\n> +    types = self.ExtractTypes(\"\"\"\n> +      // e.g. BASE_DECLARE_FEATURE(kFeature);\n> +      feature kFeature {\n> +        // BASE_FEATURE(kFeature,\"MyFeature\",\n> +        //     base::FEATURE_ENABLED_BY_DEFAULT);\n> +        const string name = \"MyFeature\";\n> +        const bool default_state = true;\n> +      };\n> +    \"\"\")\n> +    self.assertEqual('name', types['kFeature'].constants[0].mojom_name)\n> +    self.assertEqual('\"MyFeature\"', types['kFeature'].constants[0].value)\n> +    self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)\n> +    self.assertEqual('true', types['kFeature'].constants[1].value)\n> +\n> +  def testFeatureWeakKeyword(self):\n> +    \"\"\"Verifies that `feature` is a weak keyword.\"\"\"\n> +    types = self.ExtractTypes(\"\"\"\n> +      // e.g. BASE_DECLARE_FEATURE(kFeature);\n> +      [AttributeOne=ValueOne]\n> +      feature kFeature {\n> +        // BASE_FEATURE(kFeature,\"MyFeature\",\n> +        //     base::FEATURE_DISABLED_BY_DEFAULT);\n> +        const string name = \"MyFeature\";\n> +        const bool default_state = false;\n> +      };\n> +      struct MyStruct {\n> +         bool feature = true;\n> +      };\n> +      interface InterfaceName {\n> +         Method(string feature) => (int32 feature);\n> +      };\n> +    \"\"\")\n> +    self.assertEqual('name', types['kFeature'].constants[0].mojom_name)\n> +    self.assertEqual('\"MyFeature\"', types['kFeature'].constants[0].value)\n> +    self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)\n> +    self.assertEqual('false', types['kFeature'].constants[1].value)\n> +\n> +  def testFeatureAttributesAreFeatures(self):\n> +    \"\"\"Verifies that feature values in attributes are really feature types.\"\"\"\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(\n> +        a_mojom, 'module a;'\n> +        'feature F { const string name = \"f\";'\n> +        'const bool default_state = false; };')\n> +    b_mojom = 'b.mojom'\n> +    self.WriteFile(\n> +        b_mojom, 'module b;'\n> +        'import \"a.mojom\";'\n> +        'feature G'\n> +        '{const string name = \"g\"; const bool default_state = false;};'\n> +        '[Attri=a.F] interface Foo { Foo(); };'\n> +        '[Boink=G] interface Bar {};')\n> +    self.ParseMojoms([a_mojom, b_mojom])\n> +    b = self.LoadModule(b_mojom)\n> +    self.assertEqual(b.interfaces[0].attributes['Attri'].mojom_name, 'F')\n> +    self.assertEqual(b.interfaces[1].attributes['Boink'].mojom_name, 'G')\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n> index 51facc0ccb84..a0edf0eb5d2c 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -8,6 +8,7 @@ group(\"mojom\") {\n>      \"error.py\",\n>      \"fileutil.py\",\n>      \"generate/__init__.py\",\n> +    \"generate/check.py\",\n>      \"generate/generator.py\",\n>      \"generate/module.py\",\n>      \"generate/pack.py\",\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/ipc/mojo/public/tools/mojom/mojom/error.py\n> index 8a1e03da5c1d..dd53b835a49e 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n> index bf626f54798c..124f12c134b9 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n> @@ -1,9 +1,8 @@\n> -# Copyright 2015 The Chromium Authors. All rights reserved.\n> +# Copyright 2015 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n>  import errno\n> -import imp\n>  import os.path\n>  import sys\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n> index ff5753a29114..c93d22898d29 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n> @@ -1,20 +1,17 @@\n> -# Copyright 2015 The Chromium Authors. All rights reserved.\n> +# Copyright 2015 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n>  import os.path\n>  import shutil\n> -import sys\n>  import tempfile\n>  import unittest\n>  \n>  from mojom import fileutil\n>  \n> -\n>  class FileUtilTest(unittest.TestCase):\n>    def testEnsureDirectoryExists(self):\n> -    \"\"\"Test that EnsureDirectoryExists fuctions correctly.\"\"\"\n> +    \"\"\"Test that EnsureDirectoryExists functions correctly.\"\"\"\n>  \n>      temp_dir = tempfile.mkdtemp()\n>      try:\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py\n> new file mode 100644\n> index 000000000000..1efe2022342b\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py\n> @@ -0,0 +1,26 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Code shared by the various pre-generation mojom checkers.\"\"\"\n> +\n> +\n> +class CheckException(Exception):\n> +  def __init__(self, module, message):\n> +    self.module = module\n> +    self.message = message\n> +    super().__init__(self.message)\n> +\n> +  def __str__(self):\n> +    return \"Failed mojo pre-generation check for {}:\\n{}\".format(\n> +        self.module.path, self.message)\n> +\n> +\n> +class Check:\n> +  def __init__(self, module):\n> +    self.module = module\n> +\n> +  def CheckModule(self):\n> +    \"\"\" Subclass should return True if its Checks pass, and throw an\n> +    exception otherwise. CheckModule will be called immediately before\n> +    mojom.generate.Generator.GenerateFiles()\"\"\"\n> +    raise NotImplementedError(\"Subclasses must override/implement this method\")\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n> deleted file mode 100644\n> index 0dfd996e355d..000000000000\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n> +++ /dev/null\n> @@ -1,93 +0,0 @@\n> -# Copyright 2015 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\"\"\"Resolves the values used for constants and enums.\"\"\"\n> -\n> -from itertools import ifilter\n> -\n> -from mojom.generate import module as mojom\n> -\n> -\n> -def ResolveConstants(module, expression_to_text):\n> -  in_progress = set()\n> -  computed = set()\n> -\n> -  def GetResolvedValue(named_value):\n> -    assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue))\n> -    if isinstance(named_value, mojom.EnumValue):\n> -      field = next(\n> -          ifilter(lambda field: field.name == named_value.name,\n> -                  named_value.enum.fields), None)\n> -      if not field:\n> -        raise RuntimeError(\n> -            'Unable to get computed value for field %s of enum %s' %\n> -            (named_value.name, named_value.enum.name))\n> -      if field not in computed:\n> -        ResolveEnum(named_value.enum)\n> -      return field.resolved_value\n> -    else:\n> -      ResolveConstant(named_value.constant)\n> -      named_value.resolved_value = named_value.constant.resolved_value\n> -      return named_value.resolved_value\n> -\n> -  def ResolveConstant(constant):\n> -    if constant in computed:\n> -      return\n> -    if constant in in_progress:\n> -      raise RuntimeError('Circular dependency for constant: %s' % constant.name)\n> -    in_progress.add(constant)\n> -    if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):\n> -      resolved_value = GetResolvedValue(constant.value)\n> -    else:\n> -      resolved_value = expression_to_text(constant.value)\n> -    constant.resolved_value = resolved_value\n> -    in_progress.remove(constant)\n> -    computed.add(constant)\n> -\n> -  def ResolveEnum(enum):\n> -    def ResolveEnumField(enum, field, default_value):\n> -      if field in computed:\n> -        return\n> -      if field in in_progress:\n> -        raise RuntimeError('Circular dependency for enum: %s' % enum.name)\n> -      in_progress.add(field)\n> -      if field.value:\n> -        if isinstance(field.value, mojom.EnumValue):\n> -          resolved_value = GetResolvedValue(field.value)\n> -        elif isinstance(field.value, str):\n> -          resolved_value = int(field.value, 0)\n> -        else:\n> -          raise RuntimeError('Unexpected value: %s' % field.value)\n> -      else:\n> -        resolved_value = default_value\n> -      field.resolved_value = resolved_value\n> -      in_progress.remove(field)\n> -      computed.add(field)\n> -\n> -    current_value = 0\n> -    for field in enum.fields:\n> -      ResolveEnumField(enum, field, current_value)\n> -      current_value = field.resolved_value + 1\n> -\n> -  for constant in module.constants:\n> -    ResolveConstant(constant)\n> -\n> -  for enum in module.enums:\n> -    ResolveEnum(enum)\n> -\n> -  for struct in module.structs:\n> -    for constant in struct.constants:\n> -      ResolveConstant(constant)\n> -    for enum in struct.enums:\n> -      ResolveEnum(enum)\n> -    for field in struct.fields:\n> -      if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):\n> -        field.default.resolved_value = GetResolvedValue(field.default)\n> -\n> -  for interface in module.interfaces:\n> -    for constant in interface.constants:\n> -      ResolveConstant(constant)\n> -    for enum in interface.enums:\n> -      ResolveEnum(enum)\n> -\n> -  return module\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n> index 4a1c73fcf82f..96fe3a2d0920 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Code shared by the various language-specific code generators.\"\"\"\n> @@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier):\n>    return _ToSnakeCase(identifier, upper=False)\n>  \n>  \n> -class Stylizer(object):\n> +class Stylizer:\n>    \"\"\"Stylizers specify naming rules to map mojom names to names in generated\n>    code. For example, if you would like method_name in mojom to be mapped to\n>    MethodName in the generated code, you need to define a subclass of Stylizer\n> @@ -130,6 +130,9 @@ class Stylizer(object):\n>    def StylizeEnum(self, mojom_name):\n>      return mojom_name\n>  \n> +  def StylizeFeature(self, mojom_name):\n> +    return mojom_name\n> +\n>    def StylizeModule(self, mojom_namespace):\n>      return mojom_namespace\n>  \n> @@ -233,7 +236,7 @@ def AddComputedData(module):\n>      _AddInterfaceComputedData(interface)\n>  \n>  \n> -class Generator(object):\n> +class Generator:\n>    # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all\n>    # files to stdout.\n>    def __init__(self,\n> @@ -243,7 +246,6 @@ class Generator(object):\n>                 variant=None,\n>                 bytecode_path=None,\n>                 for_blink=False,\n> -               js_bindings_mode=\"new\",\n>                 js_generate_struct_deserializers=False,\n>                 export_attribute=None,\n>                 export_header=None,\n> @@ -262,7 +264,6 @@ class Generator(object):\n>      self.variant = variant\n>      self.bytecode_path = bytecode_path\n>      self.for_blink = for_blink\n> -    self.js_bindings_mode = js_bindings_mode\n>      self.js_generate_struct_deserializers = js_generate_struct_deserializers\n>      self.export_attribute = export_attribute\n>      self.export_header = export_header\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n> index 32c884a8c02e..7143e07c4d7e 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n> @@ -1,13 +1,12 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> +import importlib.util\n>  import os.path\n>  import sys\n>  import unittest\n>  \n> -\n>  def _GetDirAbove(dirname):\n>    \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n>    also be \"above\" this file).\"\"\"\n> @@ -20,12 +19,11 @@ def _GetDirAbove(dirname):\n>  \n>  \n>  try:\n> -  imp.find_module(\"mojom\")\n> +  importlib.util.find_spec(\"mojom\")\n>  except ImportError:\n>    sys.path.append(os.path.join(_GetDirAbove(\"pylib\"), \"pylib\"))\n>  from mojom.generate import generator\n>  \n> -\n>  class StringManipulationTest(unittest.TestCase):\n>    \"\"\"generator contains some string utilities, this tests only those.\"\"\"\n>  \n> @@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase):\n>      self.assertEquals(\"SNAKE_D3D11_CASE\",\n>                        generator.ToUpperSnakeCase(\"snakeD3d11Case\"))\n>  \n> -\n>  if __name__ == \"__main__\":\n>    unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n> index 9bdb28e05f65..ca71059d53b1 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -12,15 +12,14 @@\n>  # method = interface.AddMethod('Tat', 0)\n>  # method.AddParameter('baz', 0, mojom.INT32)\n>  \n> -import sys\n> -if sys.version_info.major == 2:\n> -  import cPickle as pickle\n> -else:\n> -  import pickle\n> +import pickle\n> +from collections import OrderedDict\n>  from uuid import UUID\n>  \n> +# pylint: disable=raise-missing-from\n>  \n> -class BackwardCompatibilityChecker(object):\n> +\n> +class BackwardCompatibilityChecker:\n>    \"\"\"Used for memoization while recursively checking two type definitions for\n>    backward-compatibility.\"\"\"\n>  \n> @@ -64,23 +63,20 @@ def Repr(obj, as_ref=True):\n>      return obj.Repr(as_ref=as_ref)\n>    # Since we cannot implement Repr for existing container types, we\n>    # handle them here.\n> -  elif isinstance(obj, list):\n> +  if isinstance(obj, list):\n>      if not obj:\n>        return '[]'\n> -    else:\n> -      return ('[\\n%s\\n]' % (',\\n'.join(\n> -          '    %s' % Repr(elem, as_ref).replace('\\n', '\\n    ')\n> -          for elem in obj)))\n> -  elif isinstance(obj, dict):\n> +    return ('[\\n%s\\n]' %\n> +            (',\\n'.join('    %s' % Repr(elem, as_ref).replace('\\n', '\\n    ')\n> +                        for elem in obj)))\n> +  if isinstance(obj, dict):\n>      if not obj:\n>        return '{}'\n> -    else:\n> -      return ('{\\n%s\\n}' % (',\\n'.join(\n> -          '    %s: %s' % (Repr(key, as_ref).replace('\\n', '\\n    '),\n> -                          Repr(val, as_ref).replace('\\n', '\\n    '))\n> -          for key, val in obj.items())))\n> -  else:\n> -    return repr(obj)\n> +    return ('{\\n%s\\n}' % (',\\n'.join('    %s: %s' %\n> +                                     (Repr(key, as_ref).replace('\\n', '\\n    '),\n> +                                      Repr(val, as_ref).replace('\\n', '\\n    '))\n> +                                     for key, val in obj.items())))\n> +  return repr(obj)\n>  \n>  \n>  def GenericRepr(obj, names):\n> @@ -104,7 +100,7 @@ def GenericRepr(obj, names):\n>        ReprIndent(name, as_ref) for (name, as_ref) in names.items()))\n>  \n>  \n> -class Kind(object):\n> +class Kind:\n>    \"\"\"Kind represents a type (e.g. int8, string).\n>  \n>    Attributes:\n> @@ -112,16 +108,43 @@ class Kind(object):\n>      module: {Module} The defining module. Set to None for built-in types.\n>      parent_kind: The enclosing type. For example, an enum defined\n>          inside an interface has that interface as its parent. May be None.\n> +    is_nullable: True if the type is nullable.\n>    \"\"\"\n>  \n> -  def __init__(self, spec=None, module=None):\n> +  def __init__(self, spec=None, is_nullable=False, module=None):\n>      self.spec = spec\n>      self.module = module\n>      self.parent_kind = None\n> +    self.is_nullable = is_nullable\n> +    self.shared_definition = {}\n> +\n> +  @classmethod\n> +  def AddSharedProperty(cls, name):\n> +    \"\"\"Adds a property |name| to |cls|, which accesses the corresponding item in\n> +       |shared_definition|.\n> +\n> +       The reason of adding such indirection is to enable sharing definition\n> +       between a reference kind and its nullable variation. For example:\n> +         a = Struct('test_struct_1')\n> +         b = a.MakeNullableKind()\n> +         a.name = 'test_struct_2'\n> +         print(b.name)  # Outputs 'test_struct_2'.\n> +    \"\"\"\n> +    def Get(self):\n> +      try:\n> +        return self.shared_definition[name]\n> +      except KeyError:  # Must raise AttributeError if property doesn't exist.\n> +        raise AttributeError\n> +\n> +    def Set(self, value):\n> +      self.shared_definition[name] = value\n> +\n> +    setattr(cls, name, property(Get, Set))\n>  \n>    def Repr(self, as_ref=True):\n>      # pylint: disable=unused-argument\n> -    return '<%s spec=%r>' % (self.__class__.__name__, self.spec)\n> +    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,\n> +                                            self.is_nullable)\n>  \n>    def __repr__(self):\n>      # Gives us a decent __repr__ for all kinds.\n> @@ -130,7 +153,8 @@ class Kind(object):\n>    def __eq__(self, rhs):\n>      # pylint: disable=unidiomatic-typecheck\n>      return (type(self) == type(rhs)\n> -            and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind))\n> +            and (self.spec, self.parent_kind, self.is_nullable)\n> +            == (rhs.spec, rhs.parent_kind, rhs.is_nullable))\n>  \n>    def __hash__(self):\n>      # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind\n> @@ -138,32 +162,113 @@ class Kind(object):\n>      # some primitive Kinds as dict keys. The default hash (object identity)\n>      # breaks these dicts when a pickled Module instance is unpickled and used\n>      # during a subsequent run of the parser.\n> -    return hash((self.spec, self.parent_kind))\n> +    return hash((self.spec, self.parent_kind, self.is_nullable))\n>  \n>    # pylint: disable=unused-argument\n>    def IsBackwardCompatible(self, rhs, checker):\n>      return self == rhs\n>  \n>  \n> +class ValueKind(Kind):\n> +  \"\"\"ValueKind represents values that aren't reference kinds.\n> +\n> +  The primary difference is the wire representation for nullable value kinds\n> +  still reserves space for the value type itself, even if that value itself\n> +  is logically null.\n> +  \"\"\"\n> +  def __init__(self, spec=None, is_nullable=False, module=None):\n> +    assert spec is None or is_nullable == spec.startswith('?')\n> +    Kind.__init__(self, spec, is_nullable, module)\n> +\n> +  def MakeNullableKind(self):\n> +    assert not self.is_nullable\n> +\n> +    if self == BOOL:\n> +      return NULLABLE_BOOL\n> +    if self == INT8:\n> +      return NULLABLE_INT8\n> +    if self == INT16:\n> +      return NULLABLE_INT16\n> +    if self == INT32:\n> +      return NULLABLE_INT32\n> +    if self == INT64:\n> +      return NULLABLE_INT64\n> +    if self == UINT8:\n> +      return NULLABLE_UINT8\n> +    if self == UINT16:\n> +      return NULLABLE_UINT16\n> +    if self == UINT32:\n> +      return NULLABLE_UINT32\n> +    if self == UINT64:\n> +      return NULLABLE_UINT64\n> +    if self == FLOAT:\n> +      return NULLABLE_FLOAT\n> +    if self == DOUBLE:\n> +      return NULLABLE_DOUBLE\n> +\n> +    nullable_kind = type(self)()\n> +    nullable_kind.shared_definition = self.shared_definition\n> +    if self.spec is not None:\n> +      nullable_kind.spec = '?' + self.spec\n> +    nullable_kind.is_nullable = True\n> +    nullable_kind.parent_kind = self.parent_kind\n> +    nullable_kind.module = self.module\n> +\n> +    return nullable_kind\n> +\n> +  def MakeUnnullableKind(self):\n> +    assert self.is_nullable\n> +\n> +    if self == NULLABLE_BOOL:\n> +      return BOOL\n> +    if self == NULLABLE_INT8:\n> +      return INT8\n> +    if self == NULLABLE_INT16:\n> +      return INT16\n> +    if self == NULLABLE_INT32:\n> +      return INT32\n> +    if self == NULLABLE_INT64:\n> +      return INT64\n> +    if self == NULLABLE_UINT8:\n> +      return UINT8\n> +    if self == NULLABLE_UINT16:\n> +      return UINT16\n> +    if self == NULLABLE_UINT32:\n> +      return UINT32\n> +    if self == NULLABLE_UINT64:\n> +      return UINT64\n> +    if self == NULLABLE_FLOAT:\n> +      return FLOAT\n> +    if self == NULLABLE_DOUBLE:\n> +      return DOUBLE\n> +\n> +    nullable_kind = type(self)()\n> +    nullable_kind.shared_definition = self.shared_definition\n> +    if self.spec is not None:\n> +      nullable_kind.spec = self.spec[1:]\n> +    nullable_kind.is_nullable = False\n> +    nullable_kind.parent_kind = self.parent_kind\n> +    nullable_kind.module = self.module\n> +\n> +    return nullable_kind\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, ValueKind) and super().__eq__(rhs))\n> +\n> +  def __hash__(self):  # pylint: disable=useless-super-delegation\n> +    return super().__hash__()\n> +\n> +\n>  class ReferenceKind(Kind):\n>    \"\"\"ReferenceKind represents pointer and handle types.\n>  \n>    A type is nullable if null (for pointer types) or invalid handle (for handle\n>    types) is a legal value for the type.\n> -\n> -  Attributes:\n> -    is_nullable: True if the type is nullable.\n>    \"\"\"\n>  \n>    def __init__(self, spec=None, is_nullable=False, module=None):\n>      assert spec is None or is_nullable == spec.startswith('?')\n> -    Kind.__init__(self, spec, module)\n> -    self.is_nullable = is_nullable\n> -    self.shared_definition = {}\n> -\n> -  def Repr(self, as_ref=True):\n> -    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,\n> -                                            self.is_nullable)\n> +    Kind.__init__(self, spec, is_nullable, module)\n>  \n>    def MakeNullableKind(self):\n>      assert not self.is_nullable\n> @@ -193,55 +298,65 @@ class ReferenceKind(Kind):\n>  \n>      return nullable_kind\n>  \n> -  @classmethod\n> -  def AddSharedProperty(cls, name):\n> -    \"\"\"Adds a property |name| to |cls|, which accesses the corresponding item in\n> -       |shared_definition|.\n> +  def MakeUnnullableKind(self):\n> +    assert self.is_nullable\n>  \n> -       The reason of adding such indirection is to enable sharing definition\n> -       between a reference kind and its nullable variation. For example:\n> -         a = Struct('test_struct_1')\n> -         b = a.MakeNullableKind()\n> -         a.name = 'test_struct_2'\n> -         print(b.name)  # Outputs 'test_struct_2'.\n> -    \"\"\"\n> +    if self == NULLABLE_STRING:\n> +      return STRING\n> +    if self == NULLABLE_HANDLE:\n> +      return HANDLE\n> +    if self == NULLABLE_DCPIPE:\n> +      return DCPIPE\n> +    if self == NULLABLE_DPPIPE:\n> +      return DPPIPE\n> +    if self == NULLABLE_MSGPIPE:\n> +      return MSGPIPE\n> +    if self == NULLABLE_SHAREDBUFFER:\n> +      return SHAREDBUFFER\n> +    if self == NULLABLE_PLATFORMHANDLE:\n> +      return PLATFORMHANDLE\n>  \n> -    def Get(self):\n> -      try:\n> -        return self.shared_definition[name]\n> -      except KeyError:  # Must raise AttributeError if property doesn't exist.\n> -        raise AttributeError\n> +    unnullable_kind = type(self)()\n> +    unnullable_kind.shared_definition = self.shared_definition\n> +    if self.spec is not None:\n> +      assert self.spec[0] == '?'\n> +      unnullable_kind.spec = self.spec[1:]\n> +    unnullable_kind.is_nullable = False\n> +    unnullable_kind.parent_kind = self.parent_kind\n> +    unnullable_kind.module = self.module\n>  \n> -    def Set(self, value):\n> -      self.shared_definition[name] = value\n> -\n> -    setattr(cls, name, property(Get, Set))\n> +    return unnullable_kind\n>  \n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, ReferenceKind)\n> -            and super(ReferenceKind, self).__eq__(rhs)\n> -            and self.is_nullable == rhs.is_nullable)\n> +    return (isinstance(rhs, ReferenceKind) and super().__eq__(rhs))\n>  \n> -  def __hash__(self):\n> -    return hash((super(ReferenceKind, self).__hash__(), self.is_nullable))\n> -\n> -  def IsBackwardCompatible(self, rhs, checker):\n> -    return (super(ReferenceKind, self).IsBackwardCompatible(rhs, checker)\n> -            and self.is_nullable == rhs.is_nullable)\n> +  def __hash__(self):  # pylint: disable=useless-super-delegation\n> +    return super().__hash__()\n>  \n>  \n>  # Initialize the set of primitive types. These can be accessed by clients.\n> -BOOL = Kind('b')\n> -INT8 = Kind('i8')\n> -INT16 = Kind('i16')\n> -INT32 = Kind('i32')\n> -INT64 = Kind('i64')\n> -UINT8 = Kind('u8')\n> -UINT16 = Kind('u16')\n> -UINT32 = Kind('u32')\n> -UINT64 = Kind('u64')\n> -FLOAT = Kind('f')\n> -DOUBLE = Kind('d')\n> +BOOL = ValueKind('b')\n> +INT8 = ValueKind('i8')\n> +INT16 = ValueKind('i16')\n> +INT32 = ValueKind('i32')\n> +INT64 = ValueKind('i64')\n> +UINT8 = ValueKind('u8')\n> +UINT16 = ValueKind('u16')\n> +UINT32 = ValueKind('u32')\n> +UINT64 = ValueKind('u64')\n> +FLOAT = ValueKind('f')\n> +DOUBLE = ValueKind('d')\n> +NULLABLE_BOOL = ValueKind('?b', True)\n> +NULLABLE_INT8 = ValueKind('?i8', True)\n> +NULLABLE_INT16 = ValueKind('?i16', True)\n> +NULLABLE_INT32 = ValueKind('?i32', True)\n> +NULLABLE_INT64 = ValueKind('?i64', True)\n> +NULLABLE_UINT8 = ValueKind('?u8', True)\n> +NULLABLE_UINT16 = ValueKind('?u16', True)\n> +NULLABLE_UINT32 = ValueKind('?u32', True)\n> +NULLABLE_UINT64 = ValueKind('?u64', True)\n> +NULLABLE_FLOAT = ValueKind('?f', True)\n> +NULLABLE_DOUBLE = ValueKind('?d', True)\n>  STRING = ReferenceKind('s')\n>  HANDLE = ReferenceKind('h')\n>  DCPIPE = ReferenceKind('h:d:c')\n> @@ -270,6 +385,17 @@ PRIMITIVES = (\n>      UINT64,\n>      FLOAT,\n>      DOUBLE,\n> +    NULLABLE_BOOL,\n> +    NULLABLE_INT8,\n> +    NULLABLE_INT16,\n> +    NULLABLE_INT32,\n> +    NULLABLE_INT64,\n> +    NULLABLE_UINT8,\n> +    NULLABLE_UINT16,\n> +    NULLABLE_UINT32,\n> +    NULLABLE_UINT64,\n> +    NULLABLE_FLOAT,\n> +    NULLABLE_DOUBLE,\n>      STRING,\n>      HANDLE,\n>      DCPIPE,\n> @@ -291,12 +417,17 @@ ATTRIBUTE_DEFAULT = 'Default'\n>  ATTRIBUTE_EXTENSIBLE = 'Extensible'\n>  ATTRIBUTE_NO_INTERRUPT = 'NoInterrupt'\n>  ATTRIBUTE_STABLE = 'Stable'\n> +ATTRIBUTE_SUPPORTS_URGENT = 'SupportsUrgent'\n>  ATTRIBUTE_SYNC = 'Sync'\n>  ATTRIBUTE_UNLIMITED_SIZE = 'UnlimitedSize'\n>  ATTRIBUTE_UUID = 'Uuid'\n> +ATTRIBUTE_SERVICE_SANDBOX = 'ServiceSandbox'\n> +ATTRIBUTE_REQUIRE_CONTEXT = 'RequireContext'\n> +ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext'\n> +ATTRIBUTE_RUNTIME_FEATURE = 'RuntimeFeature'\n>  \n>  \n> -class NamedValue(object):\n> +class NamedValue:\n>    def __init__(self, module, parent_kind, mojom_name):\n>      self.module = module\n>      self.parent_kind = parent_kind\n> @@ -316,7 +447,7 @@ class NamedValue(object):\n>      return hash((self.parent_kind, self.mojom_name))\n>  \n>  \n> -class BuiltinValue(object):\n> +class BuiltinValue:\n>    def __init__(self, value):\n>      self.value = value\n>  \n> @@ -350,7 +481,7 @@ class EnumValue(NamedValue):\n>      return self.field.name\n>  \n>  \n> -class Constant(object):\n> +class Constant:\n>    def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None):\n>      self.mojom_name = mojom_name\n>      self.name = None\n> @@ -368,7 +499,7 @@ class Constant(object):\n>                                         rhs.parent_kind))\n>  \n>  \n> -class Field(object):\n> +class Field:\n>    def __init__(self,\n>                 mojom_name=None,\n>                 kind=None,\n> @@ -414,7 +545,18 @@ class StructField(Field):\n>  \n>  \n>  class UnionField(Field):\n> -  pass\n> +  def __init__(self,\n> +               mojom_name=None,\n> +               kind=None,\n> +               ordinal=None,\n> +               default=None,\n> +               attributes=None):\n> +    Field.__init__(self, mojom_name, kind, ordinal, default, attributes)\n> +\n> +  @property\n> +  def is_default(self):\n> +    return self.attributes.get(ATTRIBUTE_DEFAULT, False) \\\n> +        if self.attributes else False\n>  \n>  \n>  def _IsFieldBackwardCompatible(new_field, old_field, checker):\n> @@ -424,6 +566,38 @@ def _IsFieldBackwardCompatible(new_field, old_field, checker):\n>    return checker.IsBackwardCompatible(new_field.kind, old_field.kind)\n>  \n>  \n> +class Feature(ReferenceKind):\n> +  \"\"\"A runtime enabled feature defined from mojom.\n> +\n> +  Attributes:\n> +    mojom_name: {str} The name of the feature type as defined in mojom.\n> +    name: {str} The stylized name. (Note: not the \"name\" used by FeatureList.)\n> +    constants: {List[Constant]} The constants defined in the feature scope.\n> +    attributes: {dict} Additional information about the feature.\n> +  \"\"\"\n> +\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('constants')\n> +  Kind.AddSharedProperty('attributes')\n> +\n> +  def __init__(self, mojom_name=None, module=None, attributes=None):\n> +    if mojom_name is not None:\n> +      spec = 'x:' + mojom_name\n> +    else:\n> +      spec = None\n> +    ReferenceKind.__init__(self, spec, False, module)\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.constants = []\n> +    self.attributes = attributes\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeFeature(self.mojom_name)\n> +    for constant in self.constants:\n> +      constant.Stylize(stylizer)\n> +\n> +\n>  class Struct(ReferenceKind):\n>    \"\"\"A struct with typed fields.\n>  \n> @@ -441,14 +615,14 @@ class Struct(ReferenceKind):\n>          if it's a native struct.\n>    \"\"\"\n>  \n> -  ReferenceKind.AddSharedProperty('mojom_name')\n> -  ReferenceKind.AddSharedProperty('name')\n> -  ReferenceKind.AddSharedProperty('native_only')\n> -  ReferenceKind.AddSharedProperty('custom_serializer')\n> -  ReferenceKind.AddSharedProperty('fields')\n> -  ReferenceKind.AddSharedProperty('enums')\n> -  ReferenceKind.AddSharedProperty('constants')\n> -  ReferenceKind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('native_only')\n> +  Kind.AddSharedProperty('custom_serializer')\n> +  Kind.AddSharedProperty('fields')\n> +  Kind.AddSharedProperty('enums')\n> +  Kind.AddSharedProperty('constants')\n> +  Kind.AddSharedProperty('attributes')\n>  \n>    def __init__(self, mojom_name=None, module=None, attributes=None):\n>      if mojom_name is not None:\n> @@ -470,12 +644,11 @@ class Struct(ReferenceKind):\n>        return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__,\n>                                                 self.mojom_name,\n>                                                 Repr(self.module, as_ref=True))\n> -    else:\n> -      return GenericRepr(self, {\n> -          'mojom_name': False,\n> -          'fields': False,\n> -          'module': True\n> -      })\n> +    return GenericRepr(self, {\n> +        'mojom_name': False,\n> +        'fields': False,\n> +        'module': True\n> +    })\n>  \n>    def AddField(self,\n>                 mojom_name,\n> @@ -496,13 +669,13 @@ class Struct(ReferenceKind):\n>      for constant in self.constants:\n>        constant.Stylize(stylizer)\n>  \n> -  def IsBackwardCompatible(self, older_struct, checker):\n> -    \"\"\"This struct is backward-compatible with older_struct if and only if all\n> -    of the following conditions hold:\n> +  def IsBackwardCompatible(self, rhs, checker):\n> +    \"\"\"This struct is backward-compatible with rhs (older_struct) if and only if\n> +    all of the following conditions hold:\n>        - Any newly added field is tagged with a [MinVersion] attribute specifying\n>          a version number greater than all previously used [MinVersion]\n>          attributes within the struct.\n> -      - All fields present in older_struct remain present in the new struct,\n> +      - All fields present in rhs remain present in the new struct,\n>          with the same ordinal position, same optional or non-optional status,\n>          same (or backward-compatible) type and where applicable, the same\n>          [MinVersion] attribute value.\n> @@ -521,7 +694,7 @@ class Struct(ReferenceKind):\n>        return fields_by_ordinal\n>  \n>      new_fields = buildOrdinalFieldMap(self)\n> -    old_fields = buildOrdinalFieldMap(older_struct)\n> +    old_fields = buildOrdinalFieldMap(rhs)\n>      if len(new_fields) < len(old_fields):\n>        # At least one field was removed, which is not OK.\n>        return False\n> @@ -574,11 +747,18 @@ class Struct(ReferenceKind):\n>        prefix = self.module.GetNamespacePrefix()\n>      return '%s%s' % (prefix, self.mojom_name)\n>  \n> +  def _tuple(self):\n> +    return (self.mojom_name, self.native_only, self.fields, self.constants,\n> +            self.attributes)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Struct) and\n> -            (self.mojom_name, self.native_only, self.fields, self.constants,\n> -             self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields,\n> -                                  rhs.constants, rhs.attributes))\n> +    return isinstance(rhs, Struct) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>    def __hash__(self):\n>      return id(self)\n> @@ -595,10 +775,11 @@ class Union(ReferenceKind):\n>          which Java class name to use to represent it in the generated\n>          bindings.\n>    \"\"\"\n> -  ReferenceKind.AddSharedProperty('mojom_name')\n> -  ReferenceKind.AddSharedProperty('name')\n> -  ReferenceKind.AddSharedProperty('fields')\n> -  ReferenceKind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('fields')\n> +  Kind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('default_field')\n>  \n>    def __init__(self, mojom_name=None, module=None, attributes=None):\n>      if mojom_name is not None:\n> @@ -610,14 +791,14 @@ class Union(ReferenceKind):\n>      self.name = None\n>      self.fields = []\n>      self.attributes = attributes\n> +    self.default_field = None\n>  \n>    def Repr(self, as_ref=True):\n>      if as_ref:\n>        return '<%s spec=%r is_nullable=%r fields=%s>' % (\n>            self.__class__.__name__, self.spec, self.is_nullable, Repr(\n>                self.fields))\n> -    else:\n> -      return GenericRepr(self, {'fields': True, 'is_nullable': False})\n> +    return GenericRepr(self, {'fields': True, 'is_nullable': False})\n>  \n>    def AddField(self, mojom_name, kind, ordinal=None, attributes=None):\n>      field = UnionField(mojom_name, kind, ordinal, None, attributes)\n> @@ -629,13 +810,13 @@ class Union(ReferenceKind):\n>      for field in self.fields:\n>        field.Stylize(stylizer)\n>  \n> -  def IsBackwardCompatible(self, older_union, checker):\n> -    \"\"\"This union is backward-compatible with older_union if and only if all\n> -    of the following conditions hold:\n> +  def IsBackwardCompatible(self, rhs, checker):\n> +    \"\"\"This union is backward-compatible with rhs (older_union) if and only if\n> +    all of the following conditions hold:\n>        - Any newly added field is tagged with a [MinVersion] attribute specifying\n>          a version number greater than all previously used [MinVersion]\n>          attributes within the union.\n> -      - All fields present in older_union remain present in the new union,\n> +      - All fields present in rhs remain present in the new union,\n>          with the same ordinal value, same optional or non-optional status,\n>          same (or backward-compatible) type, and where applicable, the same\n>          [MinVersion] attribute value.\n> @@ -651,7 +832,7 @@ class Union(ReferenceKind):\n>        return fields_by_ordinal\n>  \n>      new_fields = buildOrdinalFieldMap(self)\n> -    old_fields = buildOrdinalFieldMap(older_union)\n> +    old_fields = buildOrdinalFieldMap(rhs)\n>      if len(new_fields) < len(old_fields):\n>        # At least one field was removed, which is not OK.\n>        return False\n> @@ -677,6 +858,11 @@ class Union(ReferenceKind):\n>  \n>      return True\n>  \n> +  @property\n> +  def extensible(self):\n> +    return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \\\n> +        if self.attributes else False\n> +\n>    @property\n>    def stable(self):\n>      return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n> @@ -690,10 +876,17 @@ class Union(ReferenceKind):\n>        prefix = self.module.GetNamespacePrefix()\n>      return '%s%s' % (prefix, self.mojom_name)\n>  \n> +  def _tuple(self):\n> +    return (self.mojom_name, self.fields, self.attributes)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Union) and\n> -            (self.mojom_name, self.fields,\n> -             self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes))\n> +    return isinstance(rhs, Union) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>    def __hash__(self):\n>      return id(self)\n> @@ -707,8 +900,8 @@ class Array(ReferenceKind):\n>      length: The number of elements. None if unknown.\n>    \"\"\"\n>  \n> -  ReferenceKind.AddSharedProperty('kind')\n> -  ReferenceKind.AddSharedProperty('length')\n> +  Kind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('length')\n>  \n>    def __init__(self, kind=None, length=None):\n>      if kind is not None:\n> @@ -728,12 +921,11 @@ class Array(ReferenceKind):\n>        return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (\n>            self.__class__.__name__, self.spec, self.is_nullable, Repr(\n>                self.kind), self.length)\n> -    else:\n> -      return GenericRepr(self, {\n> -          'kind': True,\n> -          'length': False,\n> -          'is_nullable': False\n> -      })\n> +    return GenericRepr(self, {\n> +        'kind': True,\n> +        'length': False,\n> +        'is_nullable': False\n> +    })\n>  \n>    def __eq__(self, rhs):\n>      return (isinstance(rhs, Array)\n> @@ -754,8 +946,8 @@ class Map(ReferenceKind):\n>      key_kind: {Kind} The type of the keys. May be None.\n>      value_kind: {Kind} The type of the elements. May be None.\n>    \"\"\"\n> -  ReferenceKind.AddSharedProperty('key_kind')\n> -  ReferenceKind.AddSharedProperty('value_kind')\n> +  Kind.AddSharedProperty('key_kind')\n> +  Kind.AddSharedProperty('value_kind')\n>  \n>    def __init__(self, key_kind=None, value_kind=None):\n>      if (key_kind is not None and value_kind is not None):\n> @@ -780,8 +972,7 @@ class Map(ReferenceKind):\n>        return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (\n>            self.__class__.__name__, self.spec, self.is_nullable,\n>            Repr(self.key_kind), Repr(self.value_kind))\n> -    else:\n> -      return GenericRepr(self, {'key_kind': True, 'value_kind': True})\n> +    return GenericRepr(self, {'key_kind': True, 'value_kind': True})\n>  \n>    def __eq__(self, rhs):\n>      return (isinstance(rhs, Map) and\n> @@ -797,7 +988,7 @@ class Map(ReferenceKind):\n>  \n>  \n>  class PendingRemote(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -822,7 +1013,7 @@ class PendingRemote(ReferenceKind):\n>  \n>  \n>  class PendingReceiver(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -847,7 +1038,7 @@ class PendingReceiver(ReferenceKind):\n>  \n>  \n>  class PendingAssociatedRemote(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -873,7 +1064,7 @@ class PendingAssociatedRemote(ReferenceKind):\n>  \n>  \n>  class PendingAssociatedReceiver(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -899,7 +1090,7 @@ class PendingAssociatedReceiver(ReferenceKind):\n>  \n>  \n>  class InterfaceRequest(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -923,7 +1114,7 @@ class InterfaceRequest(ReferenceKind):\n>  \n>  \n>  class AssociatedInterfaceRequest(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -949,7 +1140,7 @@ class AssociatedInterfaceRequest(ReferenceKind):\n>              self.kind, rhs.kind)\n>  \n>  \n> -class Parameter(object):\n> +class Parameter:\n>    def __init__(self,\n>                 mojom_name=None,\n>                 kind=None,\n> @@ -983,7 +1174,7 @@ class Parameter(object):\n>                                        rhs.default, rhs.attributes))\n>  \n>  \n> -class Method(object):\n> +class Method:\n>    def __init__(self, interface, mojom_name, ordinal=None, attributes=None):\n>      self.interface = interface\n>      self.mojom_name = mojom_name\n> @@ -999,12 +1190,11 @@ class Method(object):\n>    def Repr(self, as_ref=True):\n>      if as_ref:\n>        return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n> -    else:\n> -      return GenericRepr(self, {\n> -          'mojom_name': False,\n> -          'parameters': True,\n> -          'response_parameters': True\n> -      })\n> +    return GenericRepr(self, {\n> +        'mojom_name': False,\n> +        'parameters': True,\n> +        'response_parameters': True\n> +    })\n>  \n>    def AddParameter(self,\n>                     mojom_name,\n> @@ -1061,21 +1251,49 @@ class Method(object):\n>      return self.attributes.get(ATTRIBUTE_UNLIMITED_SIZE) \\\n>          if self.attributes else False\n>  \n> +  @property\n> +  def allowed_context(self):\n> +    return self.attributes.get(ATTRIBUTE_ALLOWED_CONTEXT) \\\n> +        if self.attributes else None\n> +\n> +  @property\n> +  def supports_urgent(self):\n> +    return self.attributes.get(ATTRIBUTE_SUPPORTS_URGENT) \\\n> +        if self.attributes else None\n> +\n> +  @property\n> +  def runtime_feature(self):\n> +    if not self.attributes:\n> +      return None\n> +    runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None)\n> +    if runtime_feature is None:\n> +      return None\n> +    if not isinstance(runtime_feature, Feature):\n> +      raise Exception(\"RuntimeFeature attribute on %s must be a feature.\" %\n> +                      self.name)\n> +    return runtime_feature\n> +\n> +  def _tuple(self):\n> +    return (self.mojom_name, self.ordinal, self.parameters,\n> +            self.response_parameters, self.attributes)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Method) and\n> -            (self.mojom_name, self.ordinal, self.parameters,\n> -             self.response_parameters,\n> -             self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters,\n> -                                  rhs.response_parameters, rhs.attributes))\n> +    return isinstance(rhs, Method) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>  \n>  class Interface(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('mojom_name')\n> -  ReferenceKind.AddSharedProperty('name')\n> -  ReferenceKind.AddSharedProperty('methods')\n> -  ReferenceKind.AddSharedProperty('enums')\n> -  ReferenceKind.AddSharedProperty('constants')\n> -  ReferenceKind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('methods')\n> +  Kind.AddSharedProperty('enums')\n> +  Kind.AddSharedProperty('constants')\n> +  Kind.AddSharedProperty('attributes')\n>  \n>    def __init__(self, mojom_name=None, module=None, attributes=None):\n>      if mojom_name is not None:\n> @@ -1093,12 +1311,11 @@ class Interface(ReferenceKind):\n>    def Repr(self, as_ref=True):\n>      if as_ref:\n>        return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n> -    else:\n> -      return GenericRepr(self, {\n> -          'mojom_name': False,\n> -          'attributes': False,\n> -          'methods': False\n> -      })\n> +    return GenericRepr(self, {\n> +        'mojom_name': False,\n> +        'attributes': False,\n> +        'methods': False\n> +    })\n>  \n>    def AddMethod(self, mojom_name, ordinal=None, attributes=None):\n>      method = Method(self, mojom_name, ordinal, attributes)\n> @@ -1114,10 +1331,10 @@ class Interface(ReferenceKind):\n>      for constant in self.constants:\n>        constant.Stylize(stylizer)\n>  \n> -  def IsBackwardCompatible(self, older_interface, checker):\n> -    \"\"\"This interface is backward-compatible with older_interface if and only\n> -    if all of the following conditions hold:\n> -      - All defined methods in older_interface (when identified by ordinal) have\n> +  def IsBackwardCompatible(self, rhs, checker):\n> +    \"\"\"This interface is backward-compatible with rhs (older_interface) if and\n> +    only if all of the following conditions hold:\n> +      - All defined methods in rhs (when identified by ordinal) have\n>          backward-compatible definitions in this interface. For each method this\n>          means:\n>            - The parameter list is backward-compatible, according to backward-\n> @@ -1131,7 +1348,7 @@ class Interface(ReferenceKind):\n>              rules for structs.\n>        - All newly introduced methods in this interface have a [MinVersion]\n>          attribute specifying a version greater than any method in\n> -        older_interface.\n> +        rhs.\n>      \"\"\"\n>  \n>      def buildOrdinalMethodMap(interface):\n> @@ -1144,7 +1361,7 @@ class Interface(ReferenceKind):\n>        return methods_by_ordinal\n>  \n>      new_methods = buildOrdinalMethodMap(self)\n> -    old_methods = buildOrdinalMethodMap(older_interface)\n> +    old_methods = buildOrdinalMethodMap(rhs)\n>      max_old_min_version = 0\n>      for ordinal, old_method in old_methods.items():\n>        new_method = new_methods.get(ordinal)\n> @@ -1186,6 +1403,39 @@ class Interface(ReferenceKind):\n>  \n>      return True\n>  \n> +  @property\n> +  def service_sandbox(self):\n> +    if not self.attributes:\n> +      return None\n> +    service_sandbox = self.attributes.get(ATTRIBUTE_SERVICE_SANDBOX, None)\n> +    if service_sandbox is None:\n> +      return None\n> +    # Constants are only allowed to refer to an enum here, so replace.\n> +    if isinstance(service_sandbox, Constant):\n> +      service_sandbox = service_sandbox.value\n> +    if not isinstance(service_sandbox, EnumValue):\n> +      raise Exception(\"ServiceSandbox attribute on %s must be an enum value.\" %\n> +                      self.module.name)\n> +    return service_sandbox\n> +\n> +  @property\n> +  def runtime_feature(self):\n> +    if not self.attributes:\n> +      return None\n> +    runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None)\n> +    if runtime_feature is None:\n> +      return None\n> +    if not isinstance(runtime_feature, Feature):\n> +      raise Exception(\"RuntimeFeature attribute on %s must be a feature.\" %\n> +                      self.name)\n> +    return runtime_feature\n> +\n> +  @property\n> +  def require_context(self):\n> +    if not self.attributes:\n> +      return None\n> +    return self.attributes.get(ATTRIBUTE_REQUIRE_CONTEXT, None)\n> +\n>    @property\n>    def stable(self):\n>      return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n> @@ -1199,11 +1449,18 @@ class Interface(ReferenceKind):\n>        prefix = self.module.GetNamespacePrefix()\n>      return '%s%s' % (prefix, self.mojom_name)\n>  \n> +  def _tuple(self):\n> +    return (self.mojom_name, self.methods, self.enums, self.constants,\n> +            self.attributes)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Interface)\n> -            and (self.mojom_name, self.methods, self.enums, self.constants,\n> -                 self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums,\n> -                                      rhs.constants, rhs.attributes))\n> +    return isinstance(rhs, Interface) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>    @property\n>    def uuid(self):\n> @@ -1224,7 +1481,7 @@ class Interface(ReferenceKind):\n>  \n>  \n>  class AssociatedInterface(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -1249,7 +1506,7 @@ class AssociatedInterface(ReferenceKind):\n>                            self.kind, rhs.kind)\n>  \n>  \n> -class EnumField(object):\n> +class EnumField:\n>    def __init__(self,\n>                 mojom_name=None,\n>                 value=None,\n> @@ -1281,16 +1538,25 @@ class EnumField(object):\n>                                           rhs.attributes, rhs.numeric_value))\n>  \n>  \n> -class Enum(Kind):\n> +class Enum(ValueKind):\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('native_only')\n> +  Kind.AddSharedProperty('fields')\n> +  Kind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('min_value')\n> +  Kind.AddSharedProperty('max_value')\n> +  Kind.AddSharedProperty('default_field')\n> +\n>    def __init__(self, mojom_name=None, module=None, attributes=None):\n> -    self.mojom_name = mojom_name\n> -    self.name = None\n> -    self.native_only = False\n>      if mojom_name is not None:\n>        spec = 'x:' + mojom_name\n>      else:\n>        spec = None\n> -    Kind.__init__(self, spec, module)\n> +    ValueKind.__init__(self, spec, False, module)\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.native_only = False\n>      self.fields = []\n>      self.attributes = attributes\n>      self.min_value = None\n> @@ -1300,8 +1566,7 @@ class Enum(Kind):\n>    def Repr(self, as_ref=True):\n>      if as_ref:\n>        return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n> -    else:\n> -      return GenericRepr(self, {'mojom_name': False, 'fields': False})\n> +    return GenericRepr(self, {'mojom_name': False, 'fields': False})\n>  \n>    def Stylize(self, stylizer):\n>      self.name = stylizer.StylizeEnum(self.mojom_name)\n> @@ -1327,14 +1592,14 @@ class Enum(Kind):\n>      return '%s%s' % (prefix, self.mojom_name)\n>  \n>    # pylint: disable=unused-argument\n> -  def IsBackwardCompatible(self, older_enum, checker):\n> -    \"\"\"This enum is backward-compatible with older_enum if and only if one of\n> -    the following conditions holds:\n> +  def IsBackwardCompatible(self, rhs, checker):\n> +    \"\"\"This enum is backward-compatible with rhs (older_enum) if and only if one\n> +    of the following conditions holds:\n>          - Neither enum is [Extensible] and both have the exact same set of valid\n>            numeric values. Field names and aliases for the same numeric value do\n>            not affect compatibility.\n> -        - older_enum is [Extensible], and for every version defined by\n> -          older_enum, this enum has the exact same set of valid numeric values.\n> +        - rhs is [Extensible], and for every version defined by\n> +          rhs, this enum has the exact same set of valid numeric values.\n>      \"\"\"\n>  \n>      def buildVersionFieldMap(enum):\n> @@ -1345,32 +1610,49 @@ class Enum(Kind):\n>          fields_by_min_version[field.min_version].add(field.numeric_value)\n>        return fields_by_min_version\n>  \n> -    old_fields = buildVersionFieldMap(older_enum)\n> +    old_fields = buildVersionFieldMap(rhs)\n>      new_fields = buildVersionFieldMap(self)\n>  \n> -    if new_fields.keys() != old_fields.keys() and not older_enum.extensible:\n> -      return False\n> +    if new_fields.keys() != old_fields.keys() and not rhs.extensible:\n> +      raise Exception(\"Non-extensible enum cannot be modified\")\n>  \n>      for min_version, valid_values in old_fields.items():\n> -      if (min_version not in new_fields\n> -          or new_fields[min_version] != valid_values):\n> -        return False\n> +      if min_version not in new_fields:\n> +        raise Exception('New values added to an extensible enum '\n> +                        'do not specify MinVersion: %s' % new_fields)\n>  \n> +      if (new_fields[min_version] != valid_values):\n> +        if (len(new_fields[min_version]) < len(valid_values)):\n> +          raise Exception('Removing values for an existing MinVersion %s '\n> +                          'is not allowed' % min_version)\n> +\n> +        raise Exception(\n> +            'New values don\\'t match old values'\n> +            'for an existing MinVersion %s,'\n> +            ' please specify MinVersion equal to \"Next version\" '\n> +            'in the enum description'\n> +            ' for the following values:\\n%s' %\n> +            (min_version, new_fields[min_version].difference(valid_values)))\n>      return True\n>  \n> +  def _tuple(self):\n> +    return (self.mojom_name, self.native_only, self.fields, self.attributes,\n> +            self.min_value, self.max_value, self.default_field)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Enum) and\n> -            (self.mojom_name, self.native_only, self.fields, self.attributes,\n> -             self.min_value, self.max_value,\n> -             self.default_field) == (rhs.mojom_name, rhs.native_only,\n> -                                     rhs.fields, rhs.attributes, rhs.min_value,\n> -                                     rhs.max_value, rhs.default_field))\n> +    return isinstance(rhs, Enum) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>    def __hash__(self):\n>      return id(self)\n>  \n>  \n> -class Module(object):\n> +class Module:\n>    def __init__(self, path=None, mojom_namespace=None, attributes=None):\n>      self.path = path\n>      self.mojom_namespace = mojom_namespace\n> @@ -1379,24 +1661,26 @@ class Module(object):\n>      self.unions = []\n>      self.interfaces = []\n>      self.enums = []\n> +    self.features = []\n>      self.constants = []\n> -    self.kinds = {}\n> +    self.kinds = OrderedDict()\n>      self.attributes = attributes\n>      self.imports = []\n> -    self.imported_kinds = {}\n> -    self.metadata = {}\n> +    self.imported_kinds = OrderedDict()\n> +    self.metadata = OrderedDict()\n>  \n>    def __repr__(self):\n>      # Gives us a decent __repr__ for modules.\n>      return self.Repr()\n>  \n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Module) and\n> -            (self.path, self.attributes, self.mojom_namespace, self.imports,\n> -             self.constants, self.enums, self.structs, self.unions,\n> -             self.interfaces) == (rhs.path, rhs.attributes, rhs.mojom_namespace,\n> -                                  rhs.imports, rhs.constants, rhs.enums,\n> -                                  rhs.structs, rhs.unions, rhs.interfaces))\n> +    return (isinstance(rhs, Module)\n> +            and (self.path, self.attributes, self.mojom_namespace, self.imports,\n> +                 self.constants, self.enums, self.structs, self.unions,\n> +                 self.interfaces, self.features)\n> +            == (rhs.path, rhs.attributes, rhs.mojom_namespace, rhs.imports,\n> +                rhs.constants, rhs.enums, rhs.structs, rhs.unions,\n> +                rhs.interfaces, rhs.features))\n>  \n>    def __hash__(self):\n>      return id(self)\n> @@ -1405,16 +1689,16 @@ class Module(object):\n>      if as_ref:\n>        return '<%s path=%r mojom_namespace=%r>' % (\n>            self.__class__.__name__, self.path, self.mojom_namespace)\n> -    else:\n> -      return GenericRepr(\n> -          self, {\n> -              'path': False,\n> -              'mojom_namespace': False,\n> -              'attributes': False,\n> -              'structs': False,\n> -              'interfaces': False,\n> -              'unions': False\n> -          })\n> +    return GenericRepr(\n> +        self, {\n> +            'path': False,\n> +            'mojom_namespace': False,\n> +            'attributes': False,\n> +            'structs': False,\n> +            'interfaces': False,\n> +            'unions': False,\n> +            'features': False,\n> +        })\n>  \n>    def GetNamespacePrefix(self):\n>      return '%s.' % self.mojom_namespace if self.mojom_namespace else ''\n> @@ -1434,6 +1718,11 @@ class Module(object):\n>      self.unions.append(union)\n>      return union\n>  \n> +  def AddFeature(self, mojom_name, attributes=None):\n> +    feature = Feature(mojom_name, self, attributes)\n> +    self.features.append(feature)\n> +    return feature\n> +\n>    def Stylize(self, stylizer):\n>      self.namespace = stylizer.StylizeModule(self.mojom_namespace)\n>      for struct in self.structs:\n> @@ -1446,12 +1735,14 @@ class Module(object):\n>        enum.Stylize(stylizer)\n>      for constant in self.constants:\n>        constant.Stylize(stylizer)\n> +    for feature in self.features:\n> +      feature.Stylize(stylizer)\n>  \n>      for imported_module in self.imports:\n>        imported_module.Stylize(stylizer)\n>  \n>    def Dump(self, f):\n> -    pickle.dump(self, f, 2)\n> +    pickle.dump(self, f)\n>  \n>    @classmethod\n>    def Load(cls, f):\n> @@ -1461,15 +1752,15 @@ class Module(object):\n>  \n>  \n>  def IsBoolKind(kind):\n> -  return kind.spec == BOOL.spec\n> +  return kind.spec == BOOL.spec or kind.spec == NULLABLE_BOOL.spec\n>  \n>  \n>  def IsFloatKind(kind):\n> -  return kind.spec == FLOAT.spec\n> +  return kind.spec == FLOAT.spec or kind.spec == NULLABLE_FLOAT.spec\n>  \n>  \n>  def IsDoubleKind(kind):\n> -  return kind.spec == DOUBLE.spec\n> +  return kind.spec == DOUBLE.spec or kind.spec == NULLABLE_DOUBLE.spec\n>  \n>  \n>  def IsIntegralKind(kind):\n> @@ -1477,7 +1768,14 @@ def IsIntegralKind(kind):\n>            or kind.spec == INT16.spec or kind.spec == INT32.spec\n>            or kind.spec == INT64.spec or kind.spec == UINT8.spec\n>            or kind.spec == UINT16.spec or kind.spec == UINT32.spec\n> -          or kind.spec == UINT64.spec)\n> +          or kind.spec == UINT64.spec or kind.spec == NULLABLE_BOOL.spec\n> +          or kind.spec == NULLABLE_INT8.spec or kind.spec == NULLABLE_INT16.spec\n> +          or kind.spec == NULLABLE_INT32.spec\n> +          or kind.spec == NULLABLE_INT64.spec\n> +          or kind.spec == NULLABLE_UINT8.spec\n> +          or kind.spec == NULLABLE_UINT16.spec\n> +          or kind.spec == NULLABLE_UINT32.spec\n> +          or kind.spec == NULLABLE_UINT64.spec)\n>  \n>  \n>  def IsStringKind(kind):\n> @@ -1522,6 +1820,10 @@ def IsArrayKind(kind):\n>    return isinstance(kind, Array)\n>  \n>  \n> +def IsFeatureKind(kind):\n> +  return isinstance(kind, Feature)\n> +\n> +\n>  def IsInterfaceKind(kind):\n>    return isinstance(kind, Interface)\n>  \n> @@ -1558,12 +1860,16 @@ def IsEnumKind(kind):\n>    return isinstance(kind, Enum)\n>  \n>  \n> +def IsValueKind(kind):\n> +  return isinstance(kind, ValueKind)\n> +\n> +\n>  def IsReferenceKind(kind):\n>    return isinstance(kind, ReferenceKind)\n>  \n>  \n>  def IsNullableKind(kind):\n> -  return IsReferenceKind(kind) and kind.is_nullable\n> +  return kind.is_nullable\n>  \n>  \n>  def IsMapKind(kind):\n> @@ -1664,11 +1970,8 @@ def MethodPassesInterfaces(method):\n>    return _AnyMethodParameterRecursive(method, IsInterfaceKind)\n>  \n>  \n> -def HasSyncMethods(interface):\n> -  for method in interface.methods:\n> -    if method.sync:\n> -      return True\n> -  return False\n> +def GetSyncMethodOrdinals(interface):\n> +  return [method.ordinal for method in interface.methods if method.sync]\n>  \n>  \n>  def HasUninterruptableMethods(interface):\n> @@ -1700,18 +2003,17 @@ def ContainsHandlesOrInterfaces(kind):\n>      checked.add(kind.spec)\n>      if IsStructKind(kind):\n>        return any(Check(field.kind) for field in kind.fields)\n> -    elif IsUnionKind(kind):\n> +    if IsUnionKind(kind):\n>        return any(Check(field.kind) for field in kind.fields)\n> -    elif IsAnyHandleKind(kind):\n> +    if IsAnyHandleKind(kind):\n>        return True\n> -    elif IsAnyInterfaceKind(kind):\n> +    if IsAnyInterfaceKind(kind):\n>        return True\n> -    elif IsArrayKind(kind):\n> +    if IsArrayKind(kind):\n>        return Check(kind.kind)\n> -    elif IsMapKind(kind):\n> +    if IsMapKind(kind):\n>        return Check(kind.key_kind) or Check(kind.value_kind)\n> -    else:\n> -      return False\n> +    return False\n>  \n>    return Check(kind)\n>  \n> @@ -1738,21 +2040,20 @@ def ContainsNativeTypes(kind):\n>      checked.add(kind.spec)\n>      if IsEnumKind(kind):\n>        return kind.native_only\n> -    elif IsStructKind(kind):\n> +    if IsStructKind(kind):\n>        if kind.native_only:\n>          return True\n>        if any(enum.native_only for enum in kind.enums):\n>          return True\n>        return any(Check(field.kind) for field in kind.fields)\n> -    elif IsUnionKind(kind):\n> +    if IsUnionKind(kind):\n>        return any(Check(field.kind) for field in kind.fields)\n> -    elif IsInterfaceKind(kind):\n> +    if IsInterfaceKind(kind):\n>        return any(enum.native_only for enum in kind.enums)\n> -    elif IsArrayKind(kind):\n> +    if IsArrayKind(kind):\n>        return Check(kind.kind)\n> -    elif IsMapKind(kind):\n> +    if IsMapKind(kind):\n>        return Check(kind.key_kind) or Check(kind.value_kind)\n> -    else:\n> -      return False\n> +    return False\n>  \n>    return Check(kind)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n> index e8fd4936ce82..2a4e852c54d0 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n> index 88b77c9887c5..612404260f75 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n> @@ -1,7 +1,8 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> +import copy\n>  from mojom.generate import module as mojom\n>  \n>  # This module provides a mechanism for determining the packed order and offsets\n> @@ -15,7 +16,7 @@ from mojom.generate import module as mojom\n>  HEADER_SIZE = 8\n>  \n>  \n> -class PackedField(object):\n> +class PackedField:\n>    kind_to_size = {\n>        mojom.BOOL: 1,\n>        mojom.INT8: 1,\n> @@ -75,18 +76,55 @@ class PackedField(object):\n>        return 8\n>      return cls.GetSizeForKind(kind)\n>  \n> -  def __init__(self, field, index, ordinal):\n> +  def __init__(self,\n> +               field,\n> +               index,\n> +               ordinal,\n> +               original_field=None,\n> +               sub_ordinal=None,\n> +               linked_value_packed_field=None):\n>      \"\"\"\n>      Args:\n>        field: the original field.\n>        index: the position of the original field in the struct.\n>        ordinal: the ordinal of the field for serialization.\n> +      original_field: See below.\n> +      sub_ordinal: See below.\n> +      linked_value_packed_field: See below.\n> +\n> +    original_field, sub_ordinal, and linked_value_packed_field are used to\n> +    support nullable ValueKind fields. For legacy reasons, nullable ValueKind\n> +    fields actually generate two PackedFields. This allows:\n> +\n> +    - backwards compatibility prior to Mojo support for nullable ValueKinds.\n> +    - correct packing of fields for the aforementioned backwards compatibility.\n> +\n> +    When translating Fields to PackedFields, the original field is turned into\n> +    two PackedFields: the first PackedField always has type mojom.BOOL, while\n> +    the second PackedField has the non-nullable version of the field's kind.\n> +\n> +    When constructing these PackedFields, original_field references the field\n> +    as defined in the mojom; the name as defined in the mojom will be used for\n> +    all layers above the wire/data layer.\n> +\n> +    sub_ordinal is used to sort the two PackedFields correctly with respect to\n> +    each other: the first mojom.BOOL field always has sub_ordinal 0, while the\n> +    second field always has sub_ordinal 1.\n> +\n> +    Finally, linked_value_packed_field is used by the serialization and\n> +    deserialization helpers, which generally just iterate over a PackedStruct's\n> +    PackedField's in ordinal order. This allows the helpers to easily reference\n> +    any related PackedFields rather than having to lookup related PackedFields\n> +    by index while iterating.\n>      \"\"\"\n>      self.field = field\n>      self.index = index\n>      self.ordinal = ordinal\n> -    self.size = self.GetSizeForKind(field.kind)\n> -    self.alignment = self.GetAlignmentForKind(field.kind)\n> +    self.original_field = original_field\n> +    self.sub_ordinal = sub_ordinal\n> +    self.linked_value_packed_field = linked_value_packed_field\n> +    self.size = self.GetSizeForKind(self.field.kind)\n> +    self.alignment = self.GetAlignmentForKind(self.field.kind)\n>      self.offset = None\n>      self.bit = None\n>      self.min_version = None\n> @@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field):\n>    return offset + pad\n>  \n>  \n> -class PackedStruct(object):\n> +def IsNullableValueKindPackedField(field):\n> +  \"\"\"Returns true if `field` is derived from a nullable ValueKind field.\n> +\n> +  Nullable ValueKind fields often require special handling in the bindings due\n> +  to the way the implementation is constrained for wire compatibility.\n> +  \"\"\"\n> +  assert isinstance(field, PackedField)\n> +  return field.sub_ordinal is not None\n> +\n> +\n> +def IsPrimaryNullableValueKindPackedField(field):\n> +  \"\"\"Returns true if `field` is derived from a nullable ValueKind mojom field\n> +  and is the \"primary\" field.\n> +\n> +  The primary field is a bool PackedField that controls if the field should be\n> +  considered as present or not; it will have a reference to the PackedField that\n> +  holds the actual value representation if considered present.\n> +\n> +  Bindings code that translates between the wire protocol and the higher layers\n> +  can use this to simplify mapping multiple PackedFields to the single field\n> +  that is logically exposed to bindings consumers.\n> +  \"\"\"\n> +  assert isinstance(field, PackedField)\n> +  return field.linked_value_packed_field is not None\n> +\n> +\n> +class PackedStruct:\n>    def __init__(self, struct):\n>      self.struct = struct\n>      # |packed_fields| contains all the fields, in increasing offset order.\n> @@ -139,9 +203,41 @@ class PackedStruct(object):\n>      for index, field in enumerate(struct.fields):\n>        if field.ordinal is not None:\n>          ordinal = field.ordinal\n> -      src_fields.append(PackedField(field, index, ordinal))\n> +      # Nullable value types are a bit weird: they generate two PackedFields\n> +      # despite being a single ValueKind. This is for wire compatibility to\n> +      # ease the transition from legacy mojom syntax where nullable value types\n> +      # were not supported.\n> +      if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable:\n> +        # The suffixes intentionally use Unicode codepoints which are considered\n> +        # valid C++/Java/JavaScript identifiers, yet are unlikely to be used in\n> +        # actual user code.\n> +        has_value_field = copy.copy(field)\n> +        has_value_field.name = f'{field.mojom_name}_$flag'\n> +        has_value_field.kind = mojom.BOOL\n> +\n> +        value_field = copy.copy(field)\n> +        value_field.name = f'{field.mojom_name}_$value'\n> +        value_field.kind = field.kind.MakeUnnullableKind()\n> +\n> +        value_packed_field = PackedField(value_field,\n> +                                         index,\n> +                                         ordinal,\n> +                                         original_field=field,\n> +                                         sub_ordinal=1,\n> +                                         linked_value_packed_field=None)\n> +        has_value_packed_field = PackedField(\n> +            has_value_field,\n> +            index,\n> +            ordinal,\n> +            original_field=field,\n> +            sub_ordinal=0,\n> +            linked_value_packed_field=value_packed_field)\n> +        src_fields.append(has_value_packed_field)\n> +        src_fields.append(value_packed_field)\n> +      else:\n> +        src_fields.append(PackedField(field, index, ordinal))\n>        ordinal += 1\n> -    src_fields.sort(key=lambda field: field.ordinal)\n> +    src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal))\n>  \n>      # Set |min_version| for each field.\n>      next_min_version = 0\n> @@ -156,10 +252,11 @@ class PackedStruct(object):\n>        if (packed_field.min_version != 0\n>            and mojom.IsReferenceKind(packed_field.field.kind)\n>            and not packed_field.field.kind.is_nullable):\n> -        raise Exception(\"Non-nullable fields are only allowed in version 0 of \"\n> -                        \"a struct. %s.%s is defined with [MinVersion=%d].\" %\n> -                        (self.struct.name, packed_field.field.name,\n> -                         packed_field.min_version))\n> +        raise Exception(\n> +            \"Non-nullable reference fields are only allowed in version 0 of a \"\n> +            \"struct. %s.%s is defined with [MinVersion=%d].\" %\n> +            (self.struct.name, packed_field.field.name,\n> +             packed_field.min_version))\n>  \n>      src_field = src_fields[0]\n>      src_field.offset = 0\n> @@ -186,7 +283,7 @@ class PackedStruct(object):\n>          dst_fields.append(src_field)\n>  \n>  \n> -class ByteInfo(object):\n> +class ByteInfo:\n>    def __init__(self):\n>      self.is_padding = False\n>      self.packed_fields = []\n> @@ -214,10 +311,11 @@ def GetByteLayout(packed_struct):\n>    return byte_info\n>  \n>  \n> -class VersionInfo(object):\n> -  def __init__(self, version, num_fields, num_bytes):\n> +class VersionInfo:\n> +  def __init__(self, version, num_fields, num_packed_fields, num_bytes):\n>      self.version = version\n>      self.num_fields = num_fields\n> +    self.num_packed_fields = num_packed_fields\n>      self.num_bytes = num_bytes\n>  \n>  \n> @@ -235,24 +333,35 @@ def GetVersionInfo(packed_struct):\n>    versions = []\n>    last_version = 0\n>    last_num_fields = 0\n> +  last_num_packed_fields = 0\n>    last_payload_size = 0\n>  \n>    for packed_field in packed_struct.packed_fields_in_ordinal_order:\n>      if packed_field.min_version != last_version:\n>        versions.append(\n> -          VersionInfo(last_version, last_num_fields,\n> +          VersionInfo(last_version, last_num_fields, last_num_packed_fields,\n>                        last_payload_size + HEADER_SIZE))\n>        last_version = packed_field.min_version\n>  \n> -    last_num_fields += 1\n> +    # Nullable numeric fields (e.g. `int32?`) expand to two packed fields, so to\n> +    # avoid double-counting, only increment if the field is:\n> +    # - not used for representing a nullable value kind field, or\n> +    # - the primary field representing the nullable value kind field.\n> +    last_num_fields += 1 if (\n> +        not IsNullableValueKindPackedField(packed_field)\n> +        or IsPrimaryNullableValueKindPackedField(packed_field)) else 0\n> +\n> +    last_num_packed_fields += 1\n> +\n>      # The fields are iterated in ordinal order here. However, the size of a\n>      # version is determined by the last field of that version in pack order,\n>      # instead of ordinal order. Therefore, we need to calculate the max value.\n> -    last_payload_size = max(\n> -        GetPayloadSizeUpToField(packed_field), last_payload_size)\n> +    last_payload_size = max(GetPayloadSizeUpToField(packed_field),\n> +                            last_payload_size)\n>  \n> -  assert len(versions) == 0 or last_num_fields != versions[-1].num_fields\n> +  assert len(\n> +      versions) == 0 or last_num_packed_fields != versions[-1].num_packed_fields\n>    versions.append(\n> -      VersionInfo(last_version, last_num_fields,\n> +      VersionInfo(last_version, last_num_fields, last_num_packed_fields,\n>                    last_payload_size + HEADER_SIZE))\n>    return versions\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n> index 98c705add32b..7d8e4e019719 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -205,6 +205,34 @@ class PackTest(unittest.TestCase):\n>      self.assertEqual(4, versions[2].num_fields)\n>      self.assertEqual(32, versions[2].num_bytes)\n>  \n> +  def testGetVersionInfoPackedStruct(self):\n> +    \"\"\"Tests that pack.GetVersionInfo() correctly sets version, num_fields,\n> +    and num_packed_fields for a packed struct.\n> +    \"\"\"\n> +    struct = mojom.Struct('test')\n> +    struct.AddField('field_0', mojom.BOOL, ordinal=0)\n> +    struct.AddField('field_1',\n> +                    mojom.NULLABLE_BOOL,\n> +                    ordinal=1,\n> +                    attributes={'MinVersion': 1})\n> +    struct.AddField('field_2',\n> +                    mojom.NULLABLE_BOOL,\n> +                    ordinal=2,\n> +                    attributes={'MinVersion': 2})\n> +    ps = pack.PackedStruct(struct)\n> +    versions = pack.GetVersionInfo(ps)\n> +\n> +    self.assertEqual(3, len(versions))\n> +    self.assertEqual(0, versions[0].version)\n> +    self.assertEqual(1, versions[1].version)\n> +    self.assertEqual(2, versions[2].version)\n> +    self.assertEqual(1, versions[0].num_fields)\n> +    self.assertEqual(2, versions[1].num_fields)\n> +    self.assertEqual(3, versions[2].num_fields)\n> +    self.assertEqual(1, versions[0].num_packed_fields)\n> +    self.assertEqual(3, versions[1].num_packed_fields)\n> +    self.assertEqual(5, versions[2].num_packed_fields)\n> +\n>    def testInterfaceAlignment(self):\n>      \"\"\"Tests that interfaces are aligned on 4-byte boundaries, although the size\n>      of an interface is 8 bytes.\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n> index 0da900585cea..807e2a4fdfc0 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n> index 7580b78002e6..83bb297f5d44 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Convert parse tree to AST.\n> @@ -12,17 +12,294 @@ already been parsed and converted to ASTs before.\n>  import itertools\n>  import os\n>  import re\n> -import sys\n>  \n> +from collections import OrderedDict\n>  from mojom.generate import generator\n>  from mojom.generate import module as mojom\n>  from mojom.parse import ast\n>  \n>  \n> -def _IsStrOrUnicode(x):\n> -  if sys.version_info[0] < 3:\n> -    return isinstance(x, (unicode, str))\n> -  return isinstance(x, str)\n> +is_running_backwards_compatibility_check_hack = False\n> +\n> +### DO NOT ADD ENTRIES TO THIS LIST. ###\n> +_EXTENSIBLE_ENUMS_MISSING_DEFAULT = (\n> +    'x:arc.keymaster.mojom.Algorithm',\n> +    'x:arc.keymaster.mojom.Digest',\n> +    'x:arc.keymaster.mojom.SignatureResult',\n> +    'x:arc.mojom.AccessibilityActionType',\n> +    'x:arc.mojom.AccessibilityBooleanProperty',\n> +    'x:arc.mojom.AccessibilityEventIntListProperty',\n> +    'x:arc.mojom.AccessibilityEventIntProperty',\n> +    'x:arc.mojom.AccessibilityEventStringProperty',\n> +    'x:arc.mojom.AccessibilityEventType',\n> +    'x:arc.mojom.AccessibilityFilterType',\n> +    'x:arc.mojom.AccessibilityIntListProperty',\n> +    'x:arc.mojom.AccessibilityIntProperty',\n> +    'x:arc.mojom.AccessibilityLiveRegionType',\n> +    'x:arc.mojom.AccessibilityNotificationStateType',\n> +    'x:arc.mojom.AccessibilityRangeType',\n> +    'x:arc.mojom.AccessibilitySelectionMode',\n> +    'x:arc.mojom.AccessibilityStringListProperty',\n> +    'x:arc.mojom.AccessibilityStringProperty',\n> +    'x:arc.mojom.AccessibilityWindowBooleanProperty',\n> +    'x:arc.mojom.AccessibilityWindowIntListProperty',\n> +    'x:arc.mojom.AccessibilityWindowIntProperty',\n> +    'x:arc.mojom.AccessibilityWindowStringProperty',\n> +    'x:arc.mojom.AccessibilityWindowType',\n> +    'x:arc.mojom.AccountCheckStatus',\n> +    'x:arc.mojom.AccountUpdateType',\n> +    'x:arc.mojom.ActionType',\n> +    'x:arc.mojom.Algorithm',\n> +    'x:arc.mojom.AndroidIdSource',\n> +    'x:arc.mojom.AnrSource',\n> +    'x:arc.mojom.AnrType',\n> +    'x:arc.mojom.AppDiscoveryRequestState',\n> +    'x:arc.mojom.AppKillType',\n> +    'x:arc.mojom.AppPermission',\n> +    'x:arc.mojom.AppPermissionGroup',\n> +    'x:arc.mojom.AppReinstallState',\n> +    'x:arc.mojom.AppShortcutItemType',\n> +    'x:arc.mojom.ArcAuthCodeStatus',\n> +    'x:arc.mojom.ArcClipboardDragDropEvent',\n> +    'x:arc.mojom.ArcCorePriAbiMigEvent',\n> +    'x:arc.mojom.ArcDnsQuery',\n> +    'x:arc.mojom.ArcImageCopyPasteCompatAction',\n> +    'x:arc.mojom.ArcNetworkError',\n> +    'x:arc.mojom.ArcNetworkEvent',\n> +    'x:arc.mojom.ArcNotificationEvent',\n> +    'x:arc.mojom.ArcNotificationExpandState',\n> +    'x:arc.mojom.ArcNotificationPriority',\n> +    'x:arc.mojom.ArcNotificationRemoteInputState',\n> +    'x:arc.mojom.ArcNotificationShownContents',\n> +    'x:arc.mojom.ArcNotificationStyle',\n> +    'x:arc.mojom.ArcNotificationType',\n> +    'x:arc.mojom.ArcPipEvent',\n> +    'x:arc.mojom.ArcResizeLockState',\n> +    'x:arc.mojom.ArcSignInSuccess',\n> +    'x:arc.mojom.ArcTimerResult',\n> +    'x:arc.mojom.AudioSwitch',\n> +    'x:arc.mojom.BluetoothAclState',\n> +    'x:arc.mojom.BluetoothAdapterState',\n> +    'x:arc.mojom.BluetoothAdvertisingDataType',\n> +    'x:arc.mojom.BluetoothBondState',\n> +    'x:arc.mojom.BluetoothDeviceType',\n> +    'x:arc.mojom.BluetoothDiscoveryState',\n> +    'x:arc.mojom.BluetoothGattDBAttributeType',\n> +    'x:arc.mojom.BluetoothGattStatus',\n> +    'x:arc.mojom.BluetoothPropertyType',\n> +    'x:arc.mojom.BluetoothScanMode',\n> +    'x:arc.mojom.BluetoothSdpAttributeType',\n> +    'x:arc.mojom.BluetoothSocketType',\n> +    'x:arc.mojom.BluetoothStatus',\n> +    'x:arc.mojom.BootType',\n> +    'x:arc.mojom.CaptionTextShadowType',\n> +    'x:arc.mojom.ChangeType',\n> +    'x:arc.mojom.ChromeAccountType',\n> +    'x:arc.mojom.ChromeApp',\n> +    'x:arc.mojom.ChromePage',\n> +    'x:arc.mojom.ClockId',\n> +    'x:arc.mojom.CloudProvisionFlowError',\n> +    'x:arc.mojom.CommandResultType',\n> +    'x:arc.mojom.CompanionLibApiId',\n> +    'x:arc.mojom.ConnectionStateType',\n> +    'x:arc.mojom.ContentChangeType',\n> +    'x:arc.mojom.CpuRestrictionState',\n> +    'x:arc.mojom.CursorCoordinateSpace',\n> +    'x:arc.mojom.DataRestoreStatus',\n> +    'x:arc.mojom.DecoderStatus',\n> +    'x:arc.mojom.DeviceType',\n> +    'x:arc.mojom.Digest',\n> +    'x:arc.mojom.DisplayWakeLockType',\n> +    'x:arc.mojom.EapMethod',\n> +    'x:arc.mojom.EapPhase2Method',\n> +    'x:arc.mojom.FileSelectorEventType',\n> +    'x:arc.mojom.GMSCheckInError',\n> +    'x:arc.mojom.GMSSignInError',\n> +    'x:arc.mojom.GeneralSignInError',\n> +    'x:arc.mojom.GetNetworksRequestType',\n> +    'x:arc.mojom.HalPixelFormat',\n> +    'x:arc.mojom.IPAddressType',\n> +    'x:arc.mojom.InstallErrorReason',\n> +    'x:arc.mojom.KeyFormat',\n> +    'x:arc.mojom.KeyManagement',\n> +    'x:arc.mojom.KeyPurpose',\n> +    'x:arc.mojom.KeymasterError',\n> +    'x:arc.mojom.MainAccountHashMigrationStatus',\n> +    'x:arc.mojom.MainAccountResolutionStatus',\n> +    'x:arc.mojom.ManagementChangeStatus',\n> +    'x:arc.mojom.ManagementState',\n> +    'x:arc.mojom.MessageCenterVisibility',\n> +    'x:arc.mojom.MetricsType',\n> +    'x:arc.mojom.MountEvent',\n> +    'x:arc.mojom.NativeBridgeType',\n> +    'x:arc.mojom.NetworkResult',\n> +    'x:arc.mojom.NetworkType',\n> +    'x:arc.mojom.OemCryptoAlgorithm',\n> +    'x:arc.mojom.OemCryptoCipherMode',\n> +    'x:arc.mojom.OemCryptoHdcpCapability',\n> +    'x:arc.mojom.OemCryptoLicenseType',\n> +    'x:arc.mojom.OemCryptoPrivateKey',\n> +    'x:arc.mojom.OemCryptoProvisioningMethod',\n> +    'x:arc.mojom.OemCryptoResult',\n> +    'x:arc.mojom.OemCryptoRsaPaddingScheme',\n> +    'x:arc.mojom.OemCryptoUsageEntryStatus',\n> +    'x:arc.mojom.Padding',\n> +    'x:arc.mojom.PaiFlowState',\n> +    'x:arc.mojom.PatternType',\n> +    'x:arc.mojom.PressureLevel',\n> +    'x:arc.mojom.PrintColorMode',\n> +    'x:arc.mojom.PrintContentType',\n> +    'x:arc.mojom.PrintDuplexMode',\n> +    'x:arc.mojom.PrinterStatus',\n> +    'x:arc.mojom.ProcessState',\n> +    'x:arc.mojom.PurchaseState',\n> +    'x:arc.mojom.ReauthReason',\n> +    'x:arc.mojom.ScaleFactor',\n> +    'x:arc.mojom.SecurityType',\n> +    'x:arc.mojom.SegmentStyle',\n> +    'x:arc.mojom.SelectFilesActionType',\n> +    'x:arc.mojom.SetNativeChromeVoxResponse',\n> +    'x:arc.mojom.ShowPackageInfoPage',\n> +    'x:arc.mojom.SpanType',\n> +    'x:arc.mojom.SupportedLinkChangeSource',\n> +    'x:arc.mojom.TetheringClientState',\n> +    'x:arc.mojom.TextInputType',\n> +    'x:arc.mojom.TtsEventType',\n> +    'x:arc.mojom.VideoCodecProfile',\n> +    'x:arc.mojom.VideoDecodeAccelerator.Result',\n> +    'x:arc.mojom.VideoEncodeAccelerator.Error',\n> +    'x:arc.mojom.VideoFrameStorageType',\n> +    'x:arc.mojom.VideoPixelFormat',\n> +    'x:arc.mojom.WakefulnessMode',\n> +    'x:arc.mojom.WebApkInstallResult',\n> +    'x:ash.ime.mojom.InputFieldType',\n> +    'x:ash.ime.mojom.PersonalizationMode',\n> +    'x:ash.language.mojom.FeatureId',\n> +    'x:blink.mojom.ScrollRestorationType',\n> +    'x:chromeos.cdm.mojom.CdmKeyStatus',\n> +    'x:chromeos.cdm.mojom.CdmMessageType',\n> +    'x:chromeos.cdm.mojom.CdmSessionType',\n> +    'x:chromeos.cdm.mojom.DecryptStatus',\n> +    'x:chromeos.cdm.mojom.EmeInitDataType',\n> +    'x:chromeos.cdm.mojom.EncryptionScheme',\n> +    'x:chromeos.cdm.mojom.HdcpVersion',\n> +    'x:chromeos.cdm.mojom.OutputProtection.LinkType',\n> +    'x:chromeos.cdm.mojom.OutputProtection.ProtectionType',\n> +    'x:chromeos.cdm.mojom.PromiseException',\n> +    'x:chromeos.cfm.mojom.EnqueuePriority',\n> +    'x:chromeos.cfm.mojom.LoggerErrorCode',\n> +    'x:chromeos.cfm.mojom.LoggerState',\n> +    'x:chromeos.cros_healthd.mojom.CryptoAlgorithm',\n> +    'x:chromeos.cros_healthd.mojom.EncryptionState',\n> +    'x:chromeos.machine_learning.mojom.AnnotationUsecase',\n> +    'x:chromeos.machine_learning.mojom.BuiltinModelId',\n> +    'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult',\n> +    'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus',\n> +    'x:chromeos.machine_learning.mojom.EndpointReason',\n> +    'x:chromeos.machine_learning.mojom.EndpointerType',\n> +    'x:chromeos.machine_learning.mojom.ExecuteResult',\n> +    'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status',\n> +    'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status',\n> +    'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult',\n> +    'x:chromeos.machine_learning.mojom.LoadModelResult',\n> +    'x:chromeos.machine_learning.mojom.Rotation',\n> +    'x:chromeos.network_config.mojom.ConnectionStateType',\n> +    'x:chromeos.network_config.mojom.DeviceStateType',\n> +    'x:chromeos.network_config.mojom.IPConfigType',\n> +    'x:chromeos.network_config.mojom.NetworkType',\n> +    'x:chromeos.network_config.mojom.OncSource',\n> +    'x:chromeos.network_config.mojom.PolicySource',\n> +    'x:chromeos.network_config.mojom.PortalState',\n> +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent',\n> +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod',\n> +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus',\n> +    'x:cros.mojom.CameraClientType',\n> +    'x:cros.mojom.CameraMetadataSectionStart',\n> +    'x:cros.mojom.CameraMetadataTag',\n> +    'x:cros.mojom.HalPixelFormat',\n> +    'x:crosapi.mojom.AllowedPaths',\n> +    'x:crosapi.mojom.BrowserAppInstanceType',\n> +    'x:crosapi.mojom.CreationResult',\n> +    'x:crosapi.mojom.DeviceAccessResultCode',\n> +    'x:crosapi.mojom.DeviceMode',\n> +    'x:crosapi.mojom.DlpRestrictionLevel',\n> +    'x:crosapi.mojom.ExoImeSupport',\n> +    'x:crosapi.mojom.FullscreenVisibility',\n> +    'x:crosapi.mojom.GoogleServiceAuthError.State',\n> +    'x:crosapi.mojom.IsInstallableResult',\n> +    'x:crosapi.mojom.KeyTag',\n> +    'x:crosapi.mojom.KeystoreSigningAlgorithmName',\n> +    'x:crosapi.mojom.KeystoreType',\n> +    'x:crosapi.mojom.LacrosFeedbackSource',\n> +    'x:crosapi.mojom.MemoryPressureLevel',\n> +    'x:crosapi.mojom.MetricsReportingManaged',\n> +    'x:crosapi.mojom.NotificationType',\n> +    'x:crosapi.mojom.OndeviceHandwritingSupport',\n> +    'x:crosapi.mojom.OpenResult',\n> +    'x:crosapi.mojom.PolicyDomain',\n> +    'x:crosapi.mojom.RegistrationCodeType',\n> +    'x:crosapi.mojom.ScaleFactor',\n> +    'x:crosapi.mojom.SearchResult.OptionalBool',\n> +    'x:crosapi.mojom.SelectFileDialogType',\n> +    'x:crosapi.mojom.SelectFileResult',\n> +    'x:crosapi.mojom.SharesheetResult',\n> +    'x:crosapi.mojom.TouchEventType',\n> +    'x:crosapi.mojom.VideoRotation',\n> +    'x:crosapi.mojom.WallpaperLayout',\n> +    'x:crosapi.mojom.WebAppInstallResultCode',\n> +    'x:crosapi.mojom.WebAppUninstallResultCode',\n> +    'x:device.mojom.HidBusType',\n> +    'x:device.mojom.WakeLockReason',\n> +    'x:device.mojom.WakeLockType',\n> +    'x:drivefs.mojom.DialogReason.Type',\n> +    'x:drivefs.mojom.DriveError.Type',\n> +    'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus',\n> +    'x:drivefs.mojom.FileMetadata.CanPinStatus',\n> +    'x:drivefs.mojom.FileMetadata.Type',\n> +    'x:drivefs.mojom.ItemEventReason',\n> +    'x:drivefs.mojom.MirrorPathStatus',\n> +    'x:drivefs.mojom.MirrorSyncStatus',\n> +    'x:drivefs.mojom.QueryParameters.SortField',\n> +    'x:fuzz.mojom.FuzzEnum',\n> +    'x:media.mojom.FillLightMode',\n> +    'x:media.mojom.MeteringMode',\n> +    'x:media.mojom.PowerLineFrequency',\n> +    'x:media.mojom.RedEyeReduction',\n> +    'x:media.mojom.ResolutionChangePolicy',\n> +    'x:media.mojom.VideoCaptureApi',\n> +    'x:media.mojom.VideoCaptureBufferType',\n> +    'x:media.mojom.VideoCaptureError',\n> +    'x:media.mojom.VideoCaptureFrameDropReason',\n> +    'x:media.mojom.VideoCapturePixelFormat',\n> +    'x:media.mojom.VideoCaptureTransportType',\n> +    'x:media.mojom.VideoFacingMode',\n> +    'x:media_session.mojom.AudioFocusType',\n> +    'x:media_session.mojom.CameraState',\n> +    'x:media_session.mojom.EnforcementMode',\n> +    'x:media_session.mojom.MediaAudioVideoState',\n> +    'x:media_session.mojom.MediaImageBitmapColorType',\n> +    'x:media_session.mojom.MediaPictureInPictureState',\n> +    'x:media_session.mojom.MediaPlaybackState',\n> +    'x:media_session.mojom.MediaSession.SuspendType',\n> +    'x:media_session.mojom.MediaSessionAction',\n> +    'x:media_session.mojom.MediaSessionImageType',\n> +    'x:media_session.mojom.MediaSessionInfo.SessionState',\n> +    'x:media_session.mojom.MicrophoneState',\n> +    'x:ml.model_loader.mojom.ComputeResult',\n> +    'x:ml.model_loader.mojom.CreateModelLoaderResult',\n> +    'x:ml.model_loader.mojom.LoadModelResult',\n> +    'x:mojo.test.AnExtensibleEnum',\n> +    'x:mojo.test.EnumB',\n> +    'x:mojo.test.ExtensibleEmptyEnum',\n> +    'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault',\n> +    'x:network.mojom.WebSandboxFlags',\n> +    'x:payments.mojom.BillingResponseCode',\n> +    'x:payments.mojom.CreateDigitalGoodsResponseCode',\n> +    'x:payments.mojom.ItemType',\n> +    'x:printing.mojom.PrinterType',\n> +    'x:ui.mojom.KeyboardCode',\n> +)\n> +### DO NOT ADD ENTRIES TO THIS LIST. ###\n>  \n>  \n>  def _DuplicateName(values):\n> @@ -98,12 +375,6 @@ def _MapKind(kind):\n>    }\n>    if kind.endswith('?'):\n>      base_kind = _MapKind(kind[0:-1])\n> -    # NOTE: This doesn't rule out enum types. Those will be detected later, when\n> -    # cross-reference is established.\n> -    reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',\n> -                       'rma', 'rca')\n> -    if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:\n> -      raise Exception('A type (spec \"%s\") cannot be made nullable' % base_kind)\n>      return '?' + base_kind\n>    if kind.endswith('}'):\n>      lbracket = kind.rfind('{')\n> @@ -113,8 +384,6 @@ def _MapKind(kind):\n>      lbracket = kind.rfind('[')\n>      typename = kind[0:lbracket]\n>      return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)\n> -  if kind.endswith('&'):\n> -    return 'r:' + _MapKind(kind[0:-1])\n>    if kind.startswith('asso<'):\n>      assert kind.endswith('>')\n>      return 'asso:' + _MapKind(kind[5:-1])\n> @@ -135,13 +404,45 @@ def _MapKind(kind):\n>    return 'x:' + kind\n>  \n>  \n> -def _AttributeListToDict(attribute_list):\n> +def _MapAttributeValue(module, kind, value):\n> +  # True/False/None\n> +  if value is None:\n> +    return value\n> +  if not isinstance(value, str):\n> +    return value\n> +  # Is the attribute value the name of a feature?\n> +  try:\n> +    # Features cannot be nested in other types, so lookup in the global scope.\n> +    trial = _LookupKind(module.kinds, 'x:' + value,\n> +                        _GetScopeForKind(module, kind))\n> +    if isinstance(trial, mojom.Feature):\n> +      return trial\n> +  except ValueError:\n> +    pass\n> +  # Is the attribute value a constant or enum value?\n> +  try:\n> +    trial = _LookupValue(module, None, None, ('IDENTIFIER', value))\n> +    if isinstance(trial, mojom.ConstantValue):\n> +      return trial.constant\n> +    if isinstance(trial, mojom.EnumValue):\n> +      return trial\n> +  except ValueError:\n> +    pass\n> +  # If not a referenceable mojo type - return as a string.\n> +  return value\n> +\n> +\n> +def _AttributeListToDict(module, kind, attribute_list):\n>    if attribute_list is None:\n>      return None\n>    assert isinstance(attribute_list, ast.AttributeList)\n> -  # TODO(vtl): Check for duplicate keys here.\n> -  return dict(\n> -      [(attribute.key, attribute.value) for attribute in attribute_list])\n> +  attributes = dict()\n> +  for attribute in attribute_list:\n> +    if attribute.key in attributes:\n> +      raise Exception(\"Duplicate key (%s) in attribute list\" % attribute.key)\n> +    attributes[attribute.key] = _MapAttributeValue(module, kind,\n> +                                                   attribute.value)\n> +  return attributes\n>  \n>  \n>  builtin_values = frozenset([\n> @@ -257,7 +558,8 @@ def _Kind(kinds, spec, scope):\n>      return kind\n>  \n>    if spec.startswith('?'):\n> -    kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()\n> +    kind = _Kind(kinds, spec[1:], scope)\n> +    kind = kind.MakeNullableKind()\n>    elif spec.startswith('a:'):\n>      kind = mojom.Array(_Kind(kinds, spec[2:], scope))\n>    elif spec.startswith('asso:'):\n> @@ -303,7 +605,8 @@ def _Kind(kinds, spec, scope):\n>  \n>  def _Import(module, import_module):\n>    # Copy the struct kinds from our imports into the current module.\n> -  importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)\n> +  importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface,\n> +                      mojom.Feature)\n>    for kind in import_module.kinds.values():\n>      if (isinstance(kind, importable_kinds)\n>          and kind.module.path == import_module.path):\n> @@ -316,6 +619,32 @@ def _Import(module, import_module):\n>    return import_module\n>  \n>  \n> +def _Feature(module, parsed_feature):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_feature: {ast.Feature} Parsed feature.\n> +\n> +  Returns:\n> +    {mojom.Feature} AST feature.\n> +  \"\"\"\n> +  feature = mojom.Feature(module=module)\n> +  feature.mojom_name = parsed_feature.mojom_name\n> +  feature.spec = 'x:' + module.GetNamespacePrefix() + feature.mojom_name\n> +  module.kinds[feature.spec] = feature\n> +  feature.constants = []\n> +  _ProcessElements(\n> +      parsed_feature.mojom_name, parsed_feature.body, {\n> +          ast.Const:\n> +          lambda const: feature.constants.append(\n> +              _Constant(module, const, feature)),\n> +      })\n> +\n> +  feature.attributes = _AttributeListToDict(module, feature,\n> +                                            parsed_feature.attribute_list)\n> +  return feature\n> +\n> +\n>  def _Struct(module, parsed_struct):\n>    \"\"\"\n>    Args:\n> @@ -345,7 +674,8 @@ def _Struct(module, parsed_struct):\n>              struct.fields_data.append,\n>          })\n>  \n> -  struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)\n> +  struct.attributes = _AttributeListToDict(module, struct,\n> +                                           parsed_struct.attribute_list)\n>  \n>    # Enforce that a [Native] attribute is set to make native-only struct\n>    # declarations more explicit.\n> @@ -377,7 +707,8 @@ def _Union(module, parsed_union):\n>    union.fields_data = []\n>    _ProcessElements(parsed_union.mojom_name, parsed_union.body,\n>                     {ast.UnionField: union.fields_data.append})\n> -  union.attributes = _AttributeListToDict(parsed_union.attribute_list)\n> +  union.attributes = _AttributeListToDict(module, union,\n> +                                          parsed_union.attribute_list)\n>    return union\n>  \n>  \n> @@ -398,7 +729,8 @@ def _StructField(module, parsed_field, struct):\n>    field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None\n>    field.default = _LookupValue(module, struct, field.kind,\n>                                 parsed_field.default_value)\n> -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n> +  field.attributes = _AttributeListToDict(module, field,\n> +                                          parsed_field.attribute_list)\n>    return field\n>  \n>  \n> @@ -414,11 +746,22 @@ def _UnionField(module, parsed_field, union):\n>    \"\"\"\n>    field = mojom.UnionField()\n>    field.mojom_name = parsed_field.mojom_name\n> +  # Disallow unions from being self-recursive.\n> +  parsed_typename = parsed_field.typename\n> +  if parsed_typename.endswith('?'):\n> +    parsed_typename = parsed_typename[:-1]\n> +  assert parsed_typename != union.mojom_name\n>    field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),\n>                       (module.mojom_namespace, union.mojom_name))\n>    field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None\n>    field.default = None\n> -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n> +  field.attributes = _AttributeListToDict(module, field,\n> +                                          parsed_field.attribute_list)\n> +  if field.is_default and not mojom.IsNullableKind(field.kind) and \\\n> +     not mojom.IsIntegralKind(field.kind):\n> +    raise Exception(\n> +        '[Default] field for union %s must be nullable or integral type.' %\n> +        union.mojom_name)\n>    return field\n>  \n>  \n> @@ -439,7 +782,8 @@ def _Parameter(module, parsed_param, interface):\n>    parameter.ordinal = (parsed_param.ordinal.value\n>                         if parsed_param.ordinal else None)\n>    parameter.default = None  # TODO(tibell): We never have these. Remove field?\n> -  parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)\n> +  parameter.attributes = _AttributeListToDict(module, parameter,\n> +                                              parsed_param.attribute_list)\n>    return parameter\n>  \n>  \n> @@ -464,7 +808,8 @@ def _Method(module, parsed_method, interface):\n>      method.response_parameters = list(\n>          map(lambda parameter: _Parameter(module, parameter, interface),\n>              parsed_method.response_parameter_list))\n> -  method.attributes = _AttributeListToDict(parsed_method.attribute_list)\n> +  method.attributes = _AttributeListToDict(module, method,\n> +                                           parsed_method.attribute_list)\n>  \n>    # Enforce that only methods with response can have a [Sync] attribute.\n>    if method.sync and method.response_parameters is None:\n> @@ -492,7 +837,8 @@ def _Interface(module, parsed_iface):\n>    interface.mojom_name = parsed_iface.mojom_name\n>    interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name\n>    module.kinds[interface.spec] = interface\n> -  interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)\n> +  interface.attributes = _AttributeListToDict(module, interface,\n> +                                              parsed_iface.attribute_list)\n>    interface.enums = []\n>    interface.constants = []\n>    interface.methods_data = []\n> @@ -522,7 +868,8 @@ def _EnumField(module, enum, parsed_field):\n>    field = mojom.EnumField()\n>    field.mojom_name = parsed_field.mojom_name\n>    field.value = _LookupValue(module, enum, None, parsed_field.value)\n> -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n> +  field.attributes = _AttributeListToDict(module, field,\n> +                                          parsed_field.attribute_list)\n>    value = mojom.EnumValue(module, enum, field)\n>    module.values[value.GetSpec()] = value\n>    return field\n> @@ -544,7 +891,7 @@ def _ResolveNumericEnumValues(enum):\n>        prev_value += 1\n>  \n>      # Integral value (e.g: BEGIN = -0x1).\n> -    elif _IsStrOrUnicode(field.value):\n> +    elif isinstance(field.value, str):\n>        prev_value = int(field.value, 0)\n>  \n>      # Reference to a previous enum value (e.g: INIT = BEGIN).\n> @@ -560,7 +907,10 @@ def _ResolveNumericEnumValues(enum):\n>      else:\n>        raise Exception('Unresolved enum value for %s' % field.value.GetSpec())\n>  \n> -    #resolved_enum_values[field.mojom_name] = prev_value\n> +    if prev_value in (-128, -127):\n> +      raise Exception(f'{field.mojom_name} in {enum.spec} has the value '\n> +                      f'{prev_value}, which is reserved for WTF::HashTrait\\'s '\n> +                      'default enum specialization and may not be used.')\n>      field.numeric_value = prev_value\n>      if min_value is None or prev_value < min_value:\n>        min_value = prev_value\n> @@ -588,7 +938,8 @@ def _Enum(module, parsed_enum, parent_kind):\n>      mojom_name = parent_kind.mojom_name + '.' + mojom_name\n>    enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)\n>    enum.parent_kind = parent_kind\n> -  enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)\n> +  enum.attributes = _AttributeListToDict(module, enum,\n> +                                         parsed_enum.attribute_list)\n>  \n>    if not enum.native_only:\n>      enum.fields = list(\n> @@ -600,11 +951,18 @@ def _Enum(module, parsed_enum, parent_kind):\n>      for field in enum.fields:\n>        if field.default:\n>          if not enum.extensible:\n> -          raise Exception('Non-extensible enums may not specify a default')\n> -        if enum.default_field is not None:\n>            raise Exception(\n> -              'Only one enumerator value may be specified as the default')\n> +              f'Non-extensible enum {enum.spec} may not specify a default')\n> +        if enum.default_field is not None:\n> +          raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}')\n>          enum.default_field = field\n> +    # While running the backwards compatibility check, ignore errors because the\n> +    # old version of the enum might not specify [Default].\n> +    if (enum.extensible and enum.default_field is None\n> +        and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT\n> +        and not is_running_backwards_compatibility_check_hack):\n> +      raise Exception(\n> +          f'Extensible enum {enum.spec} must specify a [Default] enumerator')\n>  \n>    module.kinds[enum.spec] = enum\n>  \n> @@ -696,6 +1054,11 @@ def _CollectReferencedKinds(module, all_defined_kinds):\n>          for referenced_kind in extract_referenced_user_kinds(param.kind):\n>            sanitized_kind = sanitize_kind(referenced_kind)\n>            referenced_user_kinds[sanitized_kind.spec] = sanitized_kind\n> +  # Consts can reference imported enums.\n> +  for const in module.constants:\n> +    if not const.kind in mojom.PRIMITIVES:\n> +      sanitized_kind = sanitize_kind(const.kind)\n> +      referenced_user_kinds[sanitized_kind.spec] = sanitized_kind\n>  \n>    return referenced_user_kinds\n>  \n> @@ -741,6 +1104,16 @@ def _AssertTypeIsStable(kind):\n>            assertDependencyIsStable(response_param.kind)\n>  \n>  \n> +def _AssertStructIsValid(kind):\n> +  expected_ordinals = set(range(0, len(kind.fields)))\n> +  ordinals = set(map(lambda field: field.ordinal, kind.fields))\n> +  if ordinals != expected_ordinals:\n> +    raise Exception(\n> +        'Structs must use contiguous ordinals starting from 0. ' +\n> +        '{} is missing the following ordinals: {}.'.format(\n> +            kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals))))\n> +\n> +\n>  def _Module(tree, path, imports):\n>    \"\"\"\n>    Args:\n> @@ -778,6 +1151,8 @@ def _Module(tree, path, imports):\n>    module.structs = []\n>    module.unions = []\n>    module.interfaces = []\n> +  module.features = []\n> +\n>    _ProcessElements(\n>        filename, tree.definition_list, {\n>            ast.Const:\n> @@ -791,6 +1166,8 @@ def _Module(tree, path, imports):\n>            ast.Interface:\n>            lambda interface: module.interfaces.append(\n>                _Interface(module, interface)),\n> +          ast.Feature:\n> +          lambda feature: module.features.append(_Feature(module, feature)),\n>        })\n>  \n>    # Second pass expands fields and methods. This allows fields and parameters\n> @@ -806,12 +1183,24 @@ def _Module(tree, path, imports):\n>      for enum in struct.enums:\n>        all_defined_kinds[enum.spec] = enum\n>  \n> +  for feature in module.features:\n> +    all_defined_kinds[feature.spec] = feature\n> +\n>    for union in module.unions:\n>      union.fields = list(\n>          map(lambda field: _UnionField(module, field, union), union.fields_data))\n>      _AssignDefaultOrdinals(union.fields)\n> +    for field in union.fields:\n> +      if field.is_default:\n> +        if union.default_field is not None:\n> +          raise Exception('Multiple [Default] fields in union %s.' %\n> +                          union.mojom_name)\n> +        union.default_field = field\n>      del union.fields_data\n>      all_defined_kinds[union.spec] = union\n> +    if union.extensible and union.default_field is None:\n> +      raise Exception('Extensible union %s must specify a [Default] field' %\n> +                      union.mojom_name)\n>  \n>    for interface in module.interfaces:\n>      interface.methods = list(\n> @@ -829,8 +1218,8 @@ def _Module(tree, path, imports):\n>                                                   all_defined_kinds.values())\n>    imported_kind_specs = set(all_referenced_kinds.keys()).difference(\n>        set(all_defined_kinds.keys()))\n> -  module.imported_kinds = dict(\n> -      (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)\n> +  module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])\n> +                                      for spec in sorted(imported_kind_specs))\n>  \n>    generator.AddComputedData(module)\n>    for iface in module.interfaces:\n> @@ -847,6 +1236,9 @@ def _Module(tree, path, imports):\n>        if kind.stable:\n>          _AssertTypeIsStable(kind)\n>  \n> +  for kind in module.structs:\n> +    _AssertStructIsValid(kind)\n> +\n>    return module\n>  \n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n> index 19905c8a9540..b4fea92467d5 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n> @@ -1,17 +1,13 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> -import os.path\n> -import sys\n>  import unittest\n>  \n>  from mojom.generate import module as mojom\n>  from mojom.generate import translate\n>  from mojom.parse import ast\n>  \n> -\n>  class TranslateTest(unittest.TestCase):\n>    \"\"\"Tests |parser.Parse()|.\"\"\"\n>  \n> @@ -69,5 +65,77 @@ class TranslateTest(unittest.TestCase):\n>      # pylint: disable=W0212\n>      self.assertEquals(\n>          translate._MapKind(\"asso<SomeInterface>?\"), \"?asso:x:SomeInterface\")\n> -    self.assertEquals(\n> -        translate._MapKind(\"asso<SomeInterface&>?\"), \"?asso:r:x:SomeInterface\")\n> +    self.assertEquals(translate._MapKind(\"rca<SomeInterface>?\"),\n> +                      \"?rca:x:SomeInterface\")\n> +\n> +  def testSelfRecursiveUnions(self):\n> +    \"\"\"Verifies _UnionField() raises when a union is self-recursive.\"\"\"\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Union(\"SomeUnion\", None,\n> +                  ast.UnionBody([ast.UnionField(\"a\", None, None, \"SomeUnion\")]))\n> +    ])\n> +    with self.assertRaises(Exception):\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Union(\n> +            \"SomeUnion\", None,\n> +            ast.UnionBody([ast.UnionField(\"a\", None, None, \"SomeUnion?\")]))\n> +    ])\n> +    with self.assertRaises(Exception):\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +\n> +  def testDuplicateAttributesException(self):\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Union(\n> +            \"FakeUnion\",\n> +            ast.AttributeList([\n> +                ast.Attribute(\"key1\", \"value\"),\n> +                ast.Attribute(\"key1\", \"value\")\n> +            ]),\n> +            ast.UnionBody([\n> +                ast.UnionField(\"a\", None, None, \"int32\"),\n> +                ast.UnionField(\"b\", None, None, \"string\")\n> +            ]))\n> +    ])\n> +    with self.assertRaises(Exception):\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +\n> +  def testEnumWithReservedValues(self):\n> +    \"\"\"Verifies that assigning reserved values to enumerators fails.\"\"\"\n> +    # -128 is reserved for the empty representation in WTF::HashTraits.\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Enum(\n> +            \"MyEnum\", None,\n> +            ast.EnumValueList([\n> +                ast.EnumValue('kReserved', None, '-128'),\n> +            ]))\n> +    ])\n> +    with self.assertRaises(Exception) as context:\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +    self.assertIn(\"reserved for WTF::HashTrait\", str(context.exception))\n> +\n> +    # -127 is reserved for the deleted representation in WTF::HashTraits.\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Enum(\n> +            \"MyEnum\", None,\n> +            ast.EnumValueList([\n> +                ast.EnumValue('kReserved', None, '-127'),\n> +            ]))\n> +    ])\n> +    with self.assertRaises(Exception) as context:\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +    self.assertIn(\"reserved for WTF::HashTrait\", str(context.exception))\n> +\n> +    # Implicitly assigning a reserved value should also fail.\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Enum(\n> +            \"MyEnum\", None,\n> +            ast.EnumValueList([\n> +                ast.EnumValue('kNotReserved', None, '-129'),\n> +                ast.EnumValue('kImplicitlyReserved', None, None),\n> +            ]))\n> +    ])\n> +    with self.assertRaises(Exception) as context:\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +    self.assertIn(\"reserved for WTF::HashTrait\", str(context.exception))\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n> index 1f0db200549b..aae9cdb659bd 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Node classes for the AST for a Mojo IDL file.\"\"\"\n> @@ -8,17 +8,14 @@\n>  # and lineno). You may also define __repr__() to help with analyzing test\n>  # failures, especially for more complex types.\n>  \n> -\n> -import sys\n> +import os.path\n>  \n>  \n> -def _IsStrOrUnicode(x):\n> -  if sys.version_info[0] < 3:\n> -    return isinstance(x, (unicode, str))\n> -  return isinstance(x, str)\n> +# Instance of 'NodeListBase' has no '_list_item_type' member (no-member)\n> +# pylint: disable=no-member\n>  \n>  \n> -class NodeBase(object):\n> +class NodeBase:\n>    \"\"\"Base class for nodes in the AST.\"\"\"\n>  \n>    def __init__(self, filename=None, lineno=None):\n> @@ -43,7 +40,7 @@ class NodeListBase(NodeBase):\n>    classes, in a tuple) of the members of the list.)\"\"\"\n>  \n>    def __init__(self, item_or_items=None, **kwargs):\n> -    super(NodeListBase, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.items = []\n>      if item_or_items is None:\n>        pass\n> @@ -62,7 +59,7 @@ class NodeListBase(NodeBase):\n>      return self.items.__iter__()\n>  \n>    def __eq__(self, other):\n> -    return super(NodeListBase, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.items == other.items\n>  \n>    # Implement this so that on failure, we get slightly more sensible output.\n> @@ -96,7 +93,7 @@ class Definition(NodeBase):\n>    include parameter definitions.) This class is meant to be subclassed.\"\"\"\n>  \n>    def __init__(self, mojom_name, **kwargs):\n> -    assert _IsStrOrUnicode(mojom_name)\n> +    assert isinstance(mojom_name, str)\n>      NodeBase.__init__(self, **kwargs)\n>      self.mojom_name = mojom_name\n>  \n> @@ -108,13 +105,13 @@ class Attribute(NodeBase):\n>    \"\"\"Represents an attribute.\"\"\"\n>  \n>    def __init__(self, key, value, **kwargs):\n> -    assert _IsStrOrUnicode(key)\n> -    super(Attribute, self).__init__(**kwargs)\n> +    assert isinstance(key, str)\n> +    super().__init__(**kwargs)\n>      self.key = key\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(Attribute, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.key == other.key and \\\n>             self.value == other.value\n>  \n> @@ -131,17 +128,17 @@ class Const(Definition):\n>    def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      # The typename is currently passed through as a string.\n> -    assert _IsStrOrUnicode(typename)\n> +    assert isinstance(typename, str)\n>      # The value is either a literal (currently passed through as a string) or a\n>      # \"wrapped identifier\".\n> -    assert _IsStrOrUnicode or isinstance(value, tuple)\n> -    super(Const, self).__init__(mojom_name, **kwargs)\n> +    assert isinstance(value, (tuple, str))\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.typename = typename\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(Const, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.typename == other.typename and \\\n>             self.value == other.value\n> @@ -153,12 +150,12 @@ class Enum(Definition):\n>    def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)\n> -    super(Enum, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.enum_value_list = enum_value_list\n>  \n>    def __eq__(self, other):\n> -    return super(Enum, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.enum_value_list == other.enum_value_list\n>  \n> @@ -170,13 +167,13 @@ class EnumValue(Definition):\n>      # The optional value is either an int (which is current a string) or a\n>      # \"wrapped identifier\".\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> -    assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)\n> -    super(EnumValue, self).__init__(mojom_name, **kwargs)\n> +    assert value is None or isinstance(value, (tuple, str))\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(EnumValue, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.value == other.value\n>  \n> @@ -188,18 +185,47 @@ class EnumValueList(NodeListBase):\n>    _list_item_type = EnumValue\n>  \n>  \n> +class Feature(Definition):\n> +  \"\"\"Represents a runtime feature definition.\"\"\"\n> +  def __init__(self, mojom_name, attribute_list, body, **kwargs):\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert isinstance(body, FeatureBody) or body is None\n> +    super().__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.body = body\n> +\n> +  def __eq__(self, other):\n> +    return super().__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.body == other.body\n> +\n> +  def __repr__(self):\n> +    return \"Feature(mojom_name = %s, attribute_list = %s, body = %s)\" % (\n> +        self.mojom_name, self.attribute_list, self.body)\n> +\n> +\n> +# This needs to be declared after `FeatureConst` and `FeatureField`.\n> +class FeatureBody(NodeListBase):\n> +  \"\"\"Represents the body of (i.e., list of definitions inside) a feature.\"\"\"\n> +\n> +  # Features are compile time helpers so all fields are initializers/consts\n> +  # for the underlying platform feature type.\n> +  _list_item_type = (Const)\n> +\n> +\n>  class Import(NodeBase):\n>    \"\"\"Represents an import statement.\"\"\"\n>  \n>    def __init__(self, attribute_list, import_filename, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> -    assert _IsStrOrUnicode(import_filename)\n> -    super(Import, self).__init__(**kwargs)\n> +    assert isinstance(import_filename, str)\n> +    super().__init__(**kwargs)\n>      self.attribute_list = attribute_list\n> -    self.import_filename = import_filename\n> +    # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.\n> +    self.import_filename = os.path.normpath(import_filename).replace('\\\\', '/')\n>  \n>    def __eq__(self, other):\n> -    return super(Import, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.import_filename == other.import_filename\n>  \n> @@ -216,12 +242,12 @@ class Interface(Definition):\n>    def __init__(self, mojom_name, attribute_list, body, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert isinstance(body, InterfaceBody)\n> -    super(Interface, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.body = body\n>  \n>    def __eq__(self, other):\n> -    return super(Interface, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.body == other.body\n>  \n> @@ -236,14 +262,14 @@ class Method(Definition):\n>      assert isinstance(parameter_list, ParameterList)\n>      assert response_parameter_list is None or \\\n>             isinstance(response_parameter_list, ParameterList)\n> -    super(Method, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.ordinal = ordinal\n>      self.parameter_list = parameter_list\n>      self.response_parameter_list = response_parameter_list\n>  \n>    def __eq__(self, other):\n> -    return super(Method, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.ordinal == other.ordinal and \\\n>             self.parameter_list == other.parameter_list and \\\n> @@ -264,12 +290,12 @@ class Module(NodeBase):\n>      # |mojom_namespace| is either none or a \"wrapped identifier\".\n>      assert mojom_namespace is None or isinstance(mojom_namespace, tuple)\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> -    super(Module, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.mojom_namespace = mojom_namespace\n>      self.attribute_list = attribute_list\n>  \n>    def __eq__(self, other):\n> -    return super(Module, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.mojom_namespace == other.mojom_namespace and \\\n>             self.attribute_list == other.attribute_list\n>  \n> @@ -281,13 +307,13 @@ class Mojom(NodeBase):\n>      assert module is None or isinstance(module, Module)\n>      assert isinstance(import_list, ImportList)\n>      assert isinstance(definition_list, list)\n> -    super(Mojom, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.module = module\n>      self.import_list = import_list\n>      self.definition_list = definition_list\n>  \n>    def __eq__(self, other):\n> -    return super(Mojom, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.module == other.module and \\\n>             self.import_list == other.import_list and \\\n>             self.definition_list == other.definition_list\n> @@ -302,11 +328,11 @@ class Ordinal(NodeBase):\n>  \n>    def __init__(self, value, **kwargs):\n>      assert isinstance(value, int)\n> -    super(Ordinal, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(Ordinal, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.value == other.value\n>  \n>  \n> @@ -314,18 +340,18 @@ class Parameter(NodeBase):\n>    \"\"\"Represents a method request or response parameter.\"\"\"\n>  \n>    def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):\n> -    assert _IsStrOrUnicode(mojom_name)\n> +    assert isinstance(mojom_name, str)\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert ordinal is None or isinstance(ordinal, Ordinal)\n> -    assert _IsStrOrUnicode(typename)\n> -    super(Parameter, self).__init__(**kwargs)\n> +    assert isinstance(typename, str)\n> +    super().__init__(**kwargs)\n>      self.mojom_name = mojom_name\n>      self.attribute_list = attribute_list\n>      self.ordinal = ordinal\n>      self.typename = typename\n>  \n>    def __eq__(self, other):\n> -    return super(Parameter, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.mojom_name == other.mojom_name and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.ordinal == other.ordinal and \\\n> @@ -344,42 +370,51 @@ class Struct(Definition):\n>    def __init__(self, mojom_name, attribute_list, body, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert isinstance(body, StructBody) or body is None\n> -    super(Struct, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.body = body\n>  \n>    def __eq__(self, other):\n> -    return super(Struct, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.body == other.body\n>  \n> +  def __repr__(self):\n> +    return \"Struct(mojom_name = %s, attribute_list = %s, body = %s)\" % (\n> +        self.mojom_name, self.attribute_list, self.body)\n> +\n>  \n>  class StructField(Definition):\n>    \"\"\"Represents a struct field definition.\"\"\"\n>  \n>    def __init__(self, mojom_name, attribute_list, ordinal, typename,\n>                 default_value, **kwargs):\n> -    assert _IsStrOrUnicode(mojom_name)\n> +    assert isinstance(mojom_name, str)\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert ordinal is None or isinstance(ordinal, Ordinal)\n> -    assert _IsStrOrUnicode(typename)\n> +    assert isinstance(typename, str)\n>      # The optional default value is currently either a value as a string or a\n>      # \"wrapped identifier\".\n> -    assert default_value is None or _IsStrOrUnicode(default_value) or \\\n> -        isinstance(default_value, tuple)\n> -    super(StructField, self).__init__(mojom_name, **kwargs)\n> +    assert default_value is None or isinstance(default_value, (str, tuple))\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.ordinal = ordinal\n>      self.typename = typename\n>      self.default_value = default_value\n>  \n>    def __eq__(self, other):\n> -    return super(StructField, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.ordinal == other.ordinal and \\\n>             self.typename == other.typename and \\\n>             self.default_value == other.default_value\n>  \n> +  def __repr__(self):\n> +    return (\"StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, \"\n> +            \"typename = %s, default_value = %s\") % (\n> +                self.mojom_name, self.attribute_list, self.ordinal,\n> +                self.typename, self.default_value)\n> +\n>  \n>  # This needs to be declared after |StructField|.\n>  class StructBody(NodeListBase):\n> @@ -394,29 +429,29 @@ class Union(Definition):\n>    def __init__(self, mojom_name, attribute_list, body, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert isinstance(body, UnionBody)\n> -    super(Union, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.body = body\n>  \n>    def __eq__(self, other):\n> -    return super(Union, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.body == other.body\n>  \n>  \n>  class UnionField(Definition):\n>    def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):\n> -    assert _IsStrOrUnicode(mojom_name)\n> +    assert isinstance(mojom_name, str)\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert ordinal is None or isinstance(ordinal, Ordinal)\n> -    assert _IsStrOrUnicode(typename)\n> -    super(UnionField, self).__init__(mojom_name, **kwargs)\n> +    assert isinstance(typename, str)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.ordinal = ordinal\n>      self.typename = typename\n>  \n>    def __eq__(self, other):\n> -    return super(UnionField, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.ordinal == other.ordinal and \\\n>             self.typename == other.typename\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n> index 62798631dbce..b289f7b11f66 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n> @@ -1,32 +1,26 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> -import os.path\n> -import sys\n>  import unittest\n>  \n>  from mojom.parse import ast\n>  \n> -\n>  class _TestNode(ast.NodeBase):\n>    \"\"\"Node type for tests.\"\"\"\n>  \n>    def __init__(self, value, **kwargs):\n> -    super(_TestNode, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(_TestNode, self).__eq__(other) and self.value == other.value\n> -\n> +    return super().__eq__(other) and self.value == other.value\n>  \n>  class _TestNodeList(ast.NodeListBase):\n>    \"\"\"Node list type for tests.\"\"\"\n>  \n>    _list_item_type = _TestNode\n>  \n> -\n>  class ASTTest(unittest.TestCase):\n>    \"\"\"Tests various AST classes.\"\"\"\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n> index 3cb73c5d708c..9687edbfab1c 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2018 The Chromium Authors. All rights reserved.\n> +# Copyright 2018 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Helpers for processing conditionally enabled features in a mojom.\"\"\"\n> @@ -17,8 +17,10 @@ class EnableIfError(Error):\n>  def _IsEnabled(definition, enabled_features):\n>    \"\"\"Returns true if a definition is enabled.\n>  \n> -  A definition is enabled if it has no EnableIf attribute, or if the value of\n> -  the EnableIf attribute is in enabled_features.\n> +  A definition is enabled if it has no EnableIf/EnableIfNot attribute.\n> +  It is retained if it has an EnableIf attribute and the attribute is in\n> +  enabled_features. It is retained if it has an EnableIfNot attribute and the\n> +  attribute is not in enabled features.\n>    \"\"\"\n>    if not hasattr(definition, \"attribute_list\"):\n>      return True\n> @@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features):\n>  \n>    already_defined = False\n>    for a in definition.attribute_list:\n> -    if a.key == 'EnableIf':\n> +    if a.key == 'EnableIf' or a.key == 'EnableIfNot':\n>        if already_defined:\n>          raise EnableIfError(\n>              definition.filename,\n> -            \"EnableIf attribute may only be defined once per field.\",\n> +            \"EnableIf/EnableIfNot attribute may only be set once per field.\",\n>              definition.lineno)\n>        already_defined = True\n>  \n>    for attribute in definition.attribute_list:\n>      if attribute.key == 'EnableIf' and attribute.value not in enabled_features:\n>        return False\n> +    if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:\n> +      return False\n>    return True\n>  \n>  \n> @@ -56,15 +60,12 @@ def _FilterDefinition(definition, enabled_features):\n>    \"\"\"Filters definitions with a body.\"\"\"\n>    if isinstance(definition, ast.Enum):\n>      _FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)\n> -  elif isinstance(definition, ast.Interface):\n> -    _FilterDisabledFromNodeList(definition.body, enabled_features)\n>    elif isinstance(definition, ast.Method):\n>      _FilterDisabledFromNodeList(definition.parameter_list, enabled_features)\n>      _FilterDisabledFromNodeList(definition.response_parameter_list,\n>                                  enabled_features)\n> -  elif isinstance(definition, ast.Struct):\n> -    _FilterDisabledFromNodeList(definition.body, enabled_features)\n> -  elif isinstance(definition, ast.Union):\n> +  elif isinstance(definition,\n> +                  (ast.Interface, ast.Struct, ast.Union, ast.Feature)):\n>      _FilterDisabledFromNodeList(definition.body, enabled_features)\n>  \n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n> index aa609be73837..cca1764b1cd5 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n> @@ -1,13 +1,12 @@\n> -# Copyright 2018 The Chromium Authors. All rights reserved.\n> +# Copyright 2018 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> +import importlib.util\n>  import os\n>  import sys\n>  import unittest\n>  \n> -\n>  def _GetDirAbove(dirname):\n>    \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n>    also be \"above\" this file).\"\"\"\n> @@ -18,9 +17,8 @@ def _GetDirAbove(dirname):\n>      if tail == dirname:\n>        return path\n>  \n> -\n>  try:\n> -  imp.find_module('mojom')\n> +  importlib.util.find_spec(\"mojom\")\n>  except ImportError:\n>    sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))\n>  import mojom.parse.ast as ast\n> @@ -29,7 +27,6 @@ import mojom.parse.parser as parser\n>  \n>  ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})\n>  \n> -\n>  class ConditionalFeaturesTest(unittest.TestCase):\n>    \"\"\"Tests |mojom.parse.conditional_features|.\"\"\"\n>  \n> @@ -55,6 +52,48 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>      \"\"\"\n>      self.parseAndAssertEqual(const_source, expected_source)\n>  \n> +  def testFilterIfNotConst(self):\n> +    \"\"\"Test that Consts are correctly filtered.\"\"\"\n> +    const_source = \"\"\"\n> +      [EnableIfNot=blue]\n> +      const int kMyConst1 = 1;\n> +      [EnableIfNot=orange]\n> +      const double kMyConst2 = 2;\n> +      [EnableIf=blue]\n> +      const int kMyConst3 = 3;\n> +      [EnableIfNot=blue]\n> +      const int kMyConst4 = 4;\n> +      [EnableIfNot=purple]\n> +      const int kMyConst5 = 5;\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      [EnableIfNot=orange]\n> +      const double kMyConst2 = 2;\n> +      [EnableIf=blue]\n> +      const int kMyConst3 = 3;\n> +      [EnableIfNot=purple]\n> +      const int kMyConst5 = 5;\n> +    \"\"\"\n> +    self.parseAndAssertEqual(const_source, expected_source)\n> +\n> +  def testFilterIfNotMultipleConst(self):\n> +    \"\"\"Test that Consts are correctly filtered.\"\"\"\n> +    const_source = \"\"\"\n> +      [EnableIfNot=blue]\n> +      const int kMyConst1 = 1;\n> +      [EnableIfNot=orange]\n> +      const double kMyConst2 = 2;\n> +      [EnableIfNot=orange]\n> +      const int kMyConst3 = 3;\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      [EnableIfNot=orange]\n> +      const double kMyConst2 = 2;\n> +      [EnableIfNot=orange]\n> +      const int kMyConst3 = 3;\n> +    \"\"\"\n> +    self.parseAndAssertEqual(const_source, expected_source)\n> +\n>    def testFilterEnum(self):\n>      \"\"\"Test that EnumValues are correctly filtered from an Enum.\"\"\"\n>      enum_source = \"\"\"\n> @@ -91,6 +130,24 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>      \"\"\"\n>      self.parseAndAssertEqual(import_source, expected_source)\n>  \n> +  def testFilterIfNotImport(self):\n> +    \"\"\"Test that imports are correctly filtered from a Mojom.\"\"\"\n> +    import_source = \"\"\"\n> +      [EnableIf=blue]\n> +      import \"foo.mojom\";\n> +      [EnableIfNot=purple]\n> +      import \"bar.mojom\";\n> +      [EnableIfNot=green]\n> +      import \"baz.mojom\";\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      [EnableIf=blue]\n> +      import \"foo.mojom\";\n> +      [EnableIfNot=purple]\n> +      import \"bar.mojom\";\n> +    \"\"\"\n> +    self.parseAndAssertEqual(import_source, expected_source)\n> +\n>    def testFilterInterface(self):\n>      \"\"\"Test that definitions are correctly filtered from an Interface.\"\"\"\n>      interface_source = \"\"\"\n> @@ -175,6 +232,50 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>      \"\"\"\n>      self.parseAndAssertEqual(struct_source, expected_source)\n>  \n> +  def testFilterIfNotStruct(self):\n> +    \"\"\"Test that definitions are correctly filtered from a Struct.\"\"\"\n> +    struct_source = \"\"\"\n> +      struct MyStruct {\n> +        [EnableIf=blue]\n> +        enum MyEnum {\n> +          VALUE1,\n> +          [EnableIfNot=red]\n> +          VALUE2,\n> +        };\n> +        [EnableIfNot=yellow]\n> +        const double kMyConst = 1.23;\n> +        [EnableIf=green]\n> +        int32 a;\n> +        double b;\n> +        [EnableIfNot=purple]\n> +        int32 c;\n> +        [EnableIf=blue]\n> +        double d;\n> +        int32 e;\n> +        [EnableIfNot=red]\n> +        double f;\n> +      };\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      struct MyStruct {\n> +        [EnableIf=blue]\n> +        enum MyEnum {\n> +          VALUE1,\n> +        };\n> +        [EnableIfNot=yellow]\n> +        const double kMyConst = 1.23;\n> +        [EnableIf=green]\n> +        int32 a;\n> +        double b;\n> +        [EnableIfNot=purple]\n> +        int32 c;\n> +        [EnableIf=blue]\n> +        double d;\n> +        int32 e;\n> +      };\n> +    \"\"\"\n> +    self.parseAndAssertEqual(struct_source, expected_source)\n> +\n>    def testFilterUnion(self):\n>      \"\"\"Test that UnionFields are correctly filtered from a Union.\"\"\"\n>      union_source = \"\"\"\n> @@ -216,6 +317,25 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>      \"\"\"\n>      self.parseAndAssertEqual(mojom_source, expected_source)\n>  \n> +  def testFeaturesWithEnableIf(self):\n> +    mojom_source = \"\"\"\n> +      feature Foo {\n> +        const string name = \"FooFeature\";\n> +        [EnableIf=red]\n> +        const bool default_state = false;\n> +        [EnableIf=yellow]\n> +        const bool default_state = true;\n> +      };\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      feature Foo {\n> +        const string name = \"FooFeature\";\n> +        [EnableIf=red]\n> +        const bool default_state = false;\n> +      };\n> +    \"\"\"\n> +    self.parseAndAssertEqual(mojom_source, expected_source)\n> +\n>    def testMultipleEnableIfs(self):\n>      source = \"\"\"\n>        enum Foo {\n> @@ -228,6 +348,29 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>                        conditional_features.RemoveDisabledDefinitions,\n>                        definition, ENABLED_FEATURES)\n>  \n> +  def testMultipleEnableIfs(self):\n> +    source = \"\"\"\n> +      enum Foo {\n> +        [EnableIf=red,EnableIfNot=yellow]\n> +        kBarValue = 5,\n> +      };\n> +    \"\"\"\n> +    definition = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertRaises(conditional_features.EnableIfError,\n> +                      conditional_features.RemoveDisabledDefinitions,\n> +                      definition, ENABLED_FEATURES)\n> +\n> +  def testMultipleEnableIfs(self):\n> +    source = \"\"\"\n> +      enum Foo {\n> +        [EnableIfNot=red,EnableIfNot=yellow]\n> +        kBarValue = 5,\n> +      };\n> +    \"\"\"\n> +    definition = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertRaises(conditional_features.EnableIfError,\n> +                      conditional_features.RemoveDisabledDefinitions,\n> +                      definition, ENABLED_FEATURES)\n>  \n>  if __name__ == '__main__':\n>    unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n> index 3e084bbf22b7..00136a8bf94a 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n> @@ -1,8 +1,7 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n>  import os.path\n>  import sys\n>  \n> @@ -22,7 +21,7 @@ class LexError(Error):\n>  \n>  # We have methods which look like they could be functions:\n>  # pylint: disable=R0201\n> -class Lexer(object):\n> +class Lexer:\n>    def __init__(self, filename):\n>      self.filename = filename\n>  \n> @@ -56,6 +55,7 @@ class Lexer(object):\n>        'PENDING_RECEIVER',\n>        'PENDING_ASSOCIATED_REMOTE',\n>        'PENDING_ASSOCIATED_RECEIVER',\n> +      'FEATURE',\n>    )\n>  \n>    keyword_map = {}\n> @@ -81,7 +81,6 @@ class Lexer(object):\n>        # Operators\n>        'MINUS',\n>        'PLUS',\n> -      'AMP',\n>        'QSTN',\n>  \n>        # Assignment\n> @@ -168,7 +167,6 @@ class Lexer(object):\n>    # Operators\n>    t_MINUS = r'-'\n>    t_PLUS = r'\\+'\n> -  t_AMP = r'&'\n>    t_QSTN = r'\\?'\n>  \n>    # =\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n> index eadc6587cf94..bc9f8354316f 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n> @@ -1,13 +1,12 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> +import importlib.util\n>  import os.path\n>  import sys\n>  import unittest\n>  \n> -\n>  def _GetDirAbove(dirname):\n>    \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n>    also be \"above\" this file).\"\"\"\n> @@ -18,17 +17,15 @@ def _GetDirAbove(dirname):\n>      if tail == dirname:\n>        return path\n>  \n> -\n>  sys.path.insert(1, os.path.join(_GetDirAbove(\"mojo\"), \"third_party\"))\n>  from ply import lex\n>  \n>  try:\n> -  imp.find_module(\"mojom\")\n> +  importlib.util.find_spec(\"mojom\")\n>  except ImportError:\n>    sys.path.append(os.path.join(_GetDirAbove(\"pylib\"), \"pylib\"))\n>  import mojom.parse.lexer\n>  \n> -\n>  # This (monkey-patching LexToken to make comparison value-based) is evil, but\n>  # we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing\n>  # for object identity.)\n> @@ -146,7 +143,6 @@ class LexerTest(unittest.TestCase):\n>          self._SingleTokenForInput(\"+\"), _MakeLexToken(\"PLUS\", \"+\"))\n>      self.assertEquals(\n>          self._SingleTokenForInput(\"-\"), _MakeLexToken(\"MINUS\", \"-\"))\n> -    self.assertEquals(self._SingleTokenForInput(\"&\"), _MakeLexToken(\"AMP\", \"&\"))\n>      self.assertEquals(\n>          self._SingleTokenForInput(\"?\"), _MakeLexToken(\"QSTN\", \"?\"))\n>      self.assertEquals(\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n> index b3b803d6f334..1dffd98b92a6 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n> @@ -1,8 +1,11 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Generates a syntax tree from a Mojo IDL file.\"\"\"\n>  \n> +# Breaking parser stanzas is unhelpful so allow longer lines.\n> +# pylint: disable=line-too-long\n> +\n>  import os.path\n>  import sys\n>  \n> @@ -33,7 +36,7 @@ class ParseError(Error):\n>  \n>  # We have methods which look like they could be functions:\n>  # pylint: disable=R0201\n> -class Parser(object):\n> +class Parser:\n>    def __init__(self, lexer, source, filename):\n>      self.tokens = lexer.tokens\n>      self.source = source\n> @@ -111,7 +114,8 @@ class Parser(object):\n>                    | union\n>                    | interface\n>                    | enum\n> -                  | const\"\"\"\n> +                  | const\n> +                  | feature\"\"\"\n>      p[0] = p[1]\n>  \n>    def p_attribute_section_1(self, p):\n> @@ -140,12 +144,19 @@ class Parser(object):\n>      p[0].Append(p[3])\n>  \n>    def p_attribute_1(self, p):\n> -    \"\"\"attribute : NAME EQUALS evaled_literal\n> -                 | NAME EQUALS NAME\"\"\"\n> -    p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))\n> +    \"\"\"attribute : name_wrapped EQUALS identifier_wrapped\"\"\"\n> +    p[0] = ast.Attribute(p[1],\n> +                         p[3][1],\n> +                         filename=self.filename,\n> +                         lineno=p.lineno(1))\n>  \n>    def p_attribute_2(self, p):\n> -    \"\"\"attribute : NAME\"\"\"\n> +    \"\"\"attribute : name_wrapped EQUALS evaled_literal\n> +                 | name_wrapped EQUALS name_wrapped\"\"\"\n> +    p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))\n> +\n> +  def p_attribute_3(self, p):\n> +    \"\"\"attribute : name_wrapped\"\"\"\n>      p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))\n>  \n>    def p_evaled_literal(self, p):\n> @@ -161,11 +172,11 @@ class Parser(object):\n>        p[0] = eval(p[1])\n>  \n>    def p_struct_1(self, p):\n> -    \"\"\"struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI\"\"\"\n> +    \"\"\"struct : attribute_section STRUCT name_wrapped LBRACE struct_body RBRACE SEMI\"\"\"\n>      p[0] = ast.Struct(p[3], p[1], p[5])\n>  \n>    def p_struct_2(self, p):\n> -    \"\"\"struct : attribute_section STRUCT NAME SEMI\"\"\"\n> +    \"\"\"struct : attribute_section STRUCT name_wrapped SEMI\"\"\"\n>      p[0] = ast.Struct(p[3], p[1], None)\n>  \n>    def p_struct_body_1(self, p):\n> @@ -180,11 +191,24 @@ class Parser(object):\n>      p[0].Append(p[2])\n>  \n>    def p_struct_field(self, p):\n> -    \"\"\"struct_field : attribute_section typename NAME ordinal default SEMI\"\"\"\n> +    \"\"\"struct_field : attribute_section typename name_wrapped ordinal default SEMI\"\"\"\n>      p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5])\n>  \n> +  def p_feature(self, p):\n> +    \"\"\"feature : attribute_section FEATURE NAME LBRACE feature_body RBRACE SEMI\"\"\"\n> +    p[0] = ast.Feature(p[3], p[1], p[5])\n> +\n> +  def p_feature_body_1(self, p):\n> +    \"\"\"feature_body : \"\"\"\n> +    p[0] = ast.FeatureBody()\n> +\n> +  def p_feature_body_2(self, p):\n> +    \"\"\"feature_body : feature_body const\"\"\"\n> +    p[0] = p[1]\n> +    p[0].Append(p[2])\n> +\n>    def p_union(self, p):\n> -    \"\"\"union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI\"\"\"\n> +    \"\"\"union : attribute_section UNION name_wrapped LBRACE union_body RBRACE SEMI\"\"\"\n>      p[0] = ast.Union(p[3], p[1], p[5])\n>  \n>    def p_union_body_1(self, p):\n> @@ -197,7 +221,7 @@ class Parser(object):\n>      p[1].Append(p[2])\n>  \n>    def p_union_field(self, p):\n> -    \"\"\"union_field : attribute_section typename NAME ordinal SEMI\"\"\"\n> +    \"\"\"union_field : attribute_section typename name_wrapped ordinal SEMI\"\"\"\n>      p[0] = ast.UnionField(p[3], p[1], p[4], p[2])\n>  \n>    def p_default_1(self, p):\n> @@ -209,8 +233,7 @@ class Parser(object):\n>      p[0] = p[2]\n>  \n>    def p_interface(self, p):\n> -    \"\"\"interface : attribute_section INTERFACE NAME LBRACE interface_body \\\n> -                       RBRACE SEMI\"\"\"\n> +    \"\"\"interface : attribute_section INTERFACE name_wrapped LBRACE interface_body RBRACE SEMI\"\"\"\n>      p[0] = ast.Interface(p[3], p[1], p[5])\n>  \n>    def p_interface_body_1(self, p):\n> @@ -233,8 +256,7 @@ class Parser(object):\n>      p[0] = p[3]\n>  \n>    def p_method(self, p):\n> -    \"\"\"method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \\\n> -                    response SEMI\"\"\"\n> +    \"\"\"method : attribute_section name_wrapped ordinal LPAREN parameter_list RPAREN response SEMI\"\"\"\n>      p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])\n>  \n>    def p_parameter_list_1(self, p):\n> @@ -255,7 +277,7 @@ class Parser(object):\n>      p[0].Append(p[3])\n>  \n>    def p_parameter(self, p):\n> -    \"\"\"parameter : attribute_section typename NAME ordinal\"\"\"\n> +    \"\"\"parameter : attribute_section typename name_wrapped ordinal\"\"\"\n>      p[0] = ast.Parameter(\n>          p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3))\n>  \n> @@ -271,8 +293,7 @@ class Parser(object):\n>      \"\"\"nonnullable_typename : basictypename\n>                              | array\n>                              | fixed_array\n> -                            | associative_array\n> -                            | interfacerequest\"\"\"\n> +                            | associative_array\"\"\"\n>      p[0] = p[1]\n>  \n>    def p_basictypename(self, p):\n> @@ -297,18 +318,16 @@ class Parser(object):\n>      p[0] = \"rcv<%s>\" % p[3]\n>  \n>    def p_associatedremotetype(self, p):\n> -    \"\"\"associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \\\n> -                                  RANGLE\"\"\"\n> +    \"\"\"associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier RANGLE\"\"\"\n>      p[0] = \"rma<%s>\" % p[3]\n>  \n>    def p_associatedreceivertype(self, p):\n> -    \"\"\"associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \\\n> -                                    RANGLE\"\"\"\n> +    \"\"\"associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier RANGLE\"\"\"\n>      p[0] = \"rca<%s>\" % p[3]\n>  \n>    def p_handletype(self, p):\n>      \"\"\"handletype : HANDLE\n> -                  | HANDLE LANGLE NAME RANGLE\"\"\"\n> +                  | HANDLE LANGLE name_wrapped RANGLE\"\"\"\n>      if len(p) == 2:\n>        p[0] = p[1]\n>      else:\n> @@ -342,14 +361,6 @@ class Parser(object):\n>      \"\"\"associative_array : MAP LANGLE identifier COMMA typename RANGLE\"\"\"\n>      p[0] = p[5] + \"{\" + p[3] + \"}\"\n>  \n> -  def p_interfacerequest(self, p):\n> -    \"\"\"interfacerequest : identifier AMP\n> -                        | ASSOCIATED identifier AMP\"\"\"\n> -    if len(p) == 3:\n> -      p[0] = p[1] + \"&\"\n> -    else:\n> -      p[0] = \"asso<\" + p[2] + \"&>\"\n> -\n>    def p_ordinal_1(self, p):\n>      \"\"\"ordinal : \"\"\"\n>      p[0] = None\n> @@ -366,15 +377,14 @@ class Parser(object):\n>      p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))\n>  \n>    def p_enum_1(self, p):\n> -    \"\"\"enum : attribute_section ENUM NAME LBRACE enum_value_list \\\n> -                  RBRACE SEMI\n> -            | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \\\n> -                  COMMA RBRACE SEMI\"\"\"\n> +    \"\"\"enum : attribute_section ENUM name_wrapped LBRACE enum_value_list RBRACE SEMI\n> +            | attribute_section ENUM name_wrapped LBRACE \\\n> +                    nonempty_enum_value_list COMMA RBRACE SEMI\"\"\"\n>      p[0] = ast.Enum(\n>          p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2))\n>  \n>    def p_enum_2(self, p):\n> -    \"\"\"enum : attribute_section ENUM NAME SEMI\"\"\"\n> +    \"\"\"enum : attribute_section ENUM name_wrapped SEMI\"\"\"\n>      p[0] = ast.Enum(\n>          p[3], p[1], None, filename=self.filename, lineno=p.lineno(2))\n>  \n> @@ -396,9 +406,9 @@ class Parser(object):\n>      p[0].Append(p[3])\n>  \n>    def p_enum_value(self, p):\n> -    \"\"\"enum_value : attribute_section NAME\n> -                  | attribute_section NAME EQUALS int\n> -                  | attribute_section NAME EQUALS identifier_wrapped\"\"\"\n> +    \"\"\"enum_value : attribute_section name_wrapped\n> +                  | attribute_section name_wrapped EQUALS int\n> +                  | attribute_section name_wrapped EQUALS identifier_wrapped\"\"\"\n>      p[0] = ast.EnumValue(\n>          p[2],\n>          p[1],\n> @@ -407,7 +417,7 @@ class Parser(object):\n>          lineno=p.lineno(2))\n>  \n>    def p_const(self, p):\n> -    \"\"\"const : attribute_section CONST typename NAME EQUALS constant SEMI\"\"\"\n> +    \"\"\"const : attribute_section CONST typename name_wrapped EQUALS constant SEMI\"\"\"\n>      p[0] = ast.Const(p[4], p[1], p[3], p[6])\n>  \n>    def p_constant(self, p):\n> @@ -422,10 +432,16 @@ class Parser(object):\n>    # TODO(vtl): Make this produce a \"wrapped\" identifier (probably as an\n>    # |ast.Identifier|, to be added) and get rid of identifier_wrapped.\n>    def p_identifier(self, p):\n> -    \"\"\"identifier : NAME\n> -                  | NAME DOT identifier\"\"\"\n> +    \"\"\"identifier : name_wrapped\n> +                  | name_wrapped DOT identifier\"\"\"\n>      p[0] = ''.join(p[1:])\n>  \n> +  # Allow 'feature' to be a name literal not just a keyword.\n> +  def p_name_wrapped(self, p):\n> +    \"\"\"name_wrapped : NAME\n> +                    | FEATURE\"\"\"\n> +    p[0] = p[1]\n> +\n>    def p_literal(self, p):\n>      \"\"\"literal : int\n>                 | float\n> @@ -458,6 +474,12 @@ class Parser(object):\n>        # TODO(vtl): Can we figure out what's missing?\n>        raise ParseError(self.filename, \"Unexpected end of file\")\n>  \n> +    if e.value == 'feature':\n> +      raise ParseError(self.filename,\n> +                       \"`feature` is reserved for a future mojom keyword\",\n> +                       lineno=e.lineno,\n> +                       snippet=self._GetSnippet(e.lineno))\n> +\n>      raise ParseError(\n>          self.filename,\n>          \"Unexpected %r:\" % e.value,\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n> index 6d6b71532410..0a26307b1a32 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n> @@ -1,17 +1,13 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> -import os.path\n> -import sys\n>  import unittest\n>  \n>  from mojom.parse import ast\n>  from mojom.parse import lexer\n>  from mojom.parse import parser\n>  \n> -\n>  class ParserTest(unittest.TestCase):\n>    \"\"\"Tests |parser.Parse()|.\"\"\"\n>  \n> @@ -1086,7 +1082,7 @@ class ParserTest(unittest.TestCase):\n>            handle<data_pipe_producer>? k;\n>            handle<message_pipe>? l;\n>            handle<shared_buffer>? m;\n> -          some_interface&? n;\n> +          pending_receiver<some_interface>? n;\n>            handle<platform>? o;\n>          };\n>          \"\"\"\n> @@ -1110,7 +1106,7 @@ class ParserTest(unittest.TestCase):\n>                  ast.StructField('l', None, None, 'handle<message_pipe>?', None),\n>                  ast.StructField('m', None, None, 'handle<shared_buffer>?',\n>                                  None),\n> -                ast.StructField('n', None, None, 'some_interface&?', None),\n> +                ast.StructField('n', None, None, 'rcv<some_interface>?', None),\n>                  ast.StructField('o', None, None, 'handle<platform>?', None)\n>              ]))\n>      ])\n> @@ -1138,16 +1134,6 @@ class ParserTest(unittest.TestCase):\n>          r\" *handle\\?<data_pipe_consumer> a;$\"):\n>        parser.Parse(source2, \"my_file.mojom\")\n>  \n> -    source3 = \"\"\"\\\n> -        struct MyStruct {\n> -          some_interface?& a;\n> -        };\n> -        \"\"\"\n> -    with self.assertRaisesRegexp(\n> -        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '&':\\n\"\n> -        r\" *some_interface\\?& a;$\"):\n> -      parser.Parse(source3, \"my_file.mojom\")\n> -\n>    def testSimpleUnion(self):\n>      \"\"\"Tests a simple .mojom source that just defines a union.\"\"\"\n>      source = \"\"\"\\\n> @@ -1317,9 +1303,9 @@ class ParserTest(unittest.TestCase):\n>      source1 = \"\"\"\\\n>          struct MyStruct {\n>            associated MyInterface a;\n> -          associated MyInterface& b;\n> +          pending_associated_receiver<MyInterface> b;\n>            associated MyInterface? c;\n> -          associated MyInterface&? d;\n> +          pending_associated_receiver<MyInterface>? d;\n>          };\n>          \"\"\"\n>      expected1 = ast.Mojom(None, ast.ImportList(), [\n> @@ -1327,16 +1313,16 @@ class ParserTest(unittest.TestCase):\n>              'MyStruct', None,\n>              ast.StructBody([\n>                  ast.StructField('a', None, None, 'asso<MyInterface>', None),\n> -                ast.StructField('b', None, None, 'asso<MyInterface&>', None),\n> +                ast.StructField('b', None, None, 'rca<MyInterface>', None),\n>                  ast.StructField('c', None, None, 'asso<MyInterface>?', None),\n> -                ast.StructField('d', None, None, 'asso<MyInterface&>?', None)\n> +                ast.StructField('d', None, None, 'rca<MyInterface>?', None)\n>              ]))\n>      ])\n>      self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n>  \n>      source2 = \"\"\"\\\n>          interface MyInterface {\n> -          MyMethod(associated A a) =>(associated B& b);\n> +          MyMethod(associated A a) =>(pending_associated_receiver<B> b);\n>          };\"\"\"\n>      expected2 = ast.Mojom(None, ast.ImportList(), [\n>          ast.Interface(\n> @@ -1344,10 +1330,10 @@ class ParserTest(unittest.TestCase):\n>              ast.InterfaceBody(\n>                  ast.Method(\n>                      'MyMethod', None, None,\n> -                    ast.ParameterList(\n> -                        ast.Parameter('a', None, None, 'asso<A>')),\n> -                    ast.ParameterList(\n> -                        ast.Parameter('b', None, None, 'asso<B&>')))))\n> +                    ast.ParameterList(ast.Parameter('a', None, None,\n> +                                                    'asso<A>')),\n> +                    ast.ParameterList(ast.Parameter('b', None, None,\n> +                                                    'rca<B>')))))\n>      ])\n>      self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n>  \n> @@ -1385,6 +1371,5 @@ class ParserTest(unittest.TestCase):\n>          r\" *associated\\? MyInterface& a;$\"):\n>        parser.Parse(source3, \"my_file.mojom\")\n>  \n> -\n>  if __name__ == \"__main__\":\n>    unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n> index eb90c825f9bc..9693090e44ea 100755\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n> @@ -1,5 +1,5 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +#!/usr/bin/env python3\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Parses mojom IDL files.\n> @@ -11,6 +11,7 @@ generate usable language bindings.\n>  \"\"\"\n>  \n>  import argparse\n> +import builtins\n>  import codecs\n>  import errno\n>  import json\n> @@ -19,6 +20,7 @@ import multiprocessing\n>  import os\n>  import os.path\n>  import sys\n> +import traceback\n>  from collections import defaultdict\n>  \n>  from mojom.generate import module\n> @@ -28,16 +30,12 @@ from mojom.parse import conditional_features\n>  \n>  \n>  # Disable this for easier debugging.\n> -# In Python 2, subprocesses just hang when exceptions are thrown :(.\n> -_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2\n> +_ENABLE_MULTIPROCESSING = True\n>  \n> -if sys.version_info < (3, 4):\n> -  _MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux')\n> -else:\n> -  # https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725\n> -  if __name__ == '__main__' and sys.platform == 'darwin':\n> -    multiprocessing.set_start_method('fork')\n> -  _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'\n> +# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725\n> +if __name__ == '__main__' and sys.platform == 'darwin':\n> +  multiprocessing.set_start_method('fork')\n> +_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'\n>  \n>  \n>  def _ResolveRelativeImportPath(path, roots):\n> @@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots):\n>    raise ValueError('\"%s\" does not exist in any of %s' % (path, roots))\n>  \n>  \n> -def _RebaseAbsolutePath(path, roots):\n> +def RebaseAbsolutePath(path, roots):\n>    \"\"\"Rewrites an absolute file path as relative to an absolute directory path in\n>    roots.\n>  \n> @@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,\n>      # Already done.\n>      return\n>  \n> -  for dep_abspath, dep_path in dependencies[mojom_abspath]:\n> +  for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]):\n>      if dep_abspath not in loaded_modules:\n>        _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,\n>                           loaded_modules, module_metadata)\n> @@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):\n>  \n>    def collect(metadata_filename):\n>      processed_deps.add(metadata_filename)\n> +\n> +    # Paths in the metadata file are relative to the metadata file's dir.\n> +    metadata_dir = os.path.abspath(os.path.dirname(metadata_filename))\n> +\n> +    def to_abs(s):\n> +      return os.path.normpath(os.path.join(metadata_dir, s))\n> +\n>      with open(metadata_filename) as f:\n>        metadata = json.load(f)\n>        allowed_imports.update(\n> -          map(os.path.normcase, map(os.path.normpath, metadata['sources'])))\n> +          [os.path.normcase(to_abs(s)) for s in metadata['sources']])\n>        for dep_metadata in metadata['deps']:\n> +        dep_metadata = to_abs(dep_metadata)\n>          if dep_metadata not in processed_deps:\n>            collect(dep_metadata)\n>  \n> @@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):\n>  \n>  \n>  # multiprocessing helper.\n> -def _ParseAstHelper(args):\n> -  mojom_abspath, enabled_features = args\n> +def _ParseAstHelper(mojom_abspath, enabled_features):\n>    with codecs.open(mojom_abspath, encoding='utf-8') as f:\n>      ast = parser.Parse(f.read(), mojom_abspath)\n>      conditional_features.RemoveDisabledDefinitions(ast, enabled_features)\n> @@ -181,8 +186,7 @@ def _ParseAstHelper(args):\n>  \n>  \n>  # multiprocessing helper.\n> -def _SerializeHelper(args):\n> -  mojom_abspath, mojom_path = args\n> +def _SerializeHelper(mojom_abspath, mojom_path):\n>    module_path = os.path.join(_SerializeHelper.output_root_path,\n>                               _GetModuleFilename(mojom_path))\n>    module_dir = os.path.dirname(module_path)\n> @@ -199,12 +203,33 @@ def _SerializeHelper(args):\n>      _SerializeHelper.loaded_modules[mojom_abspath].Dump(f)\n>  \n>  \n> -def _Shard(target_func, args, processes=None):\n> -  args = list(args)\n> +class _ExceptionWrapper:\n> +  def __init__(self):\n> +    # Do not capture exception object to ensure pickling works.\n> +    self.formatted_trace = traceback.format_exc()\n> +\n> +\n> +class _FuncWrapper:\n> +  \"\"\"Marshals exceptions and spreads args.\"\"\"\n> +\n> +  def __init__(self, func):\n> +    self._func = func\n> +\n> +  def __call__(self, args):\n> +    # multiprocessing does not gracefully handle excptions.\n> +    # https://crbug.com/1219044\n> +    try:\n> +      return self._func(*args)\n> +    except:  # pylint: disable=bare-except\n> +      return _ExceptionWrapper()\n> +\n> +\n> +def _Shard(target_func, arg_list, processes=None):\n> +  arg_list = list(arg_list)\n>    if processes is None:\n>      processes = multiprocessing.cpu_count()\n>    # Seems optimal to have each process perform at least 2 tasks.\n> -  processes = min(processes, len(args) // 2)\n> +  processes = min(processes, len(arg_list) // 2)\n>  \n>    if sys.platform == 'win32':\n>      # TODO(crbug.com/1190269) - we can't use more than 56\n> @@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None):\n>  \n>    # Don't spin up processes unless there is enough work to merit doing so.\n>    if not _ENABLE_MULTIPROCESSING or processes < 2:\n> -    for result in map(target_func, args):\n> -      yield result\n> +    for arg_tuple in arg_list:\n> +      yield target_func(*arg_tuple)\n>      return\n>  \n>    pool = multiprocessing.Pool(processes=processes)\n>    try:\n> -    for result in pool.imap_unordered(target_func, args):\n> +    wrapped_func = _FuncWrapper(target_func)\n> +    for result in pool.imap_unordered(wrapped_func, arg_list):\n> +      if isinstance(result, _ExceptionWrapper):\n> +        sys.stderr.write(result.formatted_trace)\n> +        sys.exit(1)\n>        yield result\n>    finally:\n>      pool.close()\n> @@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None):\n>  def _ParseMojoms(mojom_files,\n>                   input_root_paths,\n>                   output_root_path,\n> +                 module_root_paths,\n>                   enabled_features,\n>                   module_metadata,\n>                   allowed_imports=None):\n> @@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files,\n>          are based on the mojom's relative path, rebased onto this path.\n>          Additionally, the script expects this root to contain already-generated\n>          modules for any transitive dependencies not listed in mojom_files.\n> +    module_root_paths: A list of absolute filesystem paths which contain\n> +        already-generated modules for any non-transitive dependencies.\n>      enabled_features: A list of enabled feature names, controlling which AST\n> -        nodes are filtered by [EnableIf] attributes.\n> +        nodes are filtered by [EnableIf] or [EnableIfNot] attributes.\n>      module_metadata: A list of 2-tuples representing metadata key-value pairs to\n>          attach to each compiled module output.\n>  \n> @@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files,\n>    loaded_modules = {}\n>    input_dependencies = defaultdict(set)\n>    mojom_files_to_parse = dict((os.path.normcase(abs_path),\n> -                               _RebaseAbsolutePath(abs_path, input_root_paths))\n> +                               RebaseAbsolutePath(abs_path, input_root_paths))\n>                                for abs_path in mojom_files)\n>    abs_paths = dict(\n>        (path, abs_path) for abs_path, path in mojom_files_to_parse.items())\n> @@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files,\n>      loaded_mojom_asts[mojom_abspath] = ast\n>  \n>    logging.info('Processing dependencies')\n> -  for mojom_abspath, ast in loaded_mojom_asts.items():\n> +  for mojom_abspath, ast in sorted(loaded_mojom_asts.items()):\n>      invalid_imports = []\n>      for imp in ast.import_list:\n>        import_abspath = _ResolveRelativeImportPath(imp.import_filename,\n> @@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files,\n>          # be parsed and have a module file sitting in a corresponding output\n>          # location.\n>          module_path = _GetModuleFilename(imp.import_filename)\n> -        module_abspath = _ResolveRelativeImportPath(module_path,\n> -                                                    [output_root_path])\n> +        module_abspath = _ResolveRelativeImportPath(\n> +            module_path, module_root_paths + [output_root_path])\n>          with open(module_abspath, 'rb') as module_file:\n>            loaded_modules[import_abspath] = module.Module.Load(module_file)\n>  \n> @@ -370,6 +402,15 @@ already present in the provided output root.\"\"\")\n>        'based on the relative input path, rebased onto this root. Note that '\n>        'ROOT is also searched for existing modules of any transitive imports '\n>        'which were not included in the set of inputs.')\n> +  arg_parser.add_argument(\n> +      '--module-root',\n> +      default=[],\n> +      action='append',\n> +      metavar='ROOT',\n> +      dest='module_root_paths',\n> +      help='Adds ROOT to the set of root paths to search for existing modules '\n> +      'of non-transitive imports. Provided root paths are always searched in '\n> +      'order from longest absolute path to shortest.')\n>    arg_parser.add_argument(\n>        '--mojoms',\n>        nargs='+',\n> @@ -396,9 +437,9 @@ already present in the provided output root.\"\"\")\n>        help='Enables a named feature when parsing the given mojoms. Features '\n>        'are identified by arbitrary string values. Specifying this flag with a '\n>        'given FEATURE name will cause the parser to process any syntax elements '\n> -      'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '\n> -      'provided for a given FEATURE, such tagged elements are discarded by the '\n> -      'parser and will not be present in the compiled output.')\n> +      'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '\n> +      'flag is not provided for a given FEATURE, such tagged elements are '\n> +      'discarded by the parser and will not be present in the compiled output.')\n>    arg_parser.add_argument(\n>        '--check-imports',\n>        dest='build_metadata_filename',\n> @@ -436,6 +477,7 @@ already present in the provided output root.\"\"\")\n>    mojom_files = list(map(os.path.abspath, args.mojom_files))\n>    input_roots = list(map(os.path.abspath, args.input_root_paths))\n>    output_root = os.path.abspath(args.output_root_path)\n> +  module_roots = list(map(os.path.abspath, args.module_root_paths))\n>  \n>    if args.build_metadata_filename:\n>      allowed_imports = _CollectAllowedImportsFromBuildMetadata(\n> @@ -445,13 +487,16 @@ already present in the provided output root.\"\"\")\n>  \n>    module_metadata = list(\n>        map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))\n> -  _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,\n> -               module_metadata, allowed_imports)\n> +  _ParseMojoms(mojom_files, input_roots, output_root, module_roots,\n> +               args.enabled_features, module_metadata, allowed_imports)\n>    logging.info('Finished')\n> -  # Exit without running GC, which can save multiple seconds due the large\n> -  # number of object created.\n> -  os._exit(0)\n>  \n>  \n>  if __name__ == '__main__':\n>    Run(sys.argv[1:])\n> +  # Exit without running GC, which can save multiple seconds due to the large\n> +  # number of object created. But flush is necessary as os._exit doesn't do\n> +  # that.\n> +  sys.stdout.flush()\n> +  sys.stderr.flush()\n> +  os._exit(0)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n> index e213fbfa760f..f0ee6966ac20 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase):\n>    resolution, and module serialization and deserialization.\"\"\"\n>  \n>    def __init__(self, method_name):\n> -    super(MojomParserTestCase, self).__init__(method_name)\n> +    super().__init__(method_name)\n>      self._temp_dir = None\n>  \n>    def setUp(self):\n> @@ -67,7 +67,7 @@ class MojomParserTestCase(unittest.TestCase):\n>      self.ParseMojoms([filename])\n>      m = self.LoadModule(filename)\n>      definitions = {}\n> -    for kinds in (m.enums, m.structs, m.unions, m.interfaces):\n> +    for kinds in (m.enums, m.structs, m.unions, m.interfaces, m.features):\n>        for kind in kinds:\n>          definitions[kind.mojom_name] = kind\n>      return definitions\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n> index a93f34bacb41..353a2b6e2980 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n> @@ -1,7 +1,9 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> +import json\n> +\n>  from mojom_parser_test_case import MojomParserTestCase\n>  \n>  \n> @@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase):\n>      c = 'c.mojom'\n>      c_metadata = 'out/c.build_metadata'\n>      self.WriteFile(a_metadata,\n> -                   '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(a))\n> +                   json.dumps({\n> +                       \"sources\": [self.GetPath(a)],\n> +                       \"deps\": []\n> +                   }))\n>      self.WriteFile(\n>          b_metadata,\n> -        '{\"sources\": [\"%s\"], \"deps\": [\"%s\"]}\\n' % (self.GetPath(b),\n> -                                                   self.GetPath(a_metadata)))\n> +        json.dumps({\n> +            \"sources\": [self.GetPath(b)],\n> +            \"deps\": [self.GetPath(a_metadata)]\n> +        }))\n>      self.WriteFile(\n>          c_metadata,\n> -        '{\"sources\": [\"%s\"], \"deps\": [\"%s\"]}\\n' % (self.GetPath(c),\n> -                                                   self.GetPath(b_metadata)))\n> +        json.dumps({\n> +            \"sources\": [self.GetPath(c)],\n> +            \"deps\": [self.GetPath(b_metadata)]\n> +        }))\n>      self.WriteFile(a, \"\"\"\\\n>          module a;\n>          struct Bar {};\"\"\")\n> @@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase):\n>      b = 'b.mojom'\n>      b_metadata = 'out/b.build_metadata'\n>      self.WriteFile(a_metadata,\n> -                   '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(a))\n> +                   json.dumps({\n> +                       \"sources\": [self.GetPath(a)],\n> +                       \"deps\": []\n> +                   }))\n>      self.WriteFile(b_metadata,\n> -                   '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(b))\n> +                   json.dumps({\n> +                       \"sources\": [self.GetPath(b)],\n> +                       \"deps\": []\n> +                   }))\n>      self.WriteFile(a, \"\"\"\\\n>          module a;\n>          struct Bar {};\"\"\")\n> diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n> index d45ec5862999..d10d69c68b13 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/union_unittest.py b/utils/ipc/mojo/public/tools/mojom/union_unittest.py\n> new file mode 100644\n> index 000000000000..6b2525e5c5d0\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/union_unittest.py\n> @@ -0,0 +1,44 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class UnionTest(MojomParserTestCase):\n> +  \"\"\"Tests union parsing behavior.\"\"\"\n> +\n> +  def testExtensibleMustHaveDefault(self):\n> +    \"\"\"Verifies that extensible unions must have a default field.\"\"\"\n> +    mojom = 'foo.mojom'\n> +    self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x; };')\n> +    with self.assertRaisesRegexp(Exception, 'must specify a \\[Default\\]'):\n> +      self.ParseMojoms([mojom])\n> +\n> +  def testExtensibleSingleDefault(self):\n> +    \"\"\"Verifies that extensible unions must not have multiple default fields.\"\"\"\n> +    mojom = 'foo.mojom'\n> +    self.WriteFile(\n> +        mojom, \"\"\"\\\n> +               module foo;\n> +               [Extensible] union U {\n> +                 [Default] bool x;\n> +                 [Default] bool y;\n> +               };\n> +               \"\"\")\n> +    with self.assertRaisesRegexp(Exception, 'Multiple \\[Default\\] fields'):\n> +      self.ParseMojoms([mojom])\n> +\n> +  def testExtensibleDefaultTypeValid(self):\n> +    \"\"\"Verifies that an extensible union's default field must be nullable or\n> +    integral type.\"\"\"\n> +    mojom = 'foo.mojom'\n> +    self.WriteFile(\n> +        mojom, \"\"\"\\\n> +               module foo;\n> +               [Extensible] union U {\n> +                 [Default] handle<message_pipe> p;\n> +               };\n> +               \"\"\")\n> +    with self.assertRaisesRegexp(Exception, 'must be nullable or integral'):\n> +      self.ParseMojoms([mojom])\n> diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n> index 65db4dc9cf59..45e45ec55e69 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -23,9 +23,12 @@ class VersionCompatibilityTest(MojomParserTestCase):\n>  \n>      checker = module.BackwardCompatibilityChecker()\n>      compatibility_map = {}\n> -    for name in old.keys():\n> -      compatibility_map[name] = checker.IsBackwardCompatible(\n> -          new[name], old[name])\n> +    for name in old:\n> +      try:\n> +        compatibility_map[name] = checker.IsBackwardCompatible(\n> +            new[name], old[name])\n> +      except Exception:\n> +        compatibility_map[name] = False\n>      return compatibility_map\n>  \n>    def assertBackwardCompatible(self, old_mojom, new_mojom):\n> @@ -60,40 +63,48 @@ class VersionCompatibilityTest(MojomParserTestCase):\n>      \"\"\"Adding a value to an existing version is not allowed, even if the old\n>      enum was marked [Extensible]. Note that it is irrelevant whether or not the\n>      new enum is marked [Extensible].\"\"\"\n> -    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',\n> -                                     'enum E { kFoo, kBar, kBaz };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kFoo, kBar };',\n> -        '[Extensible] enum E { kFoo, kBar, kBaz };')\n> +        '[Extensible] enum E { [Default] kFoo, kBar };',\n> +        'enum E { kFoo, kBar, kBaz };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kFoo, [MinVersion=1] kBar };',\n> +        '[Extensible] enum E { [Default] kFoo, kBar };',\n> +        '[Extensible] enum E { [Default] kFoo, kBar, kBaz };')\n> +    self.assertNotBackwardCompatible(\n> +        '[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };',\n>          'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')\n>  \n>    def testEnumValueRemoval(self):\n>      \"\"\"Removal of an enum value is never valid even for [Extensible] enums.\"\"\"\n>      self.assertNotBackwardCompatible('enum E { kFoo, kBar };',\n>                                       'enum E { kFoo };')\n> -    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',\n> -                                     '[Extensible] enum E { kFoo };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB };',\n> -        '[Extensible] enum E { kA, };')\n> +        '[Extensible] enum E { [Default] kFoo, kBar };',\n> +        '[Extensible] enum E { [Default] kFoo };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };',\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB };')\n> +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',\n> +        '[Extensible] enum E { [Default] kA, };')\n> +    self.assertNotBackwardCompatible(\n> +        \"\"\"[Extensible] enum E {\n> +          [Default] kA,\n> +          [MinVersion=1] kB,\n> +          [MinVersion=1] kZ };\"\"\",\n> +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };')\n>  \n>    def testNewExtensibleEnumValueWithMinVersion(self):\n>      \"\"\"Adding a new and properly [MinVersion]'d value to an [Extensible] enum\n>      is a backward-compatible change. Note that it is irrelevant whether or not\n>      the new enum is marked [Extensible].\"\"\"\n> -    self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',\n> +    self.assertBackwardCompatible('[Extensible] enum E { [Default] kA, kB };',\n>                                    'enum E { kA, kB, [MinVersion=1] kC };')\n>      self.assertBackwardCompatible(\n> -        '[Extensible] enum E { kA, kB };',\n> -        '[Extensible] enum E { kA, kB, [MinVersion=1] kC };')\n> +        '[Extensible] enum E { [Default] kA, kB };',\n> +        '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };')\n>      self.assertBackwardCompatible(\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB };',\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };')\n> +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',\n> +        \"\"\"[Extensible] enum E {\n> +          [Default] kA,\n> +          [MinVersion=1] kB,\n> +          [MinVersion=2] kC };\"\"\")\n>  \n>    def testRenameEnumValue(self):\n>      \"\"\"Renaming an enum value does not affect backward-compatibility. Only\n> @@ -161,14 +172,17 @@ class VersionCompatibilityTest(MojomParserTestCase):\n>          'struct S {}; struct T { S s; };',\n>          'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')\n>      self.assertBackwardCompatible(\n> -        '[Extensible] enum E { kA }; struct S { E e; };',\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };')\n> +        '[Extensible] enum E { [Default] kA }; struct S { E e; };',\n> +        \"\"\"[Extensible] enum E {\n> +          [Default] kA,\n> +          [MinVersion=1] kB };\n> +          struct S { E e; };\"\"\")\n>      self.assertNotBackwardCompatible(\n>          'struct S {}; struct T { S s; };',\n>          'struct S { int32 x; }; struct T { S s; };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kA }; struct S { E e; };',\n> -        '[Extensible] enum E { kA, kB }; struct S { E e; };')\n> +        '[Extensible] enum E { [Default] kA }; struct S { E e; };',\n> +        '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };')\n>  \n>    def testNewStructFieldWithInvalidMinVersion(self):\n>      \"\"\"Adding a new field using an existing MinVersion breaks backward-\n> @@ -305,14 +319,17 @@ class VersionCompatibilityTest(MojomParserTestCase):\n>          'struct S {}; union U { S s; };',\n>          'struct S { [MinVersion=1] int32 x; }; union U { S s; };')\n>      self.assertBackwardCompatible(\n> -        '[Extensible] enum E { kA }; union U { E e; };',\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };')\n> +        '[Extensible] enum E { [Default] kA }; union U { E e; };',\n> +        \"\"\"[Extensible] enum E {\n> +          [Default] kA,\n> +          [MinVersion=1] kB };\n> +          union U { E e; };\"\"\")\n>      self.assertNotBackwardCompatible(\n>          'struct S {}; union U { S s; };',\n>          'struct S { int32 x; }; union U { S s; };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kA }; union U { E e; };',\n> -        '[Extensible] enum E { kA, kB }; union U { E e; };')\n> +        '[Extensible] enum E { [Default] kA }; union U { E e; };',\n> +        '[Extensible] enum E { [Default] kA, kB }; union U { E e; };')\n>  \n>    def testNewUnionFieldWithInvalidMinVersion(self):\n>      \"\"\"Adding a new field using an existing MinVersion breaks backward-\n> diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/ipc/mojo/public/tools/run_all_python_unittests.py\n> index b20109583071..98bce18cbb6d 100755\n> --- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py\n> +++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py\n> @@ -1,5 +1,5 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +#!/usr/bin/env python3\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -8,11 +8,13 @@ import sys\n>  \n>  _TOOLS_DIR = os.path.dirname(__file__)\n>  _MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')\n> +_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings')\n>  _SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,\n>                          os.path.pardir)\n>  \n>  # Ensure that the mojom library is discoverable.\n>  sys.path.append(_MOJOM_DIR)\n> +sys.path.append(_BINDINGS_DIR)\n>  \n>  # Help Python find typ in //third_party/catapult/third_party/typ/\n>  sys.path.append(\n> @@ -21,7 +23,7 @@ import typ\n>  \n>  \n>  def Main():\n> -  return typ.main(top_level_dir=_MOJOM_DIR)\n> +  return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR])\n>  \n>  \n>  if __name__ == '__main__':\n> diff --git a/utils/ipc/tools/README b/utils/ipc/tools/README\n> index d5c24fc3b5cb..961cabd222e3 100644\n> --- a/utils/ipc/tools/README\n> +++ b/utils/ipc/tools/README\n> @@ -1,4 +1,4 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>  \n> -Files in this directory are imported from 9c138d992bfc of Chromium. Do not\n> +Files in this directory are imported from 9be4263648d7 of Chromium. Do not\n>  modify them manually.\n> diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/ipc/tools/diagnosis/crbug_1001171.py\n> index 478fb8c1a610..40900d10d38a 100644\n> --- a/utils/ipc/tools/diagnosis/crbug_1001171.py\n> +++ b/utils/ipc/tools/diagnosis/crbug_1001171.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2019 The Chromium Authors. All rights reserved.\n> +# Copyright 2019 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 5C51FBDB1D\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri,  5 Jan 2024 14:12:43 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 5694F61D7F;\n\tFri,  5 Jan 2024 15:12:39 +0100 (CET)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.133.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id B4E1E61D7F\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri,  5 Jan 2024 14:04:49 +0100 (CET)","from mail-wm1-f69.google.com (mail-wm1-f69.google.com\n\t[209.85.128.69]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-642-uf_7dtEvOgmsnGWjA5z7aA-1; Fri, 05 Jan 2024 08:04:45 -0500","by mail-wm1-f69.google.com with SMTP id\n\t5b1f17b1804b1-40e34df6fdcso10541085e9.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 05 Jan 2024 05:04:45 -0800 (PST)","from nuthatch (ip-77-48-47-2.net.vodafone.cz. [77.48.47.2])\n\tby smtp.gmail.com with ESMTPSA id\n\ta8-20020a05600c348800b0040d90536582sm1565497wmq.21.2024.01.05.05.04.37\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 05 Jan 2024 05:04:38 -0800 (PST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1704463959;\n\tbh=13Hq6LBk+fzG8JJTtaYWY/qx3lzjyLAHlt9TPO1/Hgk=;\n\th=To:In-Reply-To:References:Date:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=XdujnfofDyw7qNruVeWZ52PCrJHSkEEtwTOGI7Y+EIl5/flKFWlQmu7D/CW40H4Au\n\tQOLarccrGnl85IBGHRawHdHUvgq6Er0erA7ynonnYskpmduBtfst2zNiH6GxBfWduQ\n\tBWNhNrlrX5APNfch9MJ5N7Y8a4N3rtCt6yb3QBZqV+T7qcjEHcoAc1f5KcrDbpDRZb\n\tX1n9UOgrHKumlQYrDPHyRRbg1FMUd7k8+zeXoRB+2oz7yxb+xRm/kJaQZ9pgUSCEy1\n\tT2OI7UhJcOzbdOwzJsQLHtEbtcKbqXrdIArPHgMyD7op4SNR6EocpITHrptGFD1AdV\n\tIJnd4lsh31MPQ==","v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1704459888;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=XWz02rzQmLXPxRr5pm5ZFj53/clmkjeJvCPoQKQVNEc=;\n\tb=IVSxPWc8IUneo65bPcz+dhtS3e1OWjlDzgwUS77mT43gC15Zg286D2ReVLb2bmfYhllo7C\n\tbkb+2ZXf8MnDNwIX4OvJGvKhDGIvOTLfL/hYYwq5bPdzxCB3NP5kXLSd/pLWkWbwYFGWni\n\tEbtFS87OEX7msK2AUZiqzgHFcNo30GQ="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=redhat.com\n\theader.i=@redhat.com header.b=\"IVSxPWc8\"; \n\tdkim-atps=neutral","X-MC-Unique":"uf_7dtEvOgmsnGWjA5z7aA-1","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1704459883; x=1705064683;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=XWz02rzQmLXPxRr5pm5ZFj53/clmkjeJvCPoQKQVNEc=;\n\tb=jzFczpErg9WRQm1cif1Po5nPD09CIZucNfqM6fbUMoHSnzrooxKOOw+Z4kCu+s3RCZ\n\tKfZlh+Y5b1W/LhUaCNTvB1D848F+AvdB2Y/ItVO8EMLbsa33rIruNblqGbfFBTIGN3vQ\n\tQ9PwCBXwVqzI3LfyGEzk8kRYDhLwl3FjmKxUbGeson12ObJZ166lozaoHg49odYzJU2d\n\tx1jZDRwJRhHrxP2PSs7I+NzPc6Bp2JjY0eCZSv7BsLcHJdTv1ofnDwRKAfUU+zbR5t4/\n\tAV4MuFR6syoPUFKksWXUdD+o6Ui7zU3AcddcoPGj2NVEJpM4gMQgy7Beh2OpGvPkrabH\n\tW5YA==","X-Gm-Message-State":"AOJu0YwBYjGTihYzJpKX3gmRx+631Sg8SLt4cVdufucSKwsfPZhNP7yb\n\to3/8DrYTgg8Fyi4GtyKObSFeG6DNcQlJ+y6xVLbgK9ioxX3/o9ieo7VX0Z+HUd11zKo2xPx1Qww\n\td3RhsEbn2NlaGJxt2DfOQc/flJTRTNYTlkg1h/p5RxktkHpBYKQ==","X-Received":["by 2002:a05:600c:ac6:b0:40d:7264:2218 with SMTP id\n\tc6-20020a05600c0ac600b0040d72642218mr1284967wmr.17.1704459881980; \n\tFri, 05 Jan 2024 05:04:41 -0800 (PST)","by 2002:a05:600c:ac6:b0:40d:7264:2218 with SMTP id\n\tc6-20020a05600c0ac600b0040d72642218mr1284926wmr.17.1704459879724; \n\tFri, 05 Jan 2024 05:04:39 -0800 (PST)"],"X-Google-Smtp-Source":"AGHT+IGjbFoPwpLx2SFMKPcTaGdadpLUVsfR4iR5J8CX+2t37TgwWDVaJE0nlK8Nd0vn/ne2c8YcEQ==","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","In-Reply-To":"<20240104151548.2589-9-laurent.pinchart@ideasonboard.com>\n\t(Laurent Pinchart's message of \"Thu, 4 Jan 2024 17:15:48 +0200\")","References":"<20240104151548.2589-1-laurent.pinchart@ideasonboard.com>\n\t<20240104151548.2589-9-laurent.pinchart@ideasonboard.com>","Date":"Fri, 05 Jan 2024 14:04:37 +0100","Message-ID":"<87a5pkszh6.fsf@redhat.com>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","X-Mailman-Approved-At":"Fri, 05 Jan 2024 15:12:37 +0100","Subject":"Re: [libcamera-devel] [PATCH 8/8] utils: ipc: Update mojo","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Milan Zamazal via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"Milan Zamazal <mzamazal@redhat.com>","Cc":"libcamera-devel@lists.libcamera.org, Khem Raj <raj.khem@gmail.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28410,"web_url":"https://patchwork.libcamera.org/comment/28410/","msgid":"<20240109121955.GE11622@pendragon.ideasonboard.com>","date":"2024-01-09T12:19:55","subject":"Re: [libcamera-devel] [PATCH 8/8] utils: ipc: Update mojo","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"On Tue, Jan 09, 2024 at 12:16:08PM +0000, Kieran Bingham wrote:\n> In fact, I wonder if the update script should give a more precise\n> statement in the subject here. But I'm not sure what it could reference.\n> \n> The only 'catch' is that this patch was automatically detected as\n> already accepted by the patchwork bot - as the $SUBJECT matches a patch\n> that has already been merged.\n\n\"utils: ipc: Update mojo to commit ${short_sha1}\" would be a good\nsubject. Please feel free to submit a patch to the update script :-)\n\n> I wouldn't block this series on that though.\n> \n> Quoting Laurent Pinchart via libcamera-devel (2024-01-04 15:15:48)\n> > Update mojo from commit\n> > \n> > 9be4263648d7d1a04bb78be75df53f56449a5e3a \"Updating trunk VERSION from 6225.0 to 6226.0\"\n> > \n> > from the Chromium repository.\n> > \n> > The update-mojo.sh script was used for this update.\n> \n> I'm not going to 'review' this change. Just accept it.\n> \n> Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> \n> > Bug: https://bugs.libcamera.org/show_bug.cgi?id=206\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > ---\n> >  utils/ipc/mojo/README                         |   2 +-\n> >  utils/ipc/mojo/public/LICENSE                 |   2 +-\n> >  utils/ipc/mojo/public/tools/BUILD.gn          |   8 +-\n> >  utils/ipc/mojo/public/tools/bindings/BUILD.gn |  36 +-\n> >  .../ipc/mojo/public/tools/bindings/README.md  | 239 +++--\n> >  .../public/tools/bindings/checks/__init__.py  |   0\n> >  .../bindings/checks/mojom_attributes_check.py | 170 ++++\n> >  .../checks/mojom_attributes_check_unittest.py | 194 ++++\n> >  .../checks/mojom_definitions_check.py         |  34 +\n> >  .../checks/mojom_interface_feature_check.py   |  62 ++\n> >  .../mojom_interface_feature_check_unittest.py | 173 ++++\n> >  .../checks/mojom_restrictions_check.py        | 102 +++\n> >  .../mojom_restrictions_checks_unittest.py     | 254 ++++++\n> >  .../chromium_bindings_configuration.gni       |  51 --\n> >  .../tools/bindings/compile_typescript.py      |  27 -\n> >  .../tools/bindings/concatenate-files.py       |   5 +-\n> >  ...concatenate_and_replace_closure_exports.py |  10 +-\n> >  .../bindings/format_typemap_generator_args.py |  36 -\n> >  .../tools/bindings/gen_data_files_list.py     |   2 +-\n> >  .../tools/bindings/generate_type_mappings.py  |   4 +-\n> >  .../tools/bindings/minify_with_terser.py      |  47 +\n> >  .../ipc/mojo/public/tools/bindings/mojom.gni  | 853 ++++++++++--------\n> >  .../bindings/mojom_bindings_generator.py      |  62 +-\n> >  .../mojom_bindings_generator_unittest.py      |   6 +-\n> >  .../tools/bindings/mojom_types_downgrader.py  | 119 ---\n> >  .../tools/bindings/validate_typemap_config.py |   5 +-\n> >  utils/ipc/mojo/public/tools/mojom/BUILD.gn    |  18 +\n> >  .../mojom/check_stable_mojom_compatibility.py |  69 +-\n> >  ...eck_stable_mojom_compatibility_unittest.py |  87 +-\n> >  .../mojo/public/tools/mojom/const_unittest.py |   2 +-\n> >  .../mojo/public/tools/mojom/enum_unittest.py  |  30 +-\n> >  .../public/tools/mojom/feature_unittest.py    |  84 ++\n> >  .../mojo/public/tools/mojom/mojom/BUILD.gn    |   3 +-\n> >  .../mojo/public/tools/mojom/mojom/error.py    |   2 +-\n> >  .../mojo/public/tools/mojom/mojom/fileutil.py |   3 +-\n> >  .../tools/mojom/mojom/fileutil_unittest.py    |   7 +-\n> >  .../tools/mojom/mojom/generate/check.py       |  26 +\n> >  .../mojom/mojom/generate/constant_resolver.py |  93 --\n> >  .../tools/mojom/mojom/generate/generator.py   |  11 +-\n> >  .../mojom/generate/generator_unittest.py      |   9 +-\n> >  .../tools/mojom/mojom/generate/module.py      | 783 +++++++++++-----\n> >  .../mojom/mojom/generate/module_unittest.py   |   2 +-\n> >  .../public/tools/mojom/mojom/generate/pack.py | 151 +++-\n> >  .../mojom/mojom/generate/pack_unittest.py     |  30 +-\n> >  .../mojom/mojom/generate/template_expander.py |   2 +-\n> >  .../tools/mojom/mojom/generate/translate.py   | 464 +++++++++-\n> >  .../mojom/generate/translate_unittest.py      |  82 +-\n> >  .../public/tools/mojom/mojom/parse/ast.py     | 145 +--\n> >  .../tools/mojom/mojom/parse/ast_unittest.py   |  12 +-\n> >  .../mojom/mojom/parse/conditional_features.py |  21 +-\n> >  .../parse/conditional_features_unittest.py    | 155 +++-\n> >  .../public/tools/mojom/mojom/parse/lexer.py   |   8 +-\n> >  .../tools/mojom/mojom/parse/lexer_unittest.py |  10 +-\n> >  .../public/tools/mojom/mojom/parse/parser.py  | 108 ++-\n> >  .../mojom/mojom/parse/parser_unittest.py      |  39 +-\n> >  .../mojo/public/tools/mojom/mojom_parser.py   | 119 ++-\n> >  .../tools/mojom/mojom_parser_test_case.py     |   6 +-\n> >  .../tools/mojom/mojom_parser_unittest.py      |  31 +-\n> >  .../tools/mojom/stable_attribute_unittest.py  |   2 +-\n> >  .../mojo/public/tools/mojom/union_unittest.py |  44 +\n> >  .../mojom/version_compatibility_unittest.py   |  73 +-\n> >  .../public/tools/run_all_python_unittests.py  |   8 +-\n> >  utils/ipc/tools/README                        |   2 +-\n> >  utils/ipc/tools/diagnosis/crbug_1001171.py    |   2 +-\n> >  64 files changed, 3830 insertions(+), 1416 deletions(-)\n> >  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/__init__.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py\n> >  delete mode 100644 utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n> >  delete mode 100644 utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n> >  delete mode 100755 utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n> >  create mode 100755 utils/ipc/mojo/public/tools/bindings/minify_with_terser.py\n> >  delete mode 100755 utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/mojom/BUILD.gn\n> >  create mode 100644 utils/ipc/mojo/public/tools/mojom/feature_unittest.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py\n> >  delete mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n> >  create mode 100644 utils/ipc/mojo/public/tools/mojom/union_unittest.py","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 5D4EBC323E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  9 Jan 2024 12:19:49 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 8B93962B41;\n\tTue,  9 Jan 2024 13:19:48 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 89F1462B32\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  9 Jan 2024 13:19:46 +0100 (CET)","from pendragon.ideasonboard.com (213-243-189-158.bb.dnainternet.fi\n\t[213.243.189.158])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 2EC95552;\n\tTue,  9 Jan 2024 13:18:42 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1704802788;\n\tbh=3geC1qGg/SpatGeVPyxQ93npVaCU8gOfroBOU2U9MxI=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=dXR7FSdKHrHCdbz47GjT+rubqjjuS5suScvI8wiK2LTatickYp6wY762S29x0ZDqA\n\tKEdO/PSQ6RK+ktM3AbWPMjzGFn+kCqChJ2QdDUP5folBVAv5L/Gq/Zc+OovztPSyst\n\tmweIqhKyeUZ+Nzag9TGEoo+vCEAqmasSUOZumNy7jPU98mES2H8ejpaMG3pKj/K6Nq\n\t3uimS5/0iAzriUuVMcnjQ4vDiFBTknsDxBLJ3IBT4Hrp03TdDmUH6xLKdvpjE3u3il\n\tStVVY1RoHXGI22TCiLUaGIH3Cv+Tk8W+BbYs2pfZObq0jxQlU/I+I7mKf+i5uFQOCJ\n\tAJBGHE1n3Uj/w==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1704802722;\n\tbh=3geC1qGg/SpatGeVPyxQ93npVaCU8gOfroBOU2U9MxI=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=oPEN0ZUwpwC3THTrWeb0aT4rc2ccBhkGBeYCE+3V6Cz/YjR7o6TlYYEtYZPGYADXS\n\tMWLiuC1bg3ZLZDP0k65YBzb0nPX3oNNgmDTN4vhjYOcyhX2pK/oHI4R1bw9tqeveTJ\n\tWUpHYHjvLx9jwnmdE0IZW6lVj/Pgcv6EPHNUn8zw="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"oPEN0ZUw\"; dkim-atps=neutral","Date":"Tue, 9 Jan 2024 14:19:55 +0200","To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Message-ID":"<20240109121955.GE11622@pendragon.ideasonboard.com>","References":"<20240104151548.2589-1-laurent.pinchart@ideasonboard.com>\n\t<20240104151548.2589-9-laurent.pinchart@ideasonboard.com>\n\t<170480256809.3044059.17933551749909896440@ping.linuxembedded.co.uk>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<170480256809.3044059.17933551749909896440@ping.linuxembedded.co.uk>","Subject":"Re: [libcamera-devel] [PATCH 8/8] utils: ipc: Update mojo","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org, Khem Raj <raj.khem@gmail.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":28411,"web_url":"https://patchwork.libcamera.org/comment/28411/","msgid":"<170480256809.3044059.17933551749909896440@ping.linuxembedded.co.uk>","date":"2024-01-09T12:16:08","subject":"Re: [libcamera-devel] [PATCH 8/8] utils: ipc: Update mojo","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"In fact, I wonder if the update script should give a more precise\nstatement in the subject here. But I'm not sure what it could reference.\n\nThe only 'catch' is that this patch was automatically detected as\nalready accepted by the patchwork bot - as the $SUBJECT matches a patch\nthat has already been merged.\n\nI wouldn't block this series on that though.\n\nQuoting Laurent Pinchart via libcamera-devel (2024-01-04 15:15:48)\n> Update mojo from commit\n> \n> 9be4263648d7d1a04bb78be75df53f56449a5e3a \"Updating trunk VERSION from 6225.0 to 6226.0\"\n> \n> from the Chromium repository.\n> \n> The update-mojo.sh script was used for this update.\n\nI'm not going to 'review' this change. Just accept it.\n\n\nAcked-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n> \n> Bug: https://bugs.libcamera.org/show_bug.cgi?id=206\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  utils/ipc/mojo/README                         |   2 +-\n>  utils/ipc/mojo/public/LICENSE                 |   2 +-\n>  utils/ipc/mojo/public/tools/BUILD.gn          |   8 +-\n>  utils/ipc/mojo/public/tools/bindings/BUILD.gn |  36 +-\n>  .../ipc/mojo/public/tools/bindings/README.md  | 239 +++--\n>  .../public/tools/bindings/checks/__init__.py  |   0\n>  .../bindings/checks/mojom_attributes_check.py | 170 ++++\n>  .../checks/mojom_attributes_check_unittest.py | 194 ++++\n>  .../checks/mojom_definitions_check.py         |  34 +\n>  .../checks/mojom_interface_feature_check.py   |  62 ++\n>  .../mojom_interface_feature_check_unittest.py | 173 ++++\n>  .../checks/mojom_restrictions_check.py        | 102 +++\n>  .../mojom_restrictions_checks_unittest.py     | 254 ++++++\n>  .../chromium_bindings_configuration.gni       |  51 --\n>  .../tools/bindings/compile_typescript.py      |  27 -\n>  .../tools/bindings/concatenate-files.py       |   5 +-\n>  ...concatenate_and_replace_closure_exports.py |  10 +-\n>  .../bindings/format_typemap_generator_args.py |  36 -\n>  .../tools/bindings/gen_data_files_list.py     |   2 +-\n>  .../tools/bindings/generate_type_mappings.py  |   4 +-\n>  .../tools/bindings/minify_with_terser.py      |  47 +\n>  .../ipc/mojo/public/tools/bindings/mojom.gni  | 853 ++++++++++--------\n>  .../bindings/mojom_bindings_generator.py      |  62 +-\n>  .../mojom_bindings_generator_unittest.py      |   6 +-\n>  .../tools/bindings/mojom_types_downgrader.py  | 119 ---\n>  .../tools/bindings/validate_typemap_config.py |   5 +-\n>  utils/ipc/mojo/public/tools/mojom/BUILD.gn    |  18 +\n>  .../mojom/check_stable_mojom_compatibility.py |  69 +-\n>  ...eck_stable_mojom_compatibility_unittest.py |  87 +-\n>  .../mojo/public/tools/mojom/const_unittest.py |   2 +-\n>  .../mojo/public/tools/mojom/enum_unittest.py  |  30 +-\n>  .../public/tools/mojom/feature_unittest.py    |  84 ++\n>  .../mojo/public/tools/mojom/mojom/BUILD.gn    |   3 +-\n>  .../mojo/public/tools/mojom/mojom/error.py    |   2 +-\n>  .../mojo/public/tools/mojom/mojom/fileutil.py |   3 +-\n>  .../tools/mojom/mojom/fileutil_unittest.py    |   7 +-\n>  .../tools/mojom/mojom/generate/check.py       |  26 +\n>  .../mojom/mojom/generate/constant_resolver.py |  93 --\n>  .../tools/mojom/mojom/generate/generator.py   |  11 +-\n>  .../mojom/generate/generator_unittest.py      |   9 +-\n>  .../tools/mojom/mojom/generate/module.py      | 783 +++++++++++-----\n>  .../mojom/mojom/generate/module_unittest.py   |   2 +-\n>  .../public/tools/mojom/mojom/generate/pack.py | 151 +++-\n>  .../mojom/mojom/generate/pack_unittest.py     |  30 +-\n>  .../mojom/mojom/generate/template_expander.py |   2 +-\n>  .../tools/mojom/mojom/generate/translate.py   | 464 +++++++++-\n>  .../mojom/generate/translate_unittest.py      |  82 +-\n>  .../public/tools/mojom/mojom/parse/ast.py     | 145 +--\n>  .../tools/mojom/mojom/parse/ast_unittest.py   |  12 +-\n>  .../mojom/mojom/parse/conditional_features.py |  21 +-\n>  .../parse/conditional_features_unittest.py    | 155 +++-\n>  .../public/tools/mojom/mojom/parse/lexer.py   |   8 +-\n>  .../tools/mojom/mojom/parse/lexer_unittest.py |  10 +-\n>  .../public/tools/mojom/mojom/parse/parser.py  | 108 ++-\n>  .../mojom/mojom/parse/parser_unittest.py      |  39 +-\n>  .../mojo/public/tools/mojom/mojom_parser.py   | 119 ++-\n>  .../tools/mojom/mojom_parser_test_case.py     |   6 +-\n>  .../tools/mojom/mojom_parser_unittest.py      |  31 +-\n>  .../tools/mojom/stable_attribute_unittest.py  |   2 +-\n>  .../mojo/public/tools/mojom/union_unittest.py |  44 +\n>  .../mojom/version_compatibility_unittest.py   |  73 +-\n>  .../public/tools/run_all_python_unittests.py  |   8 +-\n>  utils/ipc/tools/README                        |   2 +-\n>  utils/ipc/tools/diagnosis/crbug_1001171.py    |   2 +-\n>  64 files changed, 3830 insertions(+), 1416 deletions(-)\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/__init__.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py\n>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py\n>  delete mode 100644 utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n>  delete mode 100644 utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n>  delete mode 100755 utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n>  create mode 100755 utils/ipc/mojo/public/tools/bindings/minify_with_terser.py\n>  delete mode 100755 utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/BUILD.gn\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/feature_unittest.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py\n>  delete mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n>  create mode 100644 utils/ipc/mojo/public/tools/mojom/union_unittest.py\n> \n> diff --git a/utils/ipc/mojo/README b/utils/ipc/mojo/README\n> index d5c24fc3b5cb..961cabd222e3 100644\n> --- a/utils/ipc/mojo/README\n> +++ b/utils/ipc/mojo/README\n> @@ -1,4 +1,4 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>  \n> -Files in this directory are imported from 9c138d992bfc of Chromium. Do not\n> +Files in this directory are imported from 9be4263648d7 of Chromium. Do not\n>  modify them manually.\n> diff --git a/utils/ipc/mojo/public/LICENSE b/utils/ipc/mojo/public/LICENSE\n> index 972bb2edb099..513e8a6a0a1d 100644\n> --- a/utils/ipc/mojo/public/LICENSE\n> +++ b/utils/ipc/mojo/public/LICENSE\n> @@ -1,4 +1,4 @@\n> -// Copyright 2014 The Chromium Authors. All rights reserved.\n> +// Copyright 2014 The Chromium Authors\n>  //\n>  // Redistribution and use in source and binary forms, with or without\n>  // modification, are permitted provided that the following conditions are\n> diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/ipc/mojo/public/tools/BUILD.gn\n> index eb6391a65400..5328a34a3935 100644\n> --- a/utils/ipc/mojo/public/tools/BUILD.gn\n> +++ b/utils/ipc/mojo/public/tools/BUILD.gn\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -10,7 +10,11 @@ group(\"mojo_python_unittests\") {\n>      \"run_all_python_unittests.py\",\n>      \"//testing/scripts/run_isolated_script_test.py\",\n>    ]\n> -  deps = [ \"//mojo/public/tools/mojom/mojom:tests\" ]\n> +  deps = [\n> +    \"//mojo/public/tools/bindings:tests\",\n> +    \"//mojo/public/tools/mojom:tests\",\n> +    \"//mojo/public/tools/mojom/mojom:tests\",\n> +  ]\n>    data_deps = [\n>      \"//testing:test_scripts_shared\",\n>      \"//third_party/catapult/third_party/typ/\",\n> diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/ipc/mojo/public/tools/bindings/BUILD.gn\n> index 3e2425327490..eeca73ea3d56 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn\n> +++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn\n> @@ -1,24 +1,27 @@\n> -# Copyright 2016 The Chromium Authors. All rights reserved.\n> +# Copyright 2016 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import(\"//build/config/python.gni\")\n>  import(\"//mojo/public/tools/bindings/mojom.gni\")\n>  import(\"//third_party/jinja2/jinja2.gni\")\n>  \n> -# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -python2_action(\"precompile_templates\") {\n> +action(\"precompile_templates\") {\n>    sources = mojom_generator_sources\n>    sources += [\n> +    \"$mojom_generator_root/generators/cpp_templates/cpp_macros.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/feature_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/feature_definition.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/interface_feature_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl\",\n> +    \"$mojom_generator_root/generators/cpp_templates/module-features.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-forward.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-import-headers.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-params-data.h.tmpl\",\n> @@ -26,7 +29,6 @@ python2_action(\"precompile_templates\") {\n>      \"$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl\",\n> -    \"$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module.cc.tmpl\",\n>      \"$mojom_generator_root/generators/cpp_templates/module.h.tmpl\",\n> @@ -65,9 +67,6 @@ python2_action(\"precompile_templates\") {\n>      \"$mojom_generator_root/generators/java_templates/struct.java.tmpl\",\n>      \"$mojom_generator_root/generators/java_templates/union.java.tmpl\",\n>      \"$mojom_generator_root/generators/js_templates/enum_definition.tmpl\",\n> -    \"$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl\",\n> -    \"$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl\",\n> -    \"$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl\",\n>      \"$mojom_generator_root/generators/js_templates/fuzzing.tmpl\",\n>      \"$mojom_generator_root/generators/js_templates/interface_definition.tmpl\",\n>      \"$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl\",\n> @@ -93,8 +92,11 @@ python2_action(\"precompile_templates\") {\n>      \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl\",\n>      \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl\",\n>      \"$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/enum_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/interface_definition.tmpl\",\n>      \"$mojom_generator_root/generators/ts_templates/module_definition.tmpl\",\n> -    \"$mojom_generator_root/generators/ts_templates/mojom.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/struct_definition.tmpl\",\n> +    \"$mojom_generator_root/generators/ts_templates/union_definition.tmpl\",\n>    ]\n>    script = mojom_generator_script\n>  \n> @@ -102,8 +104,8 @@ python2_action(\"precompile_templates\") {\n>    outputs = [\n>      \"$target_gen_dir/cpp_templates.zip\",\n>      \"$target_gen_dir/java_templates.zip\",\n> -    \"$target_gen_dir/mojolpm_templates.zip\",\n>      \"$target_gen_dir/js_templates.zip\",\n> +    \"$target_gen_dir/mojolpm_templates.zip\",\n>      \"$target_gen_dir/ts_templates.zip\",\n>    ]\n>    args = [\n> @@ -113,3 +115,17 @@ python2_action(\"precompile_templates\") {\n>      \"precompile\",\n>    ]\n>  }\n> +\n> +group(\"tests\") {\n> +  data = [\n> +    mojom_generator_script,\n> +    \"checks/mojom_attributes_check_unittest.py\",\n> +    \"checks/mojom_interface_feature_check_unittest.py\",\n> +    \"checks/mojom_restrictions_checks_unittest.py\",\n> +    \"mojom_bindings_generator_unittest.py\",\n> +    \"//tools/diagnosis/crbug_1001171.py\",\n> +    \"//third_party/markupsafe/\",\n> +  ]\n> +  data += mojom_generator_sources\n> +  data += jinja2_sources\n> +}\n> diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/ipc/mojo/public/tools/bindings/README.md\n> index 438824502280..b27b2d01b3fc 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/README.md\n> +++ b/utils/ipc/mojo/public/tools/bindings/README.md\n> @@ -96,7 +96,7 @@ for message parameters.\n>  | `string`                      | UTF-8 encoded string.\n>  | `array<T>`                    | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`.\n>  | `array<T, N>`                 | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant.\n> -| `map<S, T>`                   | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.\n> +| `map<S, T>`                   | Associated array mapping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type.\n>  | `handle`                      | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle.\n>  | `handle<message_pipe>`        | Generic message pipe handle.\n>  | `handle<shared_buffer>`       | Shared buffer handle.\n> @@ -188,8 +188,8 @@ struct StringPair {\n>  };\n>  \n>  enum AnEnum {\n> -  YES,\n> -  NO\n> +  kYes,\n> +  kNo\n>  };\n>  \n>  interface SampleInterface {\n> @@ -209,7 +209,7 @@ struct AllTheThings {\n>    uint64 unsigned_64bit_value;\n>    float float_value_32bit;\n>    double float_value_64bit;\n> -  AnEnum enum_value = AnEnum.YES;\n> +  AnEnum enum_value = AnEnum.kYes;\n>  \n>    // Strings may be nullable.\n>    string? maybe_a_string_maybe_not;\n> @@ -300,14 +300,14 @@ within a module or nested within the namespace of some struct or interface:\n>  module business.mojom;\n>  \n>  enum Department {\n> -  SALES = 0,\n> -  DEV,\n> +  kSales = 0,\n> +  kDev,\n>  };\n>  \n>  struct Employee {\n>    enum Type {\n> -    FULL_TIME,\n> -    PART_TIME,\n> +    kFullTime,\n> +    kPartTime,\n>    };\n>  \n>    Type type;\n> @@ -315,6 +315,9 @@ struct Employee {\n>  };\n>  ```\n>  \n> +C++ constant-style enum value names are preferred as specified in the\n> +[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names).\n> +\n>  Similar to C-style enums, individual values may be explicitly assigned within an\n>  enum definition. By default, values are based at zero and increment by\n>  1 sequentially.\n> @@ -336,8 +339,8 @@ struct Employee {\n>    const uint64 kInvalidId = 0;\n>  \n>    enum Type {\n> -    FULL_TIME,\n> -    PART_TIME,\n> +    kFullTime,\n> +    kPartTime,\n>    };\n>  \n>    uint64 id = kInvalidId;\n> @@ -348,6 +351,37 @@ struct Employee {\n>  The effect of nested definitions on generated bindings varies depending on the\n>  target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages).\n>  \n> +### Features\n> +\n> +Features can be declared with a `name` and `default_state` and can be attached\n> +in mojo to interfaces or methods using the `RuntimeFeature` attribute. If the\n> +feature is disabled at runtime, the method will crash and the interface will\n> +refuse to be bound / instantiated. Features cannot be serialized to be sent over\n> +IPC at this time.\n> +\n> +```\n> +module experimental.mojom;\n> +\n> +feature kUseElevators {\n> +  const string name = \"UseElevators\";\n> +  const bool default_state = false;\n> +}\n> +\n> +[RuntimeFeature=kUseElevators]\n> +interface Elevator {\n> +  // This interface cannot be bound or called if the feature is disabled.\n> +}\n> +\n> +interface Building {\n> +  // This method cannot be called if the feature is disabled.\n> +  [RuntimeFeature=kUseElevators]\n> +  CallElevator(int floor);\n> +\n> +  // This method can be called.\n> +  RingDoorbell(int volume);\n> +}\n> +```\n> +\n>  ### Interfaces\n>  \n>  An **interface** is a logical bundle of parameterized request messages. Each\n> @@ -396,20 +430,33 @@ interesting attributes supported today.\n>    extreme caution, because it can lead to deadlocks otherwise.\n>  \n>  * **`[Default]`**:\n> -  The `Default` attribute may be used to specify an enumerator value that\n> -  will be used if an `Extensible` enumeration does not deserialize to a known\n> -  value on the receiver side, i.e. the sender is using a newer version of the\n> -  enum. This allows unknown values to be mapped to a well-defined value that can\n> -  be appropriately handled.\n> +  The `Default` attribute may be used to specify an enumerator value or union\n> +  field that will be used if an `Extensible` enumeration or union does not\n> +  deserialize to a known value on the receiver side, i.e. the sender is using a\n> +  newer version of the enum or union. This allows unknown values to be mapped to\n> +  a well-defined value that can be appropriately handled.\n> +\n> +  Note: The `Default` field for a union must be of nullable or integral type.\n> +  When a union is defaulted to this field, the field takes on the default value\n> +  for its type: null for nullable types, and zero/false for integral types.\n>  \n>  * **`[Extensible]`**:\n> -  The `Extensible` attribute may be specified for any enum definition. This\n> -  essentially disables builtin range validation when receiving values of the\n> -  enum type in a message, allowing older bindings to tolerate unrecognized\n> -  values from newer versions of the enum.\n> +  The `Extensible` attribute may be specified for any enum or union definition.\n> +  For enums, this essentially disables builtin range validation when receiving\n> +  values of the enum type in a message, allowing older bindings to tolerate\n> +  unrecognized values from newer versions of the enum.\n>  \n> -  Note: in the future, an `Extensible` enumeration will require that a `Default`\n> -  enumerator value also be specified.\n> +  If an enum value within an extensible enum definition is affixed with the\n> +  `Default` attribute, out-of-range values for the enum will deserialize to that\n> +  default value. Only one enum value may be designated as the `Default`.\n> +\n> +  Similarly, a union marked `Extensible` will deserialize to its `Default` field\n> +  when an unrecognized field is received. Extensible unions MUST specify exactly\n> +  one `Default` field, and the field must be of nullable or integral type. When\n> +  defaulted to this field, the value is always null/zero/false as appropriate.\n> +\n> +  An `Extensible` enumeration REQUIRES that a `Default` value be specified,\n> +  so all new extensible enums should specify one.\n>  \n>  * **`[Native]`**:\n>    The `Native` attribute may be specified for an empty struct declaration to\n> @@ -422,7 +469,10 @@ interesting attributes supported today.\n>  * **`[MinVersion=N]`**:\n>    The `MinVersion` attribute is used to specify the version at which a given\n>    field, enum value, interface method, or method parameter was introduced.\n> -  See [Versioning](#Versioning) for more details.\n> +  See [Versioning](#Versioning) for more details. `MinVersion` does not apply\n> +  to interfaces, structs or enums, but to the fields of those types.\n> +  `MinVersion` is not a module-global value, but it is ok to pretend it is by\n> +  skipping versions when adding fields or parameters.\n>  \n>  * **`[Stable]`**:\n>    The `Stable` attribute specifies that a given mojom type or interface\n> @@ -442,13 +492,73 @@ interesting attributes supported today.\n>    string representation as specified by RFC 4122. New UUIDs can be generated\n>    with common tools such as `uuidgen`.\n>  \n> +* **`[RuntimeFeature=feature]`**\n> +  The `RuntimeFeature` attribute should reference a mojo `feature`. If this\n> +  feature is enabled (e.g. using `--enable-features={feature.name}`) then the\n> +  interface behaves entirely as expected. If the feature is not enabled the\n> +  interface cannot be bound to a concrete receiver or remote - attempting to do\n> +  so will result in the receiver or remote being reset() to an unbound state.\n> +  Note that this is a different concept to the build-time `EnableIf` directive.\n> +  `RuntimeFeature` is currently only supported for C++ bindings and has no\n> +  effect for, say, Java or TypeScript bindings (see https://crbug.com/1278253).\n> +\n>  * **`[EnableIf=value]`**:\n>    The `EnableIf` attribute is used to conditionally enable definitions when the\n>    mojom is parsed. If the `mojom` target in the GN file does not include the\n>    matching `value` in the list of `enabled_features`, the definition will be\n>    disabled. This is useful for mojom definitions that only make sense on one\n>    platform. Note that the `EnableIf` attribute can only be set once per\n> -  definition.\n> +  definition and cannot be set at the same time as `EnableIfNot`. Also be aware\n> +  that only one condition can be tested, `EnableIf=value,xyz` introduces a new\n> +  `xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends\n> +  only on the feature `value`. Complex conditions can be introduced via\n> +  enabled_features in `build.gn` files.\n> +\n> +* **`[EnableIfNot=value]`**:\n> +  The `EnableIfNot` attribute is used to conditionally enable definitions when\n> +  the mojom is parsed. If the `mojom` target in the GN file includes the\n> +  matching `value` in the list of `enabled_features`, the definition will be\n> +  disabled. This is useful for mojom definitions that only make sense on all but\n> +  one platform. Note that the `EnableIfNot` attribute can only be set once per\n> +  definition and cannot be set at the same time as `EnableIf`.\n> +\n> +* **`[ServiceSandbox=value]`**:\n> +  The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a\n> +  service hosting an implementation of interface will be launched in. This only\n> +  applies to `C++` bindings. `value` should match a constant defined in an\n> +  imported `sandbox.mojom.Sandbox` enum (for Chromium this is\n> +  `//sandbox/policy/mojom/sandbox.mojom`), such as `kService`.\n> +\n> +* **`[RequireContext=enum]`**:\n> +  The `RequireContext` attribute is used in Chromium to tag interfaces that\n> +  should be passed (as remotes or receivers) only to privileged process\n> +  contexts. The process context must be an enum that is imported into the\n> +  mojom that defines the tagged interface. `RequireContext` may be used in\n> +  future to DCHECK or CHECK if remotes are made available in contexts that\n> +  conflict with the one provided in the interface definition. Process contexts\n> +  are not the same as the sandbox a process is running in, but will reflect\n> +  the set of capabilities provided to the service.\n> +\n> +* **`[AllowedContext=enum]`**:\n> +  The `AllowedContext` attribute is used in Chromium to tag methods that pass\n> +  remotes or receivers of interfaces that are marked with a `RequireContext`\n> +  attribute. The enum provided on the method must be equal or better (lower\n> +  numerically) than the one required on the interface being passed. At present\n> +  failing to specify an adequate `AllowedContext` value will cause mojom\n> +  generation to fail at compile time. In future DCHECKs or CHECKs might be\n> +  added to enforce that method is only called from a process context that meets\n> +  the given `AllowedContext` value. The enum must of the same type as that\n> +  specified in the interface's `RequireContext` attribute. Adding an\n> +  `AllowedContext` attribute to a method is a strong indication that you need\n> +   a detailed security review of your design - please reach out to the security\n> +   team.\n> +\n> +* **`[SupportsUrgent]`**:\n> +  The `SupportsUrgent` attribute is used in conjunction with\n> +  `mojo::UrgentMessageScope` in Chromium to tag messages as having high\n> +  priority. The IPC layer notifies the underlying scheduler upon both receiving\n> +  and processing an urgent message. At present, this attribute only affects\n> +  channel associated messages in the renderer process.\n>  \n>  ## Generated Code For Target Languages\n>  \n> @@ -495,9 +605,9 @@ values. For example if a Mojom declares the enum:\n>  \n>  ``` cpp\n>  enum AdvancedBoolean {\n> -  TRUE = 0,\n> -  FALSE = 1,\n> -  FILE_NOT_FOUND = 2,\n> +  kTrue = 0,\n> +  kFalse = 1,\n> +  kFileNotFound = 2,\n>  };\n>  ```\n>  \n> @@ -550,10 +660,16 @@ See the documentation for\n>  \n>  *** note\n>  **NOTE:** You don't need to worry about versioning if you don't care about\n> -backwards compatibility. Specifically, all parts of Chrome are updated\n> -atomically today and there is not yet any possibility of any two Chrome\n> -processes communicating with two different versions of any given Mojom\n> -interface.\n> +backwards compatibility. Today, all parts of the Chrome browser are\n> +updated atomically and there is not yet any possibility of any two\n> +Chrome processes communicating with two different versions of any given Mojom\n> +interface. On Chrome OS, there are several places where versioning is required.\n> +For example,\n> +[ARC++](https://developer.android.com/chrome-os/intro)\n> +uses versioned mojo to send IPC to the Android container.\n> +Likewise, the\n> +[Lacros](/docs/lacros.md)\n> +browser uses versioned mojo to talk to the ash system UI.\n>  ***\n>  \n>  Services extend their interfaces to support new features over time, and clients\n> @@ -593,8 +709,8 @@ struct Employee {\n>  \n>  *** note\n>  **NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be\n> -optional (nullable). See [Primitive Types](#Primitive-Types) for details on\n> -nullable values.\n> +optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for\n> +details on nullable values.\n>  ***\n>  \n>  By default, fields belong to version 0. New fields must be appended to the\n> @@ -624,10 +740,10 @@ the following hard constraints:\n>  * For any given struct or interface, if any field or method explicitly specifies\n>      an ordinal value, all fields or methods must explicitly specify an ordinal\n>      value.\n> -* For an *N*-field struct or *N*-method interface, the set of explicitly\n> -    assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces\n> -    should include placeholder methods to fill the ordinal positions of removed\n> -    methods (for example \"Unused_Message_7@7()\" or \"RemovedMessage@42()\", etc).\n> +* For an *N*-field struct, the set of explicitly assigned ordinal values must be\n> +    limited to the range *[0, N-1]*. Structs should include placeholder fields\n> +    to fill the ordinal positions of removed fields (for example \"Unused_Field\"\n> +    or \"RemovedField\", etc).\n>  \n>  You may reorder fields, but you must ensure that the ordinal values of existing\n>  fields remain unchanged. For example, the following struct remains\n> @@ -652,6 +768,24 @@ There are two dimensions on which an interface can be extended\n>      that the version number is scoped to the whole interface rather than to any\n>      individual parameter list.\n>  \n> +``` cpp\n> +// Old version:\n> +interface HumanResourceDatabase {\n> +  QueryEmployee(uint64 id) => (Employee? employee);\n> +};\n> +\n> +// New version:\n> +interface HumanResourceDatabase {\n> +  QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)\n> +      => (Employee? employee,\n> +          [MinVersion=1] array<uint8>? finger_print);\n> +};\n> +```\n> +\n> +Similar to [versioned structs](#Versioned-Structs), when you pass the parameter\n> +list of a request or response method to a destination using an older version of\n> +an interface, unrecognized fields are silently discarded.\n> +\n>      Please note that adding a response to a message which did not previously\n>      expect a response is a not a backwards-compatible change.\n>  \n> @@ -664,17 +798,12 @@ For example:\n>  ``` cpp\n>  // Old version:\n>  interface HumanResourceDatabase {\n> -  AddEmployee(Employee employee) => (bool success);\n>    QueryEmployee(uint64 id) => (Employee? employee);\n>  };\n>  \n>  // New version:\n>  interface HumanResourceDatabase {\n> -  AddEmployee(Employee employee) => (bool success);\n> -\n> -  QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print)\n> -      => (Employee? employee,\n> -          [MinVersion=1] array<uint8>? finger_print);\n> +  QueryEmployee(uint64 id) => (Employee? employee);\n>  \n>    [MinVersion=1]\n>    AttachFingerPrint(uint64 id, array<uint8> finger_print)\n> @@ -682,10 +811,7 @@ interface HumanResourceDatabase {\n>  };\n>  ```\n>  \n> -Similar to [versioned structs](#Versioned-Structs), when you pass the parameter\n> -list of a request or response method to a destination using an older version of\n> -an interface, unrecognized fields are silently discarded. However, if the method\n> -call itself is not recognized, it is considered a validation error and the\n> +If a method call is not recognized, it is considered a validation error and the\n>  receiver will close its end of the interface pipe. For example, if a client on\n>  version 1 of the above interface sends an `AttachFingerPrint` request to an\n>  implementation of version 0, the client will be disconnected.\n> @@ -712,8 +838,8 @@ If you want an enum to be extensible in the future, you can apply the\n>  ``` cpp\n>  [Extensible]\n>  enum Department {\n> -  SALES,\n> -  DEV,\n> +  kSales,\n> +  kDev,\n>  };\n>  ```\n>  \n> @@ -722,9 +848,9 @@ And later you can extend this enum without breaking backwards compatibility:\n>  ``` cpp\n>  [Extensible]\n>  enum Department {\n> -  SALES,\n> -  DEV,\n> -  [MinVersion=1] RESEARCH,\n> +  kSales,\n> +  kDev,\n> +  [MinVersion=1] kResearch,\n>  };\n>  ```\n>  \n> @@ -782,7 +908,7 @@ Statement = ModuleStatement | ImportStatement | Definition\n>  \n>  ModuleStatement = AttributeSection \"module\" Identifier \";\"\n>  ImportStatement = \"import\" StringLiteral \";\"\n> -Definition = Struct Union Interface Enum Const\n> +Definition = Struct Union Interface Enum Feature Const\n>  \n>  AttributeSection = <empty> | \"[\" AttributeList \"]\"\n>  AttributeList = <empty> | NonEmptyAttributeList\n> @@ -809,7 +935,7 @@ InterfaceBody = <empty>\n>                | InterfaceBody Const\n>                | InterfaceBody Enum\n>                | InterfaceBody Method\n> -Method = AttributeSection Name Ordinal \"(\" ParamterList \")\" Response \";\"\n> +Method = AttributeSection Name Ordinal \"(\" ParameterList \")\" Response \";\"\n>  ParameterList = <empty> | NonEmptyParameterList\n>  NonEmptyParameterList = Parameter\n>                        | Parameter \",\" NonEmptyParameterList\n> @@ -847,6 +973,13 @@ EnumValue = AttributeSection Name\n>            | AttributeSection Name \"=\" Integer\n>            | AttributeSection Name \"=\" Identifier\n>  \n> +; Note: `feature` is a weak keyword and can appear as, say, a struct field name.\n> +Feature = AttributeSection \"feature\" Name \"{\" FeatureBody \"}\" \";\"\n> +       | AttributeSection \"feature\" Name \";\"\n> +FeatureBody = <empty>\n> +           | FeatureBody FeatureField\n> +FeatureField = AttributeSection TypeSpec Name Default \";\"\n> +\n>  Const = \"const\" TypeSpec Name \"=\" Constant \";\"\n>  \n>  Constant = Literal | Identifier \";\"\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py b/utils/ipc/mojo/public/tools/bindings/checks/__init__.py\n> new file mode 100644\n> index 000000000000..e69de29bb2d1\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py\n> new file mode 100644\n> index 000000000000..e6e4f2c9392b\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py\n> @@ -0,0 +1,170 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Validate mojo attributes are allowed in Chrome before generation.\"\"\"\n> +\n> +import mojom.generate.check as check\n> +import mojom.generate.module as module\n> +\n> +_COMMON_ATTRIBUTES = {\n> +    'EnableIf',\n> +    'EnableIfNot',\n> +}\n> +\n> +# For struct, union & parameter lists.\n> +_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'MinVersion',\n> +    'RenamedFrom',\n> +}\n> +\n> +# Note: `Default`` goes on the default _value_, not on the enum.\n> +# Note: [Stable] without [Extensible] is not allowed.\n> +_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'Extensible',\n> +    'Native',\n> +    'Stable',\n> +    'RenamedFrom',\n> +    'Uuid',\n> +}\n> +\n> +# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal.\n> +_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'Default',\n> +    'MinVersion',\n> +}\n> +\n> +_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'RenamedFrom',\n> +    'RequireContext',\n> +    'RuntimeFeature',\n> +    'ServiceSandbox',\n> +    'Stable',\n> +    'Uuid',\n> +}\n> +\n> +_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'AllowedContext',\n> +    'MinVersion',\n> +    'NoInterrupt',\n> +    'RuntimeFeature',\n> +    'SupportsUrgent',\n> +    'Sync',\n> +    'UnlimitedSize',\n> +}\n> +\n> +_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'JavaConstantsClassName',\n> +    'JavaPackage',\n> +}\n> +\n> +_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES\n> +\n> +_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'CustomSerializer',\n> +    'JavaClassName',\n> +    'Native',\n> +    'Stable',\n> +    'RenamedFrom',\n> +    'Uuid',\n> +}\n> +\n> +_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES\n> +\n> +_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | {\n> +    'Extensible',\n> +    'Stable',\n> +    'RenamedFrom',\n> +    'Uuid',\n> +}\n> +\n> +_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | {\n> +    'Default',\n> +}\n> +\n> +# TODO(https://crbug.com/1193875) empty this set and remove the allowlist.\n> +_STABLE_ONLY_ALLOWLISTED_ENUMS = {\n> +    'crosapi.mojom.OptionalBool',\n> +    'crosapi.mojom.TriState',\n> +}\n> +\n> +\n> +class Check(check.Check):\n> +  def __init__(self, *args, **kwargs):\n> +    super(Check, self).__init__(*args, **kwargs)\n> +\n> +  def _Respell(self, allowed, attribute):\n> +    for a in allowed:\n> +      if a.lower() == attribute.lower():\n> +        return f\" - Did you mean: {a}?\"\n> +    return \"\"\n> +\n> +  def _CheckAttributes(self, context, allowed, attributes):\n> +    if not attributes:\n> +      return\n> +    for attribute in attributes:\n> +      if not attribute in allowed:\n> +        # Is there a close misspelling?\n> +        hint = self._Respell(allowed, attribute)\n> +        raise check.CheckException(\n> +            self.module,\n> +            f\"attribute {attribute} not allowed on {context}{hint}\")\n> +\n> +  def _CheckEnumAttributes(self, enum):\n> +    if enum.attributes:\n> +      self._CheckAttributes(\"enum\", _ENUM_ATTRIBUTES, enum.attributes)\n> +      if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes:\n> +        full_name = f\"{self.module.mojom_namespace}.{enum.mojom_name}\"\n> +        if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS:\n> +          raise check.CheckException(\n> +              self.module,\n> +              f\"[Extensible] required on [Stable] enum {full_name}\")\n> +    for enumval in enum.fields:\n> +      self._CheckAttributes(\"enum value\", _ENUMVAL_ATTRIBUTES,\n> +                            enumval.attributes)\n> +\n> +  def _CheckInterfaceAttributes(self, interface):\n> +    self._CheckAttributes(\"interface\", _INTERFACE_ATTRIBUTES,\n> +                          interface.attributes)\n> +    for method in interface.methods:\n> +      self._CheckAttributes(\"method\", _METHOD_ATTRIBUTES, method.attributes)\n> +      for param in method.parameters:\n> +        self._CheckAttributes(\"parameter\", _PARAMETER_ATTRIBUTES,\n> +                              param.attributes)\n> +      if method.response_parameters:\n> +        for param in method.response_parameters:\n> +          self._CheckAttributes(\"parameter\", _PARAMETER_ATTRIBUTES,\n> +                                param.attributes)\n> +    for enum in interface.enums:\n> +      self._CheckEnumAttributes(enum)\n> +\n> +  def _CheckModuleAttributes(self):\n> +    self._CheckAttributes(\"module\", _MODULE_ATTRIBUTES, self.module.attributes)\n> +\n> +  def _CheckStructAttributes(self, struct):\n> +    self._CheckAttributes(\"struct\", _STRUCT_ATTRIBUTES, struct.attributes)\n> +    for field in struct.fields:\n> +      self._CheckAttributes(\"struct field\", _STRUCT_FIELD_ATTRIBUTES,\n> +                            field.attributes)\n> +    for enum in struct.enums:\n> +      self._CheckEnumAttributes(enum)\n> +\n> +  def _CheckUnionAttributes(self, union):\n> +    self._CheckAttributes(\"union\", _UNION_ATTRIBUTES, union.attributes)\n> +    for field in union.fields:\n> +      self._CheckAttributes(\"union field\", _UNION_FIELD_ATTRIBUTES,\n> +                            field.attributes)\n> +\n> +  def CheckModule(self):\n> +    \"\"\"Note that duplicate attributes are forbidden at the parse phase.\n> +    We also do not need to look at the types of any parameters, as they will be\n> +    checked where they are defined. Consts do not have attributes so can be\n> +    skipped.\"\"\"\n> +    self._CheckModuleAttributes()\n> +    for interface in self.module.interfaces:\n> +      self._CheckInterfaceAttributes(interface)\n> +    for enum in self.module.enums:\n> +      self._CheckEnumAttributes(enum)\n> +    for struct in self.module.structs:\n> +      self._CheckStructAttributes(struct)\n> +    for union in self.module.unions:\n> +      self._CheckUnionAttributes(union)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py\n> new file mode 100644\n> index 000000000000..f1a50a4ab508\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py\n> @@ -0,0 +1,194 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import unittest\n> +\n> +import mojom.generate.check as check\n> +from mojom_bindings_generator import LoadChecks, _Generate\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class FakeArgs:\n> +  \"\"\"Fakes args to _Generate - intention is to do just enough to run checks\"\"\"\n> +\n> +  def __init__(self, tester, files=None):\n> +    \"\"\" `tester` is MojomParserTestCase for paths.\n> +        `files` will have tester path added.\"\"\"\n> +    self.checks_string = 'attributes'\n> +    self.depth = tester.GetPath('')\n> +    self.filelist = None\n> +    self.filename = [tester.GetPath(x) for x in files]\n> +    self.gen_directories = tester.GetPath('gen')\n> +    self.generators_string = ''\n> +    self.import_directories = []\n> +    self.output_dir = tester.GetPath('out')\n> +    self.scrambled_message_id_salt_paths = None\n> +    self.typemaps = []\n> +    self.variant = 'none'\n> +\n> +\n> +class MojoBindingsCheckTest(MojomParserTestCase):\n> +  def _ParseAndGenerate(self, mojoms):\n> +    self.ParseMojoms(mojoms)\n> +    args = FakeArgs(self, files=mojoms)\n> +    _Generate(args, {})\n> +\n> +  def _testValid(self, filename, content):\n> +    self.WriteFile(filename, content)\n> +    self._ParseAndGenerate([filename])\n> +\n> +  def _testThrows(self, filename, content, regexp):\n> +    mojoms = []\n> +    self.WriteFile(filename, content)\n> +    mojoms.append(filename)\n> +    with self.assertRaisesRegexp(check.CheckException, regexp):\n> +      self._ParseAndGenerate(mojoms)\n> +\n> +  def testLoads(self):\n> +    \"\"\"Validate that the check is registered under the expected name.\"\"\"\n> +    check_modules = LoadChecks('attributes')\n> +    self.assertTrue(check_modules['attributes'])\n> +\n> +  def testNoAnnotations(self):\n> +    # Undecorated mojom should be fine.\n> +    self._testValid(\n> +        \"a.mojom\", \"\"\"\n> +      module a;\n> +      struct Bar { int32 a; };\n> +      enum Hello { kValue };\n> +      union Thingy { Bar b; Hello hi; };\n> +      interface Foo {\n> +        Foo(int32 a, Hello hi, Thingy t) => (Bar b);\n> +      };\n> +    \"\"\")\n> +\n> +  def testValidAnnotations(self):\n> +    # Obviously this is meaningless and won't generate, but it should pass\n> +    # the attribute check's validation.\n> +    self._testValid(\n> +        \"a.mojom\", \"\"\"\n> +      [JavaConstantsClassName=\"FakeClass\",JavaPackage=\"org.chromium.Fake\"]\n> +      module a;\n> +      [Stable, Extensible]\n> +      enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 };\n> +      [Native]\n> +      enum NativeEnum {};\n> +      [Stable,Extensible]\n> +      union Thingy { Bar b; [Default]int32 c; Hello hi; };\n> +\n> +      [Stable,RenamedFrom=\"module.other.Foo\",\n> +       Uuid=\"4C178401-4B07-4C2E-9255-5401A943D0C7\"]\n> +      struct Structure { Hello hi; };\n> +\n> +      [ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable,\n> +       Uuid=\"2F17D7DD-865A-4B1C-9394-9C94E035E82F\"]\n> +      interface Foo {\n> +        [AllowedContext=Hello.kValue]\n> +        Foo@0(int32 a) => (int32 b);\n> +        [MinVersion=2,Sync,UnlimitedSize,NoInterrupt]\n> +        Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c);\n> +      };\n> +\n> +      [RuntimeFeature=test.mojom.FeatureName]\n> +      interface FooFeatureControlled {};\n> +\n> +      interface FooMethodFeatureControlled {\n> +        [RuntimeFeature=test.mojom.FeatureName]\n> +        MethodWithFeature() => (bool c);\n> +      };\n> +    \"\"\")\n> +\n> +  def testWrongModuleStable(self):\n> +    contents = \"\"\"\n> +      // err: module cannot be Stable\n> +      [Stable]\n> +      module a;\n> +      enum Hello { kValue, kValue2, kValue3 };\n> +      enum NativeEnum {};\n> +      struct Structure { Hello hi; };\n> +\n> +      interface Foo {\n> +        Foo(int32 a) => (int32 b);\n> +        Bar(int32 b, Structure? s) => (bool c);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'attribute Stable not allowed on module')\n> +\n> +  def testWrongEnumDefault(self):\n> +    contents = \"\"\"\n> +      module a;\n> +      // err: default should go on EnumValue not Enum.\n> +      [Default=kValue]\n> +      enum Hello { kValue, kValue2, kValue3 };\n> +      enum NativeEnum {};\n> +      struct Structure { Hello hi; };\n> +\n> +      interface Foo {\n> +        Foo(int32 a) => (int32 b);\n> +        Bar(int32 b, Structure? s) => (bool c);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'attribute Default not allowed on enum')\n> +\n> +  def testWrongStructMinVersion(self):\n> +    contents = \"\"\"\n> +      module a;\n> +      enum Hello { kValue, kValue2, kValue3 };\n> +      enum NativeEnum {};\n> +      // err: struct cannot have MinVersion.\n> +      [MinVersion=2]\n> +      struct Structure { Hello hi; };\n> +\n> +      interface Foo {\n> +        Foo(int32 a) => (int32 b);\n> +        Bar(int32 b, Structure? s) => (bool c);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'attribute MinVersion not allowed on struct')\n> +\n> +  def testWrongMethodRequireContext(self):\n> +    contents = \"\"\"\n> +      module a;\n> +      enum Hello { kValue, kValue2, kValue3 };\n> +      enum NativeEnum {};\n> +      struct Structure { Hello hi; };\n> +\n> +      interface Foo {\n> +        // err: RequireContext is for interfaces.\n> +        [RequireContext=Hello.kValue]\n> +        Foo(int32 a) => (int32 b);\n> +        Bar(int32 b, Structure? s) => (bool c);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'RequireContext not allowed on method')\n> +\n> +  def testWrongMethodRequireContext(self):\n> +    # crbug.com/1230122\n> +    contents = \"\"\"\n> +      module a;\n> +      interface Foo {\n> +        // err: sync not Sync.\n> +        [sync]\n> +        Foo(int32 a) => (int32 b);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'attribute sync not allowed.*Did you mean: Sync')\n> +\n> +  def testStableExtensibleEnum(self):\n> +    # crbug.com/1193875\n> +    contents = \"\"\"\n> +      module a;\n> +      [Stable]\n> +      enum Foo {\n> +        kDefaultVal,\n> +        kOtherVal = 2,\n> +      };\n> +    \"\"\"\n> +    self._testThrows('a.mojom', contents,\n> +                     'Extensible.*?required.*?Stable.*?enum')\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py\n> new file mode 100644\n> index 000000000000..702d41c30fc1\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py\n> @@ -0,0 +1,34 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Ensure no duplicate type definitions before generation.\"\"\"\n> +\n> +import mojom.generate.check as check\n> +import mojom.generate.module as module\n> +\n> +\n> +class Check(check.Check):\n> +  def __init__(self, *args, **kwargs):\n> +    super(Check, self).__init__(*args, **kwargs)\n> +\n> +  def CheckModule(self):\n> +    kinds = dict()\n> +    for module in self.module.imports:\n> +      for kind in module.enums + module.structs + module.unions:\n> +        kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'\n> +        if kind_name in kinds:\n> +          previous_module = kinds[kind_name]\n> +          if previous_module.path != module.path:\n> +            raise check.CheckException(\n> +                self.module, f\"multiple-definition for type {kind_name}\" +\n> +                f\"(defined in both {previous_module} and {module})\")\n> +        kinds[kind_name] = kind.module\n> +\n> +    for kind in self.module.enums + self.module.structs + self.module.unions:\n> +      kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'\n> +      if kind_name in kinds:\n> +        previous_module = kinds[kind_name]\n> +        raise check.CheckException(\n> +            self.module, f\"multiple-definition for type {kind_name}\" +\n> +            f\"(previous definition in {previous_module})\")\n> +    return True\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py\n> new file mode 100644\n> index 000000000000..07f51a64feaf\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check.py\n> @@ -0,0 +1,62 @@\n> +# Copyright 2023 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Validate mojo runtime feature guarded interfaces are nullable.\"\"\"\n> +\n> +import mojom.generate.check as check\n> +import mojom.generate.module as module\n> +\n> +\n> +class Check(check.Check):\n> +  def __init__(self, *args, **kwargs):\n> +    super(Check, self).__init__(*args, **kwargs)\n> +\n> +  # `param` is an Interface of some sort.\n> +  def _CheckNonNullableFeatureGuardedInterface(self, kind):\n> +    # Only need to validate interface if it has a RuntimeFeature\n> +    if not kind.kind.runtime_feature:\n> +      return\n> +    # Nullable (optional) is ok as the interface expects they might not be sent.\n> +    if kind.is_nullable:\n> +      return\n> +    interface = kind.kind.mojom_name\n> +    raise check.CheckException(\n> +        self.module,\n> +        f\"interface {interface} has a RuntimeFeature but is not nullable\")\n> +\n> +  # `param` can be a lot of things so check if it is a remote/receiver.\n> +  # Array/Map must be recursed into.\n> +  def _CheckFieldOrParam(self, kind):\n> +    if module.IsAnyInterfaceKind(kind):\n> +      self._CheckNonNullableFeatureGuardedInterface(kind)\n> +    if module.IsArrayKind(kind):\n> +      self._CheckFieldOrParam(kind.kind)\n> +    if module.IsMapKind(kind):\n> +      self._CheckFieldOrParam(kind.key_kind)\n> +      self._CheckFieldOrParam(kind.value_kind)\n> +\n> +  def _CheckInterfaceFeatures(self, interface):\n> +    for method in interface.methods:\n> +      for param in method.parameters:\n> +        self._CheckFieldOrParam(param.kind)\n> +      if method.response_parameters:\n> +        for param in method.response_parameters:\n> +          self._CheckFieldOrParam(param.kind)\n> +\n> +  def _CheckStructFeatures(self, struct):\n> +    for field in struct.fields:\n> +      self._CheckFieldOrParam(field.kind)\n> +\n> +  def _CheckUnionFeatures(self, union):\n> +    for field in union.fields:\n> +      self._CheckFieldOrParam(field.kind)\n> +\n> +  def CheckModule(self):\n> +    \"\"\"Validate that any runtime feature guarded interfaces that might be passed\n> +    over mojo are nullable.\"\"\"\n> +    for interface in self.module.interfaces:\n> +      self._CheckInterfaceFeatures(interface)\n> +    for struct in self.module.structs:\n> +      self._CheckStructFeatures(struct)\n> +    for union in self.module.unions:\n> +      self._CheckUnionFeatures(union)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py\n> new file mode 100644\n> index 000000000000..e96152fdd0ef\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_interface_feature_check_unittest.py\n> @@ -0,0 +1,173 @@\n> +# Copyright 2023 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import unittest\n> +\n> +import mojom.generate.check as check\n> +from mojom_bindings_generator import LoadChecks, _Generate\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class FakeArgs:\n> +  \"\"\"Fakes args to _Generate - intention is to do just enough to run checks\"\"\"\n> +  def __init__(self, tester, files=None):\n> +    \"\"\" `tester` is MojomParserTestCase for paths.\n> +        `files` will have tester path added.\"\"\"\n> +    self.checks_string = 'features'\n> +    self.depth = tester.GetPath('')\n> +    self.filelist = None\n> +    self.filename = [tester.GetPath(x) for x in files]\n> +    self.gen_directories = tester.GetPath('gen')\n> +    self.generators_string = ''\n> +    self.import_directories = []\n> +    self.output_dir = tester.GetPath('out')\n> +    self.scrambled_message_id_salt_paths = None\n> +    self.typemaps = []\n> +    self.variant = 'none'\n> +\n> +\n> +class MojoBindingsCheckTest(MojomParserTestCase):\n> +  def _ParseAndGenerate(self, mojoms):\n> +    self.ParseMojoms(mojoms)\n> +    args = FakeArgs(self, files=mojoms)\n> +    _Generate(args, {})\n> +\n> +  def assertValid(self, filename, content):\n> +    self.WriteFile(filename, content)\n> +    self._ParseAndGenerate([filename])\n> +\n> +  def assertThrows(self, filename, content, regexp):\n> +    mojoms = []\n> +    self.WriteFile(filename, content)\n> +    mojoms.append(filename)\n> +    with self.assertRaisesRegexp(check.CheckException, regexp):\n> +      self._ParseAndGenerate(mojoms)\n> +\n> +  def testLoads(self):\n> +    \"\"\"Validate that the check is registered under the expected name.\"\"\"\n> +    check_modules = LoadChecks('features')\n> +    self.assertTrue(check_modules['features'])\n> +\n> +  def testNullableOk(self):\n> +    self.assertValid(\n> +        \"a.mojom\", \"\"\"\n> +          module a;\n> +          // Scaffolding.\n> +          feature kFeature {\n> +            const string name = \"Hello\";\n> +            const bool enabled_state = false;\n> +          };\n> +          [RuntimeFeature=kFeature]\n> +          interface Guarded {\n> +          };\n> +\n> +          // Unguarded interfaces should be ok everywhere.\n> +          interface NotGuarded { };\n> +\n> +          // Optional (nullable) interfaces should be ok everywhere:\n> +          struct Bar {\n> +            pending_remote<Guarded>? remote;\n> +            pending_receiver<Guarded>? receiver;\n> +          };\n> +          union Thingy {\n> +            pending_remote<Guarded>? remote;\n> +            pending_receiver<Guarded>? receiver;\n> +          };\n> +          interface Foo {\n> +            Foo(\n> +              pending_remote<Guarded>? remote,\n> +              pending_receiver<Guarded>? receiver,\n> +              pending_associated_remote<Guarded>? a_remote,\n> +              pending_associated_receiver<Guarded>? a_receiver,\n> +              // Unguarded interfaces do not have to be nullable.\n> +              pending_remote<NotGuarded> remote,\n> +              pending_receiver<NotGuarded> receiver,\n> +              pending_associated_remote<NotGuarded> a_remote,\n> +              pending_associated_receiver<NotGuarded> a_receiver\n> +            ) => (\n> +              pending_remote<Guarded>? remote,\n> +              pending_receiver<Guarded>? receiver\n> +            );\n> +            Bar(array<pending_remote<Guarded>?> remote)\n> +              => (map<string, pending_receiver<Guarded>?> a);\n> +          };\n> +    \"\"\")\n> +\n> +  def testMethodParamsMustBeNullable(self):\n> +    prelude = \"\"\"\n> +      module a;\n> +      // Scaffolding.\n> +      feature kFeature {\n> +        const string name = \"Hello\";\n> +        const bool enabled_state = false;\n> +      };\n> +      [RuntimeFeature=kFeature]\n> +      interface Guarded { };\n> +    \"\"\"\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(pending_remote<Guarded> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(bool foo) => (pending_receiver<Guarded> a);\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(pending_receiver<Guarded> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(pending_associated_remote<Guarded> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(pending_associated_receiver<Guarded> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(array<pending_associated_receiver<Guarded>> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          interface Trial {\n> +            Method(map<string, pending_associated_receiver<Guarded>> a) => ();\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +\n> +  def testStructUnionMembersMustBeNullable(self):\n> +    prelude = \"\"\"\n> +      module a;\n> +      // Scaffolding.\n> +      feature kFeature {\n> +        const string name = \"Hello\";\n> +        const bool enabled_state = false;\n> +      };\n> +      [RuntimeFeature=kFeature]\n> +      interface Guarded { };\n> +    \"\"\"\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          struct Trial {\n> +            pending_remote<Guarded> a;\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> +    self.assertThrows(\n> +        'a.mojom', prelude + \"\"\"\n> +          union Trial {\n> +            pending_remote<Guarded> a;\n> +          };\n> +                     \"\"\", 'interface Guarded has a RuntimeFeature')\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py\n> new file mode 100644\n> index 000000000000..d570e26cea68\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py\n> @@ -0,0 +1,102 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Validate RequireContext and AllowedContext annotations before generation.\"\"\"\n> +\n> +import mojom.generate.check as check\n> +import mojom.generate.module as module\n> +\n> +\n> +class Check(check.Check):\n> +  def __init__(self, *args, **kwargs):\n> +    self.kind_to_interfaces = dict()\n> +    super(Check, self).__init__(*args, **kwargs)\n> +\n> +  def _IsPassedInterface(self, candidate):\n> +    if isinstance(\n> +        candidate.kind,\n> +        (module.PendingReceiver, module.PendingRemote,\n> +         module.PendingAssociatedReceiver, module.PendingAssociatedRemote)):\n> +      return True\n> +    return False\n> +\n> +  def _CheckInterface(self, method, param):\n> +    # |param| is a pending_x<Interface> so need .kind.kind to get Interface.\n> +    interface = param.kind.kind\n> +    if interface.require_context:\n> +      if method.allowed_context is None:\n> +        raise check.CheckException(\n> +            self.module, \"method `{}` has parameter `{}` which passes interface\"\n> +            \" `{}` that requires an AllowedContext annotation but none exists.\".\n> +            format(\n> +                method.mojom_name,\n> +                param.mojom_name,\n> +                interface.mojom_name,\n> +            ))\n> +      # If a string was provided, or if an enum was not imported, this will\n> +      # be a string and we cannot validate that it is in range.\n> +      if not isinstance(method.allowed_context, module.EnumValue):\n> +        raise check.CheckException(\n> +            self.module,\n> +            \"method `{}` has AllowedContext={} which is not a valid enum value.\"\n> +            .format(method.mojom_name, method.allowed_context))\n> +      # EnumValue must be from the same enum to be compared.\n> +      if interface.require_context.enum != method.allowed_context.enum:\n> +        raise check.CheckException(\n> +            self.module, \"method `{}` has parameter `{}` which passes interface\"\n> +            \" `{}` that requires AllowedContext={} but one of kind `{}` was \"\n> +            \"provided.\".format(\n> +                method.mojom_name,\n> +                param.mojom_name,\n> +                interface.mojom_name,\n> +                interface.require_context.enum,\n> +                method.allowed_context.enum,\n> +            ))\n> +      # RestrictContext enums have most privileged field first (lowest value).\n> +      interface_value = interface.require_context.field.numeric_value\n> +      method_value = method.allowed_context.field.numeric_value\n> +      if interface_value < method_value:\n> +        raise check.CheckException(\n> +            self.module, \"RequireContext={} > AllowedContext={} for method \"\n> +            \"`{}` which passes interface `{}`.\".format(\n> +                interface.require_context.GetSpec(),\n> +                method.allowed_context.GetSpec(), method.mojom_name,\n> +                interface.mojom_name))\n> +      return True\n> +\n> +  def _GatherReferencedInterfaces(self, field):\n> +    key = field.kind.spec\n> +    # structs/unions can nest themselves so we need to bookkeep.\n> +    if not key in self.kind_to_interfaces:\n> +      # Might reference ourselves so have to create the list first.\n> +      self.kind_to_interfaces[key] = set()\n> +      for param in field.kind.fields:\n> +        if self._IsPassedInterface(param):\n> +          self.kind_to_interfaces[key].add(param)\n> +        elif isinstance(param.kind, (module.Struct, module.Union)):\n> +          for iface in self._GatherReferencedInterfaces(param):\n> +            self.kind_to_interfaces[key].add(iface)\n> +    return self.kind_to_interfaces[key]\n> +\n> +  def _CheckParams(self, method, params):\n> +    # Note: we have to repeat _CheckParams for each method as each might have\n> +    # different AllowedContext= attributes. We cannot memoize this function,\n> +    # but can do so for gathering referenced interfaces as their RequireContext\n> +    # attributes do not change.\n> +    for param in params:\n> +      if self._IsPassedInterface(param):\n> +        self._CheckInterface(method, param)\n> +      elif isinstance(param.kind, (module.Struct, module.Union)):\n> +        for interface in self._GatherReferencedInterfaces(param):\n> +          self._CheckInterface(method, interface)\n> +\n> +  def _CheckMethod(self, method):\n> +    if method.parameters:\n> +      self._CheckParams(method, method.parameters)\n> +    if method.response_parameters:\n> +      self._CheckParams(method, method.response_parameters)\n> +\n> +  def CheckModule(self):\n> +    for interface in self.module.interfaces:\n> +      for method in interface.methods:\n> +        self._CheckMethod(method)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py\n> new file mode 100644\n> index 000000000000..a6cd71e2f1f1\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py\n> @@ -0,0 +1,254 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +import unittest\n> +\n> +import mojom.generate.check as check\n> +from mojom_bindings_generator import LoadChecks, _Generate\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +# Mojoms that we will use in multiple tests.\n> +basic_mojoms = {\n> +    'level.mojom':\n> +    \"\"\"\n> +  module level;\n> +  enum Level {\n> +    kHighest,\n> +    kMiddle,\n> +    kLowest,\n> +  };\n> +  \"\"\",\n> +    'interfaces.mojom':\n> +    \"\"\"\n> +  module interfaces;\n> +  import \"level.mojom\";\n> +  struct Foo {int32 bar;};\n> +  [RequireContext=level.Level.kHighest]\n> +  interface High {\n> +    DoFoo(Foo foo);\n> +  };\n> +  [RequireContext=level.Level.kMiddle]\n> +  interface Mid {\n> +    DoFoo(Foo foo);\n> +  };\n> +  [RequireContext=level.Level.kLowest]\n> +  interface Low {\n> +    DoFoo(Foo foo);\n> +  };\n> +  \"\"\"\n> +}\n> +\n> +\n> +class FakeArgs:\n> +  \"\"\"Fakes args to _Generate - intention is to do just enough to run checks\"\"\"\n> +\n> +  def __init__(self, tester, files=None):\n> +    \"\"\" `tester` is MojomParserTestCase for paths.\n> +        `files` will have tester path added.\"\"\"\n> +    self.checks_string = 'restrictions'\n> +    self.depth = tester.GetPath('')\n> +    self.filelist = None\n> +    self.filename = [tester.GetPath(x) for x in files]\n> +    self.gen_directories = tester.GetPath('gen')\n> +    self.generators_string = ''\n> +    self.import_directories = []\n> +    self.output_dir = tester.GetPath('out')\n> +    self.scrambled_message_id_salt_paths = None\n> +    self.typemaps = []\n> +    self.variant = 'none'\n> +\n> +\n> +class MojoBindingsCheckTest(MojomParserTestCase):\n> +  def _WriteBasicMojoms(self):\n> +    for filename, contents in basic_mojoms.items():\n> +      self.WriteFile(filename, contents)\n> +    return list(basic_mojoms.keys())\n> +\n> +  def _ParseAndGenerate(self, mojoms):\n> +    self.ParseMojoms(mojoms)\n> +    args = FakeArgs(self, files=mojoms)\n> +    _Generate(args, {})\n> +\n> +  def testLoads(self):\n> +    \"\"\"Validate that the check is registered under the expected name.\"\"\"\n> +    check_modules = LoadChecks('restrictions')\n> +    self.assertTrue(check_modules['restrictions'])\n> +\n> +  def testValidAnnotations(self):\n> +    mojoms = self._WriteBasicMojoms()\n> +\n> +    a = 'a.mojom'\n> +    self.WriteFile(\n> +        a, \"\"\"\n> +      module a;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +\n> +      interface PassesHigh {\n> +        [AllowedContext=level.Level.kHighest]\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +      interface PassesMedium {\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMedium(pending_receiver<interfaces.Mid> hi);\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMediumRem(pending_remote<interfaces.Mid> hi);\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);\n> +      };\n> +      interface PassesLow {\n> +        [AllowedContext=level.Level.kLowest]\n> +        DoLow(pending_receiver<interfaces.Low> hi);\n> +      };\n> +\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      struct Two { One one; };\n> +      interface PassesNestedHigh {\n> +        [AllowedContext=level.Level.kHighest]\n> +        DoNestedHigh(Two two);\n> +      };\n> +\n> +      // Allowed as PassesHigh is not itself restricted.\n> +      interface PassesPassesHigh {\n> +        DoPass(pending_receiver<PassesHigh> hiho);\n> +      };\n> +    \"\"\")\n> +    mojoms.append(a)\n> +    self._ParseAndGenerate(mojoms)\n> +\n> +  def _testThrows(self, filename, content, regexp):\n> +    mojoms = self._WriteBasicMojoms()\n> +    self.WriteFile(filename, content)\n> +    mojoms.append(filename)\n> +    with self.assertRaisesRegexp(check.CheckException, regexp):\n> +      self._ParseAndGenerate(mojoms)\n> +\n> +  def testMissingAnnotation(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +\n> +      interface PassesHigh {\n> +        // err: missing annotation.\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')\n> +\n> +  def testAllowTooLow(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +\n> +      interface PassesHigh {\n> +        // err: level is worse than required.\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')\n> +\n> +  def testWrongEnumInAllow(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      enum Blah {\n> +        kZero,\n> +      };\n> +      interface PassesHigh {\n> +        // err: different enums.\n> +        [AllowedContext=Blah.kZero]\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'but one of kind')\n> +\n> +  def testNotAnEnumInAllow(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      interface PassesHigh {\n> +        // err: not an enum.\n> +        [AllowedContext=doopdedoo.mojom.kWhatever]\n> +        DoHigh(pending_receiver<interfaces.High> hi);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'not a valid enum value')\n> +\n> +  def testMissingAllowedForNestedStructs(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      struct Two { One one; };\n> +      interface PassesNestedHigh {\n> +        // err: missing annotation.\n> +        DoNestedHigh(Two two);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')\n> +\n> +  def testMissingAllowedForNestedUnions(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      struct Two { One one; };\n> +      union Three {One one; Two two; };\n> +      interface PassesNestedHigh {\n> +        // err: missing annotation.\n> +        DoNestedHigh(Three three);\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')\n> +\n> +  def testMultipleInterfacesThrows(self):\n> +    contents = \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      interface PassesMultipleInterfaces {\n> +        [AllowedContext=level.Level.kMiddle]\n> +        DoMultiple(\n> +          pending_remote<interfaces.Mid> mid,\n> +          pending_receiver<interfaces.High> hi,\n> +          One one\n> +        );\n> +      };\n> +    \"\"\"\n> +    self._testThrows('b.mojom', contents,\n> +                     'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')\n> +\n> +  def testMultipleInterfacesAllowed(self):\n> +    \"\"\"Multiple interfaces can be passed, all satisfy the level.\"\"\"\n> +    mojoms = self._WriteBasicMojoms()\n> +\n> +    b = \"b.mojom\"\n> +    self.WriteFile(\n> +        b, \"\"\"\n> +      module b;\n> +      import \"level.mojom\";\n> +      import \"interfaces.mojom\";\n> +      struct One { pending_receiver<interfaces.High> hi; };\n> +      interface PassesMultipleInterfaces {\n> +        [AllowedContext=level.Level.kHighest]\n> +        DoMultiple(\n> +          pending_receiver<interfaces.High> hi,\n> +          pending_remote<interfaces.Mid> mid,\n> +          One one\n> +        );\n> +      };\n> +    \"\"\")\n> +    mojoms.append(b)\n> +    self._ParseAndGenerate(mojoms)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n> deleted file mode 100644\n> index d8a138744856..000000000000\n> --- a/utils/ipc/mojo/public/tools/bindings/chromium_bindings_configuration.gni\n> +++ /dev/null\n> @@ -1,51 +0,0 @@\n> -# Copyright 2016 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\n> -_typemap_imports = [\n> -  \"//chrome/chrome_cleaner/mojom/typemaps/typemaps.gni\",\n> -  \"//chrome/common/importer/typemaps.gni\",\n> -  \"//chrome/common/media_router/mojom/typemaps.gni\",\n> -  \"//chrome/typemaps.gni\",\n> -  \"//chromecast/typemaps.gni\",\n> -  \"//chromeos/typemaps.gni\",\n> -  \"//chromeos/components/multidevice/mojom/typemaps.gni\",\n> -  \"//chromeos/services/cros_healthd/public/mojom/typemaps.gni\",\n> -  \"//chromeos/services/device_sync/public/mojom/typemaps.gni\",\n> -  \"//chromeos/services/network_config/public/mojom/typemaps.gni\",\n> -  \"//chromeos/services/secure_channel/public/mojom/typemaps.gni\",\n> -  \"//components/arc/mojom/typemaps.gni\",\n> -  \"//components/chromeos_camera/common/typemaps.gni\",\n> -  \"//components/services/storage/public/cpp/filesystem/typemaps.gni\",\n> -  \"//components/sync/mojom/typemaps.gni\",\n> -  \"//components/typemaps.gni\",\n> -  \"//content/browser/typemaps.gni\",\n> -  \"//content/public/common/typemaps.gni\",\n> -  \"//sandbox/mac/mojom/typemaps.gni\",\n> -  \"//services/media_session/public/cpp/typemaps.gni\",\n> -  \"//services/proxy_resolver/public/cpp/typemaps.gni\",\n> -  \"//services/resource_coordinator/public/cpp/typemaps.gni\",\n> -  \"//services/service_manager/public/cpp/typemaps.gni\",\n> -  \"//services/tracing/public/mojom/typemaps.gni\",\n> -]\n> -\n> -_typemaps = []\n> -foreach(typemap_import, _typemap_imports) {\n> -  # Avoid reassignment error by assigning to empty scope first.\n> -  _imported = {\n> -  }\n> -  _imported = read_file(typemap_import, \"scope\")\n> -  _typemaps += _imported.typemaps\n> -}\n> -\n> -typemaps = []\n> -foreach(typemap, _typemaps) {\n> -  typemaps += [\n> -    {\n> -      filename = typemap\n> -      config = read_file(typemap, \"scope\")\n> -    },\n> -  ]\n> -}\n> -\n> -component_macro_suffix = \"\"\n> diff --git a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py b/utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n> deleted file mode 100644\n> index a978901bc033..000000000000\n> --- a/utils/ipc/mojo/public/tools/bindings/compile_typescript.py\n> +++ /dev/null\n> @@ -1,27 +0,0 @@\n> -# Copyright 2019 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\n> -import os\n> -import sys\n> -import argparse\n> -\n> -_HERE_PATH = os.path.dirname(__file__)\n> -_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))\n> -\n> -sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))\n> -import node\n> -import node_modules\n> -\n> -def main(argv):\n> -  parser = argparse.ArgumentParser()\n> -  parser.add_argument('--tsconfig_path', required=True)\n> -  args = parser.parse_args(argv)\n> -\n> -  result = node.RunNode([node_modules.PathToTypescript()] +\n> -                        ['--project', args.tsconfig_path])\n> -  if len(result) != 0:\n> -    raise RuntimeError('Failed to compile Typescript: \\n%s' % result)\n> -\n> -if __name__ == '__main__':\n> -  main(sys.argv[1:])\n> diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n> index 48bc66fd0f6e..4dd26d4aea8b 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2019 The Chromium Authors. All rights reserved.\n> +# Copyright 2019 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  #\n> @@ -15,6 +15,7 @@\n>  from __future__ import print_function\n>  \n>  import optparse\n> +import sys\n>  \n>  \n>  def Concatenate(filenames):\n> @@ -47,7 +48,7 @@ def main():\n>    parser.set_usage(\"\"\"Concatenate several files into one.\n>        Equivalent to: cat file1 ... > target.\"\"\")\n>    (_options, args) = parser.parse_args()\n> -  exit(0 if Concatenate(args) else 1)\n> +  sys.exit(0 if Concatenate(args) else 1)\n>  \n>  \n>  if __name__ == \"__main__\":\n> diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n> index be8985cedc99..7d56c9f962c3 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/concatenate_and_replace_closure_exports.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2018 The Chromium Authors. All rights reserved.\n> +# Copyright 2018 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -20,6 +20,7 @@ from __future__ import print_function\n>  \n>  import optparse\n>  import re\n> +import sys\n>  \n>  \n>  _MOJO_INTERNAL_MODULE_NAME = \"mojo.internal\"\n> @@ -31,10 +32,10 @@ def FilterLine(filename, line, output):\n>      return\n>  \n>    if line.startswith(\"goog.provide\"):\n> -    match = re.match(\"goog.provide\\('([^']+)'\\);\", line)\n> +    match = re.match(r\"goog.provide\\('([^']+)'\\);\", line)\n>      if not match:\n>        print(\"Invalid goog.provide line in %s:\\n%s\" % (filename, line))\n> -      exit(1)\n> +      sys.exit(1)\n>  \n>      module_name = match.group(1)\n>      if module_name == _MOJO_INTERNAL_MODULE_NAME:\n> @@ -67,7 +68,8 @@ def main():\n>      Concatenate several files into one, stripping Closure provide and\n>      require directives along the way.\"\"\")\n>    (_, args) = parser.parse_args()\n> -  exit(0 if ConcatenateAndReplaceExports(args) else 1)\n> +  sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)\n> +\n>  \n>  if __name__ == \"__main__\":\n>    main()\n> diff --git a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py b/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n> deleted file mode 100755\n> index 7ac4af5faef1..000000000000\n> --- a/utils/ipc/mojo/public/tools/bindings/format_typemap_generator_args.py\n> +++ /dev/null\n> @@ -1,36 +0,0 @@\n> -#!/usr/bin/env python\n> -# Copyright 2016 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\n> -from __future__ import print_function\n> -\n> -import sys\n> -\n> -# This utility converts mojom dependencies into their corresponding typemap\n> -# paths and formats them to be consumed by generate_type_mappings.py.\n> -\n> -\n> -def FormatTypemap(typemap_filename):\n> -  # A simple typemap is valid Python with a minor alteration.\n> -  with open(typemap_filename) as f:\n> -    typemap_content = f.read().replace('=\\n', '=')\n> -  typemap = {}\n> -  exec typemap_content in typemap\n> -\n> -  for header in typemap.get('public_headers', []):\n> -    yield 'public_headers=%s' % header\n> -  for header in typemap.get('traits_headers', []):\n> -    yield 'traits_headers=%s' % header\n> -  for header in typemap.get('type_mappings', []):\n> -    yield 'type_mappings=%s' % header\n> -\n> -\n> -def main():\n> -  typemaps = sys.argv[1:]\n> -  print(' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap))\n> -                 for typemap in typemaps))\n> -\n> -\n> -if __name__ == '__main__':\n> -  sys.exit(main())\n> diff --git a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n> index 8b78d0924185..c6daff034f7c 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2017 The Chromium Authors. All rights reserved.\n> +# Copyright 2017 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Generates a list of all files in a directory.\n> diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n> index a009664945fe..4a53e2bffe1e 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2016 The Chromium Authors. All rights reserved.\n> +# Copyright 2016 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Generates a JSON typemap from its command-line arguments and dependencies.\n> @@ -82,10 +82,12 @@ def LoadCppTypemapConfig(path):\n>        for entry in config['types']:\n>          configs[entry['mojom']] = {\n>              'typename': entry['cpp'],\n> +            'forward_declaration': entry.get('forward_declaration', None),\n>              'public_headers': config.get('traits_headers', []),\n>              'traits_headers': config.get('traits_private_headers', []),\n>              'copyable_pass_by_value': entry.get('copyable_pass_by_value',\n>                                                  False),\n> +            'default_constructible': entry.get('default_constructible', True),\n>              'force_serialize': entry.get('force_serialize', False),\n>              'hashable': entry.get('hashable', False),\n>              'move_only': entry.get('move_only', False),\n> diff --git a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py\n> new file mode 100755\n> index 000000000000..cefee7a401a6\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py\n> @@ -0,0 +1,47 @@\n> +#!/usr/bin/env python3\n> +# Copyright 2023 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +#\n> +# This utility minifies JS files with terser.\n> +#\n> +# Instance of 'node' has no 'RunNode' member (no-member)\n> +# pylint: disable=no-member\n> +\n> +import argparse\n> +import os\n> +import sys\n> +\n> +_HERE_PATH = os.path.dirname(__file__)\n> +_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))\n> +_CWD = os.getcwd()\n> +sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))\n> +import node\n> +import node_modules\n> +\n> +\n> +def MinifyFile(input_file, output_file):\n> +  node.RunNode([\n> +      node_modules.PathToTerser(), input_file, '--mangle', '--compress',\n> +      '--comments', 'false', '--output', output_file\n> +  ])\n> +\n> +\n> +def main(argv):\n> +  parser = argparse.ArgumentParser()\n> +  parser.add_argument('--input', required=True)\n> +  parser.add_argument('--output', required=True)\n> +  args = parser.parse_args(argv)\n> +\n> +  # Delete the output file if it already exists. It may be a sym link to the\n> +  # input, because in non-optimized/pre-Terser builds the input file is copied\n> +  # to the output location with gn copy().\n> +  out_path = os.path.join(_CWD, args.output)\n> +  if (os.path.exists(out_path)):\n> +    os.remove(out_path)\n> +\n> +  MinifyFile(os.path.join(_CWD, args.input), out_path)\n> +\n> +\n> +if __name__ == '__main__':\n> +  main(sys.argv[1:])\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/ipc/mojo/public/tools/bindings/mojom.gni\n> index fe2a1da3750a..3f6e54e0a315 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/mojom.gni\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni\n> @@ -1,25 +1,28 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import(\"//build/config/python.gni\")\n>  import(\"//third_party/closure_compiler/closure_args.gni\")\n>  import(\"//third_party/closure_compiler/compile_js.gni\")\n>  import(\"//third_party/protobuf/proto_library.gni\")\n> +import(\"//ui/webui/resources/tools/generate_grd.gni\")\n>  import(\"//ui/webui/webui_features.gni\")\n>  \n> +import(\"//build/config/cast.gni\")\n> +\n>  # TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're\n>  # used to conditionally enable message ID scrambling in a way which is\n>  # consistent across toolchains and which is affected by branded vs non-branded\n>  # Chrome builds. Ideally we could create some generic knobs here that could be\n>  # flipped elsewhere though.\n>  import(\"//build/config/chrome_build.gni\")\n> -import(\"//build/config/chromecast_build.gni\")\n>  import(\"//build/config/chromeos/ui_mode.gni\")\n> +import(\"//build/config/features.gni\")\n>  import(\"//build/config/nacl/config.gni\")\n>  import(\"//build/toolchain/kythe.gni\")\n>  import(\"//components/nacl/features.gni\")\n>  import(\"//third_party/jinja2/jinja2.gni\")\n> +import(\"//third_party/ply/ply.gni\")\n>  import(\"//tools/ipc_fuzzer/ipc_fuzzer.gni\")\n>  declare_args() {\n>    # Indicates whether typemapping should be supported in this build\n> @@ -34,21 +37,30 @@ declare_args() {\n>  \n>    # Controls message ID scrambling behavior. If |true|, message IDs are\n>    # scrambled (i.e. randomized based on the contents of //chrome/VERSION) on\n> -  # non-Chrome OS desktop platforms. Set to |false| to disable message ID\n> -  # scrambling on all platforms.\n> -  enable_mojom_message_id_scrambling = true\n> -\n> -  # Enables Closure compilation of generated JS lite bindings. In environments\n> -  # where compilation is supported, any mojom target \"foo\" will also have a\n> -  # corresponding \"foo_js_library_for_compile\" target generated.\n> -  enable_mojom_closure_compile = enable_js_type_check && optimize_webui\n> -\n> -  # Enables generating Typescript bindings and compiling them to JS bindings.\n> -  enable_typescript_bindings = false\n> +  # non-Chrome OS desktop platforms. Enabled on official builds by default.\n> +  # Set to |true| to enable message ID scrambling on a specific build.\n> +  # See also `enable_scrambled_message_ids` below for more details.\n> +  enable_mojom_message_id_scrambling = is_official_build\n>  \n>    # Enables generating javascript fuzzing-related code and the bindings for the\n>    # MojoLPM fuzzer targets. Off by default.\n>    enable_mojom_fuzzer = false\n> +\n> +  # Enables Closure compilation of generated JS lite bindings. In environments\n> +  # where compilation is supported, any mojom target \"foo\" will also have a\n> +  # corresponding \"foo_js_library_for_compile\" target generated.\n> +  if (is_chromeos_ash) {\n> +    enable_mojom_closure_compile = enable_js_type_check && optimize_webui\n> +  }\n> +}\n> +\n> +# Closure libraries are needed for mojom_closure_compile, and when\n> +# js_type_check is enabled on Ash.\n> +if (is_chromeos_ash) {\n> +  generate_mojom_closure_libraries =\n> +      enable_mojom_closure_compile || enable_js_type_check\n> +} else {\n> +  generate_mojom_closure_libraries = false\n>  }\n>  \n>  # NOTE: We would like to avoid scrambling message IDs where it doesn't add\n> @@ -69,9 +81,8 @@ declare_args() {\n>  # lacros-chrome switches to target_os=\"chromeos\"\n>  enable_scrambled_message_ids =\n>      enable_mojom_message_id_scrambling &&\n> -    (is_mac || is_win ||\n> -     (is_linux && !is_chromeos_ash && !is_chromecast && !is_chromeos_lacros) ||\n> -     ((enable_nacl || is_nacl || is_nacl_nonsfi) &&\n> +    (is_mac || is_win || (is_linux && !is_castos) ||\n> +     ((enable_nacl || is_nacl) &&\n>        (target_os != \"chromeos\" && !chromeos_is_browser_only)))\n>  \n>  _mojom_tools_root = \"//mojo/public/tools\"\n> @@ -80,7 +91,9 @@ mojom_parser_script = \"$_mojom_tools_root/mojom/mojom_parser.py\"\n>  mojom_parser_sources = [\n>    \"$_mojom_library_root/__init__.py\",\n>    \"$_mojom_library_root/error.py\",\n> +  \"$_mojom_library_root/fileutil.py\",\n>    \"$_mojom_library_root/generate/__init__.py\",\n> +  \"$_mojom_library_root/generate/check.py\",\n>    \"$_mojom_library_root/generate/generator.py\",\n>    \"$_mojom_library_root/generate/module.py\",\n>    \"$_mojom_library_root/generate/pack.py\",\n> @@ -88,21 +101,32 @@ mojom_parser_sources = [\n>    \"$_mojom_library_root/generate/translate.py\",\n>    \"$_mojom_library_root/parse/__init__.py\",\n>    \"$_mojom_library_root/parse/ast.py\",\n> +  \"$_mojom_library_root/parse/conditional_features.py\",\n>    \"$_mojom_library_root/parse/lexer.py\",\n>    \"$_mojom_library_root/parse/parser.py\",\n> +  \"//tools/diagnosis/crbug_1001171.py\",\n>  ]\n>  \n>  mojom_generator_root = \"$_mojom_tools_root/bindings\"\n>  mojom_generator_script = \"$mojom_generator_root/mojom_bindings_generator.py\"\n>  mojom_generator_sources =\n>      mojom_parser_sources + [\n> +      \"$mojom_generator_root/checks/__init__.py\",\n> +      \"$mojom_generator_root/checks/mojom_attributes_check.py\",\n> +      \"$mojom_generator_root/checks/mojom_definitions_check.py\",\n> +      \"$mojom_generator_root/checks/mojom_interface_feature_check.py\",\n> +      \"$mojom_generator_root/checks/mojom_restrictions_check.py\",\n> +      \"$mojom_generator_root/generators/__init__.py\",\n>        \"$mojom_generator_root/generators/cpp_util.py\",\n>        \"$mojom_generator_root/generators/mojom_cpp_generator.py\",\n>        \"$mojom_generator_root/generators/mojom_java_generator.py\",\n> -      \"$mojom_generator_root/generators/mojom_mojolpm_generator.py\",\n>        \"$mojom_generator_root/generators/mojom_js_generator.py\",\n> +      \"$mojom_generator_root/generators/mojom_mojolpm_generator.py\",\n>        \"$mojom_generator_root/generators/mojom_ts_generator.py\",\n>        \"$mojom_generator_script\",\n> +      \"//build/action_helpers.py\",\n> +      \"//build/gn_helpers.py\",\n> +      \"//build/zip_helpers.py\",\n>      ]\n>  \n>  if (enable_scrambled_message_ids) {\n> @@ -243,12 +267,16 @@ if (enable_scrambled_message_ids) {\n>  #       |cpp_only| is set to true, it overrides this to prevent generation of\n>  #       Java bindings.\n>  #\n> -#   enable_fuzzing (optional)\n> +#   enable_js_fuzzing (optional)\n> +#       Enables generation of javascript fuzzing sources for the target if the\n> +#       global build arg |enable_mojom_fuzzer| is also set to |true|.\n> +#       Defaults to |true|. If JS fuzzing generation is enabled for a target,\n> +#       the target will always generate JS bindings even if |cpp_only| is set to\n> +#       |true|. See note above.\n> +#\n> +#   enable_mojolpm_fuzzing (optional)\n>  #       Enables generation of fuzzing sources for the target if the global build\n> -#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If\n> -#       fuzzing generation is enabled for a target, the target will always\n> -#       generate JS bindings even if |cpp_only| is set to |true|. See note\n> -#       above.\n> +#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|.\n>  #\n>  #   support_lazy_serialization (optional)\n>  #       If set to |true|, generated C++ bindings will effectively prefer to\n> @@ -310,8 +338,15 @@ if (enable_scrambled_message_ids) {\n>  #       correct dependency order. Note that this only has an effect if\n>  #       the |enable_mojom_closure_compile| global arg is set to |true| as well.\n>  #\n> -#   use_typescript_sources (optional)\n> -#       Uses the Typescript generator to generate JavaScript bindings.\n> +#   generate_webui_js_bindings (optional)\n> +#       Generate WebUI bindings in JavaScript rather than TypeScript. Defaults\n> +#       to false. ChromeOS only parameter.\n> +#\n> +#   generate_legacy_js_bindings (optional)\n> +#       Generate js_data_deps target containing legacy JavaScript bindings files\n> +#       for Blink tests and other non-WebUI users when generating TypeScript\n> +#       bindings for WebUI. Ignored if generate_webui_js_bindings is set to\n> +#       true.\n>  #\n>  #   js_generate_struct_deserializers (optional)\n>  #       Generates JS deerialize methods for structs.\n> @@ -323,17 +358,23 @@ if (enable_scrambled_message_ids) {\n>  #   webui_module_path (optional)\n>  #       The path or URL at which modules generated by this target will be\n>  #       accessible to WebUI pages. This may either be an absolute path or\n> -#       a full URL path starting with \"chrome://resources/mojo\".\n> +#       a full URL path starting with \"chrome://resources/mojo\". If this path\n> +#       is not specified, WebUI bindings will not be generated.\n>  #\n>  #       If an absolute path, a WebUI page may only import these modules if\n> -#       they are manually packaged and mapped independently by that page's\n> -#       WebUIDataSource. The mapped path must match the path given here.\n> +#       they are added to that page's data source (usually by adding the\n> +#       modules to the mojo_files list for build_webui(), or by listing the\n> +#       files as inputs to the page's ts_library() and/or generate_grd() build\n> +#       steps.\n>  #\n>  #       If this is is instead a URL string starting with\n> -#       \"chrome://resources/mojo\", the generated resources must be added to\n> -#       content_resources.grd and registered with\n> -#       content::SharedResourcesDataSource with a corresponding path, at which\n> -#       point they will be made available to all WebUI pages at the given URL.\n> +#       \"chrome://resources/mojo\", the resulting bindings files should\n> +#       be added to one of the lists in ui/webui/resources/mojo/BUILD.gn,\n> +#       at which point they will be made available to all WebUI pages at the\n> +#       given URL.\n> +#\n> +#       Note: WebUI module bindings are generated in TypeScript by default,\n> +#       unless |generate_webui_js_bindings| is specified as true.\n>  #\n>  # The following parameters are used to support the component build. They are\n>  # needed so that bindings which are linked with a component can use the same\n> @@ -402,16 +443,41 @@ if (enable_scrambled_message_ids) {\n>  #             should be mapped in generated bindings. This is a string like\n>  #             \"::base::Value\" or \"std::vector<::base::Value>\".\n>  #\n> -#         move_only (optional)\n> -#             A boolean value (default false) which indicates whether the C++\n> -#             type is move-only. If true, generated bindings will pass the type\n> -#             by value and use std::move() at call sites.\n> -#\n>  #         copyable_pass_by_value (optional)\n>  #             A boolean value (default false) which effectively indicates\n>  #             whether the C++ type is very cheap to copy. If so, generated\n>  #             bindings will pass by value but not use std::move() at call sites.\n>  #\n> +#         default_constructible (optional)\n> +#             A boolean value (default true) which indicates whether the C++\n> +#             type is default constructible. If a C++ type is not default\n> +#             constructible (e.g. the implementor of the type prefers not to\n> +#             publicly expose a default constructor that creates an object in an\n> +#             invalid state), Mojo will instead construct C++ type with an\n> +#             argument of the type `mojo::DefaultConstruct::Tag` (essentially a\n> +#             passkey-like type specifically for this use case).\n> +#\n> +#         force_serialize (optional)\n> +#             A boolean value (default false) which disables lazy serialization\n> +#             of the typemapped type if lazy serialization is enabled for the\n> +#             mojom target applying this typemap.\n> +#\n> +#         forward_declaration (optional)\n> +#             A forward declaration of the C++ type, which bindings that don't\n> +#             need the full type definition can use to reduce the size of\n> +#             the generated code. This is a string like\n> +#             \"namespace base { class Value; }\".\n> +#\n> +#         hashable (optional)\n> +#             A boolean value (default false) indicating whether the C++ type is\n> +#             hashable. Set to true if true AND needed (i.e. you need to use the\n> +#             type as the key of a mojom map).\n> +#\n> +#         move_only (optional)\n> +#             A boolean value (default false) which indicates whether the C++\n> +#             type is move-only. If true, generated bindings will pass the type\n> +#             by value and use std::move() at call sites.\n> +#\n>  #         nullable_is_same_type (optional)\n>  #             A boolean value (default false) which indicates that the C++ type\n>  #             has some baked-in semantic notion of a \"null\" state. If true, the\n> @@ -421,16 +487,6 @@ if (enable_scrambled_message_ids) {\n>  #             type with absl::optional, and null values are simply\n>  #             absl::nullopt.\n>  #\n> -#         hashable (optional)\n> -#             A boolean value (default false) indicating whether the C++ type is\n> -#             hashable. Set to true if true AND needed (i.e. you need to use the\n> -#             type as the key of a mojom map).\n> -#\n> -#         force_serialize (optional)\n> -#             A boolean value (default false) which disables lazy serialization\n> -#             of the typemapped type if lazy serialization is enabled for the\n> -#             mojom target applying this typemap.\n> -#\n>  # Additional typemap scope parameters:\n>  #\n>  #   traits_headers (optional)\n> @@ -621,20 +677,26 @@ template(\"mojom\") {\n>    build_metadata_filename = \"$target_gen_dir/$target_name.build_metadata\"\n>    build_metadata = {\n>    }\n> -  build_metadata.sources = rebase_path(sources_list)\n> +  build_metadata.sources = rebase_path(sources_list, target_gen_dir)\n>    build_metadata.deps = []\n>    foreach(dep, all_deps) {\n>      dep_target_gen_dir = get_label_info(dep, \"target_gen_dir\")\n>      dep_name = get_label_info(dep, \"name\")\n>      build_metadata.deps +=\n> -        [ rebase_path(\"$dep_target_gen_dir/$dep_name.build_metadata\") ]\n> +        [ rebase_path(\"$dep_target_gen_dir/$dep_name.build_metadata\",\n> +                      target_gen_dir) ]\n>    }\n>    write_file(build_metadata_filename, build_metadata, \"json\")\n>  \n> -  generate_fuzzing =\n> -      (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) &&\n> +  generate_js_fuzzing =\n> +      (!defined(invoker.enable_js_fuzzing) || invoker.enable_js_fuzzing) &&\n>        enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly)\n>  \n> +  generate_mojolpm_fuzzing =\n> +      (!defined(invoker.enable_mojolpm_fuzzing) ||\n> +       invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer &&\n> +      (!defined(invoker.testonly) || !invoker.testonly)\n> +\n>    parser_target_name = \"${target_name}__parser\"\n>    parser_deps = []\n>    foreach(dep, all_deps) {\n> @@ -665,30 +727,34 @@ template(\"mojom\") {\n>          \"is_chromeos\",\n>          \"is_chromeos_ash\",\n>        ]\n> +    } else if (is_chromeos_lacros) {\n> +      enabled_features += [\n> +        \"is_chromeos\",\n> +        \"is_chromeos_lacros\",\n> +      ]\n>      } else if (is_fuchsia) {\n>        enabled_features += [ \"is_fuchsia\" ]\n>      } else if (is_ios) {\n>        enabled_features += [ \"is_ios\" ]\n> -    } else if (is_linux || is_chromeos_lacros) {\n> +    } else if (is_linux) {\n>        enabled_features += [ \"is_linux\" ]\n> -      if (is_chromeos_lacros) {\n> -        enabled_features += [\n> -          \"is_chromeos\",\n> -          \"is_chromeos_lacros\",\n> -        ]\n> -      }\n>      } else if (is_mac) {\n>        enabled_features += [ \"is_mac\" ]\n>      } else if (is_win) {\n>        enabled_features += [ \"is_win\" ]\n>      }\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(parser_target_name) {\n> +    if (is_apple) {\n> +      enabled_features += [ \"is_apple\" ]\n> +    }\n> +\n> +    action(parser_target_name) {\n> +      allow_remote = true\n> +      custom_processor = \"mojom_parser\"\n>        script = mojom_parser_script\n> -      inputs = mojom_parser_sources + [ build_metadata_filename ]\n> +      inputs = mojom_parser_sources + ply_sources + [ build_metadata_filename ]\n>        sources = sources_list\n> -      deps = parser_deps\n> +      public_deps = parser_deps\n>        outputs = []\n>        foreach(base_path, output_file_base_paths) {\n>          filename = get_path_info(base_path, \"file\")\n> @@ -698,31 +764,35 @@ template(\"mojom\") {\n>  \n>        filelist = []\n>        foreach(source, sources_list) {\n> -        filelist += [ rebase_path(source) ]\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n>        }\n> -      response_file_contents = filelist\n> +\n> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +      rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +      write_file(rsp_file, filelist)\n> +      inputs += [ rsp_file ]\n>  \n>        args = [\n>          # Resolve relative input mojom paths against both the root src dir and\n>          # the root gen dir.\n>          \"--input-root\",\n> -        rebase_path(\"//.\"),\n> +        rebase_path(\"//.\", root_build_dir),\n>          \"--input-root\",\n> -        rebase_path(root_gen_dir),\n> +        rebase_path(root_gen_dir, root_build_dir),\n>  \n>          \"--output-root\",\n> -        rebase_path(root_gen_dir),\n> +        rebase_path(root_gen_dir, root_build_dir),\n>  \n> -        \"--mojom-file-list={{response_file_name}}\",\n> +        \"--mojom-file-list=\" + rebase_path(rsp_file, root_build_dir),\n>  \n>          \"--check-imports\",\n> -        rebase_path(build_metadata_filename),\n> +        rebase_path(build_metadata_filename, root_build_dir),\n>        ]\n>  \n>        if (defined(invoker.input_root_override)) {\n>          args += [\n>            \"--input-root\",\n> -          rebase_path(invoker.input_root_override),\n> +          rebase_path(invoker.input_root_override, root_build_dir),\n>          ]\n>        }\n>  \n> @@ -738,6 +808,13 @@ template(\"mojom\") {\n>            \"--add-module-metadata\",\n>            \"webui_module_path=${invoker.webui_module_path}\",\n>          ]\n> +        if (defined(invoker.generate_webui_js_bindings) &&\n> +            invoker.generate_webui_js_bindings) {\n> +          args += [\n> +            \"--add-module-metadata\",\n> +            \"generate_webui_js=True\",\n> +          ]\n> +        }\n>        }\n>      }\n>    }\n> @@ -819,11 +896,12 @@ template(\"mojom\") {\n>        }\n>      }\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(generator_cpp_message_ids_target_name) {\n> +    action(generator_cpp_message_ids_target_name) {\n> +      allow_remote = true\n>        script = mojom_generator_script\n>        inputs = mojom_generator_sources + jinja2_sources\n> -      sources = sources_list\n> +      sources = sources_list +\n> +                [ \"$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip\" ]\n>        deps = [\n>          \":$parser_target_name\",\n>          \"//mojo/public/tools/bindings:precompile_templates\",\n> @@ -835,16 +913,22 @@ template(\"mojom\") {\n>        args = common_generator_args\n>        filelist = []\n>        foreach(source, sources_list) {\n> -        filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n>        }\n>        foreach(base_path, output_file_base_paths) {\n> +        filename = get_path_info(base_path, \"file\")\n> +        dirname = get_path_info(base_path, \"dir\")\n> +        inputs += [ \"$root_gen_dir/$dirname/${filename}-module\" ]\n>          outputs += [ \"$root_gen_dir/$base_path-shared-message-ids.h\" ]\n>        }\n>  \n> -      response_file_contents = filelist\n> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +      rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +      write_file(rsp_file, filelist)\n> +      inputs += [ rsp_file ]\n>  \n>        args += [\n> -        \"--filelist={{response_file_name}}\",\n> +        \"--filelist=\" + rebase_path(rsp_file, root_build_dir),\n>          \"--generate_non_variant_code\",\n>          \"--generate_message_ids\",\n>          \"-g\",\n> @@ -860,12 +944,13 @@ template(\"mojom\") {\n>  \n>      generator_shared_target_name = \"${target_name}_shared__generator\"\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(generator_shared_target_name) {\n> +    action(generator_shared_target_name) {\n> +      allow_remote = true\n>        visibility = [ \":*\" ]\n>        script = mojom_generator_script\n>        inputs = mojom_generator_sources + jinja2_sources\n> -      sources = sources_list\n> +      sources = sources_list +\n> +                [ \"$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip\" ]\n>        deps = [\n>          \":$parser_target_name\",\n>          \"//mojo/public/tools/bindings:precompile_templates\",\n> @@ -878,10 +963,16 @@ template(\"mojom\") {\n>        args = common_generator_args\n>        filelist = []\n>        foreach(source, sources_list) {\n> -        filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n>        }\n>        foreach(base_path, output_file_base_paths) {\n> +        # Need the mojom-module as an input to this action.\n> +        filename = get_path_info(base_path, \"file\")\n> +        dirname = get_path_info(base_path, \"dir\")\n> +        inputs += [ \"$root_gen_dir/$dirname/${filename}-module\" ]\n> +\n>          outputs += [\n> +          \"$root_gen_dir/$base_path-features.h\",\n>            \"$root_gen_dir/$base_path-params-data.h\",\n>            \"$root_gen_dir/$base_path-shared-internal.h\",\n>            \"$root_gen_dir/$base_path-shared.cc\",\n> @@ -889,10 +980,13 @@ template(\"mojom\") {\n>          ]\n>        }\n>  \n> -      response_file_contents = filelist\n> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +      rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +      write_file(rsp_file, filelist)\n> +      inputs += [ rsp_file ]\n>  \n>        args += [\n> -        \"--filelist={{response_file_name}}\",\n> +        \"--filelist=\" + rebase_path(rsp_file, root_build_dir),\n>          \"--generate_non_variant_code\",\n>          \"-g\",\n>          \"c++\",\n> @@ -923,12 +1017,14 @@ template(\"mojom\") {\n>      if (defined(invoker.testonly)) {\n>        testonly = invoker.testonly\n>      }\n> +    configs += [ \"//build/config/compiler:wexit_time_destructors\" ]\n>      deps = []\n>      public_deps = []\n>      if (output_file_base_paths != []) {\n>        sources = []\n>        foreach(base_path, output_file_base_paths) {\n>          sources += [\n> +          \"$root_gen_dir/$base_path-features.h\",\n>            \"$root_gen_dir/$base_path-params-data.h\",\n>            \"$root_gen_dir/$base_path-shared-internal.h\",\n>            \"$root_gen_dir/$base_path-shared.cc\",\n> @@ -972,7 +1068,7 @@ template(\"mojom\") {\n>      }\n>    }\n>  \n> -  if (generate_fuzzing) {\n> +  if (generate_mojolpm_fuzzing) {\n>      # This block generates the proto files used for the MojoLPM fuzzer,\n>      # and the corresponding proto targets that will be linked in the fuzzer\n>      # targets. These are independent of the typemappings, and can be done\n> @@ -981,11 +1077,15 @@ template(\"mojom\") {\n>      generator_mojolpm_proto_target_name =\n>          \"${target_name}_mojolpm_proto_generator\"\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(generator_mojolpm_proto_target_name) {\n> +    action(generator_mojolpm_proto_target_name) {\n> +      allow_remote = true\n>        script = mojom_generator_script\n>        inputs = mojom_generator_sources + jinja2_sources\n> -      sources = invoker.sources\n> +      sources =\n> +          invoker.sources + [\n> +            \"$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip\",\n> +            \"$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip\",\n> +          ]\n>        deps = [\n>          \":$parser_target_name\",\n>          \"//mojo/public/tools/bindings:precompile_templates\",\n> @@ -994,15 +1094,37 @@ template(\"mojom\") {\n>        outputs = []\n>        args = common_generator_args\n>        filelist = []\n> -      foreach(source, invoker.sources) {\n> -        filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +\n> +      # Split the input into generated and non-generated source files. They\n> +      # need to be processed separately.\n> +      gen_dir_path_wildcard = get_path_info(\"//\", \"gen_dir\") + \"/*\"\n> +      non_gen_sources =\n> +          filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])\n> +      gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])\n> +\n> +      foreach(source, non_gen_sources) {\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n> +        inputs += [ \"$target_gen_dir/$source-module\" ]\n>          outputs += [ \"$target_gen_dir/$source.mojolpm.proto\" ]\n>        }\n>  \n> -      response_file_contents = filelist\n> +      foreach(source, gen_sources) {\n> +        filelist += [ rebase_path(source, root_build_dir) ]\n> +\n> +        # For generated files, we assume they're in the target_gen_dir or a\n> +        # sub-folder of it. Rebase the path so we can get the relative location.\n> +        source_file = rebase_path(source, target_gen_dir)\n> +        inputs += [ \"$target_gen_dir/$source_file-module\" ]\n> +        outputs += [ \"$target_gen_dir/$source_file.mojolpm.proto\" ]\n> +      }\n> +\n> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +      rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +      write_file(rsp_file, filelist)\n> +      inputs += [ rsp_file ]\n>  \n>        args += [\n> -        \"--filelist={{response_file_name}}\",\n> +        \"--filelist=\" + rebase_path(rsp_file, root_build_dir),\n>          \"--generate_non_variant_code\",\n>          \"-g\",\n>          \"mojolpm\",\n> @@ -1014,9 +1136,20 @@ template(\"mojom\") {\n>        proto_library(mojolpm_proto_target_name) {\n>          testonly = true\n>          generate_python = false\n> +\n> +        # Split the input into generated and non-generated source files. They\n> +        # need to be processed separately.\n> +        gen_dir_path_wildcard = get_path_info(\"//\", \"gen_dir\") + \"/*\"\n> +        non_gen_sources =\n> +            filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])\n> +        gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])\n>          sources = process_file_template(\n> -                invoker.sources,\n> +                non_gen_sources,\n>                  [ \"{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto\" ])\n> +        sources += process_file_template(\n> +                gen_sources,\n> +                [ \"{{source_dir}}/{{source_file_part}}.mojolpm.proto\" ])\n> +\n>          import_dirs = [ \"//\" ]\n>          proto_in_dir = \"${root_gen_dir}\"\n>          proto_out_dir = \".\"\n> @@ -1055,7 +1188,7 @@ template(\"mojom\") {\n>      component_macro_suffix = \"\"\n>    }\n>    if ((!defined(invoker.disable_variants) || !invoker.disable_variants) &&\n> -      !is_ios) {\n> +      use_blink) {\n>      blink_variant = {\n>        variant = \"blink\"\n>        component_macro_suffix = \"_BLINK\"\n> @@ -1149,39 +1282,6 @@ template(\"mojom\") {\n>              \"${bindings_configuration.component_macro_suffix}_IMPL\" ]\n>      }\n>  \n> -    export_args = []\n> -    export_args_overridden = false\n> -    if (defined(bindings_configuration.for_blink) &&\n> -        bindings_configuration.for_blink) {\n> -      if (defined(invoker.export_class_attribute_blink)) {\n> -        export_args_overridden = true\n> -        export_args += [\n> -          \"--export_attribute\",\n> -          invoker.export_class_attribute_blink,\n> -          \"--export_header\",\n> -          invoker.export_header_blink,\n> -        ]\n> -      }\n> -    } else if (defined(invoker.export_class_attribute)) {\n> -      export_args_overridden = true\n> -      export_args += [\n> -        \"--export_attribute\",\n> -        invoker.export_class_attribute,\n> -        \"--export_header\",\n> -        invoker.export_header,\n> -      ]\n> -    }\n> -\n> -    if (!export_args_overridden && defined(invoker.component_macro_prefix)) {\n> -      export_args += [\n> -        \"--export_attribute\",\n> -        \"COMPONENT_EXPORT(${invoker.component_macro_prefix}\" +\n> -            \"${bindings_configuration.component_macro_suffix})\",\n> -        \"--export_header\",\n> -        \"base/component_export.h\",\n> -      ]\n> -    }\n> -\n>      generate_java = false\n>      if (!cpp_only && defined(invoker.generate_java)) {\n>        generate_java = invoker.generate_java\n> @@ -1190,6 +1290,38 @@ template(\"mojom\") {\n>      type_mappings_path =\n>          \"$target_gen_dir/${target_name}${variant_suffix}__type_mappings\"\n>      if (sources_list != []) {\n> +      export_args = []\n> +      export_args_overridden = false\n> +      if (defined(bindings_configuration.for_blink) &&\n> +          bindings_configuration.for_blink) {\n> +        if (defined(invoker.export_class_attribute_blink)) {\n> +          export_args_overridden = true\n> +          export_args += [\n> +            \"--export_attribute\",\n> +            invoker.export_class_attribute_blink,\n> +            \"--export_header\",\n> +            invoker.export_header_blink,\n> +          ]\n> +        }\n> +      } else if (defined(invoker.export_class_attribute)) {\n> +        export_args_overridden = true\n> +        export_args += [\n> +          \"--export_attribute\",\n> +          invoker.export_class_attribute,\n> +          \"--export_header\",\n> +          invoker.export_header,\n> +        ]\n> +      }\n> +      if (!export_args_overridden && defined(invoker.component_macro_prefix)) {\n> +        export_args += [\n> +          \"--export_attribute\",\n> +          \"COMPONENT_EXPORT(${invoker.component_macro_prefix}\" +\n> +              \"${bindings_configuration.component_macro_suffix})\",\n> +          \"--export_header\",\n> +          \"base/component_export.h\",\n> +        ]\n> +      }\n> +\n>        generator_cpp_output_suffixes = []\n>        variant_dash_suffix = \"\"\n>        if (defined(variant)) {\n> @@ -1198,7 +1330,6 @@ template(\"mojom\") {\n>        generator_cpp_output_suffixes += [\n>          \"${variant_dash_suffix}-forward.h\",\n>          \"${variant_dash_suffix}-import-headers.h\",\n> -        \"${variant_dash_suffix}-test-utils.cc\",\n>          \"${variant_dash_suffix}-test-utils.h\",\n>          \"${variant_dash_suffix}.cc\",\n>          \"${variant_dash_suffix}.h\",\n> @@ -1207,16 +1338,28 @@ template(\"mojom\") {\n>        generator_target_name = \"${target_name}${variant_suffix}__generator\"\n>  \n>        # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(generator_target_name) {\n> +      action(generator_target_name) {\n> +        allow_remote = true\n>          visibility = [ \":*\" ]\n>          script = mojom_generator_script\n>          inputs = mojom_generator_sources + jinja2_sources\n> -        sources = sources_list\n> +        sources =\n> +            sources_list + [\n> +              \"$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip\",\n> +              type_mappings_path,\n> +            ]\n> +        if (generate_mojolpm_fuzzing &&\n> +            !defined(bindings_configuration.variant)) {\n> +          sources += [\n> +            \"$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip\",\n> +          ]\n> +        }\n>          deps = [\n>            \":$parser_target_name\",\n>            \":$type_mappings_target_name\",\n>            \"//mojo/public/tools/bindings:precompile_templates\",\n>          ]\n> +\n>          if (defined(invoker.parser_deps)) {\n>            deps += invoker.parser_deps\n>          }\n> @@ -1224,18 +1367,22 @@ template(\"mojom\") {\n>          args = common_generator_args + export_args\n>          filelist = []\n>          foreach(source, sources_list) {\n> -          filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +          filelist += [ rebase_path(source, root_build_dir) ]\n>          }\n>          foreach(base_path, output_file_base_paths) {\n> +          filename = get_path_info(base_path, \"file\")\n> +          dirname = get_path_info(base_path, \"dir\")\n> +          inputs += [ \"$root_gen_dir/$dirname/${filename}-module\" ]\n> +\n>            outputs += [\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h\",\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h\",\n> -            \"$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc\",\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h\",\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}.cc\",\n>              \"$root_gen_dir/${base_path}${variant_dash_suffix}.h\",\n>            ]\n> -          if (generate_fuzzing && !defined(bindings_configuration.variant)) {\n> +          if (generate_mojolpm_fuzzing &&\n> +              !defined(bindings_configuration.variant)) {\n>              outputs += [\n>                \"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc\",\n>                \"$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h\",\n> @@ -1243,14 +1390,17 @@ template(\"mojom\") {\n>            }\n>          }\n>  \n> -        response_file_contents = filelist\n> -\n> +        # Workaround for https://github.com/ninja-build/ninja/issues/1966.\n> +        rsp_file = \"$target_gen_dir/${target_name}.rsp\"\n> +        write_file(rsp_file, filelist)\n> +        inputs += [ rsp_file ]\n>          args += [\n> -          \"--filelist={{response_file_name}}\",\n> +          \"--filelist=\" + rebase_path(\"$rsp_file\", root_build_dir),\n>            \"-g\",\n>          ]\n>  \n> -        if (generate_fuzzing && !defined(bindings_configuration.variant)) {\n> +        if (generate_mojolpm_fuzzing &&\n> +            !defined(bindings_configuration.variant)) {\n>            args += [ \"c++,mojolpm\" ]\n>          } else {\n>            args += [ \"c++\" ]\n> @@ -1294,6 +1444,8 @@ template(\"mojom\") {\n>                \"--extra_cpp_template_paths\",\n>                rebase_path(extra_cpp_template, root_build_dir),\n>              ]\n> +            inputs += [ extra_cpp_template ]\n> +\n>              assert(\n>                  get_path_info(extra_cpp_template, \"extension\") == \"tmpl\",\n>                  \"--extra_cpp_template_paths only accepts template files ending in extension .tmpl\")\n> @@ -1306,62 +1458,6 @@ template(\"mojom\") {\n>        }\n>      }\n>  \n> -    if (generate_fuzzing && !defined(variant)) {\n> -      # This block contains the C++ targets for the MojoLPM fuzzer, we need to\n> -      # do this here so that we can use the typemap configuration for the\n> -      # empty-variant Mojo target.\n> -\n> -      mojolpm_target_name = \"${target_name}_mojolpm\"\n> -      mojolpm_generator_target_name = \"${target_name}__generator\"\n> -      source_set(mojolpm_target_name) {\n> -        # There are still a few missing header dependencies between mojo targets\n> -        # with typemaps and the dependencies of their typemap headers. It would\n> -        # be good to enable include checking for these in the future though.\n> -        check_includes = false\n> -        testonly = true\n> -        if (defined(invoker.sources)) {\n> -          sources = process_file_template(\n> -                  invoker.sources,\n> -                  [\n> -                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc\",\n> -                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.h\",\n> -                  ])\n> -          deps = []\n> -        } else {\n> -          sources = []\n> -          deps = []\n> -        }\n> -\n> -        public_deps = [\n> -          \":$generator_shared_target_name\",\n> -\n> -          # NB: hardcoded dependency on the no-variant variant generator, since\n> -          # mojolpm only uses the no-variant type.\n> -          \":$mojolpm_generator_target_name\",\n> -          \":$mojolpm_proto_target_name\",\n> -          \"//base\",\n> -          \"//mojo/public/tools/fuzzers:mojolpm\",\n> -        ]\n> -\n> -        foreach(d, all_deps) {\n> -          # Resolve the name, so that a target //mojo/something becomes\n> -          # //mojo/something:something and we can append variant_suffix to\n> -          # get the cpp dependency name.\n> -          full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> -          public_deps += [ \"${full_name}_mojolpm\" ]\n> -        }\n> -\n> -        foreach(config, cpp_typemap_configs) {\n> -          if (defined(config.traits_deps)) {\n> -            deps += config.traits_deps\n> -          }\n> -          if (defined(config.traits_public_deps)) {\n> -            public_deps += config.traits_public_deps\n> -          }\n> -        }\n> -      }\n> -    }\n> -\n>      # Write the typemapping configuration for this target out to a file to be\n>      # validated by a Python script. This helps catch mistakes that can't\n>      # be caught by logic in GN.\n> @@ -1389,20 +1485,20 @@ template(\"mojom\") {\n>      write_file(_typemap_config_filename, _rebased_typemap_configs, \"json\")\n>      _mojom_target_name = target_name\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(_typemap_validator_target_name) {\n> +    action(_typemap_validator_target_name) {\n> +      allow_remote = true\n>        script = \"$mojom_generator_root/validate_typemap_config.py\"\n>        inputs = [ _typemap_config_filename ]\n>        outputs = [ _typemap_stamp_filename ]\n>        args = [\n>          get_label_info(_mojom_target_name, \"label_no_toolchain\"),\n> -        rebase_path(_typemap_config_filename),\n> -        rebase_path(_typemap_stamp_filename),\n> +        rebase_path(_typemap_config_filename, root_build_dir),\n> +        rebase_path(_typemap_stamp_filename, root_build_dir),\n>        ]\n>      }\n>  \n> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -    python2_action(type_mappings_target_name) {\n> +    action(type_mappings_target_name) {\n> +      allow_remote = true\n>        inputs =\n>            mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ]\n>        outputs = [ type_mappings_path ]\n> @@ -1413,6 +1509,7 @@ template(\"mojom\") {\n>          rebase_path(type_mappings_path, root_build_dir),\n>        ]\n>  \n> +      sources = []\n>        foreach(d, all_deps) {\n>          name = get_label_info(d, \"label_no_toolchain\")\n>          toolchain = get_label_info(d, \"toolchain\")\n> @@ -1422,12 +1519,11 @@ template(\"mojom\") {\n>          dependency_output_dir =\n>              get_label_info(dependency_output, \"target_gen_dir\")\n>          dependency_name = get_label_info(dependency_output, \"name\")\n> -        dependency_path =\n> -            rebase_path(\"$dependency_output_dir/${dependency_name}\",\n> -                        root_build_dir)\n> +        dependency_path = \"$dependency_output_dir/${dependency_name}\"\n> +        sources += [ dependency_path ]\n>          args += [\n>            \"--dependency\",\n> -          dependency_path,\n> +          rebase_path(dependency_path, root_build_dir),\n>          ]\n>        }\n>  \n> @@ -1485,11 +1581,15 @@ template(\"mojom\") {\n>        if (defined(output_name_override)) {\n>          output_name = output_name_override\n>        }\n> -      visibility = output_visibility + [ \":$output_target_name\" ]\n> +      visibility = output_visibility + [\n> +                     \":$output_target_name\",\n> +                     \":${target_name}_mojolpm\",\n> +                   ]\n>        if (defined(invoker.testonly)) {\n>          testonly = invoker.testonly\n>        }\n>        defines = export_defines\n> +      configs += [ \"//build/config/compiler:wexit_time_destructors\" ]\n>        configs += extra_configs\n>        if (output_file_base_paths != []) {\n>          sources = []\n> @@ -1578,13 +1678,81 @@ template(\"mojom\") {\n>        }\n>      }\n>  \n> +    if (generate_mojolpm_fuzzing && !defined(variant)) {\n> +      # This block contains the C++ targets for the MojoLPM fuzzer, we need to\n> +      # do this here so that we can use the typemap configuration for the\n> +      # empty-variant Mojo target.\n> +\n> +      mojolpm_target_name = \"${target_name}_mojolpm\"\n> +      mojolpm_generator_target_name = \"${target_name}__generator\"\n> +      source_set(mojolpm_target_name) {\n> +        # There are still a few missing header dependencies between mojo targets\n> +        # with typemaps and the dependencies of their typemap headers. It would\n> +        # be good to enable include checking for these in the future though.\n> +        check_includes = false\n> +        testonly = true\n> +        if (defined(invoker.sources)) {\n> +          # Split the input into generated and non-generated source files. They\n> +          # need to be processed separately.\n> +          gen_dir_path_wildcard = get_path_info(\"//\", \"gen_dir\") + \"/*\"\n> +          non_gen_sources =\n> +              filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])\n> +          gen_sources =\n> +              filter_include(invoker.sources, [ gen_dir_path_wildcard ])\n> +          sources = process_file_template(\n> +                  non_gen_sources,\n> +                  [\n> +                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc\",\n> +                    \"{{source_gen_dir}}/{{source_file_part}}-mojolpm.h\",\n> +                  ])\n> +          sources += process_file_template(\n> +                  gen_sources,\n> +                  [\n> +                    \"{{source_dir}}/{{source_file_part}}-mojolpm.cc\",\n> +                    \"{{source_dir}}/{{source_file_part}}-mojolpm.h\",\n> +                  ])\n> +          deps = [ \":$output_target_name\" ]\n> +        } else {\n> +          sources = []\n> +          deps = []\n> +        }\n> +\n> +        public_deps = [\n> +          \":$generator_shared_target_name\",\n> +\n> +          # NB: hardcoded dependency on the no-variant variant generator, since\n> +          # mojolpm only uses the no-variant type.\n> +          \":$mojolpm_generator_target_name\",\n> +          \":$mojolpm_proto_target_name\",\n> +          \"//base\",\n> +          \"//mojo/public/tools/fuzzers:mojolpm\",\n> +        ]\n> +\n> +        foreach(d, all_deps) {\n> +          # Resolve the name, so that a target //mojo/something becomes\n> +          # //mojo/something:something and we can append variant_suffix to\n> +          # get the cpp dependency name.\n> +          full_name = get_label_info(\"$d\", \"label_no_toolchain\")\n> +          public_deps += [ \"${full_name}_mojolpm\" ]\n> +        }\n> +\n> +        foreach(config, cpp_typemap_configs) {\n> +          if (defined(config.traits_deps)) {\n> +            deps += config.traits_deps\n> +          }\n> +          if (defined(config.traits_public_deps)) {\n> +            public_deps += config.traits_public_deps\n> +          }\n> +        }\n> +      }\n> +    }\n> +\n>      if (generate_java && is_android) {\n>        import(\"//build/config/android/rules.gni\")\n>  \n>        java_generator_target_name = target_name + \"_java__generator\"\n>        if (sources_list != []) {\n> -        # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -        python2_action(java_generator_target_name) {\n> +        action(java_generator_target_name) {\n>            script = mojom_generator_script\n>            inputs = mojom_generator_sources + jinja2_sources\n>            sources = sources_list\n> @@ -1597,7 +1765,7 @@ template(\"mojom\") {\n>            args = common_generator_args\n>            filelist = []\n>            foreach(source, sources_list) {\n> -            filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +            filelist += [ rebase_path(source, root_build_dir) ]\n>            }\n>            foreach(base_path, output_file_base_paths) {\n>              outputs += [ \"$root_gen_dir/$base_path.srcjar\" ]\n> @@ -1624,8 +1792,7 @@ template(\"mojom\") {\n>  \n>        java_srcjar_target_name = target_name + \"_java_sources\"\n>  \n> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(java_srcjar_target_name) {\n> +      action(java_srcjar_target_name) {\n>          script = \"//build/android/gyp/zip.py\"\n>          inputs = []\n>          if (output_file_base_paths != []) {\n> @@ -1651,7 +1818,6 @@ template(\"mojom\") {\n>        android_library(java_target_name) {\n>          forward_variables_from(invoker, [ \"enable_bytecode_checks\" ])\n>          deps = [\n> -          \"//base:base_java\",\n>            \"//mojo/public/java:bindings_java\",\n>            \"//mojo/public/java:system_java\",\n>            \"//third_party/androidx:androidx_annotation_annotation_java\",\n> @@ -1673,21 +1839,36 @@ template(\"mojom\") {\n>      }\n>    }\n>  \n> -  use_typescript_for_target =\n> -      enable_typescript_bindings && defined(invoker.use_typescript_sources) &&\n> -      invoker.use_typescript_sources\n> -\n> -  if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) {\n> -    not_needed(invoker, [ \"use_typescript_sources\" ])\n> +  if (defined(invoker.generate_webui_js_bindings)) {\n> +    assert(is_chromeos_ash,\n> +           \"generate_webui_js_bindings can only be used on ChromeOS Ash\")\n> +    assert(invoker.generate_webui_js_bindings,\n> +           \"generate_webui_js_bindings should be set to true or removed\")\n>    }\n>  \n> -  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&\n> -      !use_typescript_for_target) {\n> +  use_typescript_for_target = defined(invoker.webui_module_path) &&\n> +                              !defined(invoker.generate_webui_js_bindings)\n> +\n> +  generate_legacy_js = !use_typescript_for_target ||\n> +                       (defined(invoker.generate_legacy_js_bindings) &&\n> +                        invoker.generate_legacy_js_bindings)\n> +\n> +  if (!use_typescript_for_target &&\n> +      defined(invoker.generate_legacy_js_bindings)) {\n> +    not_needed(invoker, [ \"generate_legacy_js_bindings\" ])\n> +  }\n> +\n> +  # Targets needed by both TS and JS bindings targets. These are needed\n> +  # unconditionally for JS bindings targets, and are needed for TS bindings\n> +  # targets when generate_legacy_js_bindings is true. This option is provided\n> +  # since the legacy bindings are needed by Blink tests and non-Chromium users,\n> +  # which are not expected to migrate to modules or TypeScript.\n> +  if (generate_legacy_js && (generate_js_fuzzing ||\n> +                             !defined(invoker.cpp_only) || !invoker.cpp_only)) {\n>      if (sources_list != []) {\n>        generator_js_target_name = \"${target_name}_js__generator\"\n>  \n> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(generator_js_target_name) {\n> +      action(generator_js_target_name) {\n>          script = mojom_generator_script\n>          inputs = mojom_generator_sources + jinja2_sources\n>          sources = sources_list\n> @@ -1702,19 +1883,18 @@ template(\"mojom\") {\n>          args = common_generator_args\n>          filelist = []\n>          foreach(source, sources_list) {\n> -          filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> +          filelist += [ rebase_path(source, root_build_dir) ]\n>          }\n>          foreach(base_path, output_file_base_paths) {\n>            outputs += [\n>              \"$root_gen_dir/$base_path.js\",\n> -            \"$root_gen_dir/$base_path.externs.js\",\n>              \"$root_gen_dir/$base_path.m.js\",\n>              \"$root_gen_dir/$base_path-lite.js\",\n> -            \"$root_gen_dir/$base_path.html\",\n>              \"$root_gen_dir/$base_path-lite-for-compile.js\",\n>            ]\n>  \n> -          if (defined(invoker.webui_module_path)) {\n> +          if (defined(invoker.webui_module_path) &&\n> +              !use_typescript_for_target) {\n>              outputs += [ \"$root_gen_dir/mojom-webui/$base_path-webui.js\" ]\n>            }\n>          }\n> @@ -1725,7 +1905,6 @@ template(\"mojom\") {\n>            \"--filelist={{response_file_name}}\",\n>            \"-g\",\n>            \"javascript\",\n> -          \"--js_bindings_mode=new\",\n>          ]\n>  \n>          if (defined(invoker.js_generate_struct_deserializers) &&\n> @@ -1739,7 +1918,7 @@ template(\"mojom\") {\n>            args += message_scrambling_args\n>          }\n>  \n> -        if (generate_fuzzing) {\n> +        if (generate_js_fuzzing) {\n>            args += [ \"--generate_fuzzing\" ]\n>          }\n>        }\n> @@ -1783,31 +1962,13 @@ template(\"mojom\") {\n>          data_deps += [ \"${full_name}_js_data_deps\" ]\n>        }\n>      }\n> +  }\n>  \n> -    js_library_target_name = \"${target_name}_js_library\"\n> -    if (sources_list != []) {\n> -      js_library(js_library_target_name) {\n> -        extra_public_deps = [ \":$generator_js_target_name\" ]\n> -        sources = []\n> -        foreach(base_path, output_file_base_paths) {\n> -          sources += [ \"$root_gen_dir/${base_path}-lite.js\" ]\n> -        }\n> -        externs_list = [\n> -          \"${externs_path}/mojo_core.js\",\n> -          \"${externs_path}/pending.js\",\n> -        ]\n> -\n> -        deps = []\n> -        foreach(d, all_deps) {\n> -          full_name = get_label_info(d, \"label_no_toolchain\")\n> -          deps += [ \"${full_name}_js_library\" ]\n> -        }\n> -      }\n> -    } else {\n> -      group(js_library_target_name) {\n> -      }\n> -    }\n> -\n> +  # js_library() closure compiler targets, primarily used on ChromeOS. Only\n> +  # generate these targets if the mojom target is not C++ only and is not using\n> +  # TypeScript.\n> +  if (generate_mojom_closure_libraries &&\n> +      (!defined(invoker.cpp_only) || !invoker.cpp_only) && generate_legacy_js) {\n>      js_library_for_compile_target_name = \"${target_name}_js_library_for_compile\"\n>      if (sources_list != []) {\n>        js_library(js_library_for_compile_target_name) {\n> @@ -1834,35 +1995,9 @@ template(\"mojom\") {\n>        }\n>      }\n>  \n> -    js_modules_target_name = \"${target_name}_js_modules\"\n> -    if (sources_list != []) {\n> -      js_library(js_modules_target_name) {\n> -        extra_public_deps = [ \":$generator_js_target_name\" ]\n> -        sources = []\n> -        foreach(base_path, output_file_base_paths) {\n> -          sources += [ \"$root_gen_dir/${base_path}.m.js\" ]\n> -        }\n> -        externs_list = [\n> -          \"${externs_path}/mojo_core.js\",\n> -          \"${externs_path}/pending.js\",\n> -        ]\n> -        if (defined(invoker.disallow_native_types) &&\n> -            invoker.disallow_native_types) {\n> -          deps = []\n> -        } else {\n> -          deps = [ \"//mojo/public/js:bindings_uncompiled\" ]\n> -        }\n> -        foreach(d, all_deps) {\n> -          full_name = get_label_info(d, \"label_no_toolchain\")\n> -          deps += [ \"${full_name}_js_modules\" ]\n> -        }\n> -      }\n> -    } else {\n> -      group(js_modules_target_name) {\n> -      }\n> -    }\n> -\n> -    if (defined(invoker.webui_module_path)) {\n> +    # WebUI specific closure targets, not needed by targets that are generating\n> +    # TypeScript WebUI bindings or by legacy-only targets.\n> +    if (defined(invoker.webui_module_path) && !use_typescript_for_target) {\n>        webui_js_target_name = \"${target_name}_webui_js\"\n>        if (sources_list != []) {\n>          js_library(webui_js_target_name) {\n> @@ -1890,46 +2025,38 @@ template(\"mojom\") {\n>          group(webui_js_target_name) {\n>          }\n>        }\n> +\n> +      webui_grdp_target_name = \"${target_name}_webui_grdp\"\n> +      out_grd = \"$target_gen_dir/${target_name}_webui_resources.grdp\"\n> +      grd_prefix = \"${target_name}_webui\"\n> +      generate_grd(webui_grdp_target_name) {\n> +        grd_prefix = grd_prefix\n> +        out_grd = out_grd\n> +\n> +        deps = [ \":$webui_js_target_name\" ]\n> +\n> +        input_files = []\n> +        foreach(base_path, output_file_base_paths) {\n> +          input_files += [ \"${base_path}-webui.js\" ]\n> +        }\n> +\n> +        input_files_base_dir =\n> +            rebase_path(\"$root_gen_dir/mojom-webui\", \"$root_build_dir\")\n> +      }\n>      }\n>    }\n> -  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&\n> -      use_typescript_for_target) {\n> -    generator_js_target_names = []\n> -    source_filelist = []\n> -    foreach(source, sources_list) {\n> -      source_filelist += [ rebase_path(\"$source\", root_build_dir) ]\n> -    }\n> -\n> -    dependency_types = [\n> -      {\n> -        name = \"regular\"\n> -        ts_extension = \".ts\"\n> -        js_extension = \".js\"\n> -      },\n> -      {\n> -        name = \"es_modules\"\n> -        ts_extension = \".m.ts\"\n> -        js_extension = \".m.js\"\n> -      },\n> -    ]\n> -\n> -    foreach(dependency_type, dependency_types) {\n> -      ts_outputs = []\n> -      js_outputs = []\n> -\n> -      foreach(base_path, output_file_base_paths) {\n> -        ts_outputs +=\n> -            [ \"$root_gen_dir/$base_path-lite${dependency_type.ts_extension}\" ]\n> -        js_outputs +=\n> -            [ \"$root_gen_dir/$base_path-lite${dependency_type.js_extension}\" ]\n> +  if ((generate_js_fuzzing || !defined(invoker.cpp_only) ||\n> +       !invoker.cpp_only) && use_typescript_for_target) {\n> +    if (sources_list != []) {\n> +      source_filelist = []\n> +      foreach(source, sources_list) {\n> +        source_filelist += [ rebase_path(source, root_build_dir) ]\n>        }\n>  \n>        # Generate Typescript bindings.\n> -      generator_ts_target_name =\n> -          \"${target_name}_${dependency_type.name}__ts__generator\"\n> +      generator_ts_target_name = \"${target_name}_ts__generator\"\n>  \n> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(generator_ts_target_name) {\n> +      action(generator_ts_target_name) {\n>          script = mojom_generator_script\n>          inputs = mojom_generator_sources + jinja2_sources\n>          sources = sources_list\n> @@ -1938,7 +2065,10 @@ template(\"mojom\") {\n>            \"//mojo/public/tools/bindings:precompile_templates\",\n>          ]\n>  \n> -        outputs = ts_outputs\n> +        outputs = []\n> +        foreach(base_path, output_file_base_paths) {\n> +          outputs += [ \"$root_gen_dir/$base_path-webui.ts\" ]\n> +        }\n>          args = common_generator_args\n>          response_file_contents = source_filelist\n>  \n> @@ -1948,98 +2078,21 @@ template(\"mojom\") {\n>            \"typescript\",\n>          ]\n>  \n> -        if (dependency_type.name == \"es_modules\") {\n> -          args += [ \"--ts_use_es_modules\" ]\n> +        if (!defined(invoker.scramble_message_ids) ||\n> +            invoker.scramble_message_ids) {\n> +          inputs += message_scrambling_inputs\n> +          args += message_scrambling_args\n>          }\n>  \n> -        # TODO(crbug.com/1007587): Support scramble_message_ids.\n> +        if (defined(invoker.js_generate_struct_deserializers) &&\n> +            invoker.js_generate_struct_deserializers) {\n> +          args += [ \"--js_generate_struct_deserializers\" ]\n> +        }\n> +\n> +        # TODO(crbug.com/1007587): Support scramble_message_ids if above is\n> +        # insufficient.\n>          # TODO(crbug.com/1007591): Support generate_fuzzing.\n>        }\n> -\n> -      # Create tsconfig.json for the generated Typescript.\n> -      tsconfig_filename =\n> -          \"$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json\"\n> -      tsconfig = {\n> -      }\n> -      tsconfig.compilerOptions = {\n> -        composite = true\n> -        target = \"es6\"\n> -        module = \"es6\"\n> -        lib = [\n> -          \"es6\",\n> -          \"esnext.bigint\",\n> -        ]\n> -        strict = true\n> -      }\n> -      tsconfig.files = []\n> -      foreach(base_path, output_file_base_paths) {\n> -        tsconfig.files += [ rebase_path(\n> -                \"$root_gen_dir/$base_path-lite${dependency_type.ts_extension}\",\n> -                target_gen_dir,\n> -                root_gen_dir) ]\n> -      }\n> -      tsconfig.references = []\n> -\n> -      # Get tsconfigs for deps.\n> -      foreach(d, all_deps) {\n> -        dep_target_gen_dir = rebase_path(get_label_info(d, \"target_gen_dir\"))\n> -        dep_name = get_label_info(d, \"name\")\n> -        reference = {\n> -        }\n> -        reference.path = \"$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json\"\n> -        tsconfig.references += [ reference ]\n> -      }\n> -      write_file(tsconfig_filename, tsconfig, \"json\")\n> -\n> -      # Compile previously generated Typescript to Javascript.\n> -      generator_js_target_name =\n> -          \"${target_name}_${dependency_type.name}__js__generator\"\n> -      generator_js_target_names += [ generator_js_target_name ]\n> -\n> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.\n> -      python2_action(generator_js_target_name) {\n> -        script = \"$mojom_generator_root/compile_typescript.py\"\n> -        sources = ts_outputs\n> -        outputs = js_outputs\n> -        public_deps = [ \":$generator_ts_target_name\" ]\n> -        foreach(d, all_deps) {\n> -          full_name = get_label_info(d, \"label_no_toolchain\")\n> -          public_deps +=\n> -              [ \"${full_name}_${dependency_type.name}__js__generator\" ]\n> -        }\n> -\n> -        absolute_tsconfig_path =\n> -            rebase_path(tsconfig_filename, \"\", target_gen_dir)\n> -        args = [ \"--tsconfig_path=$absolute_tsconfig_path\" ]\n> -      }\n> -    }\n> -\n> -    js_target_name = target_name + \"_js\"\n> -    group(js_target_name) {\n> -      public_deps = []\n> -      if (sources_list != []) {\n> -        foreach(generator_js_target_name, generator_js_target_names) {\n> -          public_deps += [ \":$generator_js_target_name\" ]\n> -        }\n> -      }\n> -\n> -      foreach(d, all_deps) {\n> -        full_name = get_label_info(d, \"label_no_toolchain\")\n> -        public_deps += [ \"${full_name}_js\" ]\n> -      }\n> -    }\n> -\n> -    group(js_data_deps_target_name) {\n> -      data = js_outputs\n> -      deps = []\n> -      foreach(generator_js_target_name, generator_js_target_names) {\n> -        deps += [ \":$generator_js_target_name\" ]\n> -      }\n> -      data_deps = []\n> -      foreach(d, all_deps) {\n> -        full_name = get_label_info(d, \"label_no_toolchain\")\n> -        data_deps += [ \"${full_name}_js_data_deps\" ]\n> -      }\n>      }\n>    }\n>  }\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n> index da9efc71cefd..8c641c2aa8a6 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -57,10 +57,17 @@ _BUILTIN_GENERATORS = {\n>      \"typescript\": \"mojom_ts_generator\",\n>  }\n>  \n> +_BUILTIN_CHECKS = {\n> +    \"attributes\": \"mojom_attributes_check\",\n> +    \"definitions\": \"mojom_definitions_check\",\n> +    \"features\": \"mojom_interface_feature_check\",\n> +    \"restrictions\": \"mojom_restrictions_check\",\n> +}\n> +\n>  \n>  def LoadGenerators(generators_string):\n>    if not generators_string:\n> -    return []  # No generators.\n> +    return {}  # No generators.\n>  \n>    generators = {}\n>    for generator_name in [s.strip() for s in generators_string.split(\",\")]:\n> @@ -74,6 +81,21 @@ def LoadGenerators(generators_string):\n>    return generators\n>  \n>  \n> +def LoadChecks(checks_string):\n> +  if not checks_string:\n> +    return {}  # No checks.\n> +\n> +  checks = {}\n> +  for check_name in [s.strip() for s in checks_string.split(\",\")]:\n> +    check = check_name.lower()\n> +    if check not in _BUILTIN_CHECKS:\n> +      print(\"Unknown check name %s\" % check_name)\n> +      sys.exit(1)\n> +    check_module = importlib.import_module(\"checks.%s\" % _BUILTIN_CHECKS[check])\n> +    checks[check] = check_module\n> +  return checks\n> +\n> +\n>  def MakeImportStackMessage(imported_filename_stack):\n>    \"\"\"Make a (human-readable) message listing a chain of imports. (Returned\n>    string begins with a newline (if nonempty) and does not end with one.)\"\"\"\n> @@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack):\n>                      zip(imported_filename_stack[1:], imported_filename_stack)]))\n>  \n>  \n> -class RelativePath(object):\n> +class RelativePath:\n>    \"\"\"Represents a path relative to the source tree or generated output dir.\"\"\"\n>  \n>    def __init__(self, path, source_root, output_dir):\n> @@ -142,7 +164,7 @@ def ReadFileContents(filename):\n>      return f.read()\n>  \n>  \n> -class MojomProcessor(object):\n> +class MojomProcessor:\n>    \"\"\"Takes parsed mojom modules and generates language bindings from them.\n>  \n>    Attributes:\n> @@ -169,8 +191,8 @@ class MojomProcessor(object):\n>      if 'c++' in self._typemap:\n>        self._typemap['mojolpm'] = self._typemap['c++']\n>  \n> -  def _GenerateModule(self, args, remaining_args, generator_modules,\n> -                      rel_filename, imported_filename_stack):\n> +  def _GenerateModule(self, args, remaining_args, check_modules,\n> +                      generator_modules, rel_filename, imported_filename_stack):\n>      # Return the already-generated module.\n>      if rel_filename.path in self._processed_files:\n>        return self._processed_files[rel_filename.path]\n> @@ -190,12 +212,16 @@ class MojomProcessor(object):\n>        ScrambleMethodOrdinals(module.interfaces, salt)\n>  \n>      if self._should_generate(rel_filename.path):\n> +      # Run checks on module first.\n> +      for check_module in check_modules.values():\n> +        checker = check_module.Check(module)\n> +        checker.CheckModule()\n> +      # Then run generation.\n>        for language, generator_module in generator_modules.items():\n>          generator = generator_module.Generator(\n>              module, args.output_dir, typemap=self._typemap.get(language, {}),\n>              variant=args.variant, bytecode_path=args.bytecode_path,\n>              for_blink=args.for_blink,\n> -            js_bindings_mode=args.js_bindings_mode,\n>              js_generate_struct_deserializers=\\\n>                      args.js_generate_struct_deserializers,\n>              export_attribute=args.export_attribute,\n> @@ -234,6 +260,7 @@ def _Generate(args, remaining_args):\n>        args.import_directories[idx] = RelativePath(tokens[0], args.depth,\n>                                                    args.output_dir)\n>    generator_modules = LoadGenerators(args.generators_string)\n> +  check_modules = LoadChecks(args.checks_string)\n>  \n>    fileutil.EnsureDirectoryExists(args.output_dir)\n>  \n> @@ -246,7 +273,7 @@ def _Generate(args, remaining_args):\n>  \n>    for filename in args.filename:\n>      processor._GenerateModule(\n> -        args, remaining_args, generator_modules,\n> +        args, remaining_args, check_modules, generator_modules,\n>          RelativePath(filename, args.depth, args.output_dir), [])\n>  \n>    return 0\n> @@ -286,6 +313,12 @@ def main():\n>                                 metavar=\"GENERATORS\",\n>                                 default=\"c++,javascript,java,mojolpm\",\n>                                 help=\"comma-separated list of generators\")\n> +  generate_parser.add_argument(\"-c\",\n> +                               \"--checks\",\n> +                               dest=\"checks_string\",\n> +                               metavar=\"CHECKS\",\n> +                               default=\",\".join(_BUILTIN_CHECKS.keys()),\n> +                               help=\"comma-separated list of checks\")\n>    generate_parser.add_argument(\n>        \"--gen_dir\", dest=\"gen_directories\", action=\"append\", metavar=\"directory\",\n>        default=[], help=\"add a directory to be searched for the syntax trees.\")\n> @@ -308,11 +341,6 @@ def main():\n>    generate_parser.add_argument(\"--for_blink\", action=\"store_true\",\n>                                 help=\"Use WTF types as generated types for mojo \"\n>                                 \"string/array/map.\")\n> -  generate_parser.add_argument(\n> -      \"--js_bindings_mode\", choices=[\"new\", \"old\"], default=\"old\",\n> -      help=\"This option only affects the JavaScript bindings. The value could \"\n> -      \"be \\\"new\\\" to generate new-style lite JS bindings in addition to the \"\n> -      \"old, or \\\"old\\\" to only generate old bindings.\")\n>    generate_parser.add_argument(\n>        \"--js_generate_struct_deserializers\", action=\"store_true\",\n>        help=\"Generate javascript deserialize methods for structs in \"\n> @@ -387,4 +415,10 @@ def main():\n>  \n>  if __name__ == \"__main__\":\n>    with crbug_1001171.DumpStateOnLookupError():\n> -    sys.exit(main())\n> +    ret = main()\n> +    # Exit without running GC, which can save multiple seconds due to the large\n> +    # number of object created. But flush is necessary as os._exit doesn't do\n> +    # that.\n> +    sys.stdout.flush()\n> +    sys.stderr.flush()\n> +    os._exit(ret)\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n> index bddbe3f4c580..761922b6777e 100644\n> --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage\n>  from mojom_bindings_generator import ScrambleMethodOrdinals\n>  \n>  \n> -class FakeIface(object):\n> +class FakeIface:\n>    def __init__(self):\n>      self.mojom_name = None\n>      self.methods = None\n>  \n>  \n> -class FakeMethod(object):\n> +class FakeMethod:\n>    def __init__(self, explicit_ordinal=None):\n>      self.explicit_ordinal = explicit_ordinal\n>      self.ordinal = explicit_ordinal\n> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py b/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n> deleted file mode 100755\n> index 15f0e3bac1f7..000000000000\n> --- a/utils/ipc/mojo/public/tools/bindings/mojom_types_downgrader.py\n> +++ /dev/null\n> @@ -1,119 +0,0 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\"\"\"Downgrades *.mojom files to the old mojo types for remotes and receivers.\"\"\"\n> -\n> -import argparse\n> -import fnmatch\n> -import os\n> -import re\n> -import shutil\n> -import sys\n> -import tempfile\n> -\n> -# List of patterns and replacements to match and use against the contents of a\n> -# mojo file. Each replacement string will be used with Python string's format()\n> -# function, so the '{}' substring is used to mark where the mojo type should go.\n> -_MOJO_REPLACEMENTS = {\n> -    r'pending_remote': r'{}',\n> -    r'pending_receiver': r'{}&',\n> -    r'pending_associated_remote': r'associated {}',\n> -    r'pending_associated_receiver': r'associated {}&',\n> -}\n> -\n> -# Pre-compiled regular expression that matches against any of the replacements.\n> -_REGEXP_PATTERN = re.compile(\n> -    r'|'.join(\n> -        ['{}\\s*<\\s*(.*?)\\s*>'.format(k) for k in _MOJO_REPLACEMENTS.keys()]),\n> -    flags=re.DOTALL)\n> -\n> -\n> -def ReplaceFunction(match_object):\n> -  \"\"\"Returns the right replacement for the string matched against the regexp.\"\"\"\n> -  for index, (match, repl) in enumerate(_MOJO_REPLACEMENTS.items(), 1):\n> -    if match_object.group(0).startswith(match):\n> -      return repl.format(match_object.group(index))\n> -\n> -\n> -def DowngradeFile(path, output_dir=None):\n> -  \"\"\"Downgrades the mojom file specified by |path| to the old mojo types.\n> -\n> -  Optionally pass |output_dir| to place the result under a separate output\n> -  directory, preserving the relative path to the file included in |path|.\n> -  \"\"\"\n> -  # Use a temporary file to dump the new contents after replacing the patterns.\n> -  with open(path) as src_mojo_file:\n> -    with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_mojo_file:\n> -      tmp_contents = _REGEXP_PATTERN.sub(ReplaceFunction, src_mojo_file.read())\n> -      tmp_mojo_file.write(tmp_contents)\n> -\n> -  # Files should be placed in the desired output directory\n> -  if output_dir:\n> -    output_filepath = os.path.join(output_dir, os.path.basename(path))\n> -    if not os.path.exists(output_dir):\n> -      os.makedirs(output_dir)\n> -  else:\n> -    output_filepath = path\n> -\n> -  # Write the new contents preserving the original file's attributes.\n> -  shutil.copystat(path, tmp_mojo_file.name)\n> -  shutil.move(tmp_mojo_file.name, output_filepath)\n> -\n> -  # Make sure to \"touch\" the new file so that access, modify and change times\n> -  # are always newer than the source file's, otherwise Modify time will be kept\n> -  # as per the call to shutil.copystat(), causing unnecessary generations of the\n> -  # output file in subsequent builds due to ninja considering it dirty.\n> -  os.utime(output_filepath, None)\n> -\n> -\n> -def DowngradeDirectory(path, output_dir=None):\n> -  \"\"\"Downgrades mojom files inside directory |path| to the old mojo types.\n> -\n> -  Optionally pass |output_dir| to place the result under a separate output\n> -  directory, preserving the relative path to the file included in |path|.\n> -  \"\"\"\n> -  # We don't have recursive glob.glob() nor pathlib.Path.rglob() in Python 2.7\n> -  mojom_filepaths = []\n> -  for dir_path, _, filenames in os.walk(path):\n> -    for filename in fnmatch.filter(filenames, \"*mojom\"):\n> -      mojom_filepaths.append(os.path.join(dir_path, filename))\n> -\n> -  for path in mojom_filepaths:\n> -    absolute_dirpath = os.path.dirname(os.path.abspath(path))\n> -    if output_dir:\n> -      dest_dirpath = output_dir + absolute_dirpath\n> -    else:\n> -      dest_dirpath = absolute_dirpath\n> -    DowngradeFile(path, dest_dirpath)\n> -\n> -\n> -def DowngradePath(src_path, output_dir=None):\n> -  \"\"\"Downgrades the mojom files pointed by |src_path| to the old mojo types.\n> -\n> -  Optionally pass |output_dir| to place the result under a separate output\n> -  directory, preserving the relative path to the file included in |path|.\n> -  \"\"\"\n> -  if os.path.isdir(src_path):\n> -    DowngradeDirectory(src_path, output_dir)\n> -  elif os.path.isfile(src_path):\n> -    DowngradeFile(src_path, output_dir)\n> -  else:\n> -    print(\">>> {} not pointing to a valid file or directory\".format(src_path))\n> -    sys.exit(1)\n> -\n> -\n> -def main():\n> -  parser = argparse.ArgumentParser(\n> -      description=\"Downgrade *.mojom files to use the old mojo types.\")\n> -  parser.add_argument(\n> -      \"srcpath\", help=\"path to the file or directory to apply the conversion\")\n> -  parser.add_argument(\n> -      \"--outdir\", help=\"the directory to place the converted file(s) under\")\n> -  args = parser.parse_args()\n> -\n> -  DowngradePath(args.srcpath, args.outdir)\n> -\n> -\n> -if __name__ == \"__main__\":\n> -  sys.exit(main())\n> diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n> index f1783d59b951..6bb7a209310e 100755\n> --- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n> +++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py\n> @@ -1,5 +1,5 @@\n>  #!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -17,7 +17,8 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename):\n>    ])\n>    _SUPPORTED_TYPE_KEYS = set([\n>        'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',\n> -      'move_only', 'nullable_is_same_type'\n> +      'move_only', 'nullable_is_same_type', 'forward_declaration',\n> +      'default_constructible'\n>    ])\n>    with open(config_filename, 'r') as f:\n>      for config in json.load(f):\n> diff --git a/utils/ipc/mojo/public/tools/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/BUILD.gn\n> new file mode 100644\n> index 000000000000..eafb95a18ad1\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/BUILD.gn\n> @@ -0,0 +1,18 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +group(\"tests\") {\n> +  data = [\n> +    \"check_stable_mojom_compatibility_unittest.py\",\n> +    \"check_stable_mojom_compatibility.py\",\n> +    \"const_unittest.py\",\n> +    \"enum_unittest.py\",\n> +    \"feature_unittest.py\",\n> +    \"mojom_parser_test_case.py\",\n> +    \"mojom_parser_unittest.py\",\n> +    \"mojom_parser.py\",\n> +    \"stable_attribute_unittest.py\",\n> +    \"version_compatibility_unittest.py\",\n> +  ]\n> +}\n> diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n> index 08bd672f5ba5..35cd1cfde155 100755\n> --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py\n> @@ -1,5 +1,5 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +#!/usr/bin/env python3\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Verifies backward-compatibility of mojom type changes.\n> @@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to prevent developers from making\n>  breaking changes to stable mojoms.\"\"\"\n>  \n>  import argparse\n> -import errno\n>  import io\n>  import json\n>  import os\n>  import os.path\n> -import shutil\n> -import six\n>  import sys\n> -import tempfile\n>  \n>  from mojom.generate import module\n>  from mojom.generate import translate\n>  from mojom.parse import parser\n>  \n> +# pylint: disable=raise-missing-from\n> +\n>  \n>  class ParseError(Exception):\n>    pass\n> @@ -41,6 +39,8 @@ def _ValidateDelta(root, delta):\n>    transitive closure of a mojom's input dependencies all at once.\n>    \"\"\"\n>  \n> +  translate.is_running_backwards_compatibility_check_hack = True\n> +\n>    # First build a map of all files covered by the delta\n>    affected_files = set()\n>    old_files = {}\n> @@ -73,11 +73,35 @@ def _ValidateDelta(root, delta):\n>      try:\n>        ast = parser.Parse(contents, mojom)\n>      except Exception as e:\n> -      six.reraise(\n> -          ParseError,\n> -          'encountered exception {0} while parsing {1}'.format(e, mojom),\n> -          sys.exc_info()[2])\n> +      raise ParseError('encountered exception {0} while parsing {1}'.format(\n> +          e, mojom))\n> +\n> +    # Files which are generated at compile time can't be checked by this script\n> +    # (at the moment) since they may not exist in the output directory.\n> +    generated_files_to_skip = {\n> +        ('third_party/blink/public/mojom/runtime_feature_state/'\n> +         'runtime_feature.mojom'),\n> +        ('third_party/blink/public/mojom/origin_trial_feature/'\n> +         'origin_trial_feature.mojom'),\n> +    }\n> +\n> +    ast.import_list.items = [\n> +        x for x in ast.import_list.items\n> +        if x.import_filename not in generated_files_to_skip\n> +    ]\n> +\n>      for imp in ast.import_list:\n> +      if (not file_overrides.get(imp.import_filename)\n> +          and not os.path.exists(os.path.join(root, imp.import_filename))):\n> +        # Speculatively construct a path prefix to locate the import_filename\n> +        mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep)\n> +        test_prefix = ''\n> +        for path_component in mojom_path:\n> +          test_prefix = os.path.join(test_prefix, path_component)\n> +          test_import_filename = os.path.join(test_prefix, imp.import_filename)\n> +          if os.path.exists(os.path.join(root, test_import_filename)):\n> +            imp.import_filename = test_import_filename\n> +            break\n>        parseMojom(imp.import_filename, file_overrides, override_modules)\n>  \n>      # Now that the transitive set of dependencies has been imported and parsed\n> @@ -89,10 +113,10 @@ def _ValidateDelta(root, delta):\n>      modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)\n>  \n>    old_modules = {}\n> -  for mojom in old_files.keys():\n> +  for mojom in old_files:\n>      parseMojom(mojom, old_files, old_modules)\n>    new_modules = {}\n> -  for mojom in new_files.keys():\n> +  for mojom in new_files:\n>      parseMojom(mojom, new_files, new_modules)\n>  \n>    # At this point we have a complete set of translated Modules from both the\n> @@ -132,12 +156,21 @@ def _ValidateDelta(root, delta):\n>            'can be deleted by a subsequent change.' % qualified_name)\n>  \n>      checker = module.BackwardCompatibilityChecker()\n> -    if not checker.IsBackwardCompatible(new_types[new_name], kind):\n> -      raise Exception('Stable type %s appears to have changed in a way which '\n> -                      'breaks backward-compatibility. Please fix!\\n\\nIf you '\n> -                      'believe this assessment to be incorrect, please file a '\n> -                      'Chromium bug against the \"Internals>Mojo>Bindings\" '\n> -                      'component.' % qualified_name)\n> +    try:\n> +      if not checker.IsBackwardCompatible(new_types[new_name], kind):\n> +        raise Exception(\n> +            'Stable type %s appears to have changed in a way which '\n> +            'breaks backward-compatibility. Please fix!\\n\\nIf you '\n> +            'believe this assessment to be incorrect, please file a '\n> +            'Chromium bug against the \"Internals>Mojo>Bindings\" '\n> +            'component.' % qualified_name)\n> +    except Exception as e:\n> +      raise Exception(\n> +          'Stable type %s appears to have changed in a way which '\n> +          'breaks backward-compatibility: \\n\\n%s.\\nPlease fix!\\n\\nIf you '\n> +          'believe this assessment to be incorrect, please file a '\n> +          'Chromium bug against the \"Internals>Mojo>Bindings\" '\n> +          'component.' % (qualified_name, e))\n>  \n>  \n>  def Run(command_line, delta=None):\n> diff --git a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n> index 9f51ea7751fb..06769c9533c3 100755\n> --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility_unittest.py\n> @@ -1,5 +1,5 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +#!/usr/bin/env python3\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -15,7 +15,7 @@ import check_stable_mojom_compatibility\n>  from mojom.generate import module\n>  \n>  \n> -class Change(object):\n> +class Change:\n>    \"\"\"Helper to clearly define a mojom file delta to be analyzed.\"\"\"\n>  \n>    def __init__(self, filename, old=None, new=None):\n> @@ -28,7 +28,7 @@ class Change(object):\n>  \n>  class UnchangedFile(Change):\n>    def __init__(self, filename, contents):\n> -    super(UnchangedFile, self).__init__(filename, old=contents, new=contents)\n> +    super().__init__(filename, old=contents, new=contents)\n>  \n>  \n>  class CheckStableMojomCompatibilityTest(unittest.TestCase):\n> @@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase):\n>                 [Stable] struct T { foo.S s; int32 x; };\n>                 \"\"\")\n>      ])\n> +\n> +  def testWithPartialImport(self):\n> +    \"\"\"The compatibility checking tool correctly parses imports with partial\n> +    paths.\"\"\"\n> +    self.assertBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('foo/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\")\n> +    ])\n> +\n> +    self.assertBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('foo/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\")\n> +    ])\n> +\n> +    self.assertNotBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('bar/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\")\n> +    ])\n> +\n> +    self.assertNotBackwardCompatible([\n> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),\n> +        Change('bar/bar.mojom',\n> +               old=\"\"\"\\\n> +               module bar;\n> +               import \"foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\",\n> +               new=\"\"\"\\\n> +               module bar;\n> +               import \"foo/foo.mojom\";\n> +               [Stable] struct T { foo.S s; };\n> +               \"\"\")\n> +    ])\n> +\n> +  def testNewEnumDefault(self):\n> +    # Should be backwards compatible since it does not affect the wire format.\n> +    # This specific case also checks that the backwards compatibility checker\n> +    # does not throw an error due to the older version of the enum not\n> +    # specifying [Default].\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='[Extensible] enum E { One };',\n> +               new='[Extensible] enum E { [Default] One };')\n> +    ])\n> +    self.assertBackwardCompatible([\n> +        Change('foo/foo.mojom',\n> +               old='[Extensible] enum E { [Default] One, Two, };',\n> +               new='[Extensible] enum E { One, [Default] Two, };')\n> +    ])\n> diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/ipc/mojo/public/tools/mojom/const_unittest.py\n> index cb42dfac3556..e8ed36a7a5bf 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n> index d9005078671e..9269cde526b4 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase):\n>      self.assertEqual('F', b.enums[0].mojom_name)\n>      self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)\n>      self.assertEqual(37, b.enums[0].fields[0].numeric_value)\n> +\n> +  def testEnumAttributesAreEnums(self):\n> +    \"\"\"Verifies that enum values in attributes are really enum types.\"\"\"\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };')\n> +    b_mojom = 'b.mojom'\n> +    self.WriteFile(\n> +        b_mojom, 'module b;'\n> +        'import \"a.mojom\";'\n> +        '[MooCow=a.E.kFoo]'\n> +        'interface Foo { Foo(); };')\n> +    self.ParseMojoms([a_mojom, b_mojom])\n> +    b = self.LoadModule(b_mojom)\n> +    self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo')\n> +\n> +  def testConstantAttributes(self):\n> +    \"\"\"Verifies that constants as attributes are translated to the constant.\"\"\"\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(\n> +        a_mojom, 'module a;'\n> +        'enum E { kFoo, kBar };'\n> +        'const E kB = E.kFoo;'\n> +        '[Attr=kB] interface Hello { Foo(); };')\n> +    self.ParseMojoms([a_mojom])\n> +    a = self.LoadModule(a_mojom)\n> +    self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB')\n> +    self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name,\n> +                      'kFoo')\n> diff --git a/utils/ipc/mojo/public/tools/mojom/feature_unittest.py b/utils/ipc/mojo/public/tools/mojom/feature_unittest.py\n> new file mode 100644\n> index 000000000000..5f014e1cfe9a\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/feature_unittest.py\n> @@ -0,0 +1,84 @@\n> +# Copyright 2023 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class FeatureTest(MojomParserTestCase):\n> +  \"\"\"Tests feature parsing behavior.\"\"\"\n> +  def testFeatureOff(self):\n> +    \"\"\"Verifies basic parsing of feature types.\"\"\"\n> +    types = self.ExtractTypes(\"\"\"\n> +      // e.g. BASE_DECLARE_FEATURE(kFeature);\n> +      [AttributeOne=ValueOne]\n> +      feature kFeature {\n> +        // BASE_FEATURE(kFeature,\"MyFeature\",\n> +        //     base::FEATURE_DISABLED_BY_DEFAULT);\n> +        const string name = \"MyFeature\";\n> +        const bool default_state = false;\n> +      };\n> +    \"\"\")\n> +    self.assertEqual('name', types['kFeature'].constants[0].mojom_name)\n> +    self.assertEqual('\"MyFeature\"', types['kFeature'].constants[0].value)\n> +    self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)\n> +    self.assertEqual('false', types['kFeature'].constants[1].value)\n> +\n> +  def testFeatureOn(self):\n> +    \"\"\"Verifies basic parsing of feature types.\"\"\"\n> +    types = self.ExtractTypes(\"\"\"\n> +      // e.g. BASE_DECLARE_FEATURE(kFeature);\n> +      feature kFeature {\n> +        // BASE_FEATURE(kFeature,\"MyFeature\",\n> +        //     base::FEATURE_ENABLED_BY_DEFAULT);\n> +        const string name = \"MyFeature\";\n> +        const bool default_state = true;\n> +      };\n> +    \"\"\")\n> +    self.assertEqual('name', types['kFeature'].constants[0].mojom_name)\n> +    self.assertEqual('\"MyFeature\"', types['kFeature'].constants[0].value)\n> +    self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)\n> +    self.assertEqual('true', types['kFeature'].constants[1].value)\n> +\n> +  def testFeatureWeakKeyword(self):\n> +    \"\"\"Verifies that `feature` is a weak keyword.\"\"\"\n> +    types = self.ExtractTypes(\"\"\"\n> +      // e.g. BASE_DECLARE_FEATURE(kFeature);\n> +      [AttributeOne=ValueOne]\n> +      feature kFeature {\n> +        // BASE_FEATURE(kFeature,\"MyFeature\",\n> +        //     base::FEATURE_DISABLED_BY_DEFAULT);\n> +        const string name = \"MyFeature\";\n> +        const bool default_state = false;\n> +      };\n> +      struct MyStruct {\n> +         bool feature = true;\n> +      };\n> +      interface InterfaceName {\n> +         Method(string feature) => (int32 feature);\n> +      };\n> +    \"\"\")\n> +    self.assertEqual('name', types['kFeature'].constants[0].mojom_name)\n> +    self.assertEqual('\"MyFeature\"', types['kFeature'].constants[0].value)\n> +    self.assertEqual('default_state', types['kFeature'].constants[1].mojom_name)\n> +    self.assertEqual('false', types['kFeature'].constants[1].value)\n> +\n> +  def testFeatureAttributesAreFeatures(self):\n> +    \"\"\"Verifies that feature values in attributes are really feature types.\"\"\"\n> +    a_mojom = 'a.mojom'\n> +    self.WriteFile(\n> +        a_mojom, 'module a;'\n> +        'feature F { const string name = \"f\";'\n> +        'const bool default_state = false; };')\n> +    b_mojom = 'b.mojom'\n> +    self.WriteFile(\n> +        b_mojom, 'module b;'\n> +        'import \"a.mojom\";'\n> +        'feature G'\n> +        '{const string name = \"g\"; const bool default_state = false;};'\n> +        '[Attri=a.F] interface Foo { Foo(); };'\n> +        '[Boink=G] interface Bar {};')\n> +    self.ParseMojoms([a_mojom, b_mojom])\n> +    b = self.LoadModule(b_mojom)\n> +    self.assertEqual(b.interfaces[0].attributes['Attri'].mojom_name, 'F')\n> +    self.assertEqual(b.interfaces[1].attributes['Boink'].mojom_name, 'G')\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n> index 51facc0ccb84..a0edf0eb5d2c 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -8,6 +8,7 @@ group(\"mojom\") {\n>      \"error.py\",\n>      \"fileutil.py\",\n>      \"generate/__init__.py\",\n> +    \"generate/check.py\",\n>      \"generate/generator.py\",\n>      \"generate/module.py\",\n>      \"generate/pack.py\",\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/ipc/mojo/public/tools/mojom/mojom/error.py\n> index 8a1e03da5c1d..dd53b835a49e 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n> index bf626f54798c..124f12c134b9 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py\n> @@ -1,9 +1,8 @@\n> -# Copyright 2015 The Chromium Authors. All rights reserved.\n> +# Copyright 2015 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n>  import errno\n> -import imp\n>  import os.path\n>  import sys\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n> index ff5753a29114..c93d22898d29 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py\n> @@ -1,20 +1,17 @@\n> -# Copyright 2015 The Chromium Authors. All rights reserved.\n> +# Copyright 2015 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n>  import os.path\n>  import shutil\n> -import sys\n>  import tempfile\n>  import unittest\n>  \n>  from mojom import fileutil\n>  \n> -\n>  class FileUtilTest(unittest.TestCase):\n>    def testEnsureDirectoryExists(self):\n> -    \"\"\"Test that EnsureDirectoryExists fuctions correctly.\"\"\"\n> +    \"\"\"Test that EnsureDirectoryExists functions correctly.\"\"\"\n>  \n>      temp_dir = tempfile.mkdtemp()\n>      try:\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py\n> new file mode 100644\n> index 000000000000..1efe2022342b\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py\n> @@ -0,0 +1,26 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\"\"\"Code shared by the various pre-generation mojom checkers.\"\"\"\n> +\n> +\n> +class CheckException(Exception):\n> +  def __init__(self, module, message):\n> +    self.module = module\n> +    self.message = message\n> +    super().__init__(self.message)\n> +\n> +  def __str__(self):\n> +    return \"Failed mojo pre-generation check for {}:\\n{}\".format(\n> +        self.module.path, self.message)\n> +\n> +\n> +class Check:\n> +  def __init__(self, module):\n> +    self.module = module\n> +\n> +  def CheckModule(self):\n> +    \"\"\" Subclass should return True if its Checks pass, and throw an\n> +    exception otherwise. CheckModule will be called immediately before\n> +    mojom.generate.Generator.GenerateFiles()\"\"\"\n> +    raise NotImplementedError(\"Subclasses must override/implement this method\")\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n> deleted file mode 100644\n> index 0dfd996e355d..000000000000\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/constant_resolver.py\n> +++ /dev/null\n> @@ -1,93 +0,0 @@\n> -# Copyright 2015 The Chromium Authors. All rights reserved.\n> -# Use of this source code is governed by a BSD-style license that can be\n> -# found in the LICENSE file.\n> -\"\"\"Resolves the values used for constants and enums.\"\"\"\n> -\n> -from itertools import ifilter\n> -\n> -from mojom.generate import module as mojom\n> -\n> -\n> -def ResolveConstants(module, expression_to_text):\n> -  in_progress = set()\n> -  computed = set()\n> -\n> -  def GetResolvedValue(named_value):\n> -    assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue))\n> -    if isinstance(named_value, mojom.EnumValue):\n> -      field = next(\n> -          ifilter(lambda field: field.name == named_value.name,\n> -                  named_value.enum.fields), None)\n> -      if not field:\n> -        raise RuntimeError(\n> -            'Unable to get computed value for field %s of enum %s' %\n> -            (named_value.name, named_value.enum.name))\n> -      if field not in computed:\n> -        ResolveEnum(named_value.enum)\n> -      return field.resolved_value\n> -    else:\n> -      ResolveConstant(named_value.constant)\n> -      named_value.resolved_value = named_value.constant.resolved_value\n> -      return named_value.resolved_value\n> -\n> -  def ResolveConstant(constant):\n> -    if constant in computed:\n> -      return\n> -    if constant in in_progress:\n> -      raise RuntimeError('Circular dependency for constant: %s' % constant.name)\n> -    in_progress.add(constant)\n> -    if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)):\n> -      resolved_value = GetResolvedValue(constant.value)\n> -    else:\n> -      resolved_value = expression_to_text(constant.value)\n> -    constant.resolved_value = resolved_value\n> -    in_progress.remove(constant)\n> -    computed.add(constant)\n> -\n> -  def ResolveEnum(enum):\n> -    def ResolveEnumField(enum, field, default_value):\n> -      if field in computed:\n> -        return\n> -      if field in in_progress:\n> -        raise RuntimeError('Circular dependency for enum: %s' % enum.name)\n> -      in_progress.add(field)\n> -      if field.value:\n> -        if isinstance(field.value, mojom.EnumValue):\n> -          resolved_value = GetResolvedValue(field.value)\n> -        elif isinstance(field.value, str):\n> -          resolved_value = int(field.value, 0)\n> -        else:\n> -          raise RuntimeError('Unexpected value: %s' % field.value)\n> -      else:\n> -        resolved_value = default_value\n> -      field.resolved_value = resolved_value\n> -      in_progress.remove(field)\n> -      computed.add(field)\n> -\n> -    current_value = 0\n> -    for field in enum.fields:\n> -      ResolveEnumField(enum, field, current_value)\n> -      current_value = field.resolved_value + 1\n> -\n> -  for constant in module.constants:\n> -    ResolveConstant(constant)\n> -\n> -  for enum in module.enums:\n> -    ResolveEnum(enum)\n> -\n> -  for struct in module.structs:\n> -    for constant in struct.constants:\n> -      ResolveConstant(constant)\n> -    for enum in struct.enums:\n> -      ResolveEnum(enum)\n> -    for field in struct.fields:\n> -      if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)):\n> -        field.default.resolved_value = GetResolvedValue(field.default)\n> -\n> -  for interface in module.interfaces:\n> -    for constant in interface.constants:\n> -      ResolveConstant(constant)\n> -    for enum in interface.enums:\n> -      ResolveEnum(enum)\n> -\n> -  return module\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n> index 4a1c73fcf82f..96fe3a2d0920 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Code shared by the various language-specific code generators.\"\"\"\n> @@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier):\n>    return _ToSnakeCase(identifier, upper=False)\n>  \n>  \n> -class Stylizer(object):\n> +class Stylizer:\n>    \"\"\"Stylizers specify naming rules to map mojom names to names in generated\n>    code. For example, if you would like method_name in mojom to be mapped to\n>    MethodName in the generated code, you need to define a subclass of Stylizer\n> @@ -130,6 +130,9 @@ class Stylizer(object):\n>    def StylizeEnum(self, mojom_name):\n>      return mojom_name\n>  \n> +  def StylizeFeature(self, mojom_name):\n> +    return mojom_name\n> +\n>    def StylizeModule(self, mojom_namespace):\n>      return mojom_namespace\n>  \n> @@ -233,7 +236,7 @@ def AddComputedData(module):\n>      _AddInterfaceComputedData(interface)\n>  \n>  \n> -class Generator(object):\n> +class Generator:\n>    # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all\n>    # files to stdout.\n>    def __init__(self,\n> @@ -243,7 +246,6 @@ class Generator(object):\n>                 variant=None,\n>                 bytecode_path=None,\n>                 for_blink=False,\n> -               js_bindings_mode=\"new\",\n>                 js_generate_struct_deserializers=False,\n>                 export_attribute=None,\n>                 export_header=None,\n> @@ -262,7 +264,6 @@ class Generator(object):\n>      self.variant = variant\n>      self.bytecode_path = bytecode_path\n>      self.for_blink = for_blink\n> -    self.js_bindings_mode = js_bindings_mode\n>      self.js_generate_struct_deserializers = js_generate_struct_deserializers\n>      self.export_attribute = export_attribute\n>      self.export_header = export_header\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n> index 32c884a8c02e..7143e07c4d7e 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py\n> @@ -1,13 +1,12 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> +import importlib.util\n>  import os.path\n>  import sys\n>  import unittest\n>  \n> -\n>  def _GetDirAbove(dirname):\n>    \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n>    also be \"above\" this file).\"\"\"\n> @@ -20,12 +19,11 @@ def _GetDirAbove(dirname):\n>  \n>  \n>  try:\n> -  imp.find_module(\"mojom\")\n> +  importlib.util.find_spec(\"mojom\")\n>  except ImportError:\n>    sys.path.append(os.path.join(_GetDirAbove(\"pylib\"), \"pylib\"))\n>  from mojom.generate import generator\n>  \n> -\n>  class StringManipulationTest(unittest.TestCase):\n>    \"\"\"generator contains some string utilities, this tests only those.\"\"\"\n>  \n> @@ -69,6 +67,5 @@ class StringManipulationTest(unittest.TestCase):\n>      self.assertEquals(\"SNAKE_D3D11_CASE\",\n>                        generator.ToUpperSnakeCase(\"snakeD3d11Case\"))\n>  \n> -\n>  if __name__ == \"__main__\":\n>    unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n> index 9bdb28e05f65..ca71059d53b1 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -12,15 +12,14 @@\n>  # method = interface.AddMethod('Tat', 0)\n>  # method.AddParameter('baz', 0, mojom.INT32)\n>  \n> -import sys\n> -if sys.version_info.major == 2:\n> -  import cPickle as pickle\n> -else:\n> -  import pickle\n> +import pickle\n> +from collections import OrderedDict\n>  from uuid import UUID\n>  \n> +# pylint: disable=raise-missing-from\n>  \n> -class BackwardCompatibilityChecker(object):\n> +\n> +class BackwardCompatibilityChecker:\n>    \"\"\"Used for memoization while recursively checking two type definitions for\n>    backward-compatibility.\"\"\"\n>  \n> @@ -64,23 +63,20 @@ def Repr(obj, as_ref=True):\n>      return obj.Repr(as_ref=as_ref)\n>    # Since we cannot implement Repr for existing container types, we\n>    # handle them here.\n> -  elif isinstance(obj, list):\n> +  if isinstance(obj, list):\n>      if not obj:\n>        return '[]'\n> -    else:\n> -      return ('[\\n%s\\n]' % (',\\n'.join(\n> -          '    %s' % Repr(elem, as_ref).replace('\\n', '\\n    ')\n> -          for elem in obj)))\n> -  elif isinstance(obj, dict):\n> +    return ('[\\n%s\\n]' %\n> +            (',\\n'.join('    %s' % Repr(elem, as_ref).replace('\\n', '\\n    ')\n> +                        for elem in obj)))\n> +  if isinstance(obj, dict):\n>      if not obj:\n>        return '{}'\n> -    else:\n> -      return ('{\\n%s\\n}' % (',\\n'.join(\n> -          '    %s: %s' % (Repr(key, as_ref).replace('\\n', '\\n    '),\n> -                          Repr(val, as_ref).replace('\\n', '\\n    '))\n> -          for key, val in obj.items())))\n> -  else:\n> -    return repr(obj)\n> +    return ('{\\n%s\\n}' % (',\\n'.join('    %s: %s' %\n> +                                     (Repr(key, as_ref).replace('\\n', '\\n    '),\n> +                                      Repr(val, as_ref).replace('\\n', '\\n    '))\n> +                                     for key, val in obj.items())))\n> +  return repr(obj)\n>  \n>  \n>  def GenericRepr(obj, names):\n> @@ -104,7 +100,7 @@ def GenericRepr(obj, names):\n>        ReprIndent(name, as_ref) for (name, as_ref) in names.items()))\n>  \n>  \n> -class Kind(object):\n> +class Kind:\n>    \"\"\"Kind represents a type (e.g. int8, string).\n>  \n>    Attributes:\n> @@ -112,16 +108,43 @@ class Kind(object):\n>      module: {Module} The defining module. Set to None for built-in types.\n>      parent_kind: The enclosing type. For example, an enum defined\n>          inside an interface has that interface as its parent. May be None.\n> +    is_nullable: True if the type is nullable.\n>    \"\"\"\n>  \n> -  def __init__(self, spec=None, module=None):\n> +  def __init__(self, spec=None, is_nullable=False, module=None):\n>      self.spec = spec\n>      self.module = module\n>      self.parent_kind = None\n> +    self.is_nullable = is_nullable\n> +    self.shared_definition = {}\n> +\n> +  @classmethod\n> +  def AddSharedProperty(cls, name):\n> +    \"\"\"Adds a property |name| to |cls|, which accesses the corresponding item in\n> +       |shared_definition|.\n> +\n> +       The reason of adding such indirection is to enable sharing definition\n> +       between a reference kind and its nullable variation. For example:\n> +         a = Struct('test_struct_1')\n> +         b = a.MakeNullableKind()\n> +         a.name = 'test_struct_2'\n> +         print(b.name)  # Outputs 'test_struct_2'.\n> +    \"\"\"\n> +    def Get(self):\n> +      try:\n> +        return self.shared_definition[name]\n> +      except KeyError:  # Must raise AttributeError if property doesn't exist.\n> +        raise AttributeError\n> +\n> +    def Set(self, value):\n> +      self.shared_definition[name] = value\n> +\n> +    setattr(cls, name, property(Get, Set))\n>  \n>    def Repr(self, as_ref=True):\n>      # pylint: disable=unused-argument\n> -    return '<%s spec=%r>' % (self.__class__.__name__, self.spec)\n> +    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,\n> +                                            self.is_nullable)\n>  \n>    def __repr__(self):\n>      # Gives us a decent __repr__ for all kinds.\n> @@ -130,7 +153,8 @@ class Kind(object):\n>    def __eq__(self, rhs):\n>      # pylint: disable=unidiomatic-typecheck\n>      return (type(self) == type(rhs)\n> -            and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind))\n> +            and (self.spec, self.parent_kind, self.is_nullable)\n> +            == (rhs.spec, rhs.parent_kind, rhs.is_nullable))\n>  \n>    def __hash__(self):\n>      # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind\n> @@ -138,32 +162,113 @@ class Kind(object):\n>      # some primitive Kinds as dict keys. The default hash (object identity)\n>      # breaks these dicts when a pickled Module instance is unpickled and used\n>      # during a subsequent run of the parser.\n> -    return hash((self.spec, self.parent_kind))\n> +    return hash((self.spec, self.parent_kind, self.is_nullable))\n>  \n>    # pylint: disable=unused-argument\n>    def IsBackwardCompatible(self, rhs, checker):\n>      return self == rhs\n>  \n>  \n> +class ValueKind(Kind):\n> +  \"\"\"ValueKind represents values that aren't reference kinds.\n> +\n> +  The primary difference is the wire representation for nullable value kinds\n> +  still reserves space for the value type itself, even if that value itself\n> +  is logically null.\n> +  \"\"\"\n> +  def __init__(self, spec=None, is_nullable=False, module=None):\n> +    assert spec is None or is_nullable == spec.startswith('?')\n> +    Kind.__init__(self, spec, is_nullable, module)\n> +\n> +  def MakeNullableKind(self):\n> +    assert not self.is_nullable\n> +\n> +    if self == BOOL:\n> +      return NULLABLE_BOOL\n> +    if self == INT8:\n> +      return NULLABLE_INT8\n> +    if self == INT16:\n> +      return NULLABLE_INT16\n> +    if self == INT32:\n> +      return NULLABLE_INT32\n> +    if self == INT64:\n> +      return NULLABLE_INT64\n> +    if self == UINT8:\n> +      return NULLABLE_UINT8\n> +    if self == UINT16:\n> +      return NULLABLE_UINT16\n> +    if self == UINT32:\n> +      return NULLABLE_UINT32\n> +    if self == UINT64:\n> +      return NULLABLE_UINT64\n> +    if self == FLOAT:\n> +      return NULLABLE_FLOAT\n> +    if self == DOUBLE:\n> +      return NULLABLE_DOUBLE\n> +\n> +    nullable_kind = type(self)()\n> +    nullable_kind.shared_definition = self.shared_definition\n> +    if self.spec is not None:\n> +      nullable_kind.spec = '?' + self.spec\n> +    nullable_kind.is_nullable = True\n> +    nullable_kind.parent_kind = self.parent_kind\n> +    nullable_kind.module = self.module\n> +\n> +    return nullable_kind\n> +\n> +  def MakeUnnullableKind(self):\n> +    assert self.is_nullable\n> +\n> +    if self == NULLABLE_BOOL:\n> +      return BOOL\n> +    if self == NULLABLE_INT8:\n> +      return INT8\n> +    if self == NULLABLE_INT16:\n> +      return INT16\n> +    if self == NULLABLE_INT32:\n> +      return INT32\n> +    if self == NULLABLE_INT64:\n> +      return INT64\n> +    if self == NULLABLE_UINT8:\n> +      return UINT8\n> +    if self == NULLABLE_UINT16:\n> +      return UINT16\n> +    if self == NULLABLE_UINT32:\n> +      return UINT32\n> +    if self == NULLABLE_UINT64:\n> +      return UINT64\n> +    if self == NULLABLE_FLOAT:\n> +      return FLOAT\n> +    if self == NULLABLE_DOUBLE:\n> +      return DOUBLE\n> +\n> +    nullable_kind = type(self)()\n> +    nullable_kind.shared_definition = self.shared_definition\n> +    if self.spec is not None:\n> +      nullable_kind.spec = self.spec[1:]\n> +    nullable_kind.is_nullable = False\n> +    nullable_kind.parent_kind = self.parent_kind\n> +    nullable_kind.module = self.module\n> +\n> +    return nullable_kind\n> +\n> +  def __eq__(self, rhs):\n> +    return (isinstance(rhs, ValueKind) and super().__eq__(rhs))\n> +\n> +  def __hash__(self):  # pylint: disable=useless-super-delegation\n> +    return super().__hash__()\n> +\n> +\n>  class ReferenceKind(Kind):\n>    \"\"\"ReferenceKind represents pointer and handle types.\n>  \n>    A type is nullable if null (for pointer types) or invalid handle (for handle\n>    types) is a legal value for the type.\n> -\n> -  Attributes:\n> -    is_nullable: True if the type is nullable.\n>    \"\"\"\n>  \n>    def __init__(self, spec=None, is_nullable=False, module=None):\n>      assert spec is None or is_nullable == spec.startswith('?')\n> -    Kind.__init__(self, spec, module)\n> -    self.is_nullable = is_nullable\n> -    self.shared_definition = {}\n> -\n> -  def Repr(self, as_ref=True):\n> -    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,\n> -                                            self.is_nullable)\n> +    Kind.__init__(self, spec, is_nullable, module)\n>  \n>    def MakeNullableKind(self):\n>      assert not self.is_nullable\n> @@ -193,55 +298,65 @@ class ReferenceKind(Kind):\n>  \n>      return nullable_kind\n>  \n> -  @classmethod\n> -  def AddSharedProperty(cls, name):\n> -    \"\"\"Adds a property |name| to |cls|, which accesses the corresponding item in\n> -       |shared_definition|.\n> +  def MakeUnnullableKind(self):\n> +    assert self.is_nullable\n>  \n> -       The reason of adding such indirection is to enable sharing definition\n> -       between a reference kind and its nullable variation. For example:\n> -         a = Struct('test_struct_1')\n> -         b = a.MakeNullableKind()\n> -         a.name = 'test_struct_2'\n> -         print(b.name)  # Outputs 'test_struct_2'.\n> -    \"\"\"\n> +    if self == NULLABLE_STRING:\n> +      return STRING\n> +    if self == NULLABLE_HANDLE:\n> +      return HANDLE\n> +    if self == NULLABLE_DCPIPE:\n> +      return DCPIPE\n> +    if self == NULLABLE_DPPIPE:\n> +      return DPPIPE\n> +    if self == NULLABLE_MSGPIPE:\n> +      return MSGPIPE\n> +    if self == NULLABLE_SHAREDBUFFER:\n> +      return SHAREDBUFFER\n> +    if self == NULLABLE_PLATFORMHANDLE:\n> +      return PLATFORMHANDLE\n>  \n> -    def Get(self):\n> -      try:\n> -        return self.shared_definition[name]\n> -      except KeyError:  # Must raise AttributeError if property doesn't exist.\n> -        raise AttributeError\n> +    unnullable_kind = type(self)()\n> +    unnullable_kind.shared_definition = self.shared_definition\n> +    if self.spec is not None:\n> +      assert self.spec[0] == '?'\n> +      unnullable_kind.spec = self.spec[1:]\n> +    unnullable_kind.is_nullable = False\n> +    unnullable_kind.parent_kind = self.parent_kind\n> +    unnullable_kind.module = self.module\n>  \n> -    def Set(self, value):\n> -      self.shared_definition[name] = value\n> -\n> -    setattr(cls, name, property(Get, Set))\n> +    return unnullable_kind\n>  \n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, ReferenceKind)\n> -            and super(ReferenceKind, self).__eq__(rhs)\n> -            and self.is_nullable == rhs.is_nullable)\n> +    return (isinstance(rhs, ReferenceKind) and super().__eq__(rhs))\n>  \n> -  def __hash__(self):\n> -    return hash((super(ReferenceKind, self).__hash__(), self.is_nullable))\n> -\n> -  def IsBackwardCompatible(self, rhs, checker):\n> -    return (super(ReferenceKind, self).IsBackwardCompatible(rhs, checker)\n> -            and self.is_nullable == rhs.is_nullable)\n> +  def __hash__(self):  # pylint: disable=useless-super-delegation\n> +    return super().__hash__()\n>  \n>  \n>  # Initialize the set of primitive types. These can be accessed by clients.\n> -BOOL = Kind('b')\n> -INT8 = Kind('i8')\n> -INT16 = Kind('i16')\n> -INT32 = Kind('i32')\n> -INT64 = Kind('i64')\n> -UINT8 = Kind('u8')\n> -UINT16 = Kind('u16')\n> -UINT32 = Kind('u32')\n> -UINT64 = Kind('u64')\n> -FLOAT = Kind('f')\n> -DOUBLE = Kind('d')\n> +BOOL = ValueKind('b')\n> +INT8 = ValueKind('i8')\n> +INT16 = ValueKind('i16')\n> +INT32 = ValueKind('i32')\n> +INT64 = ValueKind('i64')\n> +UINT8 = ValueKind('u8')\n> +UINT16 = ValueKind('u16')\n> +UINT32 = ValueKind('u32')\n> +UINT64 = ValueKind('u64')\n> +FLOAT = ValueKind('f')\n> +DOUBLE = ValueKind('d')\n> +NULLABLE_BOOL = ValueKind('?b', True)\n> +NULLABLE_INT8 = ValueKind('?i8', True)\n> +NULLABLE_INT16 = ValueKind('?i16', True)\n> +NULLABLE_INT32 = ValueKind('?i32', True)\n> +NULLABLE_INT64 = ValueKind('?i64', True)\n> +NULLABLE_UINT8 = ValueKind('?u8', True)\n> +NULLABLE_UINT16 = ValueKind('?u16', True)\n> +NULLABLE_UINT32 = ValueKind('?u32', True)\n> +NULLABLE_UINT64 = ValueKind('?u64', True)\n> +NULLABLE_FLOAT = ValueKind('?f', True)\n> +NULLABLE_DOUBLE = ValueKind('?d', True)\n>  STRING = ReferenceKind('s')\n>  HANDLE = ReferenceKind('h')\n>  DCPIPE = ReferenceKind('h:d:c')\n> @@ -270,6 +385,17 @@ PRIMITIVES = (\n>      UINT64,\n>      FLOAT,\n>      DOUBLE,\n> +    NULLABLE_BOOL,\n> +    NULLABLE_INT8,\n> +    NULLABLE_INT16,\n> +    NULLABLE_INT32,\n> +    NULLABLE_INT64,\n> +    NULLABLE_UINT8,\n> +    NULLABLE_UINT16,\n> +    NULLABLE_UINT32,\n> +    NULLABLE_UINT64,\n> +    NULLABLE_FLOAT,\n> +    NULLABLE_DOUBLE,\n>      STRING,\n>      HANDLE,\n>      DCPIPE,\n> @@ -291,12 +417,17 @@ ATTRIBUTE_DEFAULT = 'Default'\n>  ATTRIBUTE_EXTENSIBLE = 'Extensible'\n>  ATTRIBUTE_NO_INTERRUPT = 'NoInterrupt'\n>  ATTRIBUTE_STABLE = 'Stable'\n> +ATTRIBUTE_SUPPORTS_URGENT = 'SupportsUrgent'\n>  ATTRIBUTE_SYNC = 'Sync'\n>  ATTRIBUTE_UNLIMITED_SIZE = 'UnlimitedSize'\n>  ATTRIBUTE_UUID = 'Uuid'\n> +ATTRIBUTE_SERVICE_SANDBOX = 'ServiceSandbox'\n> +ATTRIBUTE_REQUIRE_CONTEXT = 'RequireContext'\n> +ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext'\n> +ATTRIBUTE_RUNTIME_FEATURE = 'RuntimeFeature'\n>  \n>  \n> -class NamedValue(object):\n> +class NamedValue:\n>    def __init__(self, module, parent_kind, mojom_name):\n>      self.module = module\n>      self.parent_kind = parent_kind\n> @@ -316,7 +447,7 @@ class NamedValue(object):\n>      return hash((self.parent_kind, self.mojom_name))\n>  \n>  \n> -class BuiltinValue(object):\n> +class BuiltinValue:\n>    def __init__(self, value):\n>      self.value = value\n>  \n> @@ -350,7 +481,7 @@ class EnumValue(NamedValue):\n>      return self.field.name\n>  \n>  \n> -class Constant(object):\n> +class Constant:\n>    def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None):\n>      self.mojom_name = mojom_name\n>      self.name = None\n> @@ -368,7 +499,7 @@ class Constant(object):\n>                                         rhs.parent_kind))\n>  \n>  \n> -class Field(object):\n> +class Field:\n>    def __init__(self,\n>                 mojom_name=None,\n>                 kind=None,\n> @@ -414,7 +545,18 @@ class StructField(Field):\n>  \n>  \n>  class UnionField(Field):\n> -  pass\n> +  def __init__(self,\n> +               mojom_name=None,\n> +               kind=None,\n> +               ordinal=None,\n> +               default=None,\n> +               attributes=None):\n> +    Field.__init__(self, mojom_name, kind, ordinal, default, attributes)\n> +\n> +  @property\n> +  def is_default(self):\n> +    return self.attributes.get(ATTRIBUTE_DEFAULT, False) \\\n> +        if self.attributes else False\n>  \n>  \n>  def _IsFieldBackwardCompatible(new_field, old_field, checker):\n> @@ -424,6 +566,38 @@ def _IsFieldBackwardCompatible(new_field, old_field, checker):\n>    return checker.IsBackwardCompatible(new_field.kind, old_field.kind)\n>  \n>  \n> +class Feature(ReferenceKind):\n> +  \"\"\"A runtime enabled feature defined from mojom.\n> +\n> +  Attributes:\n> +    mojom_name: {str} The name of the feature type as defined in mojom.\n> +    name: {str} The stylized name. (Note: not the \"name\" used by FeatureList.)\n> +    constants: {List[Constant]} The constants defined in the feature scope.\n> +    attributes: {dict} Additional information about the feature.\n> +  \"\"\"\n> +\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('constants')\n> +  Kind.AddSharedProperty('attributes')\n> +\n> +  def __init__(self, mojom_name=None, module=None, attributes=None):\n> +    if mojom_name is not None:\n> +      spec = 'x:' + mojom_name\n> +    else:\n> +      spec = None\n> +    ReferenceKind.__init__(self, spec, False, module)\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.constants = []\n> +    self.attributes = attributes\n> +\n> +  def Stylize(self, stylizer):\n> +    self.name = stylizer.StylizeFeature(self.mojom_name)\n> +    for constant in self.constants:\n> +      constant.Stylize(stylizer)\n> +\n> +\n>  class Struct(ReferenceKind):\n>    \"\"\"A struct with typed fields.\n>  \n> @@ -441,14 +615,14 @@ class Struct(ReferenceKind):\n>          if it's a native struct.\n>    \"\"\"\n>  \n> -  ReferenceKind.AddSharedProperty('mojom_name')\n> -  ReferenceKind.AddSharedProperty('name')\n> -  ReferenceKind.AddSharedProperty('native_only')\n> -  ReferenceKind.AddSharedProperty('custom_serializer')\n> -  ReferenceKind.AddSharedProperty('fields')\n> -  ReferenceKind.AddSharedProperty('enums')\n> -  ReferenceKind.AddSharedProperty('constants')\n> -  ReferenceKind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('native_only')\n> +  Kind.AddSharedProperty('custom_serializer')\n> +  Kind.AddSharedProperty('fields')\n> +  Kind.AddSharedProperty('enums')\n> +  Kind.AddSharedProperty('constants')\n> +  Kind.AddSharedProperty('attributes')\n>  \n>    def __init__(self, mojom_name=None, module=None, attributes=None):\n>      if mojom_name is not None:\n> @@ -470,12 +644,11 @@ class Struct(ReferenceKind):\n>        return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__,\n>                                                 self.mojom_name,\n>                                                 Repr(self.module, as_ref=True))\n> -    else:\n> -      return GenericRepr(self, {\n> -          'mojom_name': False,\n> -          'fields': False,\n> -          'module': True\n> -      })\n> +    return GenericRepr(self, {\n> +        'mojom_name': False,\n> +        'fields': False,\n> +        'module': True\n> +    })\n>  \n>    def AddField(self,\n>                 mojom_name,\n> @@ -496,13 +669,13 @@ class Struct(ReferenceKind):\n>      for constant in self.constants:\n>        constant.Stylize(stylizer)\n>  \n> -  def IsBackwardCompatible(self, older_struct, checker):\n> -    \"\"\"This struct is backward-compatible with older_struct if and only if all\n> -    of the following conditions hold:\n> +  def IsBackwardCompatible(self, rhs, checker):\n> +    \"\"\"This struct is backward-compatible with rhs (older_struct) if and only if\n> +    all of the following conditions hold:\n>        - Any newly added field is tagged with a [MinVersion] attribute specifying\n>          a version number greater than all previously used [MinVersion]\n>          attributes within the struct.\n> -      - All fields present in older_struct remain present in the new struct,\n> +      - All fields present in rhs remain present in the new struct,\n>          with the same ordinal position, same optional or non-optional status,\n>          same (or backward-compatible) type and where applicable, the same\n>          [MinVersion] attribute value.\n> @@ -521,7 +694,7 @@ class Struct(ReferenceKind):\n>        return fields_by_ordinal\n>  \n>      new_fields = buildOrdinalFieldMap(self)\n> -    old_fields = buildOrdinalFieldMap(older_struct)\n> +    old_fields = buildOrdinalFieldMap(rhs)\n>      if len(new_fields) < len(old_fields):\n>        # At least one field was removed, which is not OK.\n>        return False\n> @@ -574,11 +747,18 @@ class Struct(ReferenceKind):\n>        prefix = self.module.GetNamespacePrefix()\n>      return '%s%s' % (prefix, self.mojom_name)\n>  \n> +  def _tuple(self):\n> +    return (self.mojom_name, self.native_only, self.fields, self.constants,\n> +            self.attributes)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Struct) and\n> -            (self.mojom_name, self.native_only, self.fields, self.constants,\n> -             self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields,\n> -                                  rhs.constants, rhs.attributes))\n> +    return isinstance(rhs, Struct) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>    def __hash__(self):\n>      return id(self)\n> @@ -595,10 +775,11 @@ class Union(ReferenceKind):\n>          which Java class name to use to represent it in the generated\n>          bindings.\n>    \"\"\"\n> -  ReferenceKind.AddSharedProperty('mojom_name')\n> -  ReferenceKind.AddSharedProperty('name')\n> -  ReferenceKind.AddSharedProperty('fields')\n> -  ReferenceKind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('fields')\n> +  Kind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('default_field')\n>  \n>    def __init__(self, mojom_name=None, module=None, attributes=None):\n>      if mojom_name is not None:\n> @@ -610,14 +791,14 @@ class Union(ReferenceKind):\n>      self.name = None\n>      self.fields = []\n>      self.attributes = attributes\n> +    self.default_field = None\n>  \n>    def Repr(self, as_ref=True):\n>      if as_ref:\n>        return '<%s spec=%r is_nullable=%r fields=%s>' % (\n>            self.__class__.__name__, self.spec, self.is_nullable, Repr(\n>                self.fields))\n> -    else:\n> -      return GenericRepr(self, {'fields': True, 'is_nullable': False})\n> +    return GenericRepr(self, {'fields': True, 'is_nullable': False})\n>  \n>    def AddField(self, mojom_name, kind, ordinal=None, attributes=None):\n>      field = UnionField(mojom_name, kind, ordinal, None, attributes)\n> @@ -629,13 +810,13 @@ class Union(ReferenceKind):\n>      for field in self.fields:\n>        field.Stylize(stylizer)\n>  \n> -  def IsBackwardCompatible(self, older_union, checker):\n> -    \"\"\"This union is backward-compatible with older_union if and only if all\n> -    of the following conditions hold:\n> +  def IsBackwardCompatible(self, rhs, checker):\n> +    \"\"\"This union is backward-compatible with rhs (older_union) if and only if\n> +    all of the following conditions hold:\n>        - Any newly added field is tagged with a [MinVersion] attribute specifying\n>          a version number greater than all previously used [MinVersion]\n>          attributes within the union.\n> -      - All fields present in older_union remain present in the new union,\n> +      - All fields present in rhs remain present in the new union,\n>          with the same ordinal value, same optional or non-optional status,\n>          same (or backward-compatible) type, and where applicable, the same\n>          [MinVersion] attribute value.\n> @@ -651,7 +832,7 @@ class Union(ReferenceKind):\n>        return fields_by_ordinal\n>  \n>      new_fields = buildOrdinalFieldMap(self)\n> -    old_fields = buildOrdinalFieldMap(older_union)\n> +    old_fields = buildOrdinalFieldMap(rhs)\n>      if len(new_fields) < len(old_fields):\n>        # At least one field was removed, which is not OK.\n>        return False\n> @@ -677,6 +858,11 @@ class Union(ReferenceKind):\n>  \n>      return True\n>  \n> +  @property\n> +  def extensible(self):\n> +    return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \\\n> +        if self.attributes else False\n> +\n>    @property\n>    def stable(self):\n>      return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n> @@ -690,10 +876,17 @@ class Union(ReferenceKind):\n>        prefix = self.module.GetNamespacePrefix()\n>      return '%s%s' % (prefix, self.mojom_name)\n>  \n> +  def _tuple(self):\n> +    return (self.mojom_name, self.fields, self.attributes)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Union) and\n> -            (self.mojom_name, self.fields,\n> -             self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes))\n> +    return isinstance(rhs, Union) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>    def __hash__(self):\n>      return id(self)\n> @@ -707,8 +900,8 @@ class Array(ReferenceKind):\n>      length: The number of elements. None if unknown.\n>    \"\"\"\n>  \n> -  ReferenceKind.AddSharedProperty('kind')\n> -  ReferenceKind.AddSharedProperty('length')\n> +  Kind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('length')\n>  \n>    def __init__(self, kind=None, length=None):\n>      if kind is not None:\n> @@ -728,12 +921,11 @@ class Array(ReferenceKind):\n>        return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (\n>            self.__class__.__name__, self.spec, self.is_nullable, Repr(\n>                self.kind), self.length)\n> -    else:\n> -      return GenericRepr(self, {\n> -          'kind': True,\n> -          'length': False,\n> -          'is_nullable': False\n> -      })\n> +    return GenericRepr(self, {\n> +        'kind': True,\n> +        'length': False,\n> +        'is_nullable': False\n> +    })\n>  \n>    def __eq__(self, rhs):\n>      return (isinstance(rhs, Array)\n> @@ -754,8 +946,8 @@ class Map(ReferenceKind):\n>      key_kind: {Kind} The type of the keys. May be None.\n>      value_kind: {Kind} The type of the elements. May be None.\n>    \"\"\"\n> -  ReferenceKind.AddSharedProperty('key_kind')\n> -  ReferenceKind.AddSharedProperty('value_kind')\n> +  Kind.AddSharedProperty('key_kind')\n> +  Kind.AddSharedProperty('value_kind')\n>  \n>    def __init__(self, key_kind=None, value_kind=None):\n>      if (key_kind is not None and value_kind is not None):\n> @@ -780,8 +972,7 @@ class Map(ReferenceKind):\n>        return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (\n>            self.__class__.__name__, self.spec, self.is_nullable,\n>            Repr(self.key_kind), Repr(self.value_kind))\n> -    else:\n> -      return GenericRepr(self, {'key_kind': True, 'value_kind': True})\n> +    return GenericRepr(self, {'key_kind': True, 'value_kind': True})\n>  \n>    def __eq__(self, rhs):\n>      return (isinstance(rhs, Map) and\n> @@ -797,7 +988,7 @@ class Map(ReferenceKind):\n>  \n>  \n>  class PendingRemote(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -822,7 +1013,7 @@ class PendingRemote(ReferenceKind):\n>  \n>  \n>  class PendingReceiver(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -847,7 +1038,7 @@ class PendingReceiver(ReferenceKind):\n>  \n>  \n>  class PendingAssociatedRemote(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -873,7 +1064,7 @@ class PendingAssociatedRemote(ReferenceKind):\n>  \n>  \n>  class PendingAssociatedReceiver(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -899,7 +1090,7 @@ class PendingAssociatedReceiver(ReferenceKind):\n>  \n>  \n>  class InterfaceRequest(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -923,7 +1114,7 @@ class InterfaceRequest(ReferenceKind):\n>  \n>  \n>  class AssociatedInterfaceRequest(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -949,7 +1140,7 @@ class AssociatedInterfaceRequest(ReferenceKind):\n>              self.kind, rhs.kind)\n>  \n>  \n> -class Parameter(object):\n> +class Parameter:\n>    def __init__(self,\n>                 mojom_name=None,\n>                 kind=None,\n> @@ -983,7 +1174,7 @@ class Parameter(object):\n>                                        rhs.default, rhs.attributes))\n>  \n>  \n> -class Method(object):\n> +class Method:\n>    def __init__(self, interface, mojom_name, ordinal=None, attributes=None):\n>      self.interface = interface\n>      self.mojom_name = mojom_name\n> @@ -999,12 +1190,11 @@ class Method(object):\n>    def Repr(self, as_ref=True):\n>      if as_ref:\n>        return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n> -    else:\n> -      return GenericRepr(self, {\n> -          'mojom_name': False,\n> -          'parameters': True,\n> -          'response_parameters': True\n> -      })\n> +    return GenericRepr(self, {\n> +        'mojom_name': False,\n> +        'parameters': True,\n> +        'response_parameters': True\n> +    })\n>  \n>    def AddParameter(self,\n>                     mojom_name,\n> @@ -1061,21 +1251,49 @@ class Method(object):\n>      return self.attributes.get(ATTRIBUTE_UNLIMITED_SIZE) \\\n>          if self.attributes else False\n>  \n> +  @property\n> +  def allowed_context(self):\n> +    return self.attributes.get(ATTRIBUTE_ALLOWED_CONTEXT) \\\n> +        if self.attributes else None\n> +\n> +  @property\n> +  def supports_urgent(self):\n> +    return self.attributes.get(ATTRIBUTE_SUPPORTS_URGENT) \\\n> +        if self.attributes else None\n> +\n> +  @property\n> +  def runtime_feature(self):\n> +    if not self.attributes:\n> +      return None\n> +    runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None)\n> +    if runtime_feature is None:\n> +      return None\n> +    if not isinstance(runtime_feature, Feature):\n> +      raise Exception(\"RuntimeFeature attribute on %s must be a feature.\" %\n> +                      self.name)\n> +    return runtime_feature\n> +\n> +  def _tuple(self):\n> +    return (self.mojom_name, self.ordinal, self.parameters,\n> +            self.response_parameters, self.attributes)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Method) and\n> -            (self.mojom_name, self.ordinal, self.parameters,\n> -             self.response_parameters,\n> -             self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters,\n> -                                  rhs.response_parameters, rhs.attributes))\n> +    return isinstance(rhs, Method) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>  \n>  class Interface(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('mojom_name')\n> -  ReferenceKind.AddSharedProperty('name')\n> -  ReferenceKind.AddSharedProperty('methods')\n> -  ReferenceKind.AddSharedProperty('enums')\n> -  ReferenceKind.AddSharedProperty('constants')\n> -  ReferenceKind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('methods')\n> +  Kind.AddSharedProperty('enums')\n> +  Kind.AddSharedProperty('constants')\n> +  Kind.AddSharedProperty('attributes')\n>  \n>    def __init__(self, mojom_name=None, module=None, attributes=None):\n>      if mojom_name is not None:\n> @@ -1093,12 +1311,11 @@ class Interface(ReferenceKind):\n>    def Repr(self, as_ref=True):\n>      if as_ref:\n>        return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n> -    else:\n> -      return GenericRepr(self, {\n> -          'mojom_name': False,\n> -          'attributes': False,\n> -          'methods': False\n> -      })\n> +    return GenericRepr(self, {\n> +        'mojom_name': False,\n> +        'attributes': False,\n> +        'methods': False\n> +    })\n>  \n>    def AddMethod(self, mojom_name, ordinal=None, attributes=None):\n>      method = Method(self, mojom_name, ordinal, attributes)\n> @@ -1114,10 +1331,10 @@ class Interface(ReferenceKind):\n>      for constant in self.constants:\n>        constant.Stylize(stylizer)\n>  \n> -  def IsBackwardCompatible(self, older_interface, checker):\n> -    \"\"\"This interface is backward-compatible with older_interface if and only\n> -    if all of the following conditions hold:\n> -      - All defined methods in older_interface (when identified by ordinal) have\n> +  def IsBackwardCompatible(self, rhs, checker):\n> +    \"\"\"This interface is backward-compatible with rhs (older_interface) if and\n> +    only if all of the following conditions hold:\n> +      - All defined methods in rhs (when identified by ordinal) have\n>          backward-compatible definitions in this interface. For each method this\n>          means:\n>            - The parameter list is backward-compatible, according to backward-\n> @@ -1131,7 +1348,7 @@ class Interface(ReferenceKind):\n>              rules for structs.\n>        - All newly introduced methods in this interface have a [MinVersion]\n>          attribute specifying a version greater than any method in\n> -        older_interface.\n> +        rhs.\n>      \"\"\"\n>  \n>      def buildOrdinalMethodMap(interface):\n> @@ -1144,7 +1361,7 @@ class Interface(ReferenceKind):\n>        return methods_by_ordinal\n>  \n>      new_methods = buildOrdinalMethodMap(self)\n> -    old_methods = buildOrdinalMethodMap(older_interface)\n> +    old_methods = buildOrdinalMethodMap(rhs)\n>      max_old_min_version = 0\n>      for ordinal, old_method in old_methods.items():\n>        new_method = new_methods.get(ordinal)\n> @@ -1186,6 +1403,39 @@ class Interface(ReferenceKind):\n>  \n>      return True\n>  \n> +  @property\n> +  def service_sandbox(self):\n> +    if not self.attributes:\n> +      return None\n> +    service_sandbox = self.attributes.get(ATTRIBUTE_SERVICE_SANDBOX, None)\n> +    if service_sandbox is None:\n> +      return None\n> +    # Constants are only allowed to refer to an enum here, so replace.\n> +    if isinstance(service_sandbox, Constant):\n> +      service_sandbox = service_sandbox.value\n> +    if not isinstance(service_sandbox, EnumValue):\n> +      raise Exception(\"ServiceSandbox attribute on %s must be an enum value.\" %\n> +                      self.module.name)\n> +    return service_sandbox\n> +\n> +  @property\n> +  def runtime_feature(self):\n> +    if not self.attributes:\n> +      return None\n> +    runtime_feature = self.attributes.get(ATTRIBUTE_RUNTIME_FEATURE, None)\n> +    if runtime_feature is None:\n> +      return None\n> +    if not isinstance(runtime_feature, Feature):\n> +      raise Exception(\"RuntimeFeature attribute on %s must be a feature.\" %\n> +                      self.name)\n> +    return runtime_feature\n> +\n> +  @property\n> +  def require_context(self):\n> +    if not self.attributes:\n> +      return None\n> +    return self.attributes.get(ATTRIBUTE_REQUIRE_CONTEXT, None)\n> +\n>    @property\n>    def stable(self):\n>      return self.attributes.get(ATTRIBUTE_STABLE, False) \\\n> @@ -1199,11 +1449,18 @@ class Interface(ReferenceKind):\n>        prefix = self.module.GetNamespacePrefix()\n>      return '%s%s' % (prefix, self.mojom_name)\n>  \n> +  def _tuple(self):\n> +    return (self.mojom_name, self.methods, self.enums, self.constants,\n> +            self.attributes)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Interface)\n> -            and (self.mojom_name, self.methods, self.enums, self.constants,\n> -                 self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums,\n> -                                      rhs.constants, rhs.attributes))\n> +    return isinstance(rhs, Interface) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>    @property\n>    def uuid(self):\n> @@ -1224,7 +1481,7 @@ class Interface(ReferenceKind):\n>  \n>  \n>  class AssociatedInterface(ReferenceKind):\n> -  ReferenceKind.AddSharedProperty('kind')\n> +  Kind.AddSharedProperty('kind')\n>  \n>    def __init__(self, kind=None):\n>      if kind is not None:\n> @@ -1249,7 +1506,7 @@ class AssociatedInterface(ReferenceKind):\n>                            self.kind, rhs.kind)\n>  \n>  \n> -class EnumField(object):\n> +class EnumField:\n>    def __init__(self,\n>                 mojom_name=None,\n>                 value=None,\n> @@ -1281,16 +1538,25 @@ class EnumField(object):\n>                                           rhs.attributes, rhs.numeric_value))\n>  \n>  \n> -class Enum(Kind):\n> +class Enum(ValueKind):\n> +  Kind.AddSharedProperty('mojom_name')\n> +  Kind.AddSharedProperty('name')\n> +  Kind.AddSharedProperty('native_only')\n> +  Kind.AddSharedProperty('fields')\n> +  Kind.AddSharedProperty('attributes')\n> +  Kind.AddSharedProperty('min_value')\n> +  Kind.AddSharedProperty('max_value')\n> +  Kind.AddSharedProperty('default_field')\n> +\n>    def __init__(self, mojom_name=None, module=None, attributes=None):\n> -    self.mojom_name = mojom_name\n> -    self.name = None\n> -    self.native_only = False\n>      if mojom_name is not None:\n>        spec = 'x:' + mojom_name\n>      else:\n>        spec = None\n> -    Kind.__init__(self, spec, module)\n> +    ValueKind.__init__(self, spec, False, module)\n> +    self.mojom_name = mojom_name\n> +    self.name = None\n> +    self.native_only = False\n>      self.fields = []\n>      self.attributes = attributes\n>      self.min_value = None\n> @@ -1300,8 +1566,7 @@ class Enum(Kind):\n>    def Repr(self, as_ref=True):\n>      if as_ref:\n>        return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)\n> -    else:\n> -      return GenericRepr(self, {'mojom_name': False, 'fields': False})\n> +    return GenericRepr(self, {'mojom_name': False, 'fields': False})\n>  \n>    def Stylize(self, stylizer):\n>      self.name = stylizer.StylizeEnum(self.mojom_name)\n> @@ -1327,14 +1592,14 @@ class Enum(Kind):\n>      return '%s%s' % (prefix, self.mojom_name)\n>  \n>    # pylint: disable=unused-argument\n> -  def IsBackwardCompatible(self, older_enum, checker):\n> -    \"\"\"This enum is backward-compatible with older_enum if and only if one of\n> -    the following conditions holds:\n> +  def IsBackwardCompatible(self, rhs, checker):\n> +    \"\"\"This enum is backward-compatible with rhs (older_enum) if and only if one\n> +    of the following conditions holds:\n>          - Neither enum is [Extensible] and both have the exact same set of valid\n>            numeric values. Field names and aliases for the same numeric value do\n>            not affect compatibility.\n> -        - older_enum is [Extensible], and for every version defined by\n> -          older_enum, this enum has the exact same set of valid numeric values.\n> +        - rhs is [Extensible], and for every version defined by\n> +          rhs, this enum has the exact same set of valid numeric values.\n>      \"\"\"\n>  \n>      def buildVersionFieldMap(enum):\n> @@ -1345,32 +1610,49 @@ class Enum(Kind):\n>          fields_by_min_version[field.min_version].add(field.numeric_value)\n>        return fields_by_min_version\n>  \n> -    old_fields = buildVersionFieldMap(older_enum)\n> +    old_fields = buildVersionFieldMap(rhs)\n>      new_fields = buildVersionFieldMap(self)\n>  \n> -    if new_fields.keys() != old_fields.keys() and not older_enum.extensible:\n> -      return False\n> +    if new_fields.keys() != old_fields.keys() and not rhs.extensible:\n> +      raise Exception(\"Non-extensible enum cannot be modified\")\n>  \n>      for min_version, valid_values in old_fields.items():\n> -      if (min_version not in new_fields\n> -          or new_fields[min_version] != valid_values):\n> -        return False\n> +      if min_version not in new_fields:\n> +        raise Exception('New values added to an extensible enum '\n> +                        'do not specify MinVersion: %s' % new_fields)\n>  \n> +      if (new_fields[min_version] != valid_values):\n> +        if (len(new_fields[min_version]) < len(valid_values)):\n> +          raise Exception('Removing values for an existing MinVersion %s '\n> +                          'is not allowed' % min_version)\n> +\n> +        raise Exception(\n> +            'New values don\\'t match old values'\n> +            'for an existing MinVersion %s,'\n> +            ' please specify MinVersion equal to \"Next version\" '\n> +            'in the enum description'\n> +            ' for the following values:\\n%s' %\n> +            (min_version, new_fields[min_version].difference(valid_values)))\n>      return True\n>  \n> +  def _tuple(self):\n> +    return (self.mojom_name, self.native_only, self.fields, self.attributes,\n> +            self.min_value, self.max_value, self.default_field)\n> +\n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Enum) and\n> -            (self.mojom_name, self.native_only, self.fields, self.attributes,\n> -             self.min_value, self.max_value,\n> -             self.default_field) == (rhs.mojom_name, rhs.native_only,\n> -                                     rhs.fields, rhs.attributes, rhs.min_value,\n> -                                     rhs.max_value, rhs.default_field))\n> +    return isinstance(rhs, Enum) and self._tuple() == rhs._tuple()\n> +\n> +  def __lt__(self, rhs):\n> +    if not isinstance(self, type(rhs)):\n> +      return str(type(self)) < str(type(rhs))\n> +\n> +    return self._tuple() < rhs._tuple()\n>  \n>    def __hash__(self):\n>      return id(self)\n>  \n>  \n> -class Module(object):\n> +class Module:\n>    def __init__(self, path=None, mojom_namespace=None, attributes=None):\n>      self.path = path\n>      self.mojom_namespace = mojom_namespace\n> @@ -1379,24 +1661,26 @@ class Module(object):\n>      self.unions = []\n>      self.interfaces = []\n>      self.enums = []\n> +    self.features = []\n>      self.constants = []\n> -    self.kinds = {}\n> +    self.kinds = OrderedDict()\n>      self.attributes = attributes\n>      self.imports = []\n> -    self.imported_kinds = {}\n> -    self.metadata = {}\n> +    self.imported_kinds = OrderedDict()\n> +    self.metadata = OrderedDict()\n>  \n>    def __repr__(self):\n>      # Gives us a decent __repr__ for modules.\n>      return self.Repr()\n>  \n>    def __eq__(self, rhs):\n> -    return (isinstance(rhs, Module) and\n> -            (self.path, self.attributes, self.mojom_namespace, self.imports,\n> -             self.constants, self.enums, self.structs, self.unions,\n> -             self.interfaces) == (rhs.path, rhs.attributes, rhs.mojom_namespace,\n> -                                  rhs.imports, rhs.constants, rhs.enums,\n> -                                  rhs.structs, rhs.unions, rhs.interfaces))\n> +    return (isinstance(rhs, Module)\n> +            and (self.path, self.attributes, self.mojom_namespace, self.imports,\n> +                 self.constants, self.enums, self.structs, self.unions,\n> +                 self.interfaces, self.features)\n> +            == (rhs.path, rhs.attributes, rhs.mojom_namespace, rhs.imports,\n> +                rhs.constants, rhs.enums, rhs.structs, rhs.unions,\n> +                rhs.interfaces, rhs.features))\n>  \n>    def __hash__(self):\n>      return id(self)\n> @@ -1405,16 +1689,16 @@ class Module(object):\n>      if as_ref:\n>        return '<%s path=%r mojom_namespace=%r>' % (\n>            self.__class__.__name__, self.path, self.mojom_namespace)\n> -    else:\n> -      return GenericRepr(\n> -          self, {\n> -              'path': False,\n> -              'mojom_namespace': False,\n> -              'attributes': False,\n> -              'structs': False,\n> -              'interfaces': False,\n> -              'unions': False\n> -          })\n> +    return GenericRepr(\n> +        self, {\n> +            'path': False,\n> +            'mojom_namespace': False,\n> +            'attributes': False,\n> +            'structs': False,\n> +            'interfaces': False,\n> +            'unions': False,\n> +            'features': False,\n> +        })\n>  \n>    def GetNamespacePrefix(self):\n>      return '%s.' % self.mojom_namespace if self.mojom_namespace else ''\n> @@ -1434,6 +1718,11 @@ class Module(object):\n>      self.unions.append(union)\n>      return union\n>  \n> +  def AddFeature(self, mojom_name, attributes=None):\n> +    feature = Feature(mojom_name, self, attributes)\n> +    self.features.append(feature)\n> +    return feature\n> +\n>    def Stylize(self, stylizer):\n>      self.namespace = stylizer.StylizeModule(self.mojom_namespace)\n>      for struct in self.structs:\n> @@ -1446,12 +1735,14 @@ class Module(object):\n>        enum.Stylize(stylizer)\n>      for constant in self.constants:\n>        constant.Stylize(stylizer)\n> +    for feature in self.features:\n> +      feature.Stylize(stylizer)\n>  \n>      for imported_module in self.imports:\n>        imported_module.Stylize(stylizer)\n>  \n>    def Dump(self, f):\n> -    pickle.dump(self, f, 2)\n> +    pickle.dump(self, f)\n>  \n>    @classmethod\n>    def Load(cls, f):\n> @@ -1461,15 +1752,15 @@ class Module(object):\n>  \n>  \n>  def IsBoolKind(kind):\n> -  return kind.spec == BOOL.spec\n> +  return kind.spec == BOOL.spec or kind.spec == NULLABLE_BOOL.spec\n>  \n>  \n>  def IsFloatKind(kind):\n> -  return kind.spec == FLOAT.spec\n> +  return kind.spec == FLOAT.spec or kind.spec == NULLABLE_FLOAT.spec\n>  \n>  \n>  def IsDoubleKind(kind):\n> -  return kind.spec == DOUBLE.spec\n> +  return kind.spec == DOUBLE.spec or kind.spec == NULLABLE_DOUBLE.spec\n>  \n>  \n>  def IsIntegralKind(kind):\n> @@ -1477,7 +1768,14 @@ def IsIntegralKind(kind):\n>            or kind.spec == INT16.spec or kind.spec == INT32.spec\n>            or kind.spec == INT64.spec or kind.spec == UINT8.spec\n>            or kind.spec == UINT16.spec or kind.spec == UINT32.spec\n> -          or kind.spec == UINT64.spec)\n> +          or kind.spec == UINT64.spec or kind.spec == NULLABLE_BOOL.spec\n> +          or kind.spec == NULLABLE_INT8.spec or kind.spec == NULLABLE_INT16.spec\n> +          or kind.spec == NULLABLE_INT32.spec\n> +          or kind.spec == NULLABLE_INT64.spec\n> +          or kind.spec == NULLABLE_UINT8.spec\n> +          or kind.spec == NULLABLE_UINT16.spec\n> +          or kind.spec == NULLABLE_UINT32.spec\n> +          or kind.spec == NULLABLE_UINT64.spec)\n>  \n>  \n>  def IsStringKind(kind):\n> @@ -1522,6 +1820,10 @@ def IsArrayKind(kind):\n>    return isinstance(kind, Array)\n>  \n>  \n> +def IsFeatureKind(kind):\n> +  return isinstance(kind, Feature)\n> +\n> +\n>  def IsInterfaceKind(kind):\n>    return isinstance(kind, Interface)\n>  \n> @@ -1558,12 +1860,16 @@ def IsEnumKind(kind):\n>    return isinstance(kind, Enum)\n>  \n>  \n> +def IsValueKind(kind):\n> +  return isinstance(kind, ValueKind)\n> +\n> +\n>  def IsReferenceKind(kind):\n>    return isinstance(kind, ReferenceKind)\n>  \n>  \n>  def IsNullableKind(kind):\n> -  return IsReferenceKind(kind) and kind.is_nullable\n> +  return kind.is_nullable\n>  \n>  \n>  def IsMapKind(kind):\n> @@ -1664,11 +1970,8 @@ def MethodPassesInterfaces(method):\n>    return _AnyMethodParameterRecursive(method, IsInterfaceKind)\n>  \n>  \n> -def HasSyncMethods(interface):\n> -  for method in interface.methods:\n> -    if method.sync:\n> -      return True\n> -  return False\n> +def GetSyncMethodOrdinals(interface):\n> +  return [method.ordinal for method in interface.methods if method.sync]\n>  \n>  \n>  def HasUninterruptableMethods(interface):\n> @@ -1700,18 +2003,17 @@ def ContainsHandlesOrInterfaces(kind):\n>      checked.add(kind.spec)\n>      if IsStructKind(kind):\n>        return any(Check(field.kind) for field in kind.fields)\n> -    elif IsUnionKind(kind):\n> +    if IsUnionKind(kind):\n>        return any(Check(field.kind) for field in kind.fields)\n> -    elif IsAnyHandleKind(kind):\n> +    if IsAnyHandleKind(kind):\n>        return True\n> -    elif IsAnyInterfaceKind(kind):\n> +    if IsAnyInterfaceKind(kind):\n>        return True\n> -    elif IsArrayKind(kind):\n> +    if IsArrayKind(kind):\n>        return Check(kind.kind)\n> -    elif IsMapKind(kind):\n> +    if IsMapKind(kind):\n>        return Check(kind.key_kind) or Check(kind.value_kind)\n> -    else:\n> -      return False\n> +    return False\n>  \n>    return Check(kind)\n>  \n> @@ -1738,21 +2040,20 @@ def ContainsNativeTypes(kind):\n>      checked.add(kind.spec)\n>      if IsEnumKind(kind):\n>        return kind.native_only\n> -    elif IsStructKind(kind):\n> +    if IsStructKind(kind):\n>        if kind.native_only:\n>          return True\n>        if any(enum.native_only for enum in kind.enums):\n>          return True\n>        return any(Check(field.kind) for field in kind.fields)\n> -    elif IsUnionKind(kind):\n> +    if IsUnionKind(kind):\n>        return any(Check(field.kind) for field in kind.fields)\n> -    elif IsInterfaceKind(kind):\n> +    if IsInterfaceKind(kind):\n>        return any(enum.native_only for enum in kind.enums)\n> -    elif IsArrayKind(kind):\n> +    if IsArrayKind(kind):\n>        return Check(kind.kind)\n> -    elif IsMapKind(kind):\n> +    if IsMapKind(kind):\n>        return Check(kind.key_kind) or Check(kind.value_kind)\n> -    else:\n> -      return False\n> +    return False\n>  \n>    return Check(kind)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n> index e8fd4936ce82..2a4e852c54d0 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n> index 88b77c9887c5..612404260f75 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py\n> @@ -1,7 +1,8 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> +import copy\n>  from mojom.generate import module as mojom\n>  \n>  # This module provides a mechanism for determining the packed order and offsets\n> @@ -15,7 +16,7 @@ from mojom.generate import module as mojom\n>  HEADER_SIZE = 8\n>  \n>  \n> -class PackedField(object):\n> +class PackedField:\n>    kind_to_size = {\n>        mojom.BOOL: 1,\n>        mojom.INT8: 1,\n> @@ -75,18 +76,55 @@ class PackedField(object):\n>        return 8\n>      return cls.GetSizeForKind(kind)\n>  \n> -  def __init__(self, field, index, ordinal):\n> +  def __init__(self,\n> +               field,\n> +               index,\n> +               ordinal,\n> +               original_field=None,\n> +               sub_ordinal=None,\n> +               linked_value_packed_field=None):\n>      \"\"\"\n>      Args:\n>        field: the original field.\n>        index: the position of the original field in the struct.\n>        ordinal: the ordinal of the field for serialization.\n> +      original_field: See below.\n> +      sub_ordinal: See below.\n> +      linked_value_packed_field: See below.\n> +\n> +    original_field, sub_ordinal, and linked_value_packed_field are used to\n> +    support nullable ValueKind fields. For legacy reasons, nullable ValueKind\n> +    fields actually generate two PackedFields. This allows:\n> +\n> +    - backwards compatibility prior to Mojo support for nullable ValueKinds.\n> +    - correct packing of fields for the aforementioned backwards compatibility.\n> +\n> +    When translating Fields to PackedFields, the original field is turned into\n> +    two PackedFields: the first PackedField always has type mojom.BOOL, while\n> +    the second PackedField has the non-nullable version of the field's kind.\n> +\n> +    When constructing these PackedFields, original_field references the field\n> +    as defined in the mojom; the name as defined in the mojom will be used for\n> +    all layers above the wire/data layer.\n> +\n> +    sub_ordinal is used to sort the two PackedFields correctly with respect to\n> +    each other: the first mojom.BOOL field always has sub_ordinal 0, while the\n> +    second field always has sub_ordinal 1.\n> +\n> +    Finally, linked_value_packed_field is used by the serialization and\n> +    deserialization helpers, which generally just iterate over a PackedStruct's\n> +    PackedField's in ordinal order. This allows the helpers to easily reference\n> +    any related PackedFields rather than having to lookup related PackedFields\n> +    by index while iterating.\n>      \"\"\"\n>      self.field = field\n>      self.index = index\n>      self.ordinal = ordinal\n> -    self.size = self.GetSizeForKind(field.kind)\n> -    self.alignment = self.GetAlignmentForKind(field.kind)\n> +    self.original_field = original_field\n> +    self.sub_ordinal = sub_ordinal\n> +    self.linked_value_packed_field = linked_value_packed_field\n> +    self.size = self.GetSizeForKind(self.field.kind)\n> +    self.alignment = self.GetAlignmentForKind(self.field.kind)\n>      self.offset = None\n>      self.bit = None\n>      self.min_version = None\n> @@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field):\n>    return offset + pad\n>  \n>  \n> -class PackedStruct(object):\n> +def IsNullableValueKindPackedField(field):\n> +  \"\"\"Returns true if `field` is derived from a nullable ValueKind field.\n> +\n> +  Nullable ValueKind fields often require special handling in the bindings due\n> +  to the way the implementation is constrained for wire compatibility.\n> +  \"\"\"\n> +  assert isinstance(field, PackedField)\n> +  return field.sub_ordinal is not None\n> +\n> +\n> +def IsPrimaryNullableValueKindPackedField(field):\n> +  \"\"\"Returns true if `field` is derived from a nullable ValueKind mojom field\n> +  and is the \"primary\" field.\n> +\n> +  The primary field is a bool PackedField that controls if the field should be\n> +  considered as present or not; it will have a reference to the PackedField that\n> +  holds the actual value representation if considered present.\n> +\n> +  Bindings code that translates between the wire protocol and the higher layers\n> +  can use this to simplify mapping multiple PackedFields to the single field\n> +  that is logically exposed to bindings consumers.\n> +  \"\"\"\n> +  assert isinstance(field, PackedField)\n> +  return field.linked_value_packed_field is not None\n> +\n> +\n> +class PackedStruct:\n>    def __init__(self, struct):\n>      self.struct = struct\n>      # |packed_fields| contains all the fields, in increasing offset order.\n> @@ -139,9 +203,41 @@ class PackedStruct(object):\n>      for index, field in enumerate(struct.fields):\n>        if field.ordinal is not None:\n>          ordinal = field.ordinal\n> -      src_fields.append(PackedField(field, index, ordinal))\n> +      # Nullable value types are a bit weird: they generate two PackedFields\n> +      # despite being a single ValueKind. This is for wire compatibility to\n> +      # ease the transition from legacy mojom syntax where nullable value types\n> +      # were not supported.\n> +      if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable:\n> +        # The suffixes intentionally use Unicode codepoints which are considered\n> +        # valid C++/Java/JavaScript identifiers, yet are unlikely to be used in\n> +        # actual user code.\n> +        has_value_field = copy.copy(field)\n> +        has_value_field.name = f'{field.mojom_name}_$flag'\n> +        has_value_field.kind = mojom.BOOL\n> +\n> +        value_field = copy.copy(field)\n> +        value_field.name = f'{field.mojom_name}_$value'\n> +        value_field.kind = field.kind.MakeUnnullableKind()\n> +\n> +        value_packed_field = PackedField(value_field,\n> +                                         index,\n> +                                         ordinal,\n> +                                         original_field=field,\n> +                                         sub_ordinal=1,\n> +                                         linked_value_packed_field=None)\n> +        has_value_packed_field = PackedField(\n> +            has_value_field,\n> +            index,\n> +            ordinal,\n> +            original_field=field,\n> +            sub_ordinal=0,\n> +            linked_value_packed_field=value_packed_field)\n> +        src_fields.append(has_value_packed_field)\n> +        src_fields.append(value_packed_field)\n> +      else:\n> +        src_fields.append(PackedField(field, index, ordinal))\n>        ordinal += 1\n> -    src_fields.sort(key=lambda field: field.ordinal)\n> +    src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal))\n>  \n>      # Set |min_version| for each field.\n>      next_min_version = 0\n> @@ -156,10 +252,11 @@ class PackedStruct(object):\n>        if (packed_field.min_version != 0\n>            and mojom.IsReferenceKind(packed_field.field.kind)\n>            and not packed_field.field.kind.is_nullable):\n> -        raise Exception(\"Non-nullable fields are only allowed in version 0 of \"\n> -                        \"a struct. %s.%s is defined with [MinVersion=%d].\" %\n> -                        (self.struct.name, packed_field.field.name,\n> -                         packed_field.min_version))\n> +        raise Exception(\n> +            \"Non-nullable reference fields are only allowed in version 0 of a \"\n> +            \"struct. %s.%s is defined with [MinVersion=%d].\" %\n> +            (self.struct.name, packed_field.field.name,\n> +             packed_field.min_version))\n>  \n>      src_field = src_fields[0]\n>      src_field.offset = 0\n> @@ -186,7 +283,7 @@ class PackedStruct(object):\n>          dst_fields.append(src_field)\n>  \n>  \n> -class ByteInfo(object):\n> +class ByteInfo:\n>    def __init__(self):\n>      self.is_padding = False\n>      self.packed_fields = []\n> @@ -214,10 +311,11 @@ def GetByteLayout(packed_struct):\n>    return byte_info\n>  \n>  \n> -class VersionInfo(object):\n> -  def __init__(self, version, num_fields, num_bytes):\n> +class VersionInfo:\n> +  def __init__(self, version, num_fields, num_packed_fields, num_bytes):\n>      self.version = version\n>      self.num_fields = num_fields\n> +    self.num_packed_fields = num_packed_fields\n>      self.num_bytes = num_bytes\n>  \n>  \n> @@ -235,24 +333,35 @@ def GetVersionInfo(packed_struct):\n>    versions = []\n>    last_version = 0\n>    last_num_fields = 0\n> +  last_num_packed_fields = 0\n>    last_payload_size = 0\n>  \n>    for packed_field in packed_struct.packed_fields_in_ordinal_order:\n>      if packed_field.min_version != last_version:\n>        versions.append(\n> -          VersionInfo(last_version, last_num_fields,\n> +          VersionInfo(last_version, last_num_fields, last_num_packed_fields,\n>                        last_payload_size + HEADER_SIZE))\n>        last_version = packed_field.min_version\n>  \n> -    last_num_fields += 1\n> +    # Nullable numeric fields (e.g. `int32?`) expand to two packed fields, so to\n> +    # avoid double-counting, only increment if the field is:\n> +    # - not used for representing a nullable value kind field, or\n> +    # - the primary field representing the nullable value kind field.\n> +    last_num_fields += 1 if (\n> +        not IsNullableValueKindPackedField(packed_field)\n> +        or IsPrimaryNullableValueKindPackedField(packed_field)) else 0\n> +\n> +    last_num_packed_fields += 1\n> +\n>      # The fields are iterated in ordinal order here. However, the size of a\n>      # version is determined by the last field of that version in pack order,\n>      # instead of ordinal order. Therefore, we need to calculate the max value.\n> -    last_payload_size = max(\n> -        GetPayloadSizeUpToField(packed_field), last_payload_size)\n> +    last_payload_size = max(GetPayloadSizeUpToField(packed_field),\n> +                            last_payload_size)\n>  \n> -  assert len(versions) == 0 or last_num_fields != versions[-1].num_fields\n> +  assert len(\n> +      versions) == 0 or last_num_packed_fields != versions[-1].num_packed_fields\n>    versions.append(\n> -      VersionInfo(last_version, last_num_fields,\n> +      VersionInfo(last_version, last_num_fields, last_num_packed_fields,\n>                    last_payload_size + HEADER_SIZE))\n>    return versions\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n> index 98c705add32b..7d8e4e019719 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -205,6 +205,34 @@ class PackTest(unittest.TestCase):\n>      self.assertEqual(4, versions[2].num_fields)\n>      self.assertEqual(32, versions[2].num_bytes)\n>  \n> +  def testGetVersionInfoPackedStruct(self):\n> +    \"\"\"Tests that pack.GetVersionInfo() correctly sets version, num_fields,\n> +    and num_packed_fields for a packed struct.\n> +    \"\"\"\n> +    struct = mojom.Struct('test')\n> +    struct.AddField('field_0', mojom.BOOL, ordinal=0)\n> +    struct.AddField('field_1',\n> +                    mojom.NULLABLE_BOOL,\n> +                    ordinal=1,\n> +                    attributes={'MinVersion': 1})\n> +    struct.AddField('field_2',\n> +                    mojom.NULLABLE_BOOL,\n> +                    ordinal=2,\n> +                    attributes={'MinVersion': 2})\n> +    ps = pack.PackedStruct(struct)\n> +    versions = pack.GetVersionInfo(ps)\n> +\n> +    self.assertEqual(3, len(versions))\n> +    self.assertEqual(0, versions[0].version)\n> +    self.assertEqual(1, versions[1].version)\n> +    self.assertEqual(2, versions[2].version)\n> +    self.assertEqual(1, versions[0].num_fields)\n> +    self.assertEqual(2, versions[1].num_fields)\n> +    self.assertEqual(3, versions[2].num_fields)\n> +    self.assertEqual(1, versions[0].num_packed_fields)\n> +    self.assertEqual(3, versions[1].num_packed_fields)\n> +    self.assertEqual(5, versions[2].num_packed_fields)\n> +\n>    def testInterfaceAlignment(self):\n>      \"\"\"Tests that interfaces are aligned on 4-byte boundaries, although the size\n>      of an interface is 8 bytes.\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n> index 0da900585cea..807e2a4fdfc0 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n> index 7580b78002e6..83bb297f5d44 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2013 The Chromium Authors. All rights reserved.\n> +# Copyright 2013 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Convert parse tree to AST.\n> @@ -12,17 +12,294 @@ already been parsed and converted to ASTs before.\n>  import itertools\n>  import os\n>  import re\n> -import sys\n>  \n> +from collections import OrderedDict\n>  from mojom.generate import generator\n>  from mojom.generate import module as mojom\n>  from mojom.parse import ast\n>  \n>  \n> -def _IsStrOrUnicode(x):\n> -  if sys.version_info[0] < 3:\n> -    return isinstance(x, (unicode, str))\n> -  return isinstance(x, str)\n> +is_running_backwards_compatibility_check_hack = False\n> +\n> +### DO NOT ADD ENTRIES TO THIS LIST. ###\n> +_EXTENSIBLE_ENUMS_MISSING_DEFAULT = (\n> +    'x:arc.keymaster.mojom.Algorithm',\n> +    'x:arc.keymaster.mojom.Digest',\n> +    'x:arc.keymaster.mojom.SignatureResult',\n> +    'x:arc.mojom.AccessibilityActionType',\n> +    'x:arc.mojom.AccessibilityBooleanProperty',\n> +    'x:arc.mojom.AccessibilityEventIntListProperty',\n> +    'x:arc.mojom.AccessibilityEventIntProperty',\n> +    'x:arc.mojom.AccessibilityEventStringProperty',\n> +    'x:arc.mojom.AccessibilityEventType',\n> +    'x:arc.mojom.AccessibilityFilterType',\n> +    'x:arc.mojom.AccessibilityIntListProperty',\n> +    'x:arc.mojom.AccessibilityIntProperty',\n> +    'x:arc.mojom.AccessibilityLiveRegionType',\n> +    'x:arc.mojom.AccessibilityNotificationStateType',\n> +    'x:arc.mojom.AccessibilityRangeType',\n> +    'x:arc.mojom.AccessibilitySelectionMode',\n> +    'x:arc.mojom.AccessibilityStringListProperty',\n> +    'x:arc.mojom.AccessibilityStringProperty',\n> +    'x:arc.mojom.AccessibilityWindowBooleanProperty',\n> +    'x:arc.mojom.AccessibilityWindowIntListProperty',\n> +    'x:arc.mojom.AccessibilityWindowIntProperty',\n> +    'x:arc.mojom.AccessibilityWindowStringProperty',\n> +    'x:arc.mojom.AccessibilityWindowType',\n> +    'x:arc.mojom.AccountCheckStatus',\n> +    'x:arc.mojom.AccountUpdateType',\n> +    'x:arc.mojom.ActionType',\n> +    'x:arc.mojom.Algorithm',\n> +    'x:arc.mojom.AndroidIdSource',\n> +    'x:arc.mojom.AnrSource',\n> +    'x:arc.mojom.AnrType',\n> +    'x:arc.mojom.AppDiscoveryRequestState',\n> +    'x:arc.mojom.AppKillType',\n> +    'x:arc.mojom.AppPermission',\n> +    'x:arc.mojom.AppPermissionGroup',\n> +    'x:arc.mojom.AppReinstallState',\n> +    'x:arc.mojom.AppShortcutItemType',\n> +    'x:arc.mojom.ArcAuthCodeStatus',\n> +    'x:arc.mojom.ArcClipboardDragDropEvent',\n> +    'x:arc.mojom.ArcCorePriAbiMigEvent',\n> +    'x:arc.mojom.ArcDnsQuery',\n> +    'x:arc.mojom.ArcImageCopyPasteCompatAction',\n> +    'x:arc.mojom.ArcNetworkError',\n> +    'x:arc.mojom.ArcNetworkEvent',\n> +    'x:arc.mojom.ArcNotificationEvent',\n> +    'x:arc.mojom.ArcNotificationExpandState',\n> +    'x:arc.mojom.ArcNotificationPriority',\n> +    'x:arc.mojom.ArcNotificationRemoteInputState',\n> +    'x:arc.mojom.ArcNotificationShownContents',\n> +    'x:arc.mojom.ArcNotificationStyle',\n> +    'x:arc.mojom.ArcNotificationType',\n> +    'x:arc.mojom.ArcPipEvent',\n> +    'x:arc.mojom.ArcResizeLockState',\n> +    'x:arc.mojom.ArcSignInSuccess',\n> +    'x:arc.mojom.ArcTimerResult',\n> +    'x:arc.mojom.AudioSwitch',\n> +    'x:arc.mojom.BluetoothAclState',\n> +    'x:arc.mojom.BluetoothAdapterState',\n> +    'x:arc.mojom.BluetoothAdvertisingDataType',\n> +    'x:arc.mojom.BluetoothBondState',\n> +    'x:arc.mojom.BluetoothDeviceType',\n> +    'x:arc.mojom.BluetoothDiscoveryState',\n> +    'x:arc.mojom.BluetoothGattDBAttributeType',\n> +    'x:arc.mojom.BluetoothGattStatus',\n> +    'x:arc.mojom.BluetoothPropertyType',\n> +    'x:arc.mojom.BluetoothScanMode',\n> +    'x:arc.mojom.BluetoothSdpAttributeType',\n> +    'x:arc.mojom.BluetoothSocketType',\n> +    'x:arc.mojom.BluetoothStatus',\n> +    'x:arc.mojom.BootType',\n> +    'x:arc.mojom.CaptionTextShadowType',\n> +    'x:arc.mojom.ChangeType',\n> +    'x:arc.mojom.ChromeAccountType',\n> +    'x:arc.mojom.ChromeApp',\n> +    'x:arc.mojom.ChromePage',\n> +    'x:arc.mojom.ClockId',\n> +    'x:arc.mojom.CloudProvisionFlowError',\n> +    'x:arc.mojom.CommandResultType',\n> +    'x:arc.mojom.CompanionLibApiId',\n> +    'x:arc.mojom.ConnectionStateType',\n> +    'x:arc.mojom.ContentChangeType',\n> +    'x:arc.mojom.CpuRestrictionState',\n> +    'x:arc.mojom.CursorCoordinateSpace',\n> +    'x:arc.mojom.DataRestoreStatus',\n> +    'x:arc.mojom.DecoderStatus',\n> +    'x:arc.mojom.DeviceType',\n> +    'x:arc.mojom.Digest',\n> +    'x:arc.mojom.DisplayWakeLockType',\n> +    'x:arc.mojom.EapMethod',\n> +    'x:arc.mojom.EapPhase2Method',\n> +    'x:arc.mojom.FileSelectorEventType',\n> +    'x:arc.mojom.GMSCheckInError',\n> +    'x:arc.mojom.GMSSignInError',\n> +    'x:arc.mojom.GeneralSignInError',\n> +    'x:arc.mojom.GetNetworksRequestType',\n> +    'x:arc.mojom.HalPixelFormat',\n> +    'x:arc.mojom.IPAddressType',\n> +    'x:arc.mojom.InstallErrorReason',\n> +    'x:arc.mojom.KeyFormat',\n> +    'x:arc.mojom.KeyManagement',\n> +    'x:arc.mojom.KeyPurpose',\n> +    'x:arc.mojom.KeymasterError',\n> +    'x:arc.mojom.MainAccountHashMigrationStatus',\n> +    'x:arc.mojom.MainAccountResolutionStatus',\n> +    'x:arc.mojom.ManagementChangeStatus',\n> +    'x:arc.mojom.ManagementState',\n> +    'x:arc.mojom.MessageCenterVisibility',\n> +    'x:arc.mojom.MetricsType',\n> +    'x:arc.mojom.MountEvent',\n> +    'x:arc.mojom.NativeBridgeType',\n> +    'x:arc.mojom.NetworkResult',\n> +    'x:arc.mojom.NetworkType',\n> +    'x:arc.mojom.OemCryptoAlgorithm',\n> +    'x:arc.mojom.OemCryptoCipherMode',\n> +    'x:arc.mojom.OemCryptoHdcpCapability',\n> +    'x:arc.mojom.OemCryptoLicenseType',\n> +    'x:arc.mojom.OemCryptoPrivateKey',\n> +    'x:arc.mojom.OemCryptoProvisioningMethod',\n> +    'x:arc.mojom.OemCryptoResult',\n> +    'x:arc.mojom.OemCryptoRsaPaddingScheme',\n> +    'x:arc.mojom.OemCryptoUsageEntryStatus',\n> +    'x:arc.mojom.Padding',\n> +    'x:arc.mojom.PaiFlowState',\n> +    'x:arc.mojom.PatternType',\n> +    'x:arc.mojom.PressureLevel',\n> +    'x:arc.mojom.PrintColorMode',\n> +    'x:arc.mojom.PrintContentType',\n> +    'x:arc.mojom.PrintDuplexMode',\n> +    'x:arc.mojom.PrinterStatus',\n> +    'x:arc.mojom.ProcessState',\n> +    'x:arc.mojom.PurchaseState',\n> +    'x:arc.mojom.ReauthReason',\n> +    'x:arc.mojom.ScaleFactor',\n> +    'x:arc.mojom.SecurityType',\n> +    'x:arc.mojom.SegmentStyle',\n> +    'x:arc.mojom.SelectFilesActionType',\n> +    'x:arc.mojom.SetNativeChromeVoxResponse',\n> +    'x:arc.mojom.ShowPackageInfoPage',\n> +    'x:arc.mojom.SpanType',\n> +    'x:arc.mojom.SupportedLinkChangeSource',\n> +    'x:arc.mojom.TetheringClientState',\n> +    'x:arc.mojom.TextInputType',\n> +    'x:arc.mojom.TtsEventType',\n> +    'x:arc.mojom.VideoCodecProfile',\n> +    'x:arc.mojom.VideoDecodeAccelerator.Result',\n> +    'x:arc.mojom.VideoEncodeAccelerator.Error',\n> +    'x:arc.mojom.VideoFrameStorageType',\n> +    'x:arc.mojom.VideoPixelFormat',\n> +    'x:arc.mojom.WakefulnessMode',\n> +    'x:arc.mojom.WebApkInstallResult',\n> +    'x:ash.ime.mojom.InputFieldType',\n> +    'x:ash.ime.mojom.PersonalizationMode',\n> +    'x:ash.language.mojom.FeatureId',\n> +    'x:blink.mojom.ScrollRestorationType',\n> +    'x:chromeos.cdm.mojom.CdmKeyStatus',\n> +    'x:chromeos.cdm.mojom.CdmMessageType',\n> +    'x:chromeos.cdm.mojom.CdmSessionType',\n> +    'x:chromeos.cdm.mojom.DecryptStatus',\n> +    'x:chromeos.cdm.mojom.EmeInitDataType',\n> +    'x:chromeos.cdm.mojom.EncryptionScheme',\n> +    'x:chromeos.cdm.mojom.HdcpVersion',\n> +    'x:chromeos.cdm.mojom.OutputProtection.LinkType',\n> +    'x:chromeos.cdm.mojom.OutputProtection.ProtectionType',\n> +    'x:chromeos.cdm.mojom.PromiseException',\n> +    'x:chromeos.cfm.mojom.EnqueuePriority',\n> +    'x:chromeos.cfm.mojom.LoggerErrorCode',\n> +    'x:chromeos.cfm.mojom.LoggerState',\n> +    'x:chromeos.cros_healthd.mojom.CryptoAlgorithm',\n> +    'x:chromeos.cros_healthd.mojom.EncryptionState',\n> +    'x:chromeos.machine_learning.mojom.AnnotationUsecase',\n> +    'x:chromeos.machine_learning.mojom.BuiltinModelId',\n> +    'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult',\n> +    'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus',\n> +    'x:chromeos.machine_learning.mojom.EndpointReason',\n> +    'x:chromeos.machine_learning.mojom.EndpointerType',\n> +    'x:chromeos.machine_learning.mojom.ExecuteResult',\n> +    'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status',\n> +    'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status',\n> +    'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult',\n> +    'x:chromeos.machine_learning.mojom.LoadModelResult',\n> +    'x:chromeos.machine_learning.mojom.Rotation',\n> +    'x:chromeos.network_config.mojom.ConnectionStateType',\n> +    'x:chromeos.network_config.mojom.DeviceStateType',\n> +    'x:chromeos.network_config.mojom.IPConfigType',\n> +    'x:chromeos.network_config.mojom.NetworkType',\n> +    'x:chromeos.network_config.mojom.OncSource',\n> +    'x:chromeos.network_config.mojom.PolicySource',\n> +    'x:chromeos.network_config.mojom.PortalState',\n> +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent',\n> +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod',\n> +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus',\n> +    'x:cros.mojom.CameraClientType',\n> +    'x:cros.mojom.CameraMetadataSectionStart',\n> +    'x:cros.mojom.CameraMetadataTag',\n> +    'x:cros.mojom.HalPixelFormat',\n> +    'x:crosapi.mojom.AllowedPaths',\n> +    'x:crosapi.mojom.BrowserAppInstanceType',\n> +    'x:crosapi.mojom.CreationResult',\n> +    'x:crosapi.mojom.DeviceAccessResultCode',\n> +    'x:crosapi.mojom.DeviceMode',\n> +    'x:crosapi.mojom.DlpRestrictionLevel',\n> +    'x:crosapi.mojom.ExoImeSupport',\n> +    'x:crosapi.mojom.FullscreenVisibility',\n> +    'x:crosapi.mojom.GoogleServiceAuthError.State',\n> +    'x:crosapi.mojom.IsInstallableResult',\n> +    'x:crosapi.mojom.KeyTag',\n> +    'x:crosapi.mojom.KeystoreSigningAlgorithmName',\n> +    'x:crosapi.mojom.KeystoreType',\n> +    'x:crosapi.mojom.LacrosFeedbackSource',\n> +    'x:crosapi.mojom.MemoryPressureLevel',\n> +    'x:crosapi.mojom.MetricsReportingManaged',\n> +    'x:crosapi.mojom.NotificationType',\n> +    'x:crosapi.mojom.OndeviceHandwritingSupport',\n> +    'x:crosapi.mojom.OpenResult',\n> +    'x:crosapi.mojom.PolicyDomain',\n> +    'x:crosapi.mojom.RegistrationCodeType',\n> +    'x:crosapi.mojom.ScaleFactor',\n> +    'x:crosapi.mojom.SearchResult.OptionalBool',\n> +    'x:crosapi.mojom.SelectFileDialogType',\n> +    'x:crosapi.mojom.SelectFileResult',\n> +    'x:crosapi.mojom.SharesheetResult',\n> +    'x:crosapi.mojom.TouchEventType',\n> +    'x:crosapi.mojom.VideoRotation',\n> +    'x:crosapi.mojom.WallpaperLayout',\n> +    'x:crosapi.mojom.WebAppInstallResultCode',\n> +    'x:crosapi.mojom.WebAppUninstallResultCode',\n> +    'x:device.mojom.HidBusType',\n> +    'x:device.mojom.WakeLockReason',\n> +    'x:device.mojom.WakeLockType',\n> +    'x:drivefs.mojom.DialogReason.Type',\n> +    'x:drivefs.mojom.DriveError.Type',\n> +    'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus',\n> +    'x:drivefs.mojom.FileMetadata.CanPinStatus',\n> +    'x:drivefs.mojom.FileMetadata.Type',\n> +    'x:drivefs.mojom.ItemEventReason',\n> +    'x:drivefs.mojom.MirrorPathStatus',\n> +    'x:drivefs.mojom.MirrorSyncStatus',\n> +    'x:drivefs.mojom.QueryParameters.SortField',\n> +    'x:fuzz.mojom.FuzzEnum',\n> +    'x:media.mojom.FillLightMode',\n> +    'x:media.mojom.MeteringMode',\n> +    'x:media.mojom.PowerLineFrequency',\n> +    'x:media.mojom.RedEyeReduction',\n> +    'x:media.mojom.ResolutionChangePolicy',\n> +    'x:media.mojom.VideoCaptureApi',\n> +    'x:media.mojom.VideoCaptureBufferType',\n> +    'x:media.mojom.VideoCaptureError',\n> +    'x:media.mojom.VideoCaptureFrameDropReason',\n> +    'x:media.mojom.VideoCapturePixelFormat',\n> +    'x:media.mojom.VideoCaptureTransportType',\n> +    'x:media.mojom.VideoFacingMode',\n> +    'x:media_session.mojom.AudioFocusType',\n> +    'x:media_session.mojom.CameraState',\n> +    'x:media_session.mojom.EnforcementMode',\n> +    'x:media_session.mojom.MediaAudioVideoState',\n> +    'x:media_session.mojom.MediaImageBitmapColorType',\n> +    'x:media_session.mojom.MediaPictureInPictureState',\n> +    'x:media_session.mojom.MediaPlaybackState',\n> +    'x:media_session.mojom.MediaSession.SuspendType',\n> +    'x:media_session.mojom.MediaSessionAction',\n> +    'x:media_session.mojom.MediaSessionImageType',\n> +    'x:media_session.mojom.MediaSessionInfo.SessionState',\n> +    'x:media_session.mojom.MicrophoneState',\n> +    'x:ml.model_loader.mojom.ComputeResult',\n> +    'x:ml.model_loader.mojom.CreateModelLoaderResult',\n> +    'x:ml.model_loader.mojom.LoadModelResult',\n> +    'x:mojo.test.AnExtensibleEnum',\n> +    'x:mojo.test.EnumB',\n> +    'x:mojo.test.ExtensibleEmptyEnum',\n> +    'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault',\n> +    'x:network.mojom.WebSandboxFlags',\n> +    'x:payments.mojom.BillingResponseCode',\n> +    'x:payments.mojom.CreateDigitalGoodsResponseCode',\n> +    'x:payments.mojom.ItemType',\n> +    'x:printing.mojom.PrinterType',\n> +    'x:ui.mojom.KeyboardCode',\n> +)\n> +### DO NOT ADD ENTRIES TO THIS LIST. ###\n>  \n>  \n>  def _DuplicateName(values):\n> @@ -98,12 +375,6 @@ def _MapKind(kind):\n>    }\n>    if kind.endswith('?'):\n>      base_kind = _MapKind(kind[0:-1])\n> -    # NOTE: This doesn't rule out enum types. Those will be detected later, when\n> -    # cross-reference is established.\n> -    reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',\n> -                       'rma', 'rca')\n> -    if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:\n> -      raise Exception('A type (spec \"%s\") cannot be made nullable' % base_kind)\n>      return '?' + base_kind\n>    if kind.endswith('}'):\n>      lbracket = kind.rfind('{')\n> @@ -113,8 +384,6 @@ def _MapKind(kind):\n>      lbracket = kind.rfind('[')\n>      typename = kind[0:lbracket]\n>      return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)\n> -  if kind.endswith('&'):\n> -    return 'r:' + _MapKind(kind[0:-1])\n>    if kind.startswith('asso<'):\n>      assert kind.endswith('>')\n>      return 'asso:' + _MapKind(kind[5:-1])\n> @@ -135,13 +404,45 @@ def _MapKind(kind):\n>    return 'x:' + kind\n>  \n>  \n> -def _AttributeListToDict(attribute_list):\n> +def _MapAttributeValue(module, kind, value):\n> +  # True/False/None\n> +  if value is None:\n> +    return value\n> +  if not isinstance(value, str):\n> +    return value\n> +  # Is the attribute value the name of a feature?\n> +  try:\n> +    # Features cannot be nested in other types, so lookup in the global scope.\n> +    trial = _LookupKind(module.kinds, 'x:' + value,\n> +                        _GetScopeForKind(module, kind))\n> +    if isinstance(trial, mojom.Feature):\n> +      return trial\n> +  except ValueError:\n> +    pass\n> +  # Is the attribute value a constant or enum value?\n> +  try:\n> +    trial = _LookupValue(module, None, None, ('IDENTIFIER', value))\n> +    if isinstance(trial, mojom.ConstantValue):\n> +      return trial.constant\n> +    if isinstance(trial, mojom.EnumValue):\n> +      return trial\n> +  except ValueError:\n> +    pass\n> +  # If not a referenceable mojo type - return as a string.\n> +  return value\n> +\n> +\n> +def _AttributeListToDict(module, kind, attribute_list):\n>    if attribute_list is None:\n>      return None\n>    assert isinstance(attribute_list, ast.AttributeList)\n> -  # TODO(vtl): Check for duplicate keys here.\n> -  return dict(\n> -      [(attribute.key, attribute.value) for attribute in attribute_list])\n> +  attributes = dict()\n> +  for attribute in attribute_list:\n> +    if attribute.key in attributes:\n> +      raise Exception(\"Duplicate key (%s) in attribute list\" % attribute.key)\n> +    attributes[attribute.key] = _MapAttributeValue(module, kind,\n> +                                                   attribute.value)\n> +  return attributes\n>  \n>  \n>  builtin_values = frozenset([\n> @@ -257,7 +558,8 @@ def _Kind(kinds, spec, scope):\n>      return kind\n>  \n>    if spec.startswith('?'):\n> -    kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()\n> +    kind = _Kind(kinds, spec[1:], scope)\n> +    kind = kind.MakeNullableKind()\n>    elif spec.startswith('a:'):\n>      kind = mojom.Array(_Kind(kinds, spec[2:], scope))\n>    elif spec.startswith('asso:'):\n> @@ -303,7 +605,8 @@ def _Kind(kinds, spec, scope):\n>  \n>  def _Import(module, import_module):\n>    # Copy the struct kinds from our imports into the current module.\n> -  importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface)\n> +  importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface,\n> +                      mojom.Feature)\n>    for kind in import_module.kinds.values():\n>      if (isinstance(kind, importable_kinds)\n>          and kind.module.path == import_module.path):\n> @@ -316,6 +619,32 @@ def _Import(module, import_module):\n>    return import_module\n>  \n>  \n> +def _Feature(module, parsed_feature):\n> +  \"\"\"\n> +  Args:\n> +    module: {mojom.Module} Module currently being constructed.\n> +    parsed_feature: {ast.Feature} Parsed feature.\n> +\n> +  Returns:\n> +    {mojom.Feature} AST feature.\n> +  \"\"\"\n> +  feature = mojom.Feature(module=module)\n> +  feature.mojom_name = parsed_feature.mojom_name\n> +  feature.spec = 'x:' + module.GetNamespacePrefix() + feature.mojom_name\n> +  module.kinds[feature.spec] = feature\n> +  feature.constants = []\n> +  _ProcessElements(\n> +      parsed_feature.mojom_name, parsed_feature.body, {\n> +          ast.Const:\n> +          lambda const: feature.constants.append(\n> +              _Constant(module, const, feature)),\n> +      })\n> +\n> +  feature.attributes = _AttributeListToDict(module, feature,\n> +                                            parsed_feature.attribute_list)\n> +  return feature\n> +\n> +\n>  def _Struct(module, parsed_struct):\n>    \"\"\"\n>    Args:\n> @@ -345,7 +674,8 @@ def _Struct(module, parsed_struct):\n>              struct.fields_data.append,\n>          })\n>  \n> -  struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)\n> +  struct.attributes = _AttributeListToDict(module, struct,\n> +                                           parsed_struct.attribute_list)\n>  \n>    # Enforce that a [Native] attribute is set to make native-only struct\n>    # declarations more explicit.\n> @@ -377,7 +707,8 @@ def _Union(module, parsed_union):\n>    union.fields_data = []\n>    _ProcessElements(parsed_union.mojom_name, parsed_union.body,\n>                     {ast.UnionField: union.fields_data.append})\n> -  union.attributes = _AttributeListToDict(parsed_union.attribute_list)\n> +  union.attributes = _AttributeListToDict(module, union,\n> +                                          parsed_union.attribute_list)\n>    return union\n>  \n>  \n> @@ -398,7 +729,8 @@ def _StructField(module, parsed_field, struct):\n>    field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None\n>    field.default = _LookupValue(module, struct, field.kind,\n>                                 parsed_field.default_value)\n> -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n> +  field.attributes = _AttributeListToDict(module, field,\n> +                                          parsed_field.attribute_list)\n>    return field\n>  \n>  \n> @@ -414,11 +746,22 @@ def _UnionField(module, parsed_field, union):\n>    \"\"\"\n>    field = mojom.UnionField()\n>    field.mojom_name = parsed_field.mojom_name\n> +  # Disallow unions from being self-recursive.\n> +  parsed_typename = parsed_field.typename\n> +  if parsed_typename.endswith('?'):\n> +    parsed_typename = parsed_typename[:-1]\n> +  assert parsed_typename != union.mojom_name\n>    field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),\n>                       (module.mojom_namespace, union.mojom_name))\n>    field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None\n>    field.default = None\n> -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n> +  field.attributes = _AttributeListToDict(module, field,\n> +                                          parsed_field.attribute_list)\n> +  if field.is_default and not mojom.IsNullableKind(field.kind) and \\\n> +     not mojom.IsIntegralKind(field.kind):\n> +    raise Exception(\n> +        '[Default] field for union %s must be nullable or integral type.' %\n> +        union.mojom_name)\n>    return field\n>  \n>  \n> @@ -439,7 +782,8 @@ def _Parameter(module, parsed_param, interface):\n>    parameter.ordinal = (parsed_param.ordinal.value\n>                         if parsed_param.ordinal else None)\n>    parameter.default = None  # TODO(tibell): We never have these. Remove field?\n> -  parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)\n> +  parameter.attributes = _AttributeListToDict(module, parameter,\n> +                                              parsed_param.attribute_list)\n>    return parameter\n>  \n>  \n> @@ -464,7 +808,8 @@ def _Method(module, parsed_method, interface):\n>      method.response_parameters = list(\n>          map(lambda parameter: _Parameter(module, parameter, interface),\n>              parsed_method.response_parameter_list))\n> -  method.attributes = _AttributeListToDict(parsed_method.attribute_list)\n> +  method.attributes = _AttributeListToDict(module, method,\n> +                                           parsed_method.attribute_list)\n>  \n>    # Enforce that only methods with response can have a [Sync] attribute.\n>    if method.sync and method.response_parameters is None:\n> @@ -492,7 +837,8 @@ def _Interface(module, parsed_iface):\n>    interface.mojom_name = parsed_iface.mojom_name\n>    interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name\n>    module.kinds[interface.spec] = interface\n> -  interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)\n> +  interface.attributes = _AttributeListToDict(module, interface,\n> +                                              parsed_iface.attribute_list)\n>    interface.enums = []\n>    interface.constants = []\n>    interface.methods_data = []\n> @@ -522,7 +868,8 @@ def _EnumField(module, enum, parsed_field):\n>    field = mojom.EnumField()\n>    field.mojom_name = parsed_field.mojom_name\n>    field.value = _LookupValue(module, enum, None, parsed_field.value)\n> -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)\n> +  field.attributes = _AttributeListToDict(module, field,\n> +                                          parsed_field.attribute_list)\n>    value = mojom.EnumValue(module, enum, field)\n>    module.values[value.GetSpec()] = value\n>    return field\n> @@ -544,7 +891,7 @@ def _ResolveNumericEnumValues(enum):\n>        prev_value += 1\n>  \n>      # Integral value (e.g: BEGIN = -0x1).\n> -    elif _IsStrOrUnicode(field.value):\n> +    elif isinstance(field.value, str):\n>        prev_value = int(field.value, 0)\n>  \n>      # Reference to a previous enum value (e.g: INIT = BEGIN).\n> @@ -560,7 +907,10 @@ def _ResolveNumericEnumValues(enum):\n>      else:\n>        raise Exception('Unresolved enum value for %s' % field.value.GetSpec())\n>  \n> -    #resolved_enum_values[field.mojom_name] = prev_value\n> +    if prev_value in (-128, -127):\n> +      raise Exception(f'{field.mojom_name} in {enum.spec} has the value '\n> +                      f'{prev_value}, which is reserved for WTF::HashTrait\\'s '\n> +                      'default enum specialization and may not be used.')\n>      field.numeric_value = prev_value\n>      if min_value is None or prev_value < min_value:\n>        min_value = prev_value\n> @@ -588,7 +938,8 @@ def _Enum(module, parsed_enum, parent_kind):\n>      mojom_name = parent_kind.mojom_name + '.' + mojom_name\n>    enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)\n>    enum.parent_kind = parent_kind\n> -  enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)\n> +  enum.attributes = _AttributeListToDict(module, enum,\n> +                                         parsed_enum.attribute_list)\n>  \n>    if not enum.native_only:\n>      enum.fields = list(\n> @@ -600,11 +951,18 @@ def _Enum(module, parsed_enum, parent_kind):\n>      for field in enum.fields:\n>        if field.default:\n>          if not enum.extensible:\n> -          raise Exception('Non-extensible enums may not specify a default')\n> -        if enum.default_field is not None:\n>            raise Exception(\n> -              'Only one enumerator value may be specified as the default')\n> +              f'Non-extensible enum {enum.spec} may not specify a default')\n> +        if enum.default_field is not None:\n> +          raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}')\n>          enum.default_field = field\n> +    # While running the backwards compatibility check, ignore errors because the\n> +    # old version of the enum might not specify [Default].\n> +    if (enum.extensible and enum.default_field is None\n> +        and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT\n> +        and not is_running_backwards_compatibility_check_hack):\n> +      raise Exception(\n> +          f'Extensible enum {enum.spec} must specify a [Default] enumerator')\n>  \n>    module.kinds[enum.spec] = enum\n>  \n> @@ -696,6 +1054,11 @@ def _CollectReferencedKinds(module, all_defined_kinds):\n>          for referenced_kind in extract_referenced_user_kinds(param.kind):\n>            sanitized_kind = sanitize_kind(referenced_kind)\n>            referenced_user_kinds[sanitized_kind.spec] = sanitized_kind\n> +  # Consts can reference imported enums.\n> +  for const in module.constants:\n> +    if not const.kind in mojom.PRIMITIVES:\n> +      sanitized_kind = sanitize_kind(const.kind)\n> +      referenced_user_kinds[sanitized_kind.spec] = sanitized_kind\n>  \n>    return referenced_user_kinds\n>  \n> @@ -741,6 +1104,16 @@ def _AssertTypeIsStable(kind):\n>            assertDependencyIsStable(response_param.kind)\n>  \n>  \n> +def _AssertStructIsValid(kind):\n> +  expected_ordinals = set(range(0, len(kind.fields)))\n> +  ordinals = set(map(lambda field: field.ordinal, kind.fields))\n> +  if ordinals != expected_ordinals:\n> +    raise Exception(\n> +        'Structs must use contiguous ordinals starting from 0. ' +\n> +        '{} is missing the following ordinals: {}.'.format(\n> +            kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals))))\n> +\n> +\n>  def _Module(tree, path, imports):\n>    \"\"\"\n>    Args:\n> @@ -778,6 +1151,8 @@ def _Module(tree, path, imports):\n>    module.structs = []\n>    module.unions = []\n>    module.interfaces = []\n> +  module.features = []\n> +\n>    _ProcessElements(\n>        filename, tree.definition_list, {\n>            ast.Const:\n> @@ -791,6 +1166,8 @@ def _Module(tree, path, imports):\n>            ast.Interface:\n>            lambda interface: module.interfaces.append(\n>                _Interface(module, interface)),\n> +          ast.Feature:\n> +          lambda feature: module.features.append(_Feature(module, feature)),\n>        })\n>  \n>    # Second pass expands fields and methods. This allows fields and parameters\n> @@ -806,12 +1183,24 @@ def _Module(tree, path, imports):\n>      for enum in struct.enums:\n>        all_defined_kinds[enum.spec] = enum\n>  \n> +  for feature in module.features:\n> +    all_defined_kinds[feature.spec] = feature\n> +\n>    for union in module.unions:\n>      union.fields = list(\n>          map(lambda field: _UnionField(module, field, union), union.fields_data))\n>      _AssignDefaultOrdinals(union.fields)\n> +    for field in union.fields:\n> +      if field.is_default:\n> +        if union.default_field is not None:\n> +          raise Exception('Multiple [Default] fields in union %s.' %\n> +                          union.mojom_name)\n> +        union.default_field = field\n>      del union.fields_data\n>      all_defined_kinds[union.spec] = union\n> +    if union.extensible and union.default_field is None:\n> +      raise Exception('Extensible union %s must specify a [Default] field' %\n> +                      union.mojom_name)\n>  \n>    for interface in module.interfaces:\n>      interface.methods = list(\n> @@ -829,8 +1218,8 @@ def _Module(tree, path, imports):\n>                                                   all_defined_kinds.values())\n>    imported_kind_specs = set(all_referenced_kinds.keys()).difference(\n>        set(all_defined_kinds.keys()))\n> -  module.imported_kinds = dict(\n> -      (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)\n> +  module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])\n> +                                      for spec in sorted(imported_kind_specs))\n>  \n>    generator.AddComputedData(module)\n>    for iface in module.interfaces:\n> @@ -847,6 +1236,9 @@ def _Module(tree, path, imports):\n>        if kind.stable:\n>          _AssertTypeIsStable(kind)\n>  \n> +  for kind in module.structs:\n> +    _AssertStructIsValid(kind)\n> +\n>    return module\n>  \n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n> index 19905c8a9540..b4fea92467d5 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py\n> @@ -1,17 +1,13 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> -import os.path\n> -import sys\n>  import unittest\n>  \n>  from mojom.generate import module as mojom\n>  from mojom.generate import translate\n>  from mojom.parse import ast\n>  \n> -\n>  class TranslateTest(unittest.TestCase):\n>    \"\"\"Tests |parser.Parse()|.\"\"\"\n>  \n> @@ -69,5 +65,77 @@ class TranslateTest(unittest.TestCase):\n>      # pylint: disable=W0212\n>      self.assertEquals(\n>          translate._MapKind(\"asso<SomeInterface>?\"), \"?asso:x:SomeInterface\")\n> -    self.assertEquals(\n> -        translate._MapKind(\"asso<SomeInterface&>?\"), \"?asso:r:x:SomeInterface\")\n> +    self.assertEquals(translate._MapKind(\"rca<SomeInterface>?\"),\n> +                      \"?rca:x:SomeInterface\")\n> +\n> +  def testSelfRecursiveUnions(self):\n> +    \"\"\"Verifies _UnionField() raises when a union is self-recursive.\"\"\"\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Union(\"SomeUnion\", None,\n> +                  ast.UnionBody([ast.UnionField(\"a\", None, None, \"SomeUnion\")]))\n> +    ])\n> +    with self.assertRaises(Exception):\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Union(\n> +            \"SomeUnion\", None,\n> +            ast.UnionBody([ast.UnionField(\"a\", None, None, \"SomeUnion?\")]))\n> +    ])\n> +    with self.assertRaises(Exception):\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +\n> +  def testDuplicateAttributesException(self):\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Union(\n> +            \"FakeUnion\",\n> +            ast.AttributeList([\n> +                ast.Attribute(\"key1\", \"value\"),\n> +                ast.Attribute(\"key1\", \"value\")\n> +            ]),\n> +            ast.UnionBody([\n> +                ast.UnionField(\"a\", None, None, \"int32\"),\n> +                ast.UnionField(\"b\", None, None, \"string\")\n> +            ]))\n> +    ])\n> +    with self.assertRaises(Exception):\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +\n> +  def testEnumWithReservedValues(self):\n> +    \"\"\"Verifies that assigning reserved values to enumerators fails.\"\"\"\n> +    # -128 is reserved for the empty representation in WTF::HashTraits.\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Enum(\n> +            \"MyEnum\", None,\n> +            ast.EnumValueList([\n> +                ast.EnumValue('kReserved', None, '-128'),\n> +            ]))\n> +    ])\n> +    with self.assertRaises(Exception) as context:\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +    self.assertIn(\"reserved for WTF::HashTrait\", str(context.exception))\n> +\n> +    # -127 is reserved for the deleted representation in WTF::HashTraits.\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Enum(\n> +            \"MyEnum\", None,\n> +            ast.EnumValueList([\n> +                ast.EnumValue('kReserved', None, '-127'),\n> +            ]))\n> +    ])\n> +    with self.assertRaises(Exception) as context:\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +    self.assertIn(\"reserved for WTF::HashTrait\", str(context.exception))\n> +\n> +    # Implicitly assigning a reserved value should also fail.\n> +    tree = ast.Mojom(None, ast.ImportList(), [\n> +        ast.Enum(\n> +            \"MyEnum\", None,\n> +            ast.EnumValueList([\n> +                ast.EnumValue('kNotReserved', None, '-129'),\n> +                ast.EnumValue('kImplicitlyReserved', None, None),\n> +            ]))\n> +    ])\n> +    with self.assertRaises(Exception) as context:\n> +      translate.OrderedModule(tree, \"mojom_tree\", [])\n> +    self.assertIn(\"reserved for WTF::HashTrait\", str(context.exception))\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n> index 1f0db200549b..aae9cdb659bd 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Node classes for the AST for a Mojo IDL file.\"\"\"\n> @@ -8,17 +8,14 @@\n>  # and lineno). You may also define __repr__() to help with analyzing test\n>  # failures, especially for more complex types.\n>  \n> -\n> -import sys\n> +import os.path\n>  \n>  \n> -def _IsStrOrUnicode(x):\n> -  if sys.version_info[0] < 3:\n> -    return isinstance(x, (unicode, str))\n> -  return isinstance(x, str)\n> +# Instance of 'NodeListBase' has no '_list_item_type' member (no-member)\n> +# pylint: disable=no-member\n>  \n>  \n> -class NodeBase(object):\n> +class NodeBase:\n>    \"\"\"Base class for nodes in the AST.\"\"\"\n>  \n>    def __init__(self, filename=None, lineno=None):\n> @@ -43,7 +40,7 @@ class NodeListBase(NodeBase):\n>    classes, in a tuple) of the members of the list.)\"\"\"\n>  \n>    def __init__(self, item_or_items=None, **kwargs):\n> -    super(NodeListBase, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.items = []\n>      if item_or_items is None:\n>        pass\n> @@ -62,7 +59,7 @@ class NodeListBase(NodeBase):\n>      return self.items.__iter__()\n>  \n>    def __eq__(self, other):\n> -    return super(NodeListBase, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.items == other.items\n>  \n>    # Implement this so that on failure, we get slightly more sensible output.\n> @@ -96,7 +93,7 @@ class Definition(NodeBase):\n>    include parameter definitions.) This class is meant to be subclassed.\"\"\"\n>  \n>    def __init__(self, mojom_name, **kwargs):\n> -    assert _IsStrOrUnicode(mojom_name)\n> +    assert isinstance(mojom_name, str)\n>      NodeBase.__init__(self, **kwargs)\n>      self.mojom_name = mojom_name\n>  \n> @@ -108,13 +105,13 @@ class Attribute(NodeBase):\n>    \"\"\"Represents an attribute.\"\"\"\n>  \n>    def __init__(self, key, value, **kwargs):\n> -    assert _IsStrOrUnicode(key)\n> -    super(Attribute, self).__init__(**kwargs)\n> +    assert isinstance(key, str)\n> +    super().__init__(**kwargs)\n>      self.key = key\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(Attribute, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.key == other.key and \\\n>             self.value == other.value\n>  \n> @@ -131,17 +128,17 @@ class Const(Definition):\n>    def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      # The typename is currently passed through as a string.\n> -    assert _IsStrOrUnicode(typename)\n> +    assert isinstance(typename, str)\n>      # The value is either a literal (currently passed through as a string) or a\n>      # \"wrapped identifier\".\n> -    assert _IsStrOrUnicode or isinstance(value, tuple)\n> -    super(Const, self).__init__(mojom_name, **kwargs)\n> +    assert isinstance(value, (tuple, str))\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.typename = typename\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(Const, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.typename == other.typename and \\\n>             self.value == other.value\n> @@ -153,12 +150,12 @@ class Enum(Definition):\n>    def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)\n> -    super(Enum, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.enum_value_list = enum_value_list\n>  \n>    def __eq__(self, other):\n> -    return super(Enum, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.enum_value_list == other.enum_value_list\n>  \n> @@ -170,13 +167,13 @@ class EnumValue(Definition):\n>      # The optional value is either an int (which is current a string) or a\n>      # \"wrapped identifier\".\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> -    assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)\n> -    super(EnumValue, self).__init__(mojom_name, **kwargs)\n> +    assert value is None or isinstance(value, (tuple, str))\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(EnumValue, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.value == other.value\n>  \n> @@ -188,18 +185,47 @@ class EnumValueList(NodeListBase):\n>    _list_item_type = EnumValue\n>  \n>  \n> +class Feature(Definition):\n> +  \"\"\"Represents a runtime feature definition.\"\"\"\n> +  def __init__(self, mojom_name, attribute_list, body, **kwargs):\n> +    assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> +    assert isinstance(body, FeatureBody) or body is None\n> +    super().__init__(mojom_name, **kwargs)\n> +    self.attribute_list = attribute_list\n> +    self.body = body\n> +\n> +  def __eq__(self, other):\n> +    return super().__eq__(other) and \\\n> +           self.attribute_list == other.attribute_list and \\\n> +           self.body == other.body\n> +\n> +  def __repr__(self):\n> +    return \"Feature(mojom_name = %s, attribute_list = %s, body = %s)\" % (\n> +        self.mojom_name, self.attribute_list, self.body)\n> +\n> +\n> +# This needs to be declared after `FeatureConst` and `FeatureField`.\n> +class FeatureBody(NodeListBase):\n> +  \"\"\"Represents the body of (i.e., list of definitions inside) a feature.\"\"\"\n> +\n> +  # Features are compile time helpers so all fields are initializers/consts\n> +  # for the underlying platform feature type.\n> +  _list_item_type = (Const)\n> +\n> +\n>  class Import(NodeBase):\n>    \"\"\"Represents an import statement.\"\"\"\n>  \n>    def __init__(self, attribute_list, import_filename, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> -    assert _IsStrOrUnicode(import_filename)\n> -    super(Import, self).__init__(**kwargs)\n> +    assert isinstance(import_filename, str)\n> +    super().__init__(**kwargs)\n>      self.attribute_list = attribute_list\n> -    self.import_filename = import_filename\n> +    # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.\n> +    self.import_filename = os.path.normpath(import_filename).replace('\\\\', '/')\n>  \n>    def __eq__(self, other):\n> -    return super(Import, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.import_filename == other.import_filename\n>  \n> @@ -216,12 +242,12 @@ class Interface(Definition):\n>    def __init__(self, mojom_name, attribute_list, body, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert isinstance(body, InterfaceBody)\n> -    super(Interface, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.body = body\n>  \n>    def __eq__(self, other):\n> -    return super(Interface, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.body == other.body\n>  \n> @@ -236,14 +262,14 @@ class Method(Definition):\n>      assert isinstance(parameter_list, ParameterList)\n>      assert response_parameter_list is None or \\\n>             isinstance(response_parameter_list, ParameterList)\n> -    super(Method, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.ordinal = ordinal\n>      self.parameter_list = parameter_list\n>      self.response_parameter_list = response_parameter_list\n>  \n>    def __eq__(self, other):\n> -    return super(Method, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.ordinal == other.ordinal and \\\n>             self.parameter_list == other.parameter_list and \\\n> @@ -264,12 +290,12 @@ class Module(NodeBase):\n>      # |mojom_namespace| is either none or a \"wrapped identifier\".\n>      assert mojom_namespace is None or isinstance(mojom_namespace, tuple)\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n> -    super(Module, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.mojom_namespace = mojom_namespace\n>      self.attribute_list = attribute_list\n>  \n>    def __eq__(self, other):\n> -    return super(Module, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.mojom_namespace == other.mojom_namespace and \\\n>             self.attribute_list == other.attribute_list\n>  \n> @@ -281,13 +307,13 @@ class Mojom(NodeBase):\n>      assert module is None or isinstance(module, Module)\n>      assert isinstance(import_list, ImportList)\n>      assert isinstance(definition_list, list)\n> -    super(Mojom, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.module = module\n>      self.import_list = import_list\n>      self.definition_list = definition_list\n>  \n>    def __eq__(self, other):\n> -    return super(Mojom, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.module == other.module and \\\n>             self.import_list == other.import_list and \\\n>             self.definition_list == other.definition_list\n> @@ -302,11 +328,11 @@ class Ordinal(NodeBase):\n>  \n>    def __init__(self, value, **kwargs):\n>      assert isinstance(value, int)\n> -    super(Ordinal, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(Ordinal, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.value == other.value\n>  \n>  \n> @@ -314,18 +340,18 @@ class Parameter(NodeBase):\n>    \"\"\"Represents a method request or response parameter.\"\"\"\n>  \n>    def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):\n> -    assert _IsStrOrUnicode(mojom_name)\n> +    assert isinstance(mojom_name, str)\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert ordinal is None or isinstance(ordinal, Ordinal)\n> -    assert _IsStrOrUnicode(typename)\n> -    super(Parameter, self).__init__(**kwargs)\n> +    assert isinstance(typename, str)\n> +    super().__init__(**kwargs)\n>      self.mojom_name = mojom_name\n>      self.attribute_list = attribute_list\n>      self.ordinal = ordinal\n>      self.typename = typename\n>  \n>    def __eq__(self, other):\n> -    return super(Parameter, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.mojom_name == other.mojom_name and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.ordinal == other.ordinal and \\\n> @@ -344,42 +370,51 @@ class Struct(Definition):\n>    def __init__(self, mojom_name, attribute_list, body, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert isinstance(body, StructBody) or body is None\n> -    super(Struct, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.body = body\n>  \n>    def __eq__(self, other):\n> -    return super(Struct, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.body == other.body\n>  \n> +  def __repr__(self):\n> +    return \"Struct(mojom_name = %s, attribute_list = %s, body = %s)\" % (\n> +        self.mojom_name, self.attribute_list, self.body)\n> +\n>  \n>  class StructField(Definition):\n>    \"\"\"Represents a struct field definition.\"\"\"\n>  \n>    def __init__(self, mojom_name, attribute_list, ordinal, typename,\n>                 default_value, **kwargs):\n> -    assert _IsStrOrUnicode(mojom_name)\n> +    assert isinstance(mojom_name, str)\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert ordinal is None or isinstance(ordinal, Ordinal)\n> -    assert _IsStrOrUnicode(typename)\n> +    assert isinstance(typename, str)\n>      # The optional default value is currently either a value as a string or a\n>      # \"wrapped identifier\".\n> -    assert default_value is None or _IsStrOrUnicode(default_value) or \\\n> -        isinstance(default_value, tuple)\n> -    super(StructField, self).__init__(mojom_name, **kwargs)\n> +    assert default_value is None or isinstance(default_value, (str, tuple))\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.ordinal = ordinal\n>      self.typename = typename\n>      self.default_value = default_value\n>  \n>    def __eq__(self, other):\n> -    return super(StructField, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.ordinal == other.ordinal and \\\n>             self.typename == other.typename and \\\n>             self.default_value == other.default_value\n>  \n> +  def __repr__(self):\n> +    return (\"StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, \"\n> +            \"typename = %s, default_value = %s\") % (\n> +                self.mojom_name, self.attribute_list, self.ordinal,\n> +                self.typename, self.default_value)\n> +\n>  \n>  # This needs to be declared after |StructField|.\n>  class StructBody(NodeListBase):\n> @@ -394,29 +429,29 @@ class Union(Definition):\n>    def __init__(self, mojom_name, attribute_list, body, **kwargs):\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert isinstance(body, UnionBody)\n> -    super(Union, self).__init__(mojom_name, **kwargs)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.body = body\n>  \n>    def __eq__(self, other):\n> -    return super(Union, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.body == other.body\n>  \n>  \n>  class UnionField(Definition):\n>    def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):\n> -    assert _IsStrOrUnicode(mojom_name)\n> +    assert isinstance(mojom_name, str)\n>      assert attribute_list is None or isinstance(attribute_list, AttributeList)\n>      assert ordinal is None or isinstance(ordinal, Ordinal)\n> -    assert _IsStrOrUnicode(typename)\n> -    super(UnionField, self).__init__(mojom_name, **kwargs)\n> +    assert isinstance(typename, str)\n> +    super().__init__(mojom_name, **kwargs)\n>      self.attribute_list = attribute_list\n>      self.ordinal = ordinal\n>      self.typename = typename\n>  \n>    def __eq__(self, other):\n> -    return super(UnionField, self).__eq__(other) and \\\n> +    return super().__eq__(other) and \\\n>             self.attribute_list == other.attribute_list and \\\n>             self.ordinal == other.ordinal and \\\n>             self.typename == other.typename\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n> index 62798631dbce..b289f7b11f66 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py\n> @@ -1,32 +1,26 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> -import os.path\n> -import sys\n>  import unittest\n>  \n>  from mojom.parse import ast\n>  \n> -\n>  class _TestNode(ast.NodeBase):\n>    \"\"\"Node type for tests.\"\"\"\n>  \n>    def __init__(self, value, **kwargs):\n> -    super(_TestNode, self).__init__(**kwargs)\n> +    super().__init__(**kwargs)\n>      self.value = value\n>  \n>    def __eq__(self, other):\n> -    return super(_TestNode, self).__eq__(other) and self.value == other.value\n> -\n> +    return super().__eq__(other) and self.value == other.value\n>  \n>  class _TestNodeList(ast.NodeListBase):\n>    \"\"\"Node list type for tests.\"\"\"\n>  \n>    _list_item_type = _TestNode\n>  \n> -\n>  class ASTTest(unittest.TestCase):\n>    \"\"\"Tests various AST classes.\"\"\"\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n> index 3cb73c5d708c..9687edbfab1c 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2018 The Chromium Authors. All rights reserved.\n> +# Copyright 2018 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Helpers for processing conditionally enabled features in a mojom.\"\"\"\n> @@ -17,8 +17,10 @@ class EnableIfError(Error):\n>  def _IsEnabled(definition, enabled_features):\n>    \"\"\"Returns true if a definition is enabled.\n>  \n> -  A definition is enabled if it has no EnableIf attribute, or if the value of\n> -  the EnableIf attribute is in enabled_features.\n> +  A definition is enabled if it has no EnableIf/EnableIfNot attribute.\n> +  It is retained if it has an EnableIf attribute and the attribute is in\n> +  enabled_features. It is retained if it has an EnableIfNot attribute and the\n> +  attribute is not in enabled features.\n>    \"\"\"\n>    if not hasattr(definition, \"attribute_list\"):\n>      return True\n> @@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features):\n>  \n>    already_defined = False\n>    for a in definition.attribute_list:\n> -    if a.key == 'EnableIf':\n> +    if a.key == 'EnableIf' or a.key == 'EnableIfNot':\n>        if already_defined:\n>          raise EnableIfError(\n>              definition.filename,\n> -            \"EnableIf attribute may only be defined once per field.\",\n> +            \"EnableIf/EnableIfNot attribute may only be set once per field.\",\n>              definition.lineno)\n>        already_defined = True\n>  \n>    for attribute in definition.attribute_list:\n>      if attribute.key == 'EnableIf' and attribute.value not in enabled_features:\n>        return False\n> +    if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:\n> +      return False\n>    return True\n>  \n>  \n> @@ -56,15 +60,12 @@ def _FilterDefinition(definition, enabled_features):\n>    \"\"\"Filters definitions with a body.\"\"\"\n>    if isinstance(definition, ast.Enum):\n>      _FilterDisabledFromNodeList(definition.enum_value_list, enabled_features)\n> -  elif isinstance(definition, ast.Interface):\n> -    _FilterDisabledFromNodeList(definition.body, enabled_features)\n>    elif isinstance(definition, ast.Method):\n>      _FilterDisabledFromNodeList(definition.parameter_list, enabled_features)\n>      _FilterDisabledFromNodeList(definition.response_parameter_list,\n>                                  enabled_features)\n> -  elif isinstance(definition, ast.Struct):\n> -    _FilterDisabledFromNodeList(definition.body, enabled_features)\n> -  elif isinstance(definition, ast.Union):\n> +  elif isinstance(definition,\n> +                  (ast.Interface, ast.Struct, ast.Union, ast.Feature)):\n>      _FilterDisabledFromNodeList(definition.body, enabled_features)\n>  \n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n> index aa609be73837..cca1764b1cd5 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features_unittest.py\n> @@ -1,13 +1,12 @@\n> -# Copyright 2018 The Chromium Authors. All rights reserved.\n> +# Copyright 2018 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> +import importlib.util\n>  import os\n>  import sys\n>  import unittest\n>  \n> -\n>  def _GetDirAbove(dirname):\n>    \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n>    also be \"above\" this file).\"\"\"\n> @@ -18,9 +17,8 @@ def _GetDirAbove(dirname):\n>      if tail == dirname:\n>        return path\n>  \n> -\n>  try:\n> -  imp.find_module('mojom')\n> +  importlib.util.find_spec(\"mojom\")\n>  except ImportError:\n>    sys.path.append(os.path.join(_GetDirAbove('pylib'), 'pylib'))\n>  import mojom.parse.ast as ast\n> @@ -29,7 +27,6 @@ import mojom.parse.parser as parser\n>  \n>  ENABLED_FEATURES = frozenset({'red', 'green', 'blue'})\n>  \n> -\n>  class ConditionalFeaturesTest(unittest.TestCase):\n>    \"\"\"Tests |mojom.parse.conditional_features|.\"\"\"\n>  \n> @@ -55,6 +52,48 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>      \"\"\"\n>      self.parseAndAssertEqual(const_source, expected_source)\n>  \n> +  def testFilterIfNotConst(self):\n> +    \"\"\"Test that Consts are correctly filtered.\"\"\"\n> +    const_source = \"\"\"\n> +      [EnableIfNot=blue]\n> +      const int kMyConst1 = 1;\n> +      [EnableIfNot=orange]\n> +      const double kMyConst2 = 2;\n> +      [EnableIf=blue]\n> +      const int kMyConst3 = 3;\n> +      [EnableIfNot=blue]\n> +      const int kMyConst4 = 4;\n> +      [EnableIfNot=purple]\n> +      const int kMyConst5 = 5;\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      [EnableIfNot=orange]\n> +      const double kMyConst2 = 2;\n> +      [EnableIf=blue]\n> +      const int kMyConst3 = 3;\n> +      [EnableIfNot=purple]\n> +      const int kMyConst5 = 5;\n> +    \"\"\"\n> +    self.parseAndAssertEqual(const_source, expected_source)\n> +\n> +  def testFilterIfNotMultipleConst(self):\n> +    \"\"\"Test that Consts are correctly filtered.\"\"\"\n> +    const_source = \"\"\"\n> +      [EnableIfNot=blue]\n> +      const int kMyConst1 = 1;\n> +      [EnableIfNot=orange]\n> +      const double kMyConst2 = 2;\n> +      [EnableIfNot=orange]\n> +      const int kMyConst3 = 3;\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      [EnableIfNot=orange]\n> +      const double kMyConst2 = 2;\n> +      [EnableIfNot=orange]\n> +      const int kMyConst3 = 3;\n> +    \"\"\"\n> +    self.parseAndAssertEqual(const_source, expected_source)\n> +\n>    def testFilterEnum(self):\n>      \"\"\"Test that EnumValues are correctly filtered from an Enum.\"\"\"\n>      enum_source = \"\"\"\n> @@ -91,6 +130,24 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>      \"\"\"\n>      self.parseAndAssertEqual(import_source, expected_source)\n>  \n> +  def testFilterIfNotImport(self):\n> +    \"\"\"Test that imports are correctly filtered from a Mojom.\"\"\"\n> +    import_source = \"\"\"\n> +      [EnableIf=blue]\n> +      import \"foo.mojom\";\n> +      [EnableIfNot=purple]\n> +      import \"bar.mojom\";\n> +      [EnableIfNot=green]\n> +      import \"baz.mojom\";\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      [EnableIf=blue]\n> +      import \"foo.mojom\";\n> +      [EnableIfNot=purple]\n> +      import \"bar.mojom\";\n> +    \"\"\"\n> +    self.parseAndAssertEqual(import_source, expected_source)\n> +\n>    def testFilterInterface(self):\n>      \"\"\"Test that definitions are correctly filtered from an Interface.\"\"\"\n>      interface_source = \"\"\"\n> @@ -175,6 +232,50 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>      \"\"\"\n>      self.parseAndAssertEqual(struct_source, expected_source)\n>  \n> +  def testFilterIfNotStruct(self):\n> +    \"\"\"Test that definitions are correctly filtered from a Struct.\"\"\"\n> +    struct_source = \"\"\"\n> +      struct MyStruct {\n> +        [EnableIf=blue]\n> +        enum MyEnum {\n> +          VALUE1,\n> +          [EnableIfNot=red]\n> +          VALUE2,\n> +        };\n> +        [EnableIfNot=yellow]\n> +        const double kMyConst = 1.23;\n> +        [EnableIf=green]\n> +        int32 a;\n> +        double b;\n> +        [EnableIfNot=purple]\n> +        int32 c;\n> +        [EnableIf=blue]\n> +        double d;\n> +        int32 e;\n> +        [EnableIfNot=red]\n> +        double f;\n> +      };\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      struct MyStruct {\n> +        [EnableIf=blue]\n> +        enum MyEnum {\n> +          VALUE1,\n> +        };\n> +        [EnableIfNot=yellow]\n> +        const double kMyConst = 1.23;\n> +        [EnableIf=green]\n> +        int32 a;\n> +        double b;\n> +        [EnableIfNot=purple]\n> +        int32 c;\n> +        [EnableIf=blue]\n> +        double d;\n> +        int32 e;\n> +      };\n> +    \"\"\"\n> +    self.parseAndAssertEqual(struct_source, expected_source)\n> +\n>    def testFilterUnion(self):\n>      \"\"\"Test that UnionFields are correctly filtered from a Union.\"\"\"\n>      union_source = \"\"\"\n> @@ -216,6 +317,25 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>      \"\"\"\n>      self.parseAndAssertEqual(mojom_source, expected_source)\n>  \n> +  def testFeaturesWithEnableIf(self):\n> +    mojom_source = \"\"\"\n> +      feature Foo {\n> +        const string name = \"FooFeature\";\n> +        [EnableIf=red]\n> +        const bool default_state = false;\n> +        [EnableIf=yellow]\n> +        const bool default_state = true;\n> +      };\n> +    \"\"\"\n> +    expected_source = \"\"\"\n> +      feature Foo {\n> +        const string name = \"FooFeature\";\n> +        [EnableIf=red]\n> +        const bool default_state = false;\n> +      };\n> +    \"\"\"\n> +    self.parseAndAssertEqual(mojom_source, expected_source)\n> +\n>    def testMultipleEnableIfs(self):\n>      source = \"\"\"\n>        enum Foo {\n> @@ -228,6 +348,29 @@ class ConditionalFeaturesTest(unittest.TestCase):\n>                        conditional_features.RemoveDisabledDefinitions,\n>                        definition, ENABLED_FEATURES)\n>  \n> +  def testMultipleEnableIfs(self):\n> +    source = \"\"\"\n> +      enum Foo {\n> +        [EnableIf=red,EnableIfNot=yellow]\n> +        kBarValue = 5,\n> +      };\n> +    \"\"\"\n> +    definition = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertRaises(conditional_features.EnableIfError,\n> +                      conditional_features.RemoveDisabledDefinitions,\n> +                      definition, ENABLED_FEATURES)\n> +\n> +  def testMultipleEnableIfs(self):\n> +    source = \"\"\"\n> +      enum Foo {\n> +        [EnableIfNot=red,EnableIfNot=yellow]\n> +        kBarValue = 5,\n> +      };\n> +    \"\"\"\n> +    definition = parser.Parse(source, \"my_file.mojom\")\n> +    self.assertRaises(conditional_features.EnableIfError,\n> +                      conditional_features.RemoveDisabledDefinitions,\n> +                      definition, ENABLED_FEATURES)\n>  \n>  if __name__ == '__main__':\n>    unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n> index 3e084bbf22b7..00136a8bf94a 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py\n> @@ -1,8 +1,7 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n>  import os.path\n>  import sys\n>  \n> @@ -22,7 +21,7 @@ class LexError(Error):\n>  \n>  # We have methods which look like they could be functions:\n>  # pylint: disable=R0201\n> -class Lexer(object):\n> +class Lexer:\n>    def __init__(self, filename):\n>      self.filename = filename\n>  \n> @@ -56,6 +55,7 @@ class Lexer(object):\n>        'PENDING_RECEIVER',\n>        'PENDING_ASSOCIATED_REMOTE',\n>        'PENDING_ASSOCIATED_RECEIVER',\n> +      'FEATURE',\n>    )\n>  \n>    keyword_map = {}\n> @@ -81,7 +81,6 @@ class Lexer(object):\n>        # Operators\n>        'MINUS',\n>        'PLUS',\n> -      'AMP',\n>        'QSTN',\n>  \n>        # Assignment\n> @@ -168,7 +167,6 @@ class Lexer(object):\n>    # Operators\n>    t_MINUS = r'-'\n>    t_PLUS = r'\\+'\n> -  t_AMP = r'&'\n>    t_QSTN = r'\\?'\n>  \n>    # =\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n> index eadc6587cf94..bc9f8354316f 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py\n> @@ -1,13 +1,12 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> +import importlib.util\n>  import os.path\n>  import sys\n>  import unittest\n>  \n> -\n>  def _GetDirAbove(dirname):\n>    \"\"\"Returns the directory \"above\" this file containing |dirname| (which must\n>    also be \"above\" this file).\"\"\"\n> @@ -18,17 +17,15 @@ def _GetDirAbove(dirname):\n>      if tail == dirname:\n>        return path\n>  \n> -\n>  sys.path.insert(1, os.path.join(_GetDirAbove(\"mojo\"), \"third_party\"))\n>  from ply import lex\n>  \n>  try:\n> -  imp.find_module(\"mojom\")\n> +  importlib.util.find_spec(\"mojom\")\n>  except ImportError:\n>    sys.path.append(os.path.join(_GetDirAbove(\"pylib\"), \"pylib\"))\n>  import mojom.parse.lexer\n>  \n> -\n>  # This (monkey-patching LexToken to make comparison value-based) is evil, but\n>  # we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing\n>  # for object identity.)\n> @@ -146,7 +143,6 @@ class LexerTest(unittest.TestCase):\n>          self._SingleTokenForInput(\"+\"), _MakeLexToken(\"PLUS\", \"+\"))\n>      self.assertEquals(\n>          self._SingleTokenForInput(\"-\"), _MakeLexToken(\"MINUS\", \"-\"))\n> -    self.assertEquals(self._SingleTokenForInput(\"&\"), _MakeLexToken(\"AMP\", \"&\"))\n>      self.assertEquals(\n>          self._SingleTokenForInput(\"?\"), _MakeLexToken(\"QSTN\", \"?\"))\n>      self.assertEquals(\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n> index b3b803d6f334..1dffd98b92a6 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py\n> @@ -1,8 +1,11 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Generates a syntax tree from a Mojo IDL file.\"\"\"\n>  \n> +# Breaking parser stanzas is unhelpful so allow longer lines.\n> +# pylint: disable=line-too-long\n> +\n>  import os.path\n>  import sys\n>  \n> @@ -33,7 +36,7 @@ class ParseError(Error):\n>  \n>  # We have methods which look like they could be functions:\n>  # pylint: disable=R0201\n> -class Parser(object):\n> +class Parser:\n>    def __init__(self, lexer, source, filename):\n>      self.tokens = lexer.tokens\n>      self.source = source\n> @@ -111,7 +114,8 @@ class Parser(object):\n>                    | union\n>                    | interface\n>                    | enum\n> -                  | const\"\"\"\n> +                  | const\n> +                  | feature\"\"\"\n>      p[0] = p[1]\n>  \n>    def p_attribute_section_1(self, p):\n> @@ -140,12 +144,19 @@ class Parser(object):\n>      p[0].Append(p[3])\n>  \n>    def p_attribute_1(self, p):\n> -    \"\"\"attribute : NAME EQUALS evaled_literal\n> -                 | NAME EQUALS NAME\"\"\"\n> -    p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))\n> +    \"\"\"attribute : name_wrapped EQUALS identifier_wrapped\"\"\"\n> +    p[0] = ast.Attribute(p[1],\n> +                         p[3][1],\n> +                         filename=self.filename,\n> +                         lineno=p.lineno(1))\n>  \n>    def p_attribute_2(self, p):\n> -    \"\"\"attribute : NAME\"\"\"\n> +    \"\"\"attribute : name_wrapped EQUALS evaled_literal\n> +                 | name_wrapped EQUALS name_wrapped\"\"\"\n> +    p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))\n> +\n> +  def p_attribute_3(self, p):\n> +    \"\"\"attribute : name_wrapped\"\"\"\n>      p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))\n>  \n>    def p_evaled_literal(self, p):\n> @@ -161,11 +172,11 @@ class Parser(object):\n>        p[0] = eval(p[1])\n>  \n>    def p_struct_1(self, p):\n> -    \"\"\"struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI\"\"\"\n> +    \"\"\"struct : attribute_section STRUCT name_wrapped LBRACE struct_body RBRACE SEMI\"\"\"\n>      p[0] = ast.Struct(p[3], p[1], p[5])\n>  \n>    def p_struct_2(self, p):\n> -    \"\"\"struct : attribute_section STRUCT NAME SEMI\"\"\"\n> +    \"\"\"struct : attribute_section STRUCT name_wrapped SEMI\"\"\"\n>      p[0] = ast.Struct(p[3], p[1], None)\n>  \n>    def p_struct_body_1(self, p):\n> @@ -180,11 +191,24 @@ class Parser(object):\n>      p[0].Append(p[2])\n>  \n>    def p_struct_field(self, p):\n> -    \"\"\"struct_field : attribute_section typename NAME ordinal default SEMI\"\"\"\n> +    \"\"\"struct_field : attribute_section typename name_wrapped ordinal default SEMI\"\"\"\n>      p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5])\n>  \n> +  def p_feature(self, p):\n> +    \"\"\"feature : attribute_section FEATURE NAME LBRACE feature_body RBRACE SEMI\"\"\"\n> +    p[0] = ast.Feature(p[3], p[1], p[5])\n> +\n> +  def p_feature_body_1(self, p):\n> +    \"\"\"feature_body : \"\"\"\n> +    p[0] = ast.FeatureBody()\n> +\n> +  def p_feature_body_2(self, p):\n> +    \"\"\"feature_body : feature_body const\"\"\"\n> +    p[0] = p[1]\n> +    p[0].Append(p[2])\n> +\n>    def p_union(self, p):\n> -    \"\"\"union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI\"\"\"\n> +    \"\"\"union : attribute_section UNION name_wrapped LBRACE union_body RBRACE SEMI\"\"\"\n>      p[0] = ast.Union(p[3], p[1], p[5])\n>  \n>    def p_union_body_1(self, p):\n> @@ -197,7 +221,7 @@ class Parser(object):\n>      p[1].Append(p[2])\n>  \n>    def p_union_field(self, p):\n> -    \"\"\"union_field : attribute_section typename NAME ordinal SEMI\"\"\"\n> +    \"\"\"union_field : attribute_section typename name_wrapped ordinal SEMI\"\"\"\n>      p[0] = ast.UnionField(p[3], p[1], p[4], p[2])\n>  \n>    def p_default_1(self, p):\n> @@ -209,8 +233,7 @@ class Parser(object):\n>      p[0] = p[2]\n>  \n>    def p_interface(self, p):\n> -    \"\"\"interface : attribute_section INTERFACE NAME LBRACE interface_body \\\n> -                       RBRACE SEMI\"\"\"\n> +    \"\"\"interface : attribute_section INTERFACE name_wrapped LBRACE interface_body RBRACE SEMI\"\"\"\n>      p[0] = ast.Interface(p[3], p[1], p[5])\n>  \n>    def p_interface_body_1(self, p):\n> @@ -233,8 +256,7 @@ class Parser(object):\n>      p[0] = p[3]\n>  \n>    def p_method(self, p):\n> -    \"\"\"method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \\\n> -                    response SEMI\"\"\"\n> +    \"\"\"method : attribute_section name_wrapped ordinal LPAREN parameter_list RPAREN response SEMI\"\"\"\n>      p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7])\n>  \n>    def p_parameter_list_1(self, p):\n> @@ -255,7 +277,7 @@ class Parser(object):\n>      p[0].Append(p[3])\n>  \n>    def p_parameter(self, p):\n> -    \"\"\"parameter : attribute_section typename NAME ordinal\"\"\"\n> +    \"\"\"parameter : attribute_section typename name_wrapped ordinal\"\"\"\n>      p[0] = ast.Parameter(\n>          p[3], p[1], p[4], p[2], filename=self.filename, lineno=p.lineno(3))\n>  \n> @@ -271,8 +293,7 @@ class Parser(object):\n>      \"\"\"nonnullable_typename : basictypename\n>                              | array\n>                              | fixed_array\n> -                            | associative_array\n> -                            | interfacerequest\"\"\"\n> +                            | associative_array\"\"\"\n>      p[0] = p[1]\n>  \n>    def p_basictypename(self, p):\n> @@ -297,18 +318,16 @@ class Parser(object):\n>      p[0] = \"rcv<%s>\" % p[3]\n>  \n>    def p_associatedremotetype(self, p):\n> -    \"\"\"associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier \\\n> -                                  RANGLE\"\"\"\n> +    \"\"\"associatedremotetype : PENDING_ASSOCIATED_REMOTE LANGLE identifier RANGLE\"\"\"\n>      p[0] = \"rma<%s>\" % p[3]\n>  \n>    def p_associatedreceivertype(self, p):\n> -    \"\"\"associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier \\\n> -                                    RANGLE\"\"\"\n> +    \"\"\"associatedreceivertype : PENDING_ASSOCIATED_RECEIVER LANGLE identifier RANGLE\"\"\"\n>      p[0] = \"rca<%s>\" % p[3]\n>  \n>    def p_handletype(self, p):\n>      \"\"\"handletype : HANDLE\n> -                  | HANDLE LANGLE NAME RANGLE\"\"\"\n> +                  | HANDLE LANGLE name_wrapped RANGLE\"\"\"\n>      if len(p) == 2:\n>        p[0] = p[1]\n>      else:\n> @@ -342,14 +361,6 @@ class Parser(object):\n>      \"\"\"associative_array : MAP LANGLE identifier COMMA typename RANGLE\"\"\"\n>      p[0] = p[5] + \"{\" + p[3] + \"}\"\n>  \n> -  def p_interfacerequest(self, p):\n> -    \"\"\"interfacerequest : identifier AMP\n> -                        | ASSOCIATED identifier AMP\"\"\"\n> -    if len(p) == 3:\n> -      p[0] = p[1] + \"&\"\n> -    else:\n> -      p[0] = \"asso<\" + p[2] + \"&>\"\n> -\n>    def p_ordinal_1(self, p):\n>      \"\"\"ordinal : \"\"\"\n>      p[0] = None\n> @@ -366,15 +377,14 @@ class Parser(object):\n>      p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))\n>  \n>    def p_enum_1(self, p):\n> -    \"\"\"enum : attribute_section ENUM NAME LBRACE enum_value_list \\\n> -                  RBRACE SEMI\n> -            | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \\\n> -                  COMMA RBRACE SEMI\"\"\"\n> +    \"\"\"enum : attribute_section ENUM name_wrapped LBRACE enum_value_list RBRACE SEMI\n> +            | attribute_section ENUM name_wrapped LBRACE \\\n> +                    nonempty_enum_value_list COMMA RBRACE SEMI\"\"\"\n>      p[0] = ast.Enum(\n>          p[3], p[1], p[5], filename=self.filename, lineno=p.lineno(2))\n>  \n>    def p_enum_2(self, p):\n> -    \"\"\"enum : attribute_section ENUM NAME SEMI\"\"\"\n> +    \"\"\"enum : attribute_section ENUM name_wrapped SEMI\"\"\"\n>      p[0] = ast.Enum(\n>          p[3], p[1], None, filename=self.filename, lineno=p.lineno(2))\n>  \n> @@ -396,9 +406,9 @@ class Parser(object):\n>      p[0].Append(p[3])\n>  \n>    def p_enum_value(self, p):\n> -    \"\"\"enum_value : attribute_section NAME\n> -                  | attribute_section NAME EQUALS int\n> -                  | attribute_section NAME EQUALS identifier_wrapped\"\"\"\n> +    \"\"\"enum_value : attribute_section name_wrapped\n> +                  | attribute_section name_wrapped EQUALS int\n> +                  | attribute_section name_wrapped EQUALS identifier_wrapped\"\"\"\n>      p[0] = ast.EnumValue(\n>          p[2],\n>          p[1],\n> @@ -407,7 +417,7 @@ class Parser(object):\n>          lineno=p.lineno(2))\n>  \n>    def p_const(self, p):\n> -    \"\"\"const : attribute_section CONST typename NAME EQUALS constant SEMI\"\"\"\n> +    \"\"\"const : attribute_section CONST typename name_wrapped EQUALS constant SEMI\"\"\"\n>      p[0] = ast.Const(p[4], p[1], p[3], p[6])\n>  \n>    def p_constant(self, p):\n> @@ -422,10 +432,16 @@ class Parser(object):\n>    # TODO(vtl): Make this produce a \"wrapped\" identifier (probably as an\n>    # |ast.Identifier|, to be added) and get rid of identifier_wrapped.\n>    def p_identifier(self, p):\n> -    \"\"\"identifier : NAME\n> -                  | NAME DOT identifier\"\"\"\n> +    \"\"\"identifier : name_wrapped\n> +                  | name_wrapped DOT identifier\"\"\"\n>      p[0] = ''.join(p[1:])\n>  \n> +  # Allow 'feature' to be a name literal not just a keyword.\n> +  def p_name_wrapped(self, p):\n> +    \"\"\"name_wrapped : NAME\n> +                    | FEATURE\"\"\"\n> +    p[0] = p[1]\n> +\n>    def p_literal(self, p):\n>      \"\"\"literal : int\n>                 | float\n> @@ -458,6 +474,12 @@ class Parser(object):\n>        # TODO(vtl): Can we figure out what's missing?\n>        raise ParseError(self.filename, \"Unexpected end of file\")\n>  \n> +    if e.value == 'feature':\n> +      raise ParseError(self.filename,\n> +                       \"`feature` is reserved for a future mojom keyword\",\n> +                       lineno=e.lineno,\n> +                       snippet=self._GetSnippet(e.lineno))\n> +\n>      raise ParseError(\n>          self.filename,\n>          \"Unexpected %r:\" % e.value,\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n> index 6d6b71532410..0a26307b1a32 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py\n> @@ -1,17 +1,13 @@\n> -# Copyright 2014 The Chromium Authors. All rights reserved.\n> +# Copyright 2014 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -import imp\n> -import os.path\n> -import sys\n>  import unittest\n>  \n>  from mojom.parse import ast\n>  from mojom.parse import lexer\n>  from mojom.parse import parser\n>  \n> -\n>  class ParserTest(unittest.TestCase):\n>    \"\"\"Tests |parser.Parse()|.\"\"\"\n>  \n> @@ -1086,7 +1082,7 @@ class ParserTest(unittest.TestCase):\n>            handle<data_pipe_producer>? k;\n>            handle<message_pipe>? l;\n>            handle<shared_buffer>? m;\n> -          some_interface&? n;\n> +          pending_receiver<some_interface>? n;\n>            handle<platform>? o;\n>          };\n>          \"\"\"\n> @@ -1110,7 +1106,7 @@ class ParserTest(unittest.TestCase):\n>                  ast.StructField('l', None, None, 'handle<message_pipe>?', None),\n>                  ast.StructField('m', None, None, 'handle<shared_buffer>?',\n>                                  None),\n> -                ast.StructField('n', None, None, 'some_interface&?', None),\n> +                ast.StructField('n', None, None, 'rcv<some_interface>?', None),\n>                  ast.StructField('o', None, None, 'handle<platform>?', None)\n>              ]))\n>      ])\n> @@ -1138,16 +1134,6 @@ class ParserTest(unittest.TestCase):\n>          r\" *handle\\?<data_pipe_consumer> a;$\"):\n>        parser.Parse(source2, \"my_file.mojom\")\n>  \n> -    source3 = \"\"\"\\\n> -        struct MyStruct {\n> -          some_interface?& a;\n> -        };\n> -        \"\"\"\n> -    with self.assertRaisesRegexp(\n> -        parser.ParseError, r\"^my_file\\.mojom:2: Error: Unexpected '&':\\n\"\n> -        r\" *some_interface\\?& a;$\"):\n> -      parser.Parse(source3, \"my_file.mojom\")\n> -\n>    def testSimpleUnion(self):\n>      \"\"\"Tests a simple .mojom source that just defines a union.\"\"\"\n>      source = \"\"\"\\\n> @@ -1317,9 +1303,9 @@ class ParserTest(unittest.TestCase):\n>      source1 = \"\"\"\\\n>          struct MyStruct {\n>            associated MyInterface a;\n> -          associated MyInterface& b;\n> +          pending_associated_receiver<MyInterface> b;\n>            associated MyInterface? c;\n> -          associated MyInterface&? d;\n> +          pending_associated_receiver<MyInterface>? d;\n>          };\n>          \"\"\"\n>      expected1 = ast.Mojom(None, ast.ImportList(), [\n> @@ -1327,16 +1313,16 @@ class ParserTest(unittest.TestCase):\n>              'MyStruct', None,\n>              ast.StructBody([\n>                  ast.StructField('a', None, None, 'asso<MyInterface>', None),\n> -                ast.StructField('b', None, None, 'asso<MyInterface&>', None),\n> +                ast.StructField('b', None, None, 'rca<MyInterface>', None),\n>                  ast.StructField('c', None, None, 'asso<MyInterface>?', None),\n> -                ast.StructField('d', None, None, 'asso<MyInterface&>?', None)\n> +                ast.StructField('d', None, None, 'rca<MyInterface>?', None)\n>              ]))\n>      ])\n>      self.assertEquals(parser.Parse(source1, \"my_file.mojom\"), expected1)\n>  \n>      source2 = \"\"\"\\\n>          interface MyInterface {\n> -          MyMethod(associated A a) =>(associated B& b);\n> +          MyMethod(associated A a) =>(pending_associated_receiver<B> b);\n>          };\"\"\"\n>      expected2 = ast.Mojom(None, ast.ImportList(), [\n>          ast.Interface(\n> @@ -1344,10 +1330,10 @@ class ParserTest(unittest.TestCase):\n>              ast.InterfaceBody(\n>                  ast.Method(\n>                      'MyMethod', None, None,\n> -                    ast.ParameterList(\n> -                        ast.Parameter('a', None, None, 'asso<A>')),\n> -                    ast.ParameterList(\n> -                        ast.Parameter('b', None, None, 'asso<B&>')))))\n> +                    ast.ParameterList(ast.Parameter('a', None, None,\n> +                                                    'asso<A>')),\n> +                    ast.ParameterList(ast.Parameter('b', None, None,\n> +                                                    'rca<B>')))))\n>      ])\n>      self.assertEquals(parser.Parse(source2, \"my_file.mojom\"), expected2)\n>  \n> @@ -1385,6 +1371,5 @@ class ParserTest(unittest.TestCase):\n>          r\" *associated\\? MyInterface& a;$\"):\n>        parser.Parse(source3, \"my_file.mojom\")\n>  \n> -\n>  if __name__ == \"__main__\":\n>    unittest.main()\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n> index eb90c825f9bc..9693090e44ea 100755\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py\n> @@ -1,5 +1,5 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +#!/usr/bin/env python3\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \"\"\"Parses mojom IDL files.\n> @@ -11,6 +11,7 @@ generate usable language bindings.\n>  \"\"\"\n>  \n>  import argparse\n> +import builtins\n>  import codecs\n>  import errno\n>  import json\n> @@ -19,6 +20,7 @@ import multiprocessing\n>  import os\n>  import os.path\n>  import sys\n> +import traceback\n>  from collections import defaultdict\n>  \n>  from mojom.generate import module\n> @@ -28,16 +30,12 @@ from mojom.parse import conditional_features\n>  \n>  \n>  # Disable this for easier debugging.\n> -# In Python 2, subprocesses just hang when exceptions are thrown :(.\n> -_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2\n> +_ENABLE_MULTIPROCESSING = True\n>  \n> -if sys.version_info < (3, 4):\n> -  _MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux')\n> -else:\n> -  # https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725\n> -  if __name__ == '__main__' and sys.platform == 'darwin':\n> -    multiprocessing.set_start_method('fork')\n> -  _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'\n> +# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725\n> +if __name__ == '__main__' and sys.platform == 'darwin':\n> +  multiprocessing.set_start_method('fork')\n> +_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'\n>  \n>  \n>  def _ResolveRelativeImportPath(path, roots):\n> @@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots):\n>    raise ValueError('\"%s\" does not exist in any of %s' % (path, roots))\n>  \n>  \n> -def _RebaseAbsolutePath(path, roots):\n> +def RebaseAbsolutePath(path, roots):\n>    \"\"\"Rewrites an absolute file path as relative to an absolute directory path in\n>    roots.\n>  \n> @@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,\n>      # Already done.\n>      return\n>  \n> -  for dep_abspath, dep_path in dependencies[mojom_abspath]:\n> +  for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]):\n>      if dep_abspath not in loaded_modules:\n>        _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,\n>                           loaded_modules, module_metadata)\n> @@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):\n>  \n>    def collect(metadata_filename):\n>      processed_deps.add(metadata_filename)\n> +\n> +    # Paths in the metadata file are relative to the metadata file's dir.\n> +    metadata_dir = os.path.abspath(os.path.dirname(metadata_filename))\n> +\n> +    def to_abs(s):\n> +      return os.path.normpath(os.path.join(metadata_dir, s))\n> +\n>      with open(metadata_filename) as f:\n>        metadata = json.load(f)\n>        allowed_imports.update(\n> -          map(os.path.normcase, map(os.path.normpath, metadata['sources'])))\n> +          [os.path.normcase(to_abs(s)) for s in metadata['sources']])\n>        for dep_metadata in metadata['deps']:\n> +        dep_metadata = to_abs(dep_metadata)\n>          if dep_metadata not in processed_deps:\n>            collect(dep_metadata)\n>  \n> @@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):\n>  \n>  \n>  # multiprocessing helper.\n> -def _ParseAstHelper(args):\n> -  mojom_abspath, enabled_features = args\n> +def _ParseAstHelper(mojom_abspath, enabled_features):\n>    with codecs.open(mojom_abspath, encoding='utf-8') as f:\n>      ast = parser.Parse(f.read(), mojom_abspath)\n>      conditional_features.RemoveDisabledDefinitions(ast, enabled_features)\n> @@ -181,8 +186,7 @@ def _ParseAstHelper(args):\n>  \n>  \n>  # multiprocessing helper.\n> -def _SerializeHelper(args):\n> -  mojom_abspath, mojom_path = args\n> +def _SerializeHelper(mojom_abspath, mojom_path):\n>    module_path = os.path.join(_SerializeHelper.output_root_path,\n>                               _GetModuleFilename(mojom_path))\n>    module_dir = os.path.dirname(module_path)\n> @@ -199,12 +203,33 @@ def _SerializeHelper(args):\n>      _SerializeHelper.loaded_modules[mojom_abspath].Dump(f)\n>  \n>  \n> -def _Shard(target_func, args, processes=None):\n> -  args = list(args)\n> +class _ExceptionWrapper:\n> +  def __init__(self):\n> +    # Do not capture exception object to ensure pickling works.\n> +    self.formatted_trace = traceback.format_exc()\n> +\n> +\n> +class _FuncWrapper:\n> +  \"\"\"Marshals exceptions and spreads args.\"\"\"\n> +\n> +  def __init__(self, func):\n> +    self._func = func\n> +\n> +  def __call__(self, args):\n> +    # multiprocessing does not gracefully handle excptions.\n> +    # https://crbug.com/1219044\n> +    try:\n> +      return self._func(*args)\n> +    except:  # pylint: disable=bare-except\n> +      return _ExceptionWrapper()\n> +\n> +\n> +def _Shard(target_func, arg_list, processes=None):\n> +  arg_list = list(arg_list)\n>    if processes is None:\n>      processes = multiprocessing.cpu_count()\n>    # Seems optimal to have each process perform at least 2 tasks.\n> -  processes = min(processes, len(args) // 2)\n> +  processes = min(processes, len(arg_list) // 2)\n>  \n>    if sys.platform == 'win32':\n>      # TODO(crbug.com/1190269) - we can't use more than 56\n> @@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None):\n>  \n>    # Don't spin up processes unless there is enough work to merit doing so.\n>    if not _ENABLE_MULTIPROCESSING or processes < 2:\n> -    for result in map(target_func, args):\n> -      yield result\n> +    for arg_tuple in arg_list:\n> +      yield target_func(*arg_tuple)\n>      return\n>  \n>    pool = multiprocessing.Pool(processes=processes)\n>    try:\n> -    for result in pool.imap_unordered(target_func, args):\n> +    wrapped_func = _FuncWrapper(target_func)\n> +    for result in pool.imap_unordered(wrapped_func, arg_list):\n> +      if isinstance(result, _ExceptionWrapper):\n> +        sys.stderr.write(result.formatted_trace)\n> +        sys.exit(1)\n>        yield result\n>    finally:\n>      pool.close()\n> @@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None):\n>  def _ParseMojoms(mojom_files,\n>                   input_root_paths,\n>                   output_root_path,\n> +                 module_root_paths,\n>                   enabled_features,\n>                   module_metadata,\n>                   allowed_imports=None):\n> @@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files,\n>          are based on the mojom's relative path, rebased onto this path.\n>          Additionally, the script expects this root to contain already-generated\n>          modules for any transitive dependencies not listed in mojom_files.\n> +    module_root_paths: A list of absolute filesystem paths which contain\n> +        already-generated modules for any non-transitive dependencies.\n>      enabled_features: A list of enabled feature names, controlling which AST\n> -        nodes are filtered by [EnableIf] attributes.\n> +        nodes are filtered by [EnableIf] or [EnableIfNot] attributes.\n>      module_metadata: A list of 2-tuples representing metadata key-value pairs to\n>          attach to each compiled module output.\n>  \n> @@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files,\n>    loaded_modules = {}\n>    input_dependencies = defaultdict(set)\n>    mojom_files_to_parse = dict((os.path.normcase(abs_path),\n> -                               _RebaseAbsolutePath(abs_path, input_root_paths))\n> +                               RebaseAbsolutePath(abs_path, input_root_paths))\n>                                for abs_path in mojom_files)\n>    abs_paths = dict(\n>        (path, abs_path) for abs_path, path in mojom_files_to_parse.items())\n> @@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files,\n>      loaded_mojom_asts[mojom_abspath] = ast\n>  \n>    logging.info('Processing dependencies')\n> -  for mojom_abspath, ast in loaded_mojom_asts.items():\n> +  for mojom_abspath, ast in sorted(loaded_mojom_asts.items()):\n>      invalid_imports = []\n>      for imp in ast.import_list:\n>        import_abspath = _ResolveRelativeImportPath(imp.import_filename,\n> @@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files,\n>          # be parsed and have a module file sitting in a corresponding output\n>          # location.\n>          module_path = _GetModuleFilename(imp.import_filename)\n> -        module_abspath = _ResolveRelativeImportPath(module_path,\n> -                                                    [output_root_path])\n> +        module_abspath = _ResolveRelativeImportPath(\n> +            module_path, module_root_paths + [output_root_path])\n>          with open(module_abspath, 'rb') as module_file:\n>            loaded_modules[import_abspath] = module.Module.Load(module_file)\n>  \n> @@ -370,6 +402,15 @@ already present in the provided output root.\"\"\")\n>        'based on the relative input path, rebased onto this root. Note that '\n>        'ROOT is also searched for existing modules of any transitive imports '\n>        'which were not included in the set of inputs.')\n> +  arg_parser.add_argument(\n> +      '--module-root',\n> +      default=[],\n> +      action='append',\n> +      metavar='ROOT',\n> +      dest='module_root_paths',\n> +      help='Adds ROOT to the set of root paths to search for existing modules '\n> +      'of non-transitive imports. Provided root paths are always searched in '\n> +      'order from longest absolute path to shortest.')\n>    arg_parser.add_argument(\n>        '--mojoms',\n>        nargs='+',\n> @@ -396,9 +437,9 @@ already present in the provided output root.\"\"\")\n>        help='Enables a named feature when parsing the given mojoms. Features '\n>        'are identified by arbitrary string values. Specifying this flag with a '\n>        'given FEATURE name will cause the parser to process any syntax elements '\n> -      'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '\n> -      'provided for a given FEATURE, such tagged elements are discarded by the '\n> -      'parser and will not be present in the compiled output.')\n> +      'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '\n> +      'flag is not provided for a given FEATURE, such tagged elements are '\n> +      'discarded by the parser and will not be present in the compiled output.')\n>    arg_parser.add_argument(\n>        '--check-imports',\n>        dest='build_metadata_filename',\n> @@ -436,6 +477,7 @@ already present in the provided output root.\"\"\")\n>    mojom_files = list(map(os.path.abspath, args.mojom_files))\n>    input_roots = list(map(os.path.abspath, args.input_root_paths))\n>    output_root = os.path.abspath(args.output_root_path)\n> +  module_roots = list(map(os.path.abspath, args.module_root_paths))\n>  \n>    if args.build_metadata_filename:\n>      allowed_imports = _CollectAllowedImportsFromBuildMetadata(\n> @@ -445,13 +487,16 @@ already present in the provided output root.\"\"\")\n>  \n>    module_metadata = list(\n>        map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))\n> -  _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,\n> -               module_metadata, allowed_imports)\n> +  _ParseMojoms(mojom_files, input_roots, output_root, module_roots,\n> +               args.enabled_features, module_metadata, allowed_imports)\n>    logging.info('Finished')\n> -  # Exit without running GC, which can save multiple seconds due the large\n> -  # number of object created.\n> -  os._exit(0)\n>  \n>  \n>  if __name__ == '__main__':\n>    Run(sys.argv[1:])\n> +  # Exit without running GC, which can save multiple seconds due to the large\n> +  # number of object created. But flush is necessary as os._exit doesn't do\n> +  # that.\n> +  sys.stdout.flush()\n> +  sys.stderr.flush()\n> +  os._exit(0)\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n> index e213fbfa760f..f0ee6966ac20 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase):\n>    resolution, and module serialization and deserialization.\"\"\"\n>  \n>    def __init__(self, method_name):\n> -    super(MojomParserTestCase, self).__init__(method_name)\n> +    super().__init__(method_name)\n>      self._temp_dir = None\n>  \n>    def setUp(self):\n> @@ -67,7 +67,7 @@ class MojomParserTestCase(unittest.TestCase):\n>      self.ParseMojoms([filename])\n>      m = self.LoadModule(filename)\n>      definitions = {}\n> -    for kinds in (m.enums, m.structs, m.unions, m.interfaces):\n> +    for kinds in (m.enums, m.structs, m.unions, m.interfaces, m.features):\n>        for kind in kinds:\n>          definitions[kind.mojom_name] = kind\n>      return definitions\n> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n> index a93f34bacb41..353a2b6e2980 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py\n> @@ -1,7 +1,9 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> +import json\n> +\n>  from mojom_parser_test_case import MojomParserTestCase\n>  \n>  \n> @@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase):\n>      c = 'c.mojom'\n>      c_metadata = 'out/c.build_metadata'\n>      self.WriteFile(a_metadata,\n> -                   '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(a))\n> +                   json.dumps({\n> +                       \"sources\": [self.GetPath(a)],\n> +                       \"deps\": []\n> +                   }))\n>      self.WriteFile(\n>          b_metadata,\n> -        '{\"sources\": [\"%s\"], \"deps\": [\"%s\"]}\\n' % (self.GetPath(b),\n> -                                                   self.GetPath(a_metadata)))\n> +        json.dumps({\n> +            \"sources\": [self.GetPath(b)],\n> +            \"deps\": [self.GetPath(a_metadata)]\n> +        }))\n>      self.WriteFile(\n>          c_metadata,\n> -        '{\"sources\": [\"%s\"], \"deps\": [\"%s\"]}\\n' % (self.GetPath(c),\n> -                                                   self.GetPath(b_metadata)))\n> +        json.dumps({\n> +            \"sources\": [self.GetPath(c)],\n> +            \"deps\": [self.GetPath(b_metadata)]\n> +        }))\n>      self.WriteFile(a, \"\"\"\\\n>          module a;\n>          struct Bar {};\"\"\")\n> @@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase):\n>      b = 'b.mojom'\n>      b_metadata = 'out/b.build_metadata'\n>      self.WriteFile(a_metadata,\n> -                   '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(a))\n> +                   json.dumps({\n> +                       \"sources\": [self.GetPath(a)],\n> +                       \"deps\": []\n> +                   }))\n>      self.WriteFile(b_metadata,\n> -                   '{\"sources\": [\"%s\"], \"deps\": []}\\n' % self.GetPath(b))\n> +                   json.dumps({\n> +                       \"sources\": [self.GetPath(b)],\n> +                       \"deps\": []\n> +                   }))\n>      self.WriteFile(a, \"\"\"\\\n>          module a;\n>          struct Bar {};\"\"\")\n> diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n> index d45ec5862999..d10d69c68b13 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> diff --git a/utils/ipc/mojo/public/tools/mojom/union_unittest.py b/utils/ipc/mojo/public/tools/mojom/union_unittest.py\n> new file mode 100644\n> index 000000000000..6b2525e5c5d0\n> --- /dev/null\n> +++ b/utils/ipc/mojo/public/tools/mojom/union_unittest.py\n> @@ -0,0 +1,44 @@\n> +# Copyright 2022 The Chromium Authors\n> +# Use of this source code is governed by a BSD-style license that can be\n> +# found in the LICENSE file.\n> +\n> +from mojom_parser_test_case import MojomParserTestCase\n> +\n> +\n> +class UnionTest(MojomParserTestCase):\n> +  \"\"\"Tests union parsing behavior.\"\"\"\n> +\n> +  def testExtensibleMustHaveDefault(self):\n> +    \"\"\"Verifies that extensible unions must have a default field.\"\"\"\n> +    mojom = 'foo.mojom'\n> +    self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x; };')\n> +    with self.assertRaisesRegexp(Exception, 'must specify a \\[Default\\]'):\n> +      self.ParseMojoms([mojom])\n> +\n> +  def testExtensibleSingleDefault(self):\n> +    \"\"\"Verifies that extensible unions must not have multiple default fields.\"\"\"\n> +    mojom = 'foo.mojom'\n> +    self.WriteFile(\n> +        mojom, \"\"\"\\\n> +               module foo;\n> +               [Extensible] union U {\n> +                 [Default] bool x;\n> +                 [Default] bool y;\n> +               };\n> +               \"\"\")\n> +    with self.assertRaisesRegexp(Exception, 'Multiple \\[Default\\] fields'):\n> +      self.ParseMojoms([mojom])\n> +\n> +  def testExtensibleDefaultTypeValid(self):\n> +    \"\"\"Verifies that an extensible union's default field must be nullable or\n> +    integral type.\"\"\"\n> +    mojom = 'foo.mojom'\n> +    self.WriteFile(\n> +        mojom, \"\"\"\\\n> +               module foo;\n> +               [Extensible] union U {\n> +                 [Default] handle<message_pipe> p;\n> +               };\n> +               \"\"\")\n> +    with self.assertRaisesRegexp(Exception, 'must be nullable or integral'):\n> +      self.ParseMojoms([mojom])\n> diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n> index 65db4dc9cf59..45e45ec55e69 100644\n> --- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n> +++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -23,9 +23,12 @@ class VersionCompatibilityTest(MojomParserTestCase):\n>  \n>      checker = module.BackwardCompatibilityChecker()\n>      compatibility_map = {}\n> -    for name in old.keys():\n> -      compatibility_map[name] = checker.IsBackwardCompatible(\n> -          new[name], old[name])\n> +    for name in old:\n> +      try:\n> +        compatibility_map[name] = checker.IsBackwardCompatible(\n> +            new[name], old[name])\n> +      except Exception:\n> +        compatibility_map[name] = False\n>      return compatibility_map\n>  \n>    def assertBackwardCompatible(self, old_mojom, new_mojom):\n> @@ -60,40 +63,48 @@ class VersionCompatibilityTest(MojomParserTestCase):\n>      \"\"\"Adding a value to an existing version is not allowed, even if the old\n>      enum was marked [Extensible]. Note that it is irrelevant whether or not the\n>      new enum is marked [Extensible].\"\"\"\n> -    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',\n> -                                     'enum E { kFoo, kBar, kBaz };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kFoo, kBar };',\n> -        '[Extensible] enum E { kFoo, kBar, kBaz };')\n> +        '[Extensible] enum E { [Default] kFoo, kBar };',\n> +        'enum E { kFoo, kBar, kBaz };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kFoo, [MinVersion=1] kBar };',\n> +        '[Extensible] enum E { [Default] kFoo, kBar };',\n> +        '[Extensible] enum E { [Default] kFoo, kBar, kBaz };')\n> +    self.assertNotBackwardCompatible(\n> +        '[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };',\n>          'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')\n>  \n>    def testEnumValueRemoval(self):\n>      \"\"\"Removal of an enum value is never valid even for [Extensible] enums.\"\"\"\n>      self.assertNotBackwardCompatible('enum E { kFoo, kBar };',\n>                                       'enum E { kFoo };')\n> -    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',\n> -                                     '[Extensible] enum E { kFoo };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB };',\n> -        '[Extensible] enum E { kA, };')\n> +        '[Extensible] enum E { [Default] kFoo, kBar };',\n> +        '[Extensible] enum E { [Default] kFoo };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };',\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB };')\n> +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',\n> +        '[Extensible] enum E { [Default] kA, };')\n> +    self.assertNotBackwardCompatible(\n> +        \"\"\"[Extensible] enum E {\n> +          [Default] kA,\n> +          [MinVersion=1] kB,\n> +          [MinVersion=1] kZ };\"\"\",\n> +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };')\n>  \n>    def testNewExtensibleEnumValueWithMinVersion(self):\n>      \"\"\"Adding a new and properly [MinVersion]'d value to an [Extensible] enum\n>      is a backward-compatible change. Note that it is irrelevant whether or not\n>      the new enum is marked [Extensible].\"\"\"\n> -    self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',\n> +    self.assertBackwardCompatible('[Extensible] enum E { [Default] kA, kB };',\n>                                    'enum E { kA, kB, [MinVersion=1] kC };')\n>      self.assertBackwardCompatible(\n> -        '[Extensible] enum E { kA, kB };',\n> -        '[Extensible] enum E { kA, kB, [MinVersion=1] kC };')\n> +        '[Extensible] enum E { [Default] kA, kB };',\n> +        '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };')\n>      self.assertBackwardCompatible(\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB };',\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };')\n> +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',\n> +        \"\"\"[Extensible] enum E {\n> +          [Default] kA,\n> +          [MinVersion=1] kB,\n> +          [MinVersion=2] kC };\"\"\")\n>  \n>    def testRenameEnumValue(self):\n>      \"\"\"Renaming an enum value does not affect backward-compatibility. Only\n> @@ -161,14 +172,17 @@ class VersionCompatibilityTest(MojomParserTestCase):\n>          'struct S {}; struct T { S s; };',\n>          'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')\n>      self.assertBackwardCompatible(\n> -        '[Extensible] enum E { kA }; struct S { E e; };',\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };')\n> +        '[Extensible] enum E { [Default] kA }; struct S { E e; };',\n> +        \"\"\"[Extensible] enum E {\n> +          [Default] kA,\n> +          [MinVersion=1] kB };\n> +          struct S { E e; };\"\"\")\n>      self.assertNotBackwardCompatible(\n>          'struct S {}; struct T { S s; };',\n>          'struct S { int32 x; }; struct T { S s; };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kA }; struct S { E e; };',\n> -        '[Extensible] enum E { kA, kB }; struct S { E e; };')\n> +        '[Extensible] enum E { [Default] kA }; struct S { E e; };',\n> +        '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };')\n>  \n>    def testNewStructFieldWithInvalidMinVersion(self):\n>      \"\"\"Adding a new field using an existing MinVersion breaks backward-\n> @@ -305,14 +319,17 @@ class VersionCompatibilityTest(MojomParserTestCase):\n>          'struct S {}; union U { S s; };',\n>          'struct S { [MinVersion=1] int32 x; }; union U { S s; };')\n>      self.assertBackwardCompatible(\n> -        '[Extensible] enum E { kA }; union U { E e; };',\n> -        '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };')\n> +        '[Extensible] enum E { [Default] kA }; union U { E e; };',\n> +        \"\"\"[Extensible] enum E {\n> +          [Default] kA,\n> +          [MinVersion=1] kB };\n> +          union U { E e; };\"\"\")\n>      self.assertNotBackwardCompatible(\n>          'struct S {}; union U { S s; };',\n>          'struct S { int32 x; }; union U { S s; };')\n>      self.assertNotBackwardCompatible(\n> -        '[Extensible] enum E { kA }; union U { E e; };',\n> -        '[Extensible] enum E { kA, kB }; union U { E e; };')\n> +        '[Extensible] enum E { [Default] kA }; union U { E e; };',\n> +        '[Extensible] enum E { [Default] kA, kB }; union U { E e; };')\n>  \n>    def testNewUnionFieldWithInvalidMinVersion(self):\n>      \"\"\"Adding a new field using an existing MinVersion breaks backward-\n> diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/ipc/mojo/public/tools/run_all_python_unittests.py\n> index b20109583071..98bce18cbb6d 100755\n> --- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py\n> +++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py\n> @@ -1,5 +1,5 @@\n> -#!/usr/bin/env python\n> -# Copyright 2020 The Chromium Authors. All rights reserved.\n> +#!/usr/bin/env python3\n> +# Copyright 2020 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> @@ -8,11 +8,13 @@ import sys\n>  \n>  _TOOLS_DIR = os.path.dirname(__file__)\n>  _MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')\n> +_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings')\n>  _SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,\n>                          os.path.pardir)\n>  \n>  # Ensure that the mojom library is discoverable.\n>  sys.path.append(_MOJOM_DIR)\n> +sys.path.append(_BINDINGS_DIR)\n>  \n>  # Help Python find typ in //third_party/catapult/third_party/typ/\n>  sys.path.append(\n> @@ -21,7 +23,7 @@ import typ\n>  \n>  \n>  def Main():\n> -  return typ.main(top_level_dir=_MOJOM_DIR)\n> +  return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR])\n>  \n>  \n>  if __name__ == '__main__':\n> diff --git a/utils/ipc/tools/README b/utils/ipc/tools/README\n> index d5c24fc3b5cb..961cabd222e3 100644\n> --- a/utils/ipc/tools/README\n> +++ b/utils/ipc/tools/README\n> @@ -1,4 +1,4 @@\n>  # SPDX-License-Identifier: CC0-1.0\n>  \n> -Files in this directory are imported from 9c138d992bfc of Chromium. Do not\n> +Files in this directory are imported from 9be4263648d7 of Chromium. Do not\n>  modify them manually.\n> diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/ipc/tools/diagnosis/crbug_1001171.py\n> index 478fb8c1a610..40900d10d38a 100644\n> --- a/utils/ipc/tools/diagnosis/crbug_1001171.py\n> +++ b/utils/ipc/tools/diagnosis/crbug_1001171.py\n> @@ -1,4 +1,4 @@\n> -# Copyright 2019 The Chromium Authors. All rights reserved.\n> +# Copyright 2019 The Chromium Authors\n>  # Use of this source code is governed by a BSD-style license that can be\n>  # found in the LICENSE file.\n>  \n> -- \n> Regards,\n> \n> Laurent Pinchart\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id AC0D2C323E\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  9 Jan 2024 12:20:21 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4A88062B49;\n\tTue,  9 Jan 2024 13:20:21 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id DB2A262B30\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  9 Jan 2024 13:16:11 +0100 (CET)","from pendragon.ideasonboard.com\n\t(aztw-30-b2-v4wan-166917-cust845.vm26.cable.virginm.net\n\t[82.37.23.78])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id A79A0552;\n\tTue,  9 Jan 2024 13:15:07 +0100 (CET)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1704802821;\n\tbh=E/RQNrVwooawFdAyrBN9PH1QIanrT4Kt2W7qctKdefE=;\n\th=In-Reply-To:References:To:Date:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=wsShYyub3fvdpPN1Gkv5e2pDrZCMSiaQ5w77Q+ayIGm17SiCDnNyJ3RDx13Ybdflr\n\t2nK8k4P1Z7sUFVmESbgZdm9FL+NlGabwxG5f8bto/mQkIRL/RYMwMIvczsxKKQrS3i\n\tw1DMqh+TTeOLRFluY6DpJuIJb/UbU5vsLSL3LUYWMd+VALxk2/DbAKj31bNCW/6wK8\n\tOB5NP38A/Bcse1omIqYLy66c2znUrCxv/TYEJ3h+GcWFMm+Vnys6r/kce1GOvPTgMh\n\t2OdEAZcwneYc/QKDrPyN3/8qC5uaWbVg6Srz5CqaDDCBXPNELW8ikgTe3g+7fdLRWr\n\tNx2Se8szZI8oQ==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1704802508;\n\tbh=E/RQNrVwooawFdAyrBN9PH1QIanrT4Kt2W7qctKdefE=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=LDlO1lTu88M+oUccs1iOOIyuaxoH1bCejkBIIknstDJS4sK2NKQuaixfP6amFzgzR\n\tavzRF9rd3y8ueL80qcJVQgtVDQXQlp7QoAHAffXSXhy6mUEg+Z49X04SwJ8exTNesI\n\tjSYvO7I9q08D8GmhzGLqYoxn2YFPaRYWfQxqxxgo="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"LDlO1lTu\"; dkim-atps=neutral","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<20240104151548.2589-9-laurent.pinchart@ideasonboard.com>","References":"<20240104151548.2589-1-laurent.pinchart@ideasonboard.com>\n\t<20240104151548.2589-9-laurent.pinchart@ideasonboard.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tlibcamera-devel@lists.libcamera.org","Date":"Tue, 09 Jan 2024 12:16:08 +0000","Message-ID":"<170480256809.3044059.17933551749909896440@ping.linuxembedded.co.uk>","User-Agent":"alot/0.10","X-Mailman-Approved-At":"Tue, 09 Jan 2024 13:20:20 +0100","Subject":"Re: [libcamera-devel] [PATCH 8/8] utils: ipc: Update mojo","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Kieran Bingham via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"Khem Raj <raj.khem@gmail.com>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]