[libcamera-devel,v1,1/1] utils: ipc: Update mojo
diff mbox series

Message ID 20230330095609.3341579-2-chenghaoyang@google.com
State Accepted
Headers show
Series
  • utils:ipc: Update mojo
Related show

Commit Message

Cheng-Hao Yang March 30, 2023, 9:56 a.m. UTC
Update mojo from the Chromium repository. The commit from which this was
taken is:
[white-space] Change DOM/HTML/SVG to set longhands of `white-space`

The update-mojo.sh script was used for this update.

Signed-off-by: Harvey Yang <chenghaoyang@chromium.org>
---
 utils/ipc/mojo/README                         |   2 +-
 utils/ipc/mojo/public/LICENSE                 |   2 +-
 utils/ipc/mojo/public/tools/BUILD.gn          |   8 +-
 utils/ipc/mojo/public/tools/bindings/BUILD.gn |  34 +-
 .../ipc/mojo/public/tools/bindings/README.md  | 148 ++-
 .../public/tools/bindings/checks/__init__.py  |   0
 .../bindings/checks/mojom_attributes_check.py | 168 ++++
 .../checks/mojom_attributes_check_unittest.py | 186 ++++
 .../checks/mojom_definitions_check.py         |  34 +
 .../checks/mojom_restrictions_check.py        | 102 +++
 .../mojom_restrictions_checks_unittest.py     | 254 ++++++
 .../tools/bindings/concatenate-files.py       |   5 +-
 ...concatenate_and_replace_closure_exports.py |   8 +-
 .../tools/bindings/gen_data_files_list.py     |   2 +-
 .../tools/bindings/generate_type_mappings.py  |   3 +-
 .../tools/bindings/minify_with_terser.py      |  47 +
 .../ipc/mojo/public/tools/bindings/mojom.gni  | 860 ++++++++++--------
 .../bindings/mojom_bindings_generator.py      |  62 +-
 .../mojom_bindings_generator_unittest.py      |   6 +-
 .../tools/bindings/validate_typemap_config.py |   4 +-
 utils/ipc/mojo/public/tools/mojom/BUILD.gn    |  17 +
 .../mojom/check_stable_mojom_compatibility.py |  46 +-
 ...eck_stable_mojom_compatibility_unittest.py |  87 +-
 .../mojo/public/tools/mojom/const_unittest.py |   2 +-
 .../mojo/public/tools/mojom/enum_unittest.py  |  30 +-
 .../mojo/public/tools/mojom/mojom/BUILD.gn    |   3 +-
 .../mojo/public/tools/mojom/mojom/error.py    |   2 +-
 .../mojo/public/tools/mojom/mojom/fileutil.py |   2 +-
 .../tools/mojom/mojom/fileutil_unittest.py    |   2 +-
 .../tools/mojom/mojom/generate/check.py       |  26 +
 .../tools/mojom/mojom/generate/generator.py   |   8 +-
 .../mojom/generate/generator_unittest.py      |   2 +-
 .../tools/mojom/mojom/generate/module.py      | 649 ++++++++-----
 .../mojom/mojom/generate/module_unittest.py   |   2 +-
 .../public/tools/mojom/mojom/generate/pack.py | 125 ++-
 .../mojom/mojom/generate/pack_unittest.py     |   2 +-
 .../mojom/mojom/generate/template_expander.py |   2 +-
 .../tools/mojom/mojom/generate/translate.py   | 408 ++++++++-
 .../mojom/generate/translate_unittest.py      |  39 +-
 .../public/tools/mojom/mojom/parse/ast.py     | 117 +--
 .../tools/mojom/mojom/parse/ast_unittest.py   |   6 +-
 .../mojom/mojom/parse/conditional_features.py |  14 +-
 .../parse/conditional_features_unittest.py    | 130 ++-
 .../public/tools/mojom/mojom/parse/lexer.py   |   6 +-
 .../tools/mojom/mojom/parse/lexer_unittest.py |   3 +-
 .../public/tools/mojom/mojom/parse/parser.py  |  24 +-
 .../mojom/mojom/parse/parser_unittest.py      |  34 +-
 .../mojo/public/tools/mojom/mojom_parser.py   | 119 ++-
 .../tools/mojom/mojom_parser_test_case.py     |   4 +-
 .../tools/mojom/mojom_parser_unittest.py      |  31 +-
 .../tools/mojom/stable_attribute_unittest.py  |   2 +-
 .../mojo/public/tools/mojom/union_unittest.py |  44 +
 .../mojom/version_compatibility_unittest.py   |  66 +-
 .../public/tools/run_all_python_unittests.py  |   8 +-
 utils/ipc/tools/README                        |   2 +-
 utils/ipc/tools/diagnosis/crbug_1001171.py    |   2 +-
 56 files changed, 3061 insertions(+), 940 deletions(-)
 create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/__init__.py
 create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
 create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
 create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
 create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
 create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
 create mode 100755 utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
 create mode 100644 utils/ipc/mojo/public/tools/mojom/BUILD.gn
 create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
 create mode 100644 utils/ipc/mojo/public/tools/mojom/union_unittest.py

Comments

Jacopo Mondi March 30, 2023, 6:05 p.m. UTC | #1
Hi Harvey

On Thu, Mar 30, 2023 at 09:56:09AM +0000, Harvey Yang via libcamera-devel wrote:
> Update mojo from the Chromium repository. The commit from which this was
> taken is:
> [white-space] Change DOM/HTML/SVG to set longhands of `white-space`
>
> The update-mojo.sh script was used for this update.
>
> Signed-off-by: Harvey Yang <chenghaoyang@chromium.org>

Thanks,
  I presume there are no changes in the mojom API. I've run tests but
I didn't get it running on a platform with an IPA (so that it
exercizes the IPC) yet.

Have you tried that on a device or am I overly concerned ?

Thanks
  j

> ---
>  utils/ipc/mojo/README                         |   2 +-
>  utils/ipc/mojo/public/LICENSE                 |   2 +-
>  utils/ipc/mojo/public/tools/BUILD.gn          |   8 +-
>  utils/ipc/mojo/public/tools/bindings/BUILD.gn |  34 +-
>  .../ipc/mojo/public/tools/bindings/README.md  | 148 ++-
>  .../public/tools/bindings/checks/__init__.py  |   0
>  .../bindings/checks/mojom_attributes_check.py | 168 ++++
>  .../checks/mojom_attributes_check_unittest.py | 186 ++++
>  .../checks/mojom_definitions_check.py         |  34 +
>  .../checks/mojom_restrictions_check.py        | 102 +++
>  .../mojom_restrictions_checks_unittest.py     | 254 ++++++
>  .../tools/bindings/concatenate-files.py       |   5 +-
>  ...concatenate_and_replace_closure_exports.py |   8 +-
>  .../tools/bindings/gen_data_files_list.py     |   2 +-
>  .../tools/bindings/generate_type_mappings.py  |   3 +-
>  .../tools/bindings/minify_with_terser.py      |  47 +
>  .../ipc/mojo/public/tools/bindings/mojom.gni  | 860 ++++++++++--------
>  .../bindings/mojom_bindings_generator.py      |  62 +-
>  .../mojom_bindings_generator_unittest.py      |   6 +-
>  .../tools/bindings/validate_typemap_config.py |   4 +-
>  utils/ipc/mojo/public/tools/mojom/BUILD.gn    |  17 +
>  .../mojom/check_stable_mojom_compatibility.py |  46 +-
>  ...eck_stable_mojom_compatibility_unittest.py |  87 +-
>  .../mojo/public/tools/mojom/const_unittest.py |   2 +-
>  .../mojo/public/tools/mojom/enum_unittest.py  |  30 +-
>  .../mojo/public/tools/mojom/mojom/BUILD.gn    |   3 +-
>  .../mojo/public/tools/mojom/mojom/error.py    |   2 +-
>  .../mojo/public/tools/mojom/mojom/fileutil.py |   2 +-
>  .../tools/mojom/mojom/fileutil_unittest.py    |   2 +-
>  .../tools/mojom/mojom/generate/check.py       |  26 +
>  .../tools/mojom/mojom/generate/generator.py   |   8 +-
>  .../mojom/generate/generator_unittest.py      |   2 +-
>  .../tools/mojom/mojom/generate/module.py      | 649 ++++++++-----
>  .../mojom/mojom/generate/module_unittest.py   |   2 +-
>  .../public/tools/mojom/mojom/generate/pack.py | 125 ++-
>  .../mojom/mojom/generate/pack_unittest.py     |   2 +-
>  .../mojom/mojom/generate/template_expander.py |   2 +-
>  .../tools/mojom/mojom/generate/translate.py   | 408 ++++++++-
>  .../mojom/generate/translate_unittest.py      |  39 +-
>  .../public/tools/mojom/mojom/parse/ast.py     | 117 +--
>  .../tools/mojom/mojom/parse/ast_unittest.py   |   6 +-
>  .../mojom/mojom/parse/conditional_features.py |  14 +-
>  .../parse/conditional_features_unittest.py    | 130 ++-
>  .../public/tools/mojom/mojom/parse/lexer.py   |   6 +-
>  .../tools/mojom/mojom/parse/lexer_unittest.py |   3 +-
>  .../public/tools/mojom/mojom/parse/parser.py  |  24 +-
>  .../mojom/mojom/parse/parser_unittest.py      |  34 +-
>  .../mojo/public/tools/mojom/mojom_parser.py   | 119 ++-
>  .../tools/mojom/mojom_parser_test_case.py     |   4 +-
>  .../tools/mojom/mojom_parser_unittest.py      |  31 +-
>  .../tools/mojom/stable_attribute_unittest.py  |   2 +-
>  .../mojo/public/tools/mojom/union_unittest.py |  44 +
>  .../mojom/version_compatibility_unittest.py   |  66 +-
>  .../public/tools/run_all_python_unittests.py  |   8 +-
>  utils/ipc/tools/README                        |   2 +-
>  utils/ipc/tools/diagnosis/crbug_1001171.py    |   2 +-
>  56 files changed, 3061 insertions(+), 940 deletions(-)
>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/__init__.py
>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
>  create mode 100644 utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
>  create mode 100755 utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
>  create mode 100644 utils/ipc/mojo/public/tools/mojom/BUILD.gn
>  create mode 100644 utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
>  create mode 100644 utils/ipc/mojo/public/tools/mojom/union_unittest.py
>
> diff --git a/utils/ipc/mojo/README b/utils/ipc/mojo/README
> index d5c24fc3..9a2979d3 100644
> --- a/utils/ipc/mojo/README
> +++ b/utils/ipc/mojo/README
> @@ -1,4 +1,4 @@
>  # SPDX-License-Identifier: CC0-1.0
>
> -Files in this directory are imported from 9c138d992bfc of Chromium. Do not
> +Files in this directory are imported from e2b2277a00e37 of Chromium. Do not
>  modify them manually.
> diff --git a/utils/ipc/mojo/public/LICENSE b/utils/ipc/mojo/public/LICENSE
> index 972bb2ed..513e8a6a 100644
> --- a/utils/ipc/mojo/public/LICENSE
> +++ b/utils/ipc/mojo/public/LICENSE
> @@ -1,4 +1,4 @@
> -// Copyright 2014 The Chromium Authors. All rights reserved.
> +// Copyright 2014 The Chromium Authors
>  //
>  // Redistribution and use in source and binary forms, with or without
>  // modification, are permitted provided that the following conditions are
> diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/ipc/mojo/public/tools/BUILD.gn
> index eb6391a6..5328a34a 100644
> --- a/utils/ipc/mojo/public/tools/BUILD.gn
> +++ b/utils/ipc/mojo/public/tools/BUILD.gn
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -10,7 +10,11 @@ group("mojo_python_unittests") {
>      "run_all_python_unittests.py",
>      "//testing/scripts/run_isolated_script_test.py",
>    ]
> -  deps = [ "//mojo/public/tools/mojom/mojom:tests" ]
> +  deps = [
> +    "//mojo/public/tools/bindings:tests",
> +    "//mojo/public/tools/mojom:tests",
> +    "//mojo/public/tools/mojom/mojom:tests",
> +  ]
>    data_deps = [
>      "//testing:test_scripts_shared",
>      "//third_party/catapult/third_party/typ/",
> diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> index 3e242532..203e476c 100644
> --- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> +++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> @@ -1,13 +1,11 @@
> -# Copyright 2016 The Chromium Authors. All rights reserved.
> +# Copyright 2016 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> -import("//build/config/python.gni")
>  import("//mojo/public/tools/bindings/mojom.gni")
>  import("//third_party/jinja2/jinja2.gni")
>
> -# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -python2_action("precompile_templates") {
> +action("precompile_templates") {
>    sources = mojom_generator_sources
>    sources += [
>      "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl",
> @@ -26,7 +24,6 @@ python2_action("precompile_templates") {
>      "$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl",
>      "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl",
>      "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
> -    "$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl",
>      "$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl",
>      "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
>      "$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
> @@ -65,9 +62,6 @@ python2_action("precompile_templates") {
>      "$mojom_generator_root/generators/java_templates/struct.java.tmpl",
>      "$mojom_generator_root/generators/java_templates/union.java.tmpl",
>      "$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
> -    "$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl",
> -    "$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl",
> -    "$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl",
>      "$mojom_generator_root/generators/js_templates/fuzzing.tmpl",
>      "$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
>      "$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl",
> @@ -93,8 +87,14 @@ python2_action("precompile_templates") {
>      "$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
>      "$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
>      "$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
> +    "$mojom_generator_root/generators/ts_templates/enum_definition.tmpl",
> +    "$mojom_generator_root/generators/ts_templates/interface_definition.tmpl",
>      "$mojom_generator_root/generators/ts_templates/module_definition.tmpl",
> -    "$mojom_generator_root/generators/ts_templates/mojom.tmpl",
> +    "$mojom_generator_root/generators/ts_templates/struct_definition.tmpl",
> +    "$mojom_generator_root/generators/ts_templates/union_definition.tmpl",
> +    "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_impl.cc.tmpl",
> +    "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_impl.h.tmpl",
> +    "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_macros.tmpl",
>    ]
>    script = mojom_generator_script
>
> @@ -102,8 +102,9 @@ python2_action("precompile_templates") {
>    outputs = [
>      "$target_gen_dir/cpp_templates.zip",
>      "$target_gen_dir/java_templates.zip",
> -    "$target_gen_dir/mojolpm_templates.zip",
> +    "$target_gen_dir/webui_js_bridge_templates.zip",
>      "$target_gen_dir/js_templates.zip",
> +    "$target_gen_dir/mojolpm_templates.zip",
>      "$target_gen_dir/ts_templates.zip",
>    ]
>    args = [
> @@ -113,3 +114,16 @@ python2_action("precompile_templates") {
>      "precompile",
>    ]
>  }
> +
> +group("tests") {
> +  data = [
> +    mojom_generator_script,
> +    "checks/mojom_attributes_check_unittest.py",
> +    "checks/mojom_restrictions_checks_unittest.py",
> +    "mojom_bindings_generator_unittest.py",
> +    "//tools/diagnosis/crbug_1001171.py",
> +    "//third_party/markupsafe/",
> +  ]
> +  data += mojom_generator_sources
> +  data += jinja2_sources
> +}
> diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/ipc/mojo/public/tools/bindings/README.md
> index 43882450..683aa2f0 100644
> --- a/utils/ipc/mojo/public/tools/bindings/README.md
> +++ b/utils/ipc/mojo/public/tools/bindings/README.md
> @@ -188,8 +188,8 @@ struct StringPair {
>  };
>
>  enum AnEnum {
> -  YES,
> -  NO
> +  kYes,
> +  kNo
>  };
>
>  interface SampleInterface {
> @@ -209,7 +209,7 @@ struct AllTheThings {
>    uint64 unsigned_64bit_value;
>    float float_value_32bit;
>    double float_value_64bit;
> -  AnEnum enum_value = AnEnum.YES;
> +  AnEnum enum_value = AnEnum.kYes;
>
>    // Strings may be nullable.
>    string? maybe_a_string_maybe_not;
> @@ -300,14 +300,14 @@ within a module or nested within the namespace of some struct or interface:
>  module business.mojom;
>
>  enum Department {
> -  SALES = 0,
> -  DEV,
> +  kSales = 0,
> +  kDev,
>  };
>
>  struct Employee {
>    enum Type {
> -    FULL_TIME,
> -    PART_TIME,
> +    kFullTime,
> +    kPartTime,
>    };
>
>    Type type;
> @@ -315,6 +315,9 @@ struct Employee {
>  };
>  ```
>
> +C++ constant-style enum value names are preferred as specified in the
> +[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names).
> +
>  Similar to C-style enums, individual values may be explicitly assigned within an
>  enum definition. By default, values are based at zero and increment by
>  1 sequentially.
> @@ -336,8 +339,8 @@ struct Employee {
>    const uint64 kInvalidId = 0;
>
>    enum Type {
> -    FULL_TIME,
> -    PART_TIME,
> +    kFullTime,
> +    kPartTime,
>    };
>
>    uint64 id = kInvalidId;
> @@ -396,20 +399,33 @@ interesting attributes supported today.
>    extreme caution, because it can lead to deadlocks otherwise.
>
>  * **`[Default]`**:
> -  The `Default` attribute may be used to specify an enumerator value that
> -  will be used if an `Extensible` enumeration does not deserialize to a known
> -  value on the receiver side, i.e. the sender is using a newer version of the
> -  enum. This allows unknown values to be mapped to a well-defined value that can
> -  be appropriately handled.
> +  The `Default` attribute may be used to specify an enumerator value or union
> +  field that will be used if an `Extensible` enumeration or union does not
> +  deserialize to a known value on the receiver side, i.e. the sender is using a
> +  newer version of the enum or union. This allows unknown values to be mapped to
> +  a well-defined value that can be appropriately handled.
> +
> +  Note: The `Default` field for a union must be of nullable or integral type.
> +  When a union is defaulted to this field, the field takes on the default value
> +  for its type: null for nullable types, and zero/false for integral types.
>
>  * **`[Extensible]`**:
> -  The `Extensible` attribute may be specified for any enum definition. This
> -  essentially disables builtin range validation when receiving values of the
> -  enum type in a message, allowing older bindings to tolerate unrecognized
> -  values from newer versions of the enum.
> +  The `Extensible` attribute may be specified for any enum or union definition.
> +  For enums, this essentially disables builtin range validation when receiving
> +  values of the enum type in a message, allowing older bindings to tolerate
> +  unrecognized values from newer versions of the enum.
> +
> +  If an enum value within an extensible enum definition is affixed with the
> +  `Default` attribute, out-of-range values for the enum will deserialize to that
> +  default value. Only one enum value may be designated as the `Default`.
>
> -  Note: in the future, an `Extensible` enumeration will require that a `Default`
> -  enumerator value also be specified.
> +  Similarly, a union marked `Extensible` will deserialize to its `Default` field
> +  when an unrecognized field is received. Extensible unions MUST specify exactly
> +  one `Default` field, and the field must be of nullable or integral type. When
> +  defaulted to this field, the value is always null/zero/false as appropriate.
> +
> +  An `Extensible` enumeration REQUIRES that a `Default` value be specified,
> +  so all new extensible enums should specify one.
>
>  * **`[Native]`**:
>    The `Native` attribute may be specified for an empty struct declaration to
> @@ -422,7 +438,10 @@ interesting attributes supported today.
>  * **`[MinVersion=N]`**:
>    The `MinVersion` attribute is used to specify the version at which a given
>    field, enum value, interface method, or method parameter was introduced.
> -  See [Versioning](#Versioning) for more details.
> +  See [Versioning](#Versioning) for more details. `MinVersion` does not apply
> +  to interfaces, structs or enums, but to the fields of those types.
> +  `MinVersion` is not a module-global value, but it is ok to pretend it is by
> +  skipping versions when adding fields or parameters.
>
>  * **`[Stable]`**:
>    The `Stable` attribute specifies that a given mojom type or interface
> @@ -448,7 +467,50 @@ interesting attributes supported today.
>    matching `value` in the list of `enabled_features`, the definition will be
>    disabled. This is useful for mojom definitions that only make sense on one
>    platform. Note that the `EnableIf` attribute can only be set once per
> -  definition.
> +  definition and cannot be set at the same time as `EnableIfNot`. Also be aware
> +  that only one condition can be tested, `EnableIf=value,xyz` introduces a new
> +  `xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends
> +  only on the feature `value`. Complex conditions can be introduced via
> +  enabled_features in `build.gn` files.
> +
> +* **`[EnableIfNot=value]`**:
> +  The `EnableIfNot` attribute is used to conditionally enable definitions when
> +  the mojom is parsed. If the `mojom` target in the GN file includes the
> +  matching `value` in the list of `enabled_features`, the definition will be
> +  disabled. This is useful for mojom definitions that only make sense on all but
> +  one platform. Note that the `EnableIfNot` attribute can only be set once per
> +  definition and cannot be set at the same time as `EnableIf`.
> +
> +* **`[ServiceSandbox=value]`**:
> +  The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a
> +  service hosting an implementation of interface will be launched in. This only
> +  applies to `C++` bindings. `value` should match a constant defined in an
> +  imported `sandbox.mojom.Sandbox` enum (for Chromium this is
> +  `//sandbox/policy/mojom/sandbox.mojom`), such as `kService`.
> +
> +* **`[RequireContext=enum]`**:
> +  The `RequireContext` attribute is used in Chromium to tag interfaces that
> +  should be passed (as remotes or receivers) only to privileged process
> +  contexts. The process context must be an enum that is imported into the
> +  mojom that defines the tagged interface. `RequireContext` may be used in
> +  future to DCHECK or CHECK if remotes are made available in contexts that
> +  conflict with the one provided in the interface definition. Process contexts
> +  are not the same as the sandbox a process is running in, but will reflect
> +  the set of capabilities provided to the service.
> +
> +* **`[AllowedContext=enum]`**:
> +  The `AllowedContext` attribute is used in Chromium to tag methods that pass
> +  remotes or receivers of interfaces that are marked with a `RequireContext`
> +  attribute. The enum provided on the method must be equal or better (lower
> +  numerically) than the one required on the interface being passed. At present
> +  failing to specify an adequate `AllowedContext` value will cause mojom
> +  generation to fail at compile time. In future DCHECKs or CHECKs might be
> +  added to enforce that method is only called from a process context that meets
> +  the given `AllowedContext` value. The enum must of the same type as that
> +  specified in the interface's `RequireContext` attribute. Adding an
> +  `AllowedContext` attribute to a method is a strong indication that you need
> +   a detailed security review of your design - please reach out to the security
> +   team.
>
>  ## Generated Code For Target Languages
>
> @@ -495,9 +557,9 @@ values. For example if a Mojom declares the enum:
>
>  ``` cpp
>  enum AdvancedBoolean {
> -  TRUE = 0,
> -  FALSE = 1,
> -  FILE_NOT_FOUND = 2,
> +  kTrue = 0,
> +  kFalse = 1,
> +  kFileNotFound = 2,
>  };
>  ```
>
> @@ -550,10 +612,16 @@ See the documentation for
>
>  *** note
>  **NOTE:** You don't need to worry about versioning if you don't care about
> -backwards compatibility. Specifically, all parts of Chrome are updated
> -atomically today and there is not yet any possibility of any two Chrome
> -processes communicating with two different versions of any given Mojom
> -interface.
> +backwards compatibility. Today, all parts of the Chrome browser are
> +updated atomically and there is not yet any possibility of any two
> +Chrome processes communicating with two different versions of any given Mojom
> +interface. On Chrome OS, there are several places where versioning is required.
> +For example,
> +[ARC++](https://developer.android.com/chrome-os/intro)
> +uses versioned mojo to send IPC to the Android container.
> +Likewise, the
> +[Lacros](/docs/lacros.md)
> +browser uses versioned mojo to talk to the ash system UI.
>  ***
>
>  Services extend their interfaces to support new features over time, and clients
> @@ -593,8 +661,8 @@ struct Employee {
>
>  *** note
>  **NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be
> -optional (nullable). See [Primitive Types](#Primitive-Types) for details on
> -nullable values.
> +optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for
> +details on nullable values.
>  ***
>
>  By default, fields belong to version 0. New fields must be appended to the
> @@ -624,10 +692,10 @@ the following hard constraints:
>  * For any given struct or interface, if any field or method explicitly specifies
>      an ordinal value, all fields or methods must explicitly specify an ordinal
>      value.
> -* For an *N*-field struct or *N*-method interface, the set of explicitly
> -    assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces
> -    should include placeholder methods to fill the ordinal positions of removed
> -    methods (for example "Unused_Message_7@7()" or "RemovedMessage@42()", etc).
> +* For an *N*-field struct, the set of explicitly assigned ordinal values must be
> +    limited to the range *[0, N-1]*. Structs should include placeholder fields
> +    to fill the ordinal positions of removed fields (for example "Unused_Field"
> +    or "RemovedField", etc).
>
>  You may reorder fields, but you must ensure that the ordinal values of existing
>  fields remain unchanged. For example, the following struct remains
> @@ -712,8 +780,8 @@ If you want an enum to be extensible in the future, you can apply the
>  ``` cpp
>  [Extensible]
>  enum Department {
> -  SALES,
> -  DEV,
> +  kSales,
> +  kDev,
>  };
>  ```
>
> @@ -722,9 +790,9 @@ And later you can extend this enum without breaking backwards compatibility:
>  ``` cpp
>  [Extensible]
>  enum Department {
> -  SALES,
> -  DEV,
> -  [MinVersion=1] RESEARCH,
> +  kSales,
> +  kDev,
> +  [MinVersion=1] kResearch,
>  };
>  ```
>
> diff --git a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py b/utils/ipc/mojo/public/tools/bindings/checks/__init__.py
> new file mode 100644
> index 00000000..e69de29b
> 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
> new file mode 100644
> index 00000000..3a2d2a3b
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
> @@ -0,0 +1,168 @@
> +# Copyright 2022 The Chromium Authors
> +# Use of this source code is governed by a BSD-style license that can be
> +# found in the LICENSE file.
> +"""Validate mojo attributes are allowed in Chrome before generation."""
> +
> +import mojom.generate.check as check
> +import mojom.generate.module as module
> +
> +_COMMON_ATTRIBUTES = {
> +    'EnableIf',
> +    'EnableIfNot',
> +}
> +
> +# For struct, union & parameter lists.
> +_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> +    'MinVersion',
> +    'RenamedFrom',
> +}
> +
> +# Note: `Default`` goes on the default _value_, not on the enum.
> +# Note: [Stable] without [Extensible] is not allowed.
> +_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> +    'Extensible',
> +    'Native',
> +    'Stable',
> +    'RenamedFrom',
> +    'Uuid',
> +}
> +
> +# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal.
> +_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> +    'Default',
> +    'MinVersion',
> +}
> +
> +_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> +    'WebUIJsBridge',
> +    'RenamedFrom',
> +    'RequireContext',
> +    'ServiceSandbox',
> +    'Stable',
> +    'Uuid',
> +}
> +
> +_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> +    'AllowedContext',
> +    'MinVersion',
> +    'NoInterrupt',
> +    'Sync',
> +    'UnlimitedSize',
> +}
> +
> +_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> +    'JavaConstantsClassName',
> +    'JavaPackage',
> +}
> +
> +_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
> +
> +_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> +    'CustomSerializer',
> +    'JavaClassName',
> +    'Native',
> +    'Stable',
> +    'RenamedFrom',
> +    'Uuid',
> +}
> +
> +_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
> +
> +_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> +    'Extensible',
> +    'Stable',
> +    'RenamedFrom',
> +    'Uuid',
> +}
> +
> +_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | {
> +    'Default',
> +}
> +
> +# TODO(https://crbug.com/1193875) empty this set and remove the allowlist.
> +_STABLE_ONLY_ALLOWLISTED_ENUMS = {
> +    'crosapi.mojom.OptionalBool',
> +    'crosapi.mojom.TriState',
> +}
> +
> +
> +class Check(check.Check):
> +  def __init__(self, *args, **kwargs):
> +    super(Check, self).__init__(*args, **kwargs)
> +
> +  def _Respell(self, allowed, attribute):
> +    for a in allowed:
> +      if a.lower() == attribute.lower():
> +        return f" - Did you mean: {a}?"
> +    return ""
> +
> +  def _CheckAttributes(self, context, allowed, attributes):
> +    if not attributes:
> +      return
> +    for attribute in attributes:
> +      if not attribute in allowed:
> +        # Is there a close misspelling?
> +        hint = self._Respell(allowed, attribute)
> +        raise check.CheckException(
> +            self.module,
> +            f"attribute {attribute} not allowed on {context}{hint}")
> +
> +  def _CheckEnumAttributes(self, enum):
> +    if enum.attributes:
> +      self._CheckAttributes("enum", _ENUM_ATTRIBUTES, enum.attributes)
> +      if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes:
> +        full_name = f"{self.module.mojom_namespace}.{enum.mojom_name}"
> +        if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS:
> +          raise check.CheckException(
> +              self.module,
> +              f"[Extensible] required on [Stable] enum {full_name}")
> +    for enumval in enum.fields:
> +      self._CheckAttributes("enum value", _ENUMVAL_ATTRIBUTES,
> +                            enumval.attributes)
> +
> +  def _CheckInterfaceAttributes(self, interface):
> +    self._CheckAttributes("interface", _INTERFACE_ATTRIBUTES,
> +                          interface.attributes)
> +    for method in interface.methods:
> +      self._CheckAttributes("method", _METHOD_ATTRIBUTES, method.attributes)
> +      for param in method.parameters:
> +        self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
> +                              param.attributes)
> +      if method.response_parameters:
> +        for param in method.response_parameters:
> +          self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
> +                                param.attributes)
> +    for enum in interface.enums:
> +      self._CheckEnumAttributes(enum)
> +
> +  def _CheckModuleAttributes(self):
> +    self._CheckAttributes("module", _MODULE_ATTRIBUTES, self.module.attributes)
> +
> +  def _CheckStructAttributes(self, struct):
> +    self._CheckAttributes("struct", _STRUCT_ATTRIBUTES, struct.attributes)
> +    for field in struct.fields:
> +      self._CheckAttributes("struct field", _STRUCT_FIELD_ATTRIBUTES,
> +                            field.attributes)
> +    for enum in struct.enums:
> +      self._CheckEnumAttributes(enum)
> +
> +  def _CheckUnionAttributes(self, union):
> +    self._CheckAttributes("union", _UNION_ATTRIBUTES, union.attributes)
> +    for field in union.fields:
> +      self._CheckAttributes("union field", _UNION_FIELD_ATTRIBUTES,
> +                            field.attributes)
> +
> +  def CheckModule(self):
> +    """Note that duplicate attributes are forbidden at the parse phase.
> +    We also do not need to look at the types of any parameters, as they will be
> +    checked where they are defined. Consts do not have attributes so can be
> +    skipped."""
> +    self._CheckModuleAttributes()
> +    for interface in self.module.interfaces:
> +      self._CheckInterfaceAttributes(interface)
> +    for enum in self.module.enums:
> +      self._CheckEnumAttributes(enum)
> +    for struct in self.module.structs:
> +      self._CheckStructAttributes(struct)
> +    for union in self.module.unions:
> +      self._CheckUnionAttributes(union)
> 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
> new file mode 100644
> index 00000000..8c7f3c2c
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
> @@ -0,0 +1,186 @@
> +# Copyright 2022 The Chromium Authors
> +# Use of this source code is governed by a BSD-style license that can be
> +# found in the LICENSE file.
> +
> +import unittest
> +
> +import mojom.generate.check as check
> +from mojom_bindings_generator import LoadChecks, _Generate
> +from mojom_parser_test_case import MojomParserTestCase
> +
> +
> +class FakeArgs:
> +  """Fakes args to _Generate - intention is to do just enough to run checks"""
> +
> +  def __init__(self, tester, files=None):
> +    """ `tester` is MojomParserTestCase for paths.
> +        `files` will have tester path added."""
> +    self.checks_string = 'attributes'
> +    self.depth = tester.GetPath('')
> +    self.filelist = None
> +    self.filename = [tester.GetPath(x) for x in files]
> +    self.gen_directories = tester.GetPath('gen')
> +    self.generators_string = ''
> +    self.import_directories = []
> +    self.output_dir = tester.GetPath('out')
> +    self.scrambled_message_id_salt_paths = None
> +    self.typemaps = []
> +    self.variant = 'none'
> +
> +
> +class MojoBindingsCheckTest(MojomParserTestCase):
> +  def _ParseAndGenerate(self, mojoms):
> +    self.ParseMojoms(mojoms)
> +    args = FakeArgs(self, files=mojoms)
> +    _Generate(args, {})
> +
> +  def _testValid(self, filename, content):
> +    self.WriteFile(filename, content)
> +    self._ParseAndGenerate([filename])
> +
> +  def _testThrows(self, filename, content, regexp):
> +    mojoms = []
> +    self.WriteFile(filename, content)
> +    mojoms.append(filename)
> +    with self.assertRaisesRegexp(check.CheckException, regexp):
> +      self._ParseAndGenerate(mojoms)
> +
> +  def testLoads(self):
> +    """Validate that the check is registered under the expected name."""
> +    check_modules = LoadChecks('attributes')
> +    self.assertTrue(check_modules['attributes'])
> +
> +  def testNoAnnotations(self):
> +    # Undecorated mojom should be fine.
> +    self._testValid(
> +        "a.mojom", """
> +      module a;
> +      struct Bar { int32 a; };
> +      enum Hello { kValue };
> +      union Thingy { Bar b; Hello hi; };
> +      interface Foo {
> +        Foo(int32 a, Hello hi, Thingy t) => (Bar b);
> +      };
> +    """)
> +
> +  def testValidAnnotations(self):
> +    # Obviously this is meaningless and won't generate, but it should pass
> +    # the attribute check's validation.
> +    self._testValid(
> +        "a.mojom", """
> +      [JavaConstantsClassName="FakeClass",JavaPackage="org.chromium.Fake"]
> +      module a;
> +      [Stable, Extensible]
> +      enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 };
> +      [Native]
> +      enum NativeEnum {};
> +      [Stable,Extensible]
> +      union Thingy { Bar b; [Default]int32 c; Hello hi; };
> +
> +      [Stable,RenamedFrom="module.other.Foo",
> +       Uuid="4C178401-4B07-4C2E-9255-5401A943D0C7"]
> +      struct Structure { Hello hi; };
> +
> +      [ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable,
> +       Uuid="2F17D7DD-865A-4B1C-9394-9C94E035E82F"]
> +      interface Foo {
> +        [AllowedContext=Hello.kValue]
> +        Foo@0(int32 a) => (int32 b);
> +        [MinVersion=2,Sync,UnlimitedSize,NoInterrupt]
> +        Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c);
> +      };
> +    """)
> +
> +  def testWrongModuleStable(self):
> +    contents = """
> +      // err: module cannot be Stable
> +      [Stable]
> +      module a;
> +      enum Hello { kValue, kValue2, kValue3 };
> +      enum NativeEnum {};
> +      struct Structure { Hello hi; };
> +
> +      interface Foo {
> +        Foo(int32 a) => (int32 b);
> +        Bar(int32 b, Structure? s) => (bool c);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents,
> +                     'attribute Stable not allowed on module')
> +
> +  def testWrongEnumDefault(self):
> +    contents = """
> +      module a;
> +      // err: default should go on EnumValue not Enum.
> +      [Default=kValue]
> +      enum Hello { kValue, kValue2, kValue3 };
> +      enum NativeEnum {};
> +      struct Structure { Hello hi; };
> +
> +      interface Foo {
> +        Foo(int32 a) => (int32 b);
> +        Bar(int32 b, Structure? s) => (bool c);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents,
> +                     'attribute Default not allowed on enum')
> +
> +  def testWrongStructMinVersion(self):
> +    contents = """
> +      module a;
> +      enum Hello { kValue, kValue2, kValue3 };
> +      enum NativeEnum {};
> +      // err: struct cannot have MinVersion.
> +      [MinVersion=2]
> +      struct Structure { Hello hi; };
> +
> +      interface Foo {
> +        Foo(int32 a) => (int32 b);
> +        Bar(int32 b, Structure? s) => (bool c);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents,
> +                     'attribute MinVersion not allowed on struct')
> +
> +  def testWrongMethodRequireContext(self):
> +    contents = """
> +      module a;
> +      enum Hello { kValue, kValue2, kValue3 };
> +      enum NativeEnum {};
> +      struct Structure { Hello hi; };
> +
> +      interface Foo {
> +        // err: RequireContext is for interfaces.
> +        [RequireContext=Hello.kValue]
> +        Foo(int32 a) => (int32 b);
> +        Bar(int32 b, Structure? s) => (bool c);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents,
> +                     'RequireContext not allowed on method')
> +
> +  def testWrongMethodRequireContext(self):
> +    # crbug.com/1230122
> +    contents = """
> +      module a;
> +      interface Foo {
> +        // err: sync not Sync.
> +        [sync]
> +        Foo(int32 a) => (int32 b);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents,
> +                     'attribute sync not allowed.*Did you mean: Sync')
> +
> +  def testStableExtensibleEnum(self):
> +    # crbug.com/1193875
> +    contents = """
> +      module a;
> +      [Stable]
> +      enum Foo {
> +        kDefaultVal,
> +        kOtherVal = 2,
> +      };
> +    """
> +    self._testThrows('a.mojom', contents,
> +                     'Extensible.*?required.*?Stable.*?enum')
> 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
> new file mode 100644
> index 00000000..702d41c3
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
> @@ -0,0 +1,34 @@
> +# Copyright 2022 The Chromium Authors
> +# Use of this source code is governed by a BSD-style license that can be
> +# found in the LICENSE file.
> +"""Ensure no duplicate type definitions before generation."""
> +
> +import mojom.generate.check as check
> +import mojom.generate.module as module
> +
> +
> +class Check(check.Check):
> +  def __init__(self, *args, **kwargs):
> +    super(Check, self).__init__(*args, **kwargs)
> +
> +  def CheckModule(self):
> +    kinds = dict()
> +    for module in self.module.imports:
> +      for kind in module.enums + module.structs + module.unions:
> +        kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
> +        if kind_name in kinds:
> +          previous_module = kinds[kind_name]
> +          if previous_module.path != module.path:
> +            raise check.CheckException(
> +                self.module, f"multiple-definition for type {kind_name}" +
> +                f"(defined in both {previous_module} and {module})")
> +        kinds[kind_name] = kind.module
> +
> +    for kind in self.module.enums + self.module.structs + self.module.unions:
> +      kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
> +      if kind_name in kinds:
> +        previous_module = kinds[kind_name]
> +        raise check.CheckException(
> +            self.module, f"multiple-definition for type {kind_name}" +
> +            f"(previous definition in {previous_module})")
> +    return True
> 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
> new file mode 100644
> index 00000000..d570e26c
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
> @@ -0,0 +1,102 @@
> +# Copyright 2022 The Chromium Authors
> +# Use of this source code is governed by a BSD-style license that can be
> +# found in the LICENSE file.
> +"""Validate RequireContext and AllowedContext annotations before generation."""
> +
> +import mojom.generate.check as check
> +import mojom.generate.module as module
> +
> +
> +class Check(check.Check):
> +  def __init__(self, *args, **kwargs):
> +    self.kind_to_interfaces = dict()
> +    super(Check, self).__init__(*args, **kwargs)
> +
> +  def _IsPassedInterface(self, candidate):
> +    if isinstance(
> +        candidate.kind,
> +        (module.PendingReceiver, module.PendingRemote,
> +         module.PendingAssociatedReceiver, module.PendingAssociatedRemote)):
> +      return True
> +    return False
> +
> +  def _CheckInterface(self, method, param):
> +    # |param| is a pending_x<Interface> so need .kind.kind to get Interface.
> +    interface = param.kind.kind
> +    if interface.require_context:
> +      if method.allowed_context is None:
> +        raise check.CheckException(
> +            self.module, "method `{}` has parameter `{}` which passes interface"
> +            " `{}` that requires an AllowedContext annotation but none exists.".
> +            format(
> +                method.mojom_name,
> +                param.mojom_name,
> +                interface.mojom_name,
> +            ))
> +      # If a string was provided, or if an enum was not imported, this will
> +      # be a string and we cannot validate that it is in range.
> +      if not isinstance(method.allowed_context, module.EnumValue):
> +        raise check.CheckException(
> +            self.module,
> +            "method `{}` has AllowedContext={} which is not a valid enum value."
> +            .format(method.mojom_name, method.allowed_context))
> +      # EnumValue must be from the same enum to be compared.
> +      if interface.require_context.enum != method.allowed_context.enum:
> +        raise check.CheckException(
> +            self.module, "method `{}` has parameter `{}` which passes interface"
> +            " `{}` that requires AllowedContext={} but one of kind `{}` was "
> +            "provided.".format(
> +                method.mojom_name,
> +                param.mojom_name,
> +                interface.mojom_name,
> +                interface.require_context.enum,
> +                method.allowed_context.enum,
> +            ))
> +      # RestrictContext enums have most privileged field first (lowest value).
> +      interface_value = interface.require_context.field.numeric_value
> +      method_value = method.allowed_context.field.numeric_value
> +      if interface_value < method_value:
> +        raise check.CheckException(
> +            self.module, "RequireContext={} > AllowedContext={} for method "
> +            "`{}` which passes interface `{}`.".format(
> +                interface.require_context.GetSpec(),
> +                method.allowed_context.GetSpec(), method.mojom_name,
> +                interface.mojom_name))
> +      return True
> +
> +  def _GatherReferencedInterfaces(self, field):
> +    key = field.kind.spec
> +    # structs/unions can nest themselves so we need to bookkeep.
> +    if not key in self.kind_to_interfaces:
> +      # Might reference ourselves so have to create the list first.
> +      self.kind_to_interfaces[key] = set()
> +      for param in field.kind.fields:
> +        if self._IsPassedInterface(param):
> +          self.kind_to_interfaces[key].add(param)
> +        elif isinstance(param.kind, (module.Struct, module.Union)):
> +          for iface in self._GatherReferencedInterfaces(param):
> +            self.kind_to_interfaces[key].add(iface)
> +    return self.kind_to_interfaces[key]
> +
> +  def _CheckParams(self, method, params):
> +    # Note: we have to repeat _CheckParams for each method as each might have
> +    # different AllowedContext= attributes. We cannot memoize this function,
> +    # but can do so for gathering referenced interfaces as their RequireContext
> +    # attributes do not change.
> +    for param in params:
> +      if self._IsPassedInterface(param):
> +        self._CheckInterface(method, param)
> +      elif isinstance(param.kind, (module.Struct, module.Union)):
> +        for interface in self._GatherReferencedInterfaces(param):
> +          self._CheckInterface(method, interface)
> +
> +  def _CheckMethod(self, method):
> +    if method.parameters:
> +      self._CheckParams(method, method.parameters)
> +    if method.response_parameters:
> +      self._CheckParams(method, method.response_parameters)
> +
> +  def CheckModule(self):
> +    for interface in self.module.interfaces:
> +      for method in interface.methods:
> +        self._CheckMethod(method)
> 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
> new file mode 100644
> index 00000000..a6cd71e2
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
> @@ -0,0 +1,254 @@
> +# Copyright 2022 The Chromium Authors
> +# Use of this source code is governed by a BSD-style license that can be
> +# found in the LICENSE file.
> +
> +import unittest
> +
> +import mojom.generate.check as check
> +from mojom_bindings_generator import LoadChecks, _Generate
> +from mojom_parser_test_case import MojomParserTestCase
> +
> +# Mojoms that we will use in multiple tests.
> +basic_mojoms = {
> +    'level.mojom':
> +    """
> +  module level;
> +  enum Level {
> +    kHighest,
> +    kMiddle,
> +    kLowest,
> +  };
> +  """,
> +    'interfaces.mojom':
> +    """
> +  module interfaces;
> +  import "level.mojom";
> +  struct Foo {int32 bar;};
> +  [RequireContext=level.Level.kHighest]
> +  interface High {
> +    DoFoo(Foo foo);
> +  };
> +  [RequireContext=level.Level.kMiddle]
> +  interface Mid {
> +    DoFoo(Foo foo);
> +  };
> +  [RequireContext=level.Level.kLowest]
> +  interface Low {
> +    DoFoo(Foo foo);
> +  };
> +  """
> +}
> +
> +
> +class FakeArgs:
> +  """Fakes args to _Generate - intention is to do just enough to run checks"""
> +
> +  def __init__(self, tester, files=None):
> +    """ `tester` is MojomParserTestCase for paths.
> +        `files` will have tester path added."""
> +    self.checks_string = 'restrictions'
> +    self.depth = tester.GetPath('')
> +    self.filelist = None
> +    self.filename = [tester.GetPath(x) for x in files]
> +    self.gen_directories = tester.GetPath('gen')
> +    self.generators_string = ''
> +    self.import_directories = []
> +    self.output_dir = tester.GetPath('out')
> +    self.scrambled_message_id_salt_paths = None
> +    self.typemaps = []
> +    self.variant = 'none'
> +
> +
> +class MojoBindingsCheckTest(MojomParserTestCase):
> +  def _WriteBasicMojoms(self):
> +    for filename, contents in basic_mojoms.items():
> +      self.WriteFile(filename, contents)
> +    return list(basic_mojoms.keys())
> +
> +  def _ParseAndGenerate(self, mojoms):
> +    self.ParseMojoms(mojoms)
> +    args = FakeArgs(self, files=mojoms)
> +    _Generate(args, {})
> +
> +  def testLoads(self):
> +    """Validate that the check is registered under the expected name."""
> +    check_modules = LoadChecks('restrictions')
> +    self.assertTrue(check_modules['restrictions'])
> +
> +  def testValidAnnotations(self):
> +    mojoms = self._WriteBasicMojoms()
> +
> +    a = 'a.mojom'
> +    self.WriteFile(
> +        a, """
> +      module a;
> +      import "level.mojom";
> +      import "interfaces.mojom";
> +
> +      interface PassesHigh {
> +        [AllowedContext=level.Level.kHighest]
> +        DoHigh(pending_receiver<interfaces.High> hi);
> +      };
> +      interface PassesMedium {
> +        [AllowedContext=level.Level.kMiddle]
> +        DoMedium(pending_receiver<interfaces.Mid> hi);
> +        [AllowedContext=level.Level.kMiddle]
> +        DoMediumRem(pending_remote<interfaces.Mid> hi);
> +        [AllowedContext=level.Level.kMiddle]
> +        DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);
> +        [AllowedContext=level.Level.kMiddle]
> +        DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);
> +      };
> +      interface PassesLow {
> +        [AllowedContext=level.Level.kLowest]
> +        DoLow(pending_receiver<interfaces.Low> hi);
> +      };
> +
> +      struct One { pending_receiver<interfaces.High> hi; };
> +      struct Two { One one; };
> +      interface PassesNestedHigh {
> +        [AllowedContext=level.Level.kHighest]
> +        DoNestedHigh(Two two);
> +      };
> +
> +      // Allowed as PassesHigh is not itself restricted.
> +      interface PassesPassesHigh {
> +        DoPass(pending_receiver<PassesHigh> hiho);
> +      };
> +    """)
> +    mojoms.append(a)
> +    self._ParseAndGenerate(mojoms)
> +
> +  def _testThrows(self, filename, content, regexp):
> +    mojoms = self._WriteBasicMojoms()
> +    self.WriteFile(filename, content)
> +    mojoms.append(filename)
> +    with self.assertRaisesRegexp(check.CheckException, regexp):
> +      self._ParseAndGenerate(mojoms)
> +
> +  def testMissingAnnotation(self):
> +    contents = """
> +      module b;
> +      import "level.mojom";
> +      import "interfaces.mojom";
> +
> +      interface PassesHigh {
> +        // err: missing annotation.
> +        DoHigh(pending_receiver<interfaces.High> hi);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
> +
> +  def testAllowTooLow(self):
> +    contents = """
> +      module b;
> +      import "level.mojom";
> +      import "interfaces.mojom";
> +
> +      interface PassesHigh {
> +        // err: level is worse than required.
> +        [AllowedContext=level.Level.kMiddle]
> +        DoHigh(pending_receiver<interfaces.High> hi);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents,
> +                     'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
> +
> +  def testWrongEnumInAllow(self):
> +    contents = """
> +      module b;
> +      import "level.mojom";
> +      import "interfaces.mojom";
> +      enum Blah {
> +        kZero,
> +      };
> +      interface PassesHigh {
> +        // err: different enums.
> +        [AllowedContext=Blah.kZero]
> +        DoHigh(pending_receiver<interfaces.High> hi);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents, 'but one of kind')
> +
> +  def testNotAnEnumInAllow(self):
> +    contents = """
> +      module b;
> +      import "level.mojom";
> +      import "interfaces.mojom";
> +      interface PassesHigh {
> +        // err: not an enum.
> +        [AllowedContext=doopdedoo.mojom.kWhatever]
> +        DoHigh(pending_receiver<interfaces.High> hi);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents, 'not a valid enum value')
> +
> +  def testMissingAllowedForNestedStructs(self):
> +    contents = """
> +      module b;
> +      import "level.mojom";
> +      import "interfaces.mojom";
> +      struct One { pending_receiver<interfaces.High> hi; };
> +      struct Two { One one; };
> +      interface PassesNestedHigh {
> +        // err: missing annotation.
> +        DoNestedHigh(Two two);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
> +
> +  def testMissingAllowedForNestedUnions(self):
> +    contents = """
> +      module b;
> +      import "level.mojom";
> +      import "interfaces.mojom";
> +      struct One { pending_receiver<interfaces.High> hi; };
> +      struct Two { One one; };
> +      union Three {One one; Two two; };
> +      interface PassesNestedHigh {
> +        // err: missing annotation.
> +        DoNestedHigh(Three three);
> +      };
> +    """
> +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
> +
> +  def testMultipleInterfacesThrows(self):
> +    contents = """
> +      module b;
> +      import "level.mojom";
> +      import "interfaces.mojom";
> +      struct One { pending_receiver<interfaces.High> hi; };
> +      interface PassesMultipleInterfaces {
> +        [AllowedContext=level.Level.kMiddle]
> +        DoMultiple(
> +          pending_remote<interfaces.Mid> mid,
> +          pending_receiver<interfaces.High> hi,
> +          One one
> +        );
> +      };
> +    """
> +    self._testThrows('b.mojom', contents,
> +                     'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
> +
> +  def testMultipleInterfacesAllowed(self):
> +    """Multiple interfaces can be passed, all satisfy the level."""
> +    mojoms = self._WriteBasicMojoms()
> +
> +    b = "b.mojom"
> +    self.WriteFile(
> +        b, """
> +      module b;
> +      import "level.mojom";
> +      import "interfaces.mojom";
> +      struct One { pending_receiver<interfaces.High> hi; };
> +      interface PassesMultipleInterfaces {
> +        [AllowedContext=level.Level.kHighest]
> +        DoMultiple(
> +          pending_receiver<interfaces.High> hi,
> +          pending_remote<interfaces.Mid> mid,
> +          One one
> +        );
> +      };
> +    """)
> +    mojoms.append(b)
> +    self._ParseAndGenerate(mojoms)
> diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> index 48bc66fd..4dd26d4a 100755
> --- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> +++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> @@ -1,5 +1,5 @@
>  #!/usr/bin/env python
> -# Copyright 2019 The Chromium Authors. All rights reserved.
> +# Copyright 2019 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  #
> @@ -15,6 +15,7 @@
>  from __future__ import print_function
>
>  import optparse
> +import sys
>
>
>  def Concatenate(filenames):
> @@ -47,7 +48,7 @@ def main():
>    parser.set_usage("""Concatenate several files into one.
>        Equivalent to: cat file1 ... > target.""")
>    (_options, args) = parser.parse_args()
> -  exit(0 if Concatenate(args) else 1)
> +  sys.exit(0 if Concatenate(args) else 1)
>
>
>  if __name__ == "__main__":
> 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
> index be8985ce..770081e1 100755
> --- 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
> @@ -1,5 +1,5 @@
>  #!/usr/bin/env python
> -# Copyright 2018 The Chromium Authors. All rights reserved.
> +# Copyright 2018 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -20,6 +20,7 @@ from __future__ import print_function
>
>  import optparse
>  import re
> +import sys
>
>
>  _MOJO_INTERNAL_MODULE_NAME = "mojo.internal"
> @@ -34,7 +35,7 @@ def FilterLine(filename, line, output):
>      match = re.match("goog.provide\('([^']+)'\);", line)
>      if not match:
>        print("Invalid goog.provide line in %s:\n%s" % (filename, line))
> -      exit(1)
> +      sys.exit(1)
>
>      module_name = match.group(1)
>      if module_name == _MOJO_INTERNAL_MODULE_NAME:
> @@ -67,7 +68,8 @@ def main():
>      Concatenate several files into one, stripping Closure provide and
>      require directives along the way.""")
>    (_, args) = parser.parse_args()
> -  exit(0 if ConcatenateAndReplaceExports(args) else 1)
> +  sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)
> +
>
>  if __name__ == "__main__":
>    main()
> 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
> index 8b78d092..c6daff03 100644
> --- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
> +++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
> @@ -1,4 +1,4 @@
> -# Copyright 2017 The Chromium Authors. All rights reserved.
> +# Copyright 2017 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  """Generates a list of all files in a directory.
> diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> index a0096649..d73c6ea4 100755
> --- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> +++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> @@ -1,5 +1,5 @@
>  #!/usr/bin/env python
> -# Copyright 2016 The Chromium Authors. All rights reserved.
> +# Copyright 2016 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  """Generates a JSON typemap from its command-line arguments and dependencies.
> @@ -82,6 +82,7 @@ def LoadCppTypemapConfig(path):
>        for entry in config['types']:
>          configs[entry['mojom']] = {
>              'typename': entry['cpp'],
> +            'forward_declaration': entry.get('forward_declaration', None),
>              'public_headers': config.get('traits_headers', []),
>              'traits_headers': config.get('traits_private_headers', []),
>              'copyable_pass_by_value': entry.get('copyable_pass_by_value',
> diff --git a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
> new file mode 100755
> index 00000000..cefee7a4
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
> @@ -0,0 +1,47 @@
> +#!/usr/bin/env python3
> +# Copyright 2023 The Chromium Authors
> +# Use of this source code is governed by a BSD-style license that can be
> +# found in the LICENSE file.
> +#
> +# This utility minifies JS files with terser.
> +#
> +# Instance of 'node' has no 'RunNode' member (no-member)
> +# pylint: disable=no-member
> +
> +import argparse
> +import os
> +import sys
> +
> +_HERE_PATH = os.path.dirname(__file__)
> +_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
> +_CWD = os.getcwd()
> +sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
> +import node
> +import node_modules
> +
> +
> +def MinifyFile(input_file, output_file):
> +  node.RunNode([
> +      node_modules.PathToTerser(), input_file, '--mangle', '--compress',
> +      '--comments', 'false', '--output', output_file
> +  ])
> +
> +
> +def main(argv):
> +  parser = argparse.ArgumentParser()
> +  parser.add_argument('--input', required=True)
> +  parser.add_argument('--output', required=True)
> +  args = parser.parse_args(argv)
> +
> +  # Delete the output file if it already exists. It may be a sym link to the
> +  # input, because in non-optimized/pre-Terser builds the input file is copied
> +  # to the output location with gn copy().
> +  out_path = os.path.join(_CWD, args.output)
> +  if (os.path.exists(out_path)):
> +    os.remove(out_path)
> +
> +  MinifyFile(os.path.join(_CWD, args.input), out_path)
> +
> +
> +if __name__ == '__main__':
> +  main(sys.argv[1:])
> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/ipc/mojo/public/tools/bindings/mojom.gni
> index fe2a1da3..ded53259 100644
> --- a/utils/ipc/mojo/public/tools/bindings/mojom.gni
> +++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni
> @@ -1,11 +1,11 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> -import("//build/config/python.gni")
>  import("//third_party/closure_compiler/closure_args.gni")
>  import("//third_party/closure_compiler/compile_js.gni")
>  import("//third_party/protobuf/proto_library.gni")
> +import("//ui/webui/resources/tools/generate_grd.gni")
>  import("//ui/webui/webui_features.gni")
>
>  # TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're
> @@ -16,10 +16,12 @@ import("//ui/webui/webui_features.gni")
>  import("//build/config/chrome_build.gni")
>  import("//build/config/chromecast_build.gni")
>  import("//build/config/chromeos/ui_mode.gni")
> +import("//build/config/features.gni")
>  import("//build/config/nacl/config.gni")
>  import("//build/toolchain/kythe.gni")
>  import("//components/nacl/features.gni")
>  import("//third_party/jinja2/jinja2.gni")
> +import("//third_party/ply/ply.gni")
>  import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
>  declare_args() {
>    # Indicates whether typemapping should be supported in this build
> @@ -38,17 +40,25 @@ declare_args() {
>    # scrambling on all platforms.
>    enable_mojom_message_id_scrambling = true
>
> +  # Enables generating javascript fuzzing-related code and the bindings for the
> +  # MojoLPM fuzzer targets. Off by default.
> +  enable_mojom_fuzzer = false
> +
>    # Enables Closure compilation of generated JS lite bindings. In environments
>    # where compilation is supported, any mojom target "foo" will also have a
>    # corresponding "foo_js_library_for_compile" target generated.
> -  enable_mojom_closure_compile = enable_js_type_check && optimize_webui
> -
> -  # Enables generating Typescript bindings and compiling them to JS bindings.
> -  enable_typescript_bindings = false
> +  if (is_chromeos_ash) {
> +    enable_mojom_closure_compile = enable_js_type_check && optimize_webui
> +  }
> +}
>
> -  # Enables generating javascript fuzzing-related code and the bindings for the
> -  # MojoLPM fuzzer targets. Off by default.
> -  enable_mojom_fuzzer = false
> +# Closure libraries are needed for mojom_closure_compile, and when
> +# js_type_check is enabled on Ash.
> +if (is_chromeos_ash) {
> +  generate_mojom_closure_libraries =
> +      enable_mojom_closure_compile || enable_js_type_check
> +} else {
> +  generate_mojom_closure_libraries = false
>  }
>
>  # NOTE: We would like to avoid scrambling message IDs where it doesn't add
> @@ -69,9 +79,8 @@ declare_args() {
>  # lacros-chrome switches to target_os="chromeos"
>  enable_scrambled_message_ids =
>      enable_mojom_message_id_scrambling &&
> -    (is_mac || is_win ||
> -     (is_linux && !is_chromeos_ash && !is_chromecast && !is_chromeos_lacros) ||
> -     ((enable_nacl || is_nacl || is_nacl_nonsfi) &&
> +    (is_mac || is_win || (is_linux && !is_castos) ||
> +     ((enable_nacl || is_nacl) &&
>        (target_os != "chromeos" && !chromeos_is_browser_only)))
>
>  _mojom_tools_root = "//mojo/public/tools"
> @@ -80,7 +89,9 @@ mojom_parser_script = "$_mojom_tools_root/mojom/mojom_parser.py"
>  mojom_parser_sources = [
>    "$_mojom_library_root/__init__.py",
>    "$_mojom_library_root/error.py",
> +  "$_mojom_library_root/fileutil.py",
>    "$_mojom_library_root/generate/__init__.py",
> +  "$_mojom_library_root/generate/check.py",
>    "$_mojom_library_root/generate/generator.py",
>    "$_mojom_library_root/generate/module.py",
>    "$_mojom_library_root/generate/pack.py",
> @@ -88,20 +99,28 @@ mojom_parser_sources = [
>    "$_mojom_library_root/generate/translate.py",
>    "$_mojom_library_root/parse/__init__.py",
>    "$_mojom_library_root/parse/ast.py",
> +  "$_mojom_library_root/parse/conditional_features.py",
>    "$_mojom_library_root/parse/lexer.py",
>    "$_mojom_library_root/parse/parser.py",
> +  "//tools/diagnosis/crbug_1001171.py",
>  ]
>
>  mojom_generator_root = "$_mojom_tools_root/bindings"
>  mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py"
>  mojom_generator_sources =
>      mojom_parser_sources + [
> +      "$mojom_generator_root/checks/__init__.py",
> +      "$mojom_generator_root/checks/mojom_attributes_check.py",
> +      "$mojom_generator_root/checks/mojom_definitions_check.py",
> +      "$mojom_generator_root/checks/mojom_restrictions_check.py",
> +      "$mojom_generator_root/generators/__init__.py",
>        "$mojom_generator_root/generators/cpp_util.py",
>        "$mojom_generator_root/generators/mojom_cpp_generator.py",
>        "$mojom_generator_root/generators/mojom_java_generator.py",
> -      "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
>        "$mojom_generator_root/generators/mojom_js_generator.py",
> +      "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
>        "$mojom_generator_root/generators/mojom_ts_generator.py",
> +      "$mojom_generator_root/generators/mojom_webui_js_bridge_generator.py",
>        "$mojom_generator_script",
>      ]
>
> @@ -243,12 +262,16 @@ if (enable_scrambled_message_ids) {
>  #       |cpp_only| is set to true, it overrides this to prevent generation of
>  #       Java bindings.
>  #
> -#   enable_fuzzing (optional)
> +#   enable_js_fuzzing (optional)
> +#       Enables generation of javascript fuzzing sources for the target if the
> +#       global build arg |enable_mojom_fuzzer| is also set to |true|.
> +#       Defaults to |true|. If JS fuzzing generation is enabled for a target,
> +#       the target will always generate JS bindings even if |cpp_only| is set to
> +#       |true|. See note above.
> +#
> +#   enable_mojolpm_fuzzing (optional)
>  #       Enables generation of fuzzing sources for the target if the global build
> -#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If
> -#       fuzzing generation is enabled for a target, the target will always
> -#       generate JS bindings even if |cpp_only| is set to |true|. See note
> -#       above.
> +#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|.
>  #
>  #   support_lazy_serialization (optional)
>  #       If set to |true|, generated C++ bindings will effectively prefer to
> @@ -313,6 +336,16 @@ if (enable_scrambled_message_ids) {
>  #   use_typescript_sources (optional)
>  #       Uses the Typescript generator to generate JavaScript bindings.
>  #
> +#   generate_legacy_js_bindings (optional)
> +#       Generate js_data_deps target containing legacy JavaScript bindings files
> +#       for Blink tests and other non-WebUI users when generating TypeScript
> +#       bindings for WebUI. Ignored if use_typescript_sources is not set to
> +#       true.
> +#
> +#   webui_js_bridge_config (optional)
> +#       Prefer to use the `mojom_with_webui_js_bridge` target below instead
> +#       of using this argument directly.
> +#
>  #   js_generate_struct_deserializers (optional)
>  #       Generates JS deerialize methods for structs.
>  #
> @@ -402,6 +435,12 @@ if (enable_scrambled_message_ids) {
>  #             should be mapped in generated bindings. This is a string like
>  #             "::base::Value" or "std::vector<::base::Value>".
>  #
> +#         forward_declaration (optional)
> +#             A forward declaration of the C++ type, which bindings that don't
> +#             need the full type definition can use to reduce the size of
> +#             the generated code. This is a string like
> +#             "namespace base { class Value; }".
> +#
>  #         move_only (optional)
>  #             A boolean value (default false) which indicates whether the C++
>  #             type is move-only. If true, generated bindings will pass the type
> @@ -621,20 +660,26 @@ template("mojom") {
>    build_metadata_filename = "$target_gen_dir/$target_name.build_metadata"
>    build_metadata = {
>    }
> -  build_metadata.sources = rebase_path(sources_list)
> +  build_metadata.sources = rebase_path(sources_list, target_gen_dir)
>    build_metadata.deps = []
>    foreach(dep, all_deps) {
>      dep_target_gen_dir = get_label_info(dep, "target_gen_dir")
>      dep_name = get_label_info(dep, "name")
>      build_metadata.deps +=
> -        [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata") ]
> +        [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata",
> +                      target_gen_dir) ]
>    }
>    write_file(build_metadata_filename, build_metadata, "json")
>
> -  generate_fuzzing =
> -      (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) &&
> +  generate_js_fuzzing =
> +      (!defined(invoker.enable_js_fuzzing) || invoker.enable_js_fuzzing) &&
>        enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly)
>
> +  generate_mojolpm_fuzzing =
> +      (!defined(invoker.enable_mojolpm_fuzzing) ||
> +       invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer &&
> +      (!defined(invoker.testonly) || !invoker.testonly)
> +
>    parser_target_name = "${target_name}__parser"
>    parser_deps = []
>    foreach(dep, all_deps) {
> @@ -683,12 +728,17 @@ template("mojom") {
>        enabled_features += [ "is_win" ]
>      }
>
> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -    python2_action(parser_target_name) {
> +    if (is_apple) {
> +      enabled_features += [ "is_apple" ]
> +    }
> +
> +    action(parser_target_name) {
> +      allow_remote = true
> +      custom_processor = "mojom_parser"
>        script = mojom_parser_script
> -      inputs = mojom_parser_sources + [ build_metadata_filename ]
> +      inputs = mojom_parser_sources + ply_sources + [ build_metadata_filename ]
>        sources = sources_list
> -      deps = parser_deps
> +      public_deps = parser_deps
>        outputs = []
>        foreach(base_path, output_file_base_paths) {
>          filename = get_path_info(base_path, "file")
> @@ -698,31 +748,35 @@ template("mojom") {
>
>        filelist = []
>        foreach(source, sources_list) {
> -        filelist += [ rebase_path(source) ]
> +        filelist += [ rebase_path(source, root_build_dir) ]
>        }
> -      response_file_contents = filelist
> +
> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> +      rsp_file = "$target_gen_dir/${target_name}.rsp"
> +      write_file(rsp_file, filelist)
> +      inputs += [ rsp_file ]
>
>        args = [
>          # Resolve relative input mojom paths against both the root src dir and
>          # the root gen dir.
>          "--input-root",
> -        rebase_path("//."),
> +        rebase_path("//.", root_build_dir),
>          "--input-root",
> -        rebase_path(root_gen_dir),
> +        rebase_path(root_gen_dir, root_build_dir),
>
>          "--output-root",
> -        rebase_path(root_gen_dir),
> +        rebase_path(root_gen_dir, root_build_dir),
>
> -        "--mojom-file-list={{response_file_name}}",
> +        "--mojom-file-list=" + rebase_path(rsp_file, root_build_dir),
>
>          "--check-imports",
> -        rebase_path(build_metadata_filename),
> +        rebase_path(build_metadata_filename, root_build_dir),
>        ]
>
>        if (defined(invoker.input_root_override)) {
>          args += [
>            "--input-root",
> -          rebase_path(invoker.input_root_override),
> +          rebase_path(invoker.input_root_override, root_build_dir),
>          ]
>        }
>
> @@ -738,6 +792,9 @@ template("mojom") {
>            "--add-module-metadata",
>            "webui_module_path=${invoker.webui_module_path}",
>          ]
> +        if (defined(invoker.generate_legacy_js_bindings)) {
> +          args += [ "legacy_js_only=${invoker.generate_legacy_js_bindings}" ]
> +        }
>        }
>      }
>    }
> @@ -819,11 +876,12 @@ template("mojom") {
>        }
>      }
>
> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -    python2_action(generator_cpp_message_ids_target_name) {
> +    action(generator_cpp_message_ids_target_name) {
> +      allow_remote = true
>        script = mojom_generator_script
>        inputs = mojom_generator_sources + jinja2_sources
> -      sources = sources_list
> +      sources = sources_list +
> +                [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
>        deps = [
>          ":$parser_target_name",
>          "//mojo/public/tools/bindings:precompile_templates",
> @@ -835,16 +893,22 @@ template("mojom") {
>        args = common_generator_args
>        filelist = []
>        foreach(source, sources_list) {
> -        filelist += [ rebase_path("$source", root_build_dir) ]
> +        filelist += [ rebase_path(source, root_build_dir) ]
>        }
>        foreach(base_path, output_file_base_paths) {
> +        filename = get_path_info(base_path, "file")
> +        dirname = get_path_info(base_path, "dir")
> +        inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
>          outputs += [ "$root_gen_dir/$base_path-shared-message-ids.h" ]
>        }
>
> -      response_file_contents = filelist
> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> +      rsp_file = "$target_gen_dir/${target_name}.rsp"
> +      write_file(rsp_file, filelist)
> +      inputs += [ rsp_file ]
>
>        args += [
> -        "--filelist={{response_file_name}}",
> +        "--filelist=" + rebase_path(rsp_file, root_build_dir),
>          "--generate_non_variant_code",
>          "--generate_message_ids",
>          "-g",
> @@ -860,12 +924,13 @@ template("mojom") {
>
>      generator_shared_target_name = "${target_name}_shared__generator"
>
> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -    python2_action(generator_shared_target_name) {
> +    action(generator_shared_target_name) {
> +      allow_remote = true
>        visibility = [ ":*" ]
>        script = mojom_generator_script
>        inputs = mojom_generator_sources + jinja2_sources
> -      sources = sources_list
> +      sources = sources_list +
> +                [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
>        deps = [
>          ":$parser_target_name",
>          "//mojo/public/tools/bindings:precompile_templates",
> @@ -878,9 +943,14 @@ template("mojom") {
>        args = common_generator_args
>        filelist = []
>        foreach(source, sources_list) {
> -        filelist += [ rebase_path("$source", root_build_dir) ]
> +        filelist += [ rebase_path(source, root_build_dir) ]
>        }
>        foreach(base_path, output_file_base_paths) {
> +        # Need the mojom-module as an input to this action.
> +        filename = get_path_info(base_path, "file")
> +        dirname = get_path_info(base_path, "dir")
> +        inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
> +
>          outputs += [
>            "$root_gen_dir/$base_path-params-data.h",
>            "$root_gen_dir/$base_path-shared-internal.h",
> @@ -889,10 +959,13 @@ template("mojom") {
>          ]
>        }
>
> -      response_file_contents = filelist
> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> +      rsp_file = "$target_gen_dir/${target_name}.rsp"
> +      write_file(rsp_file, filelist)
> +      inputs += [ rsp_file ]
>
>        args += [
> -        "--filelist={{response_file_name}}",
> +        "--filelist=" + rebase_path(rsp_file, root_build_dir),
>          "--generate_non_variant_code",
>          "-g",
>          "c++",
> @@ -972,7 +1045,7 @@ template("mojom") {
>      }
>    }
>
> -  if (generate_fuzzing) {
> +  if (generate_mojolpm_fuzzing) {
>      # This block generates the proto files used for the MojoLPM fuzzer,
>      # and the corresponding proto targets that will be linked in the fuzzer
>      # targets. These are independent of the typemappings, and can be done
> @@ -981,11 +1054,15 @@ template("mojom") {
>      generator_mojolpm_proto_target_name =
>          "${target_name}_mojolpm_proto_generator"
>
> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -    python2_action(generator_mojolpm_proto_target_name) {
> +    action(generator_mojolpm_proto_target_name) {
> +      allow_remote = true
>        script = mojom_generator_script
>        inputs = mojom_generator_sources + jinja2_sources
> -      sources = invoker.sources
> +      sources =
> +          invoker.sources + [
> +            "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
> +            "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
> +          ]
>        deps = [
>          ":$parser_target_name",
>          "//mojo/public/tools/bindings:precompile_templates",
> @@ -994,15 +1071,37 @@ template("mojom") {
>        outputs = []
>        args = common_generator_args
>        filelist = []
> -      foreach(source, invoker.sources) {
> -        filelist += [ rebase_path("$source", root_build_dir) ]
> +
> +      # Split the input into generated and non-generated source files. They
> +      # need to be processed separately.
> +      gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
> +      non_gen_sources =
> +          filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
> +      gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])
> +
> +      foreach(source, non_gen_sources) {
> +        filelist += [ rebase_path(source, root_build_dir) ]
> +        inputs += [ "$target_gen_dir/$source-module" ]
>          outputs += [ "$target_gen_dir/$source.mojolpm.proto" ]
>        }
>
> -      response_file_contents = filelist
> +      foreach(source, gen_sources) {
> +        filelist += [ rebase_path(source, root_build_dir) ]
> +
> +        # For generated files, we assume they're in the target_gen_dir or a
> +        # sub-folder of it. Rebase the path so we can get the relative location.
> +        source_file = rebase_path(source, target_gen_dir)
> +        inputs += [ "$target_gen_dir/$source_file-module" ]
> +        outputs += [ "$target_gen_dir/$source_file.mojolpm.proto" ]
> +      }
> +
> +      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> +      rsp_file = "$target_gen_dir/${target_name}.rsp"
> +      write_file(rsp_file, filelist)
> +      inputs += [ rsp_file ]
>
>        args += [
> -        "--filelist={{response_file_name}}",
> +        "--filelist=" + rebase_path(rsp_file, root_build_dir),
>          "--generate_non_variant_code",
>          "-g",
>          "mojolpm",
> @@ -1014,9 +1113,20 @@ template("mojom") {
>        proto_library(mojolpm_proto_target_name) {
>          testonly = true
>          generate_python = false
> +
> +        # Split the input into generated and non-generated source files. They
> +        # need to be processed separately.
> +        gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
> +        non_gen_sources =
> +            filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
> +        gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])
>          sources = process_file_template(
> -                invoker.sources,
> +                non_gen_sources,
>                  [ "{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto" ])
> +        sources += process_file_template(
> +                gen_sources,
> +                [ "{{source_dir}}/{{source_file_part}}.mojolpm.proto" ])
> +
>          import_dirs = [ "//" ]
>          proto_in_dir = "${root_gen_dir}"
>          proto_out_dir = "."
> @@ -1055,7 +1165,7 @@ template("mojom") {
>      component_macro_suffix = ""
>    }
>    if ((!defined(invoker.disable_variants) || !invoker.disable_variants) &&
> -      !is_ios) {
> +      use_blink) {
>      blink_variant = {
>        variant = "blink"
>        component_macro_suffix = "_BLINK"
> @@ -1149,39 +1259,6 @@ template("mojom") {
>              "${bindings_configuration.component_macro_suffix}_IMPL" ]
>      }
>
> -    export_args = []
> -    export_args_overridden = false
> -    if (defined(bindings_configuration.for_blink) &&
> -        bindings_configuration.for_blink) {
> -      if (defined(invoker.export_class_attribute_blink)) {
> -        export_args_overridden = true
> -        export_args += [
> -          "--export_attribute",
> -          invoker.export_class_attribute_blink,
> -          "--export_header",
> -          invoker.export_header_blink,
> -        ]
> -      }
> -    } else if (defined(invoker.export_class_attribute)) {
> -      export_args_overridden = true
> -      export_args += [
> -        "--export_attribute",
> -        invoker.export_class_attribute,
> -        "--export_header",
> -        invoker.export_header,
> -      ]
> -    }
> -
> -    if (!export_args_overridden && defined(invoker.component_macro_prefix)) {
> -      export_args += [
> -        "--export_attribute",
> -        "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
> -            "${bindings_configuration.component_macro_suffix})",
> -        "--export_header",
> -        "base/component_export.h",
> -      ]
> -    }
> -
>      generate_java = false
>      if (!cpp_only && defined(invoker.generate_java)) {
>        generate_java = invoker.generate_java
> @@ -1190,6 +1267,38 @@ template("mojom") {
>      type_mappings_path =
>          "$target_gen_dir/${target_name}${variant_suffix}__type_mappings"
>      if (sources_list != []) {
> +      export_args = []
> +      export_args_overridden = false
> +      if (defined(bindings_configuration.for_blink) &&
> +          bindings_configuration.for_blink) {
> +        if (defined(invoker.export_class_attribute_blink)) {
> +          export_args_overridden = true
> +          export_args += [
> +            "--export_attribute",
> +            invoker.export_class_attribute_blink,
> +            "--export_header",
> +            invoker.export_header_blink,
> +          ]
> +        }
> +      } else if (defined(invoker.export_class_attribute)) {
> +        export_args_overridden = true
> +        export_args += [
> +          "--export_attribute",
> +          invoker.export_class_attribute,
> +          "--export_header",
> +          invoker.export_header,
> +        ]
> +      }
> +      if (!export_args_overridden && defined(invoker.component_macro_prefix)) {
> +        export_args += [
> +          "--export_attribute",
> +          "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
> +              "${bindings_configuration.component_macro_suffix})",
> +          "--export_header",
> +          "base/component_export.h",
> +        ]
> +      }
> +
>        generator_cpp_output_suffixes = []
>        variant_dash_suffix = ""
>        if (defined(variant)) {
> @@ -1198,7 +1307,6 @@ template("mojom") {
>        generator_cpp_output_suffixes += [
>          "${variant_dash_suffix}-forward.h",
>          "${variant_dash_suffix}-import-headers.h",
> -        "${variant_dash_suffix}-test-utils.cc",
>          "${variant_dash_suffix}-test-utils.h",
>          "${variant_dash_suffix}.cc",
>          "${variant_dash_suffix}.h",
> @@ -1207,16 +1315,28 @@ template("mojom") {
>        generator_target_name = "${target_name}${variant_suffix}__generator"
>
>        # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -      python2_action(generator_target_name) {
> +      action(generator_target_name) {
> +        allow_remote = true
>          visibility = [ ":*" ]
>          script = mojom_generator_script
>          inputs = mojom_generator_sources + jinja2_sources
> -        sources = sources_list
> +        sources =
> +            sources_list + [
> +              "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
> +              type_mappings_path,
> +            ]
> +        if (generate_mojolpm_fuzzing &&
> +            !defined(bindings_configuration.variant)) {
> +          sources += [
> +            "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
> +          ]
> +        }
>          deps = [
>            ":$parser_target_name",
>            ":$type_mappings_target_name",
>            "//mojo/public/tools/bindings:precompile_templates",
>          ]
> +
>          if (defined(invoker.parser_deps)) {
>            deps += invoker.parser_deps
>          }
> @@ -1224,18 +1344,22 @@ template("mojom") {
>          args = common_generator_args + export_args
>          filelist = []
>          foreach(source, sources_list) {
> -          filelist += [ rebase_path("$source", root_build_dir) ]
> +          filelist += [ rebase_path(source, root_build_dir) ]
>          }
>          foreach(base_path, output_file_base_paths) {
> +          filename = get_path_info(base_path, "file")
> +          dirname = get_path_info(base_path, "dir")
> +          inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
> +
>            outputs += [
>              "$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h",
>              "$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h",
> -            "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc",
>              "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h",
>              "$root_gen_dir/${base_path}${variant_dash_suffix}.cc",
>              "$root_gen_dir/${base_path}${variant_dash_suffix}.h",
>            ]
> -          if (generate_fuzzing && !defined(bindings_configuration.variant)) {
> +          if (generate_mojolpm_fuzzing &&
> +              !defined(bindings_configuration.variant)) {
>              outputs += [
>                "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc",
>                "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h",
> @@ -1243,14 +1367,17 @@ template("mojom") {
>            }
>          }
>
> -        response_file_contents = filelist
> -
> +        # Workaround for https://github.com/ninja-build/ninja/issues/1966.
> +        rsp_file = "$target_gen_dir/${target_name}.rsp"
> +        write_file(rsp_file, filelist)
> +        inputs += [ rsp_file ]
>          args += [
> -          "--filelist={{response_file_name}}",
> +          "--filelist=" + rebase_path("$rsp_file", root_build_dir),
>            "-g",
>          ]
>
> -        if (generate_fuzzing && !defined(bindings_configuration.variant)) {
> +        if (generate_mojolpm_fuzzing &&
> +            !defined(bindings_configuration.variant)) {
>            args += [ "c++,mojolpm" ]
>          } else {
>            args += [ "c++" ]
> @@ -1294,6 +1421,8 @@ template("mojom") {
>                "--extra_cpp_template_paths",
>                rebase_path(extra_cpp_template, root_build_dir),
>              ]
> +            inputs += [ extra_cpp_template ]
> +
>              assert(
>                  get_path_info(extra_cpp_template, "extension") == "tmpl",
>                  "--extra_cpp_template_paths only accepts template files ending in extension .tmpl")
> @@ -1306,62 +1435,6 @@ template("mojom") {
>        }
>      }
>
> -    if (generate_fuzzing && !defined(variant)) {
> -      # This block contains the C++ targets for the MojoLPM fuzzer, we need to
> -      # do this here so that we can use the typemap configuration for the
> -      # empty-variant Mojo target.
> -
> -      mojolpm_target_name = "${target_name}_mojolpm"
> -      mojolpm_generator_target_name = "${target_name}__generator"
> -      source_set(mojolpm_target_name) {
> -        # There are still a few missing header dependencies between mojo targets
> -        # with typemaps and the dependencies of their typemap headers. It would
> -        # be good to enable include checking for these in the future though.
> -        check_includes = false
> -        testonly = true
> -        if (defined(invoker.sources)) {
> -          sources = process_file_template(
> -                  invoker.sources,
> -                  [
> -                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
> -                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
> -                  ])
> -          deps = []
> -        } else {
> -          sources = []
> -          deps = []
> -        }
> -
> -        public_deps = [
> -          ":$generator_shared_target_name",
> -
> -          # NB: hardcoded dependency on the no-variant variant generator, since
> -          # mojolpm only uses the no-variant type.
> -          ":$mojolpm_generator_target_name",
> -          ":$mojolpm_proto_target_name",
> -          "//base",
> -          "//mojo/public/tools/fuzzers:mojolpm",
> -        ]
> -
> -        foreach(d, all_deps) {
> -          # Resolve the name, so that a target //mojo/something becomes
> -          # //mojo/something:something and we can append variant_suffix to
> -          # get the cpp dependency name.
> -          full_name = get_label_info("$d", "label_no_toolchain")
> -          public_deps += [ "${full_name}_mojolpm" ]
> -        }
> -
> -        foreach(config, cpp_typemap_configs) {
> -          if (defined(config.traits_deps)) {
> -            deps += config.traits_deps
> -          }
> -          if (defined(config.traits_public_deps)) {
> -            public_deps += config.traits_public_deps
> -          }
> -        }
> -      }
> -    }
> -
>      # Write the typemapping configuration for this target out to a file to be
>      # validated by a Python script. This helps catch mistakes that can't
>      # be caught by logic in GN.
> @@ -1389,20 +1462,20 @@ template("mojom") {
>      write_file(_typemap_config_filename, _rebased_typemap_configs, "json")
>      _mojom_target_name = target_name
>
> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -    python2_action(_typemap_validator_target_name) {
> +    action(_typemap_validator_target_name) {
> +      allow_remote = true
>        script = "$mojom_generator_root/validate_typemap_config.py"
>        inputs = [ _typemap_config_filename ]
>        outputs = [ _typemap_stamp_filename ]
>        args = [
>          get_label_info(_mojom_target_name, "label_no_toolchain"),
> -        rebase_path(_typemap_config_filename),
> -        rebase_path(_typemap_stamp_filename),
> +        rebase_path(_typemap_config_filename, root_build_dir),
> +        rebase_path(_typemap_stamp_filename, root_build_dir),
>        ]
>      }
>
> -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -    python2_action(type_mappings_target_name) {
> +    action(type_mappings_target_name) {
> +      allow_remote = true
>        inputs =
>            mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ]
>        outputs = [ type_mappings_path ]
> @@ -1413,6 +1486,7 @@ template("mojom") {
>          rebase_path(type_mappings_path, root_build_dir),
>        ]
>
> +      sources = []
>        foreach(d, all_deps) {
>          name = get_label_info(d, "label_no_toolchain")
>          toolchain = get_label_info(d, "toolchain")
> @@ -1422,12 +1496,11 @@ template("mojom") {
>          dependency_output_dir =
>              get_label_info(dependency_output, "target_gen_dir")
>          dependency_name = get_label_info(dependency_output, "name")
> -        dependency_path =
> -            rebase_path("$dependency_output_dir/${dependency_name}",
> -                        root_build_dir)
> +        dependency_path = "$dependency_output_dir/${dependency_name}"
> +        sources += [ dependency_path ]
>          args += [
>            "--dependency",
> -          dependency_path,
> +          rebase_path(dependency_path, root_build_dir),
>          ]
>        }
>
> @@ -1485,11 +1558,15 @@ template("mojom") {
>        if (defined(output_name_override)) {
>          output_name = output_name_override
>        }
> -      visibility = output_visibility + [ ":$output_target_name" ]
> +      visibility = output_visibility + [
> +                     ":$output_target_name",
> +                     ":${target_name}_mojolpm",
> +                   ]
>        if (defined(invoker.testonly)) {
>          testonly = invoker.testonly
>        }
>        defines = export_defines
> +      configs += [ "//build/config/compiler:wexit_time_destructors" ]
>        configs += extra_configs
>        if (output_file_base_paths != []) {
>          sources = []
> @@ -1578,13 +1655,81 @@ template("mojom") {
>        }
>      }
>
> +    if (generate_mojolpm_fuzzing && !defined(variant)) {
> +      # This block contains the C++ targets for the MojoLPM fuzzer, we need to
> +      # do this here so that we can use the typemap configuration for the
> +      # empty-variant Mojo target.
> +
> +      mojolpm_target_name = "${target_name}_mojolpm"
> +      mojolpm_generator_target_name = "${target_name}__generator"
> +      source_set(mojolpm_target_name) {
> +        # There are still a few missing header dependencies between mojo targets
> +        # with typemaps and the dependencies of their typemap headers. It would
> +        # be good to enable include checking for these in the future though.
> +        check_includes = false
> +        testonly = true
> +        if (defined(invoker.sources)) {
> +          # Split the input into generated and non-generated source files. They
> +          # need to be processed separately.
> +          gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
> +          non_gen_sources =
> +              filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
> +          gen_sources =
> +              filter_include(invoker.sources, [ gen_dir_path_wildcard ])
> +          sources = process_file_template(
> +                  non_gen_sources,
> +                  [
> +                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
> +                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
> +                  ])
> +          sources += process_file_template(
> +                  gen_sources,
> +                  [
> +                    "{{source_dir}}/{{source_file_part}}-mojolpm.cc",
> +                    "{{source_dir}}/{{source_file_part}}-mojolpm.h",
> +                  ])
> +          deps = [ ":$output_target_name" ]
> +        } else {
> +          sources = []
> +          deps = []
> +        }
> +
> +        public_deps = [
> +          ":$generator_shared_target_name",
> +
> +          # NB: hardcoded dependency on the no-variant variant generator, since
> +          # mojolpm only uses the no-variant type.
> +          ":$mojolpm_generator_target_name",
> +          ":$mojolpm_proto_target_name",
> +          "//base",
> +          "//mojo/public/tools/fuzzers:mojolpm",
> +        ]
> +
> +        foreach(d, all_deps) {
> +          # Resolve the name, so that a target //mojo/something becomes
> +          # //mojo/something:something and we can append variant_suffix to
> +          # get the cpp dependency name.
> +          full_name = get_label_info("$d", "label_no_toolchain")
> +          public_deps += [ "${full_name}_mojolpm" ]
> +        }
> +
> +        foreach(config, cpp_typemap_configs) {
> +          if (defined(config.traits_deps)) {
> +            deps += config.traits_deps
> +          }
> +          if (defined(config.traits_public_deps)) {
> +            public_deps += config.traits_public_deps
> +          }
> +        }
> +      }
> +    }
> +
>      if (generate_java && is_android) {
>        import("//build/config/android/rules.gni")
>
>        java_generator_target_name = target_name + "_java__generator"
>        if (sources_list != []) {
> -        # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -        python2_action(java_generator_target_name) {
> +        action(java_generator_target_name) {
>            script = mojom_generator_script
>            inputs = mojom_generator_sources + jinja2_sources
>            sources = sources_list
> @@ -1597,7 +1742,7 @@ template("mojom") {
>            args = common_generator_args
>            filelist = []
>            foreach(source, sources_list) {
> -            filelist += [ rebase_path("$source", root_build_dir) ]
> +            filelist += [ rebase_path(source, root_build_dir) ]
>            }
>            foreach(base_path, output_file_base_paths) {
>              outputs += [ "$root_gen_dir/$base_path.srcjar" ]
> @@ -1624,8 +1769,7 @@ template("mojom") {
>
>        java_srcjar_target_name = target_name + "_java_sources"
>
> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -      python2_action(java_srcjar_target_name) {
> +      action(java_srcjar_target_name) {
>          script = "//build/android/gyp/zip.py"
>          inputs = []
>          if (output_file_base_paths != []) {
> @@ -1651,7 +1795,6 @@ template("mojom") {
>        android_library(java_target_name) {
>          forward_variables_from(invoker, [ "enable_bytecode_checks" ])
>          deps = [
> -          "//base:base_java",
>            "//mojo/public/java:bindings_java",
>            "//mojo/public/java:system_java",
>            "//third_party/androidx:androidx_annotation_annotation_java",
> @@ -1673,21 +1816,103 @@ template("mojom") {
>      }
>    }
>
> +  if (defined(invoker.webui_js_bridge_config)) {
> +    if (sources_list != []) {
> +      bridge_config = invoker.webui_js_bridge_config
> +
> +      generator_webui_js_bridge_impl_target_name =
> +          "${target_name}_webui_js_bridge_impl__generator"
> +      action(generator_webui_js_bridge_impl_target_name) {
> +        visibility = [ ":*" ]
> +        script = mojom_generator_script
> +        inputs = mojom_generator_sources + jinja2_sources
> +        sources = sources_list
> +
> +        deps = [
> +          ":$parser_target_name",
> +          "//mojo/public/tools/bindings:precompile_templates",
> +        ]
> +
> +        filelist = []
> +        foreach(source, sources_list) {
> +          filelist += [ rebase_path(source, root_build_dir) ]
> +        }
> +
> +        outputs = []
> +        foreach(base_path, output_file_base_paths) {
> +          outputs += [
> +            "$root_gen_dir/${base_path}-webui-js-bridge-impl.cc",
> +            "$root_gen_dir/${base_path}-webui-js-bridge-impl.h",
> +          ]
> +        }
> +
> +        response_file_contents = filelist
> +
> +        args = common_generator_args + export_args
> +        args += [
> +          "--filelist={{response_file_name}}",
> +          "-g",
> +          "webui_js_bridge",
> +        ]
> +
> +        rebased_webui_controller_header =
> +            rebase_path(bridge_config.webui_controller_header, "//")
> +
> +        # Use a single string for the argument to avoid it being parsed as a
> +        # filename by the mojom_bindings_generator.py argument parser.
> +        config_arg =
> +            "--webui_js_bridge_config=" + bridge_config.webui_controller + "=" +
> +            rebased_webui_controller_header
> +
> +        args += [ config_arg ]
> +      }
> +
> +      cpp_target_name = target_name
> +      source_set("${target_name}_webui_js_bridge_impl") {
> +        if (defined(invoker.testonly)) {
> +          testonly = invoker.testonly
> +        }
> +
> +        sources = []
> +        foreach(base_path, output_file_base_paths) {
> +          sources += [
> +            "$root_gen_dir/${base_path}-webui-js-bridge-impl.cc",
> +            "$root_gen_dir/${base_path}-webui-js-bridge-impl.h",
> +          ]
> +        }
> +        deps = [
> +          ":$cpp_target_name",
> +          ":$generator_webui_js_bridge_impl_target_name",
> +        ]
> +
> +        deps += bridge_config.webui_controller_deps
> +      }
> +    }
> +  }
> +
>    use_typescript_for_target =
> -      enable_typescript_bindings && defined(invoker.use_typescript_sources) &&
> -      invoker.use_typescript_sources
> +      defined(invoker.use_typescript_sources) &&
> +      invoker.use_typescript_sources && defined(invoker.webui_module_path)
>
>    if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) {
>      not_needed(invoker, [ "use_typescript_sources" ])
>    }
>
> -  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&
> -      !use_typescript_for_target) {
> +  generate_legacy_js = !use_typescript_for_target ||
> +                       (defined(invoker.generate_legacy_js_bindings) &&
> +                        invoker.generate_legacy_js_bindings)
> +
> +  # Targets needed by both TS and JS bindings targets. These are needed
> +  # unconditionally for JS bindings targets, and are needed for TS bindings
> +  # targets when generate_legacy_js_bindings is true. This option is provided
> +  # since the legacy bindings are needed by Blink tests and non-Chromium users,
> +  # which are not expected to migrate to modules or TypeScript.
> +  if (generate_legacy_js && (generate_js_fuzzing ||
> +                             !defined(invoker.cpp_only) || !invoker.cpp_only)) {
>      if (sources_list != []) {
>        generator_js_target_name = "${target_name}_js__generator"
>
> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -      python2_action(generator_js_target_name) {
> +      action(generator_js_target_name) {
>          script = mojom_generator_script
>          inputs = mojom_generator_sources + jinja2_sources
>          sources = sources_list
> @@ -1702,19 +1927,18 @@ template("mojom") {
>          args = common_generator_args
>          filelist = []
>          foreach(source, sources_list) {
> -          filelist += [ rebase_path("$source", root_build_dir) ]
> +          filelist += [ rebase_path(source, root_build_dir) ]
>          }
>          foreach(base_path, output_file_base_paths) {
>            outputs += [
>              "$root_gen_dir/$base_path.js",
> -            "$root_gen_dir/$base_path.externs.js",
>              "$root_gen_dir/$base_path.m.js",
>              "$root_gen_dir/$base_path-lite.js",
> -            "$root_gen_dir/$base_path.html",
>              "$root_gen_dir/$base_path-lite-for-compile.js",
>            ]
>
> -          if (defined(invoker.webui_module_path)) {
> +          if (defined(invoker.webui_module_path) &&
> +              !use_typescript_for_target) {
>              outputs += [ "$root_gen_dir/mojom-webui/$base_path-webui.js" ]
>            }
>          }
> @@ -1725,7 +1949,6 @@ template("mojom") {
>            "--filelist={{response_file_name}}",
>            "-g",
>            "javascript",
> -          "--js_bindings_mode=new",
>          ]
>
>          if (defined(invoker.js_generate_struct_deserializers) &&
> @@ -1739,7 +1962,7 @@ template("mojom") {
>            args += message_scrambling_args
>          }
>
> -        if (generate_fuzzing) {
> +        if (generate_js_fuzzing) {
>            args += [ "--generate_fuzzing" ]
>          }
>        }
> @@ -1783,31 +2006,13 @@ template("mojom") {
>          data_deps += [ "${full_name}_js_data_deps" ]
>        }
>      }
> +  }
>
> -    js_library_target_name = "${target_name}_js_library"
> -    if (sources_list != []) {
> -      js_library(js_library_target_name) {
> -        extra_public_deps = [ ":$generator_js_target_name" ]
> -        sources = []
> -        foreach(base_path, output_file_base_paths) {
> -          sources += [ "$root_gen_dir/${base_path}-lite.js" ]
> -        }
> -        externs_list = [
> -          "${externs_path}/mojo_core.js",
> -          "${externs_path}/pending.js",
> -        ]
> -
> -        deps = []
> -        foreach(d, all_deps) {
> -          full_name = get_label_info(d, "label_no_toolchain")
> -          deps += [ "${full_name}_js_library" ]
> -        }
> -      }
> -    } else {
> -      group(js_library_target_name) {
> -      }
> -    }
> -
> +  # js_library() closure compiler targets, primarily used on ChromeOS. Only
> +  # generate these targets if the mojom target is not C++ only and is not using
> +  # TypeScript.
> +  if (generate_mojom_closure_libraries &&
> +      (!defined(invoker.cpp_only) || !invoker.cpp_only) && generate_legacy_js) {
>      js_library_for_compile_target_name = "${target_name}_js_library_for_compile"
>      if (sources_list != []) {
>        js_library(js_library_for_compile_target_name) {
> @@ -1834,35 +2039,9 @@ template("mojom") {
>        }
>      }
>
> -    js_modules_target_name = "${target_name}_js_modules"
> -    if (sources_list != []) {
> -      js_library(js_modules_target_name) {
> -        extra_public_deps = [ ":$generator_js_target_name" ]
> -        sources = []
> -        foreach(base_path, output_file_base_paths) {
> -          sources += [ "$root_gen_dir/${base_path}.m.js" ]
> -        }
> -        externs_list = [
> -          "${externs_path}/mojo_core.js",
> -          "${externs_path}/pending.js",
> -        ]
> -        if (defined(invoker.disallow_native_types) &&
> -            invoker.disallow_native_types) {
> -          deps = []
> -        } else {
> -          deps = [ "//mojo/public/js:bindings_uncompiled" ]
> -        }
> -        foreach(d, all_deps) {
> -          full_name = get_label_info(d, "label_no_toolchain")
> -          deps += [ "${full_name}_js_modules" ]
> -        }
> -      }
> -    } else {
> -      group(js_modules_target_name) {
> -      }
> -    }
> -
> -    if (defined(invoker.webui_module_path)) {
> +    # WebUI specific closure targets, not needed by targets that are generating
> +    # TypeScript WebUI bindings or by legacy-only targets.
> +    if (defined(invoker.webui_module_path) && !use_typescript_for_target) {
>        webui_js_target_name = "${target_name}_webui_js"
>        if (sources_list != []) {
>          js_library(webui_js_target_name) {
> @@ -1890,46 +2069,38 @@ template("mojom") {
>          group(webui_js_target_name) {
>          }
>        }
> -    }
> -  }
> -  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&
> -      use_typescript_for_target) {
> -    generator_js_target_names = []
> -    source_filelist = []
> -    foreach(source, sources_list) {
> -      source_filelist += [ rebase_path("$source", root_build_dir) ]
> -    }
>
> -    dependency_types = [
> -      {
> -        name = "regular"
> -        ts_extension = ".ts"
> -        js_extension = ".js"
> -      },
> -      {
> -        name = "es_modules"
> -        ts_extension = ".m.ts"
> -        js_extension = ".m.js"
> -      },
> -    ]
> +      webui_grdp_target_name = "${target_name}_webui_grdp"
> +      out_grd = "$target_gen_dir/${target_name}_webui_resources.grdp"
> +      grd_prefix = "${target_name}_webui"
> +      generate_grd(webui_grdp_target_name) {
> +        grd_prefix = grd_prefix
> +        out_grd = out_grd
>
> -    foreach(dependency_type, dependency_types) {
> -      ts_outputs = []
> -      js_outputs = []
> +        deps = [ ":$webui_js_target_name" ]
>
> -      foreach(base_path, output_file_base_paths) {
> -        ts_outputs +=
> -            [ "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}" ]
> -        js_outputs +=
> -            [ "$root_gen_dir/$base_path-lite${dependency_type.js_extension}" ]
> +        input_files = []
> +        foreach(base_path, output_file_base_paths) {
> +          input_files += [ "${base_path}-webui.js" ]
> +        }
> +
> +        input_files_base_dir =
> +            rebase_path("$root_gen_dir/mojom-webui", "$root_build_dir")
> +      }
> +    }
> +  }
> +  if ((generate_js_fuzzing || !defined(invoker.cpp_only) ||
> +       !invoker.cpp_only) && use_typescript_for_target) {
> +    if (sources_list != []) {
> +      source_filelist = []
> +      foreach(source, sources_list) {
> +        source_filelist += [ rebase_path(source, root_build_dir) ]
>        }
>
>        # Generate Typescript bindings.
> -      generator_ts_target_name =
> -          "${target_name}_${dependency_type.name}__ts__generator"
> +      generator_ts_target_name = "${target_name}_ts__generator"
>
> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -      python2_action(generator_ts_target_name) {
> +      action(generator_ts_target_name) {
>          script = mojom_generator_script
>          inputs = mojom_generator_sources + jinja2_sources
>          sources = sources_list
> @@ -1938,7 +2109,10 @@ template("mojom") {
>            "//mojo/public/tools/bindings:precompile_templates",
>          ]
>
> -        outputs = ts_outputs
> +        outputs = []
> +        foreach(base_path, output_file_base_paths) {
> +          outputs += [ "$root_gen_dir/$base_path-webui.ts" ]
> +        }
>          args = common_generator_args
>          response_file_contents = source_filelist
>
> @@ -1948,98 +2122,16 @@ template("mojom") {
>            "typescript",
>          ]
>
> -        if (dependency_type.name == "es_modules") {
> -          args += [ "--ts_use_es_modules" ]
> +        if (!defined(invoker.scramble_message_ids) ||
> +            invoker.scramble_message_ids) {
> +          inputs += message_scrambling_inputs
> +          args += message_scrambling_args
>          }
>
> -        # TODO(crbug.com/1007587): Support scramble_message_ids.
> +        # TODO(crbug.com/1007587): Support scramble_message_ids if above is
> +        # insufficient.
>          # TODO(crbug.com/1007591): Support generate_fuzzing.
>        }
> -
> -      # Create tsconfig.json for the generated Typescript.
> -      tsconfig_filename =
> -          "$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json"
> -      tsconfig = {
> -      }
> -      tsconfig.compilerOptions = {
> -        composite = true
> -        target = "es6"
> -        module = "es6"
> -        lib = [
> -          "es6",
> -          "esnext.bigint",
> -        ]
> -        strict = true
> -      }
> -      tsconfig.files = []
> -      foreach(base_path, output_file_base_paths) {
> -        tsconfig.files += [ rebase_path(
> -                "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}",
> -                target_gen_dir,
> -                root_gen_dir) ]
> -      }
> -      tsconfig.references = []
> -
> -      # Get tsconfigs for deps.
> -      foreach(d, all_deps) {
> -        dep_target_gen_dir = rebase_path(get_label_info(d, "target_gen_dir"))
> -        dep_name = get_label_info(d, "name")
> -        reference = {
> -        }
> -        reference.path = "$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json"
> -        tsconfig.references += [ reference ]
> -      }
> -      write_file(tsconfig_filename, tsconfig, "json")
> -
> -      # Compile previously generated Typescript to Javascript.
> -      generator_js_target_name =
> -          "${target_name}_${dependency_type.name}__js__generator"
> -      generator_js_target_names += [ generator_js_target_name ]
> -
> -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> -      python2_action(generator_js_target_name) {
> -        script = "$mojom_generator_root/compile_typescript.py"
> -        sources = ts_outputs
> -        outputs = js_outputs
> -        public_deps = [ ":$generator_ts_target_name" ]
> -        foreach(d, all_deps) {
> -          full_name = get_label_info(d, "label_no_toolchain")
> -          public_deps +=
> -              [ "${full_name}_${dependency_type.name}__js__generator" ]
> -        }
> -
> -        absolute_tsconfig_path =
> -            rebase_path(tsconfig_filename, "", target_gen_dir)
> -        args = [ "--tsconfig_path=$absolute_tsconfig_path" ]
> -      }
> -    }
> -
> -    js_target_name = target_name + "_js"
> -    group(js_target_name) {
> -      public_deps = []
> -      if (sources_list != []) {
> -        foreach(generator_js_target_name, generator_js_target_names) {
> -          public_deps += [ ":$generator_js_target_name" ]
> -        }
> -      }
> -
> -      foreach(d, all_deps) {
> -        full_name = get_label_info(d, "label_no_toolchain")
> -        public_deps += [ "${full_name}_js" ]
> -      }
> -    }
> -
> -    group(js_data_deps_target_name) {
> -      data = js_outputs
> -      deps = []
> -      foreach(generator_js_target_name, generator_js_target_names) {
> -        deps += [ ":$generator_js_target_name" ]
> -      }
> -      data_deps = []
> -      foreach(d, all_deps) {
> -        full_name = get_label_info(d, "label_no_toolchain")
> -        data_deps += [ "${full_name}_js_data_deps" ]
> -      }
>      }
>    }
>  }
> @@ -2063,3 +2155,53 @@ template("mojom_component") {
>      component_macro_prefix = invoker.macro_prefix
>    }
>  }
> +
> +# A helper for the mojom() template above for defining a WebUIJsBridge.
> +# Supports all the same arguments as mojom() except for `sources` and adds
> +# `source`, `webui_controller`, `webui_controller_header`, and
> +# `webui_controller_deps` which are *required*. See below for more details.
> +#
> +# A WebUIJsBridge is a Mojo interface that contains binders for all interfaces
> +# that can be bound from a WebUI's JavaScript. This taret
> +# will generate a C++ implementation of the WebUIJsBridge interface
> +# and a {target_name}_webui_js_bridge_impl target that can be depended
> +# on.
> +#
> +# The following arguments are required:
> +#  - `source`: The mojom file that defines the WebUIJsBridge interface. It's
> +#    allowed to define other interfaces.
> +#  - `webui_controller`: The name of the WebUIController associated with the
> +#    WebUIJsBridge.
> +#  - `webui_controller_header`: The header where the WebUIController
> +#    is declared.
> +#  - `webui_controller_deps`: The target containing the WebUIController.
> +template("mojom_with_webui_js_bridge") {
> +  assert(defined(invoker.source))
> +  assert(defined(invoker.webui_controller),
> +         "mojom_with_webui_js_bridge should have a " +
> +             "`webui_controller` parameter e.g. `foo::FooUI`.")
> +  assert(defined(invoker.webui_controller_header),
> +         "mojom_with_webui_js_bridge should have a " +
> +             "`webui_controller_header` parameter.")
> +  assert(defined(invoker.webui_controller_deps),
> +         "mojom_with_webui_js_bridge should have a " +
> +             "`webui_controller_deps` parameter.")
> +
> +  mojom(target_name) {
> +    forward_variables_from(invoker,
> +                           "*",
> +                           [
> +                             "source",
> +                             "webui_controller",
> +                             "webui_controller_header",
> +                             "webui_controller_deps",
> +                           ])
> +    sources = [ invoker.source ]
> +
> +    webui_js_bridge_config = {
> +      webui_controller = invoker.webui_controller
> +      webui_controller_header = invoker.webui_controller_header
> +      webui_controller_deps = invoker.webui_controller_deps
> +    }
> +  }
> +}
> diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> index da9efc71..98cac149 100755
> --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> @@ -1,5 +1,5 @@
>  #!/usr/bin/env python
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# Copyright 2013 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -51,16 +51,23 @@ import crbug_1001171
>
>  _BUILTIN_GENERATORS = {
>      "c++": "mojom_cpp_generator",
> +    "webui_js_bridge": "mojom_webui_js_bridge_generator",
>      "javascript": "mojom_js_generator",
>      "java": "mojom_java_generator",
>      "mojolpm": "mojom_mojolpm_generator",
>      "typescript": "mojom_ts_generator",
>  }
>
> +_BUILTIN_CHECKS = {
> +    "attributes": "mojom_attributes_check",
> +    "definitions": "mojom_definitions_check",
> +    "restrictions": "mojom_restrictions_check",
> +}
> +
>
>  def LoadGenerators(generators_string):
>    if not generators_string:
> -    return []  # No generators.
> +    return {}  # No generators.
>
>    generators = {}
>    for generator_name in [s.strip() for s in generators_string.split(",")]:
> @@ -74,6 +81,21 @@ def LoadGenerators(generators_string):
>    return generators
>
>
> +def LoadChecks(checks_string):
> +  if not checks_string:
> +    return {}  # No checks.
> +
> +  checks = {}
> +  for check_name in [s.strip() for s in checks_string.split(",")]:
> +    check = check_name.lower()
> +    if check not in _BUILTIN_CHECKS:
> +      print("Unknown check name %s" % check_name)
> +      sys.exit(1)
> +    check_module = importlib.import_module("checks.%s" % _BUILTIN_CHECKS[check])
> +    checks[check] = check_module
> +  return checks
> +
> +
>  def MakeImportStackMessage(imported_filename_stack):
>    """Make a (human-readable) message listing a chain of imports. (Returned
>    string begins with a newline (if nonempty) and does not end with one.)"""
> @@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack):
>                      zip(imported_filename_stack[1:], imported_filename_stack)]))
>
>
> -class RelativePath(object):
> +class RelativePath:
>    """Represents a path relative to the source tree or generated output dir."""
>
>    def __init__(self, path, source_root, output_dir):
> @@ -142,7 +164,7 @@ def ReadFileContents(filename):
>      return f.read()
>
>
> -class MojomProcessor(object):
> +class MojomProcessor:
>    """Takes parsed mojom modules and generates language bindings from them.
>
>    Attributes:
> @@ -169,8 +191,8 @@ class MojomProcessor(object):
>      if 'c++' in self._typemap:
>        self._typemap['mojolpm'] = self._typemap['c++']
>
> -  def _GenerateModule(self, args, remaining_args, generator_modules,
> -                      rel_filename, imported_filename_stack):
> +  def _GenerateModule(self, args, remaining_args, check_modules,
> +                      generator_modules, rel_filename, imported_filename_stack):
>      # Return the already-generated module.
>      if rel_filename.path in self._processed_files:
>        return self._processed_files[rel_filename.path]
> @@ -190,12 +212,16 @@ class MojomProcessor(object):
>        ScrambleMethodOrdinals(module.interfaces, salt)
>
>      if self._should_generate(rel_filename.path):
> +      # Run checks on module first.
> +      for check_module in check_modules.values():
> +        checker = check_module.Check(module)
> +        checker.CheckModule()
> +      # Then run generation.
>        for language, generator_module in generator_modules.items():
>          generator = generator_module.Generator(
>              module, args.output_dir, typemap=self._typemap.get(language, {}),
>              variant=args.variant, bytecode_path=args.bytecode_path,
>              for_blink=args.for_blink,
> -            js_bindings_mode=args.js_bindings_mode,
>              js_generate_struct_deserializers=\
>                      args.js_generate_struct_deserializers,
>              export_attribute=args.export_attribute,
> @@ -234,6 +260,7 @@ def _Generate(args, remaining_args):
>        args.import_directories[idx] = RelativePath(tokens[0], args.depth,
>                                                    args.output_dir)
>    generator_modules = LoadGenerators(args.generators_string)
> +  check_modules = LoadChecks(args.checks_string)
>
>    fileutil.EnsureDirectoryExists(args.output_dir)
>
> @@ -246,7 +273,7 @@ def _Generate(args, remaining_args):
>
>    for filename in args.filename:
>      processor._GenerateModule(
> -        args, remaining_args, generator_modules,
> +        args, remaining_args, check_modules, generator_modules,
>          RelativePath(filename, args.depth, args.output_dir), [])
>
>    return 0
> @@ -286,6 +313,12 @@ def main():
>                                 metavar="GENERATORS",
>                                 default="c++,javascript,java,mojolpm",
>                                 help="comma-separated list of generators")
> +  generate_parser.add_argument("-c",
> +                               "--checks",
> +                               dest="checks_string",
> +                               metavar="CHECKS",
> +                               default="attributes,definitions,restrictions",
> +                               help="comma-separated list of checks")
>    generate_parser.add_argument(
>        "--gen_dir", dest="gen_directories", action="append", metavar="directory",
>        default=[], help="add a directory to be searched for the syntax trees.")
> @@ -308,11 +341,6 @@ def main():
>    generate_parser.add_argument("--for_blink", action="store_true",
>                                 help="Use WTF types as generated types for mojo "
>                                 "string/array/map.")
> -  generate_parser.add_argument(
> -      "--js_bindings_mode", choices=["new", "old"], default="old",
> -      help="This option only affects the JavaScript bindings. The value could "
> -      "be \"new\" to generate new-style lite JS bindings in addition to the "
> -      "old, or \"old\" to only generate old bindings.")
>    generate_parser.add_argument(
>        "--js_generate_struct_deserializers", action="store_true",
>        help="Generate javascript deserialize methods for structs in "
> @@ -387,4 +415,10 @@ def main():
>
>  if __name__ == "__main__":
>    with crbug_1001171.DumpStateOnLookupError():
> -    sys.exit(main())
> +    ret = main()
> +    # Exit without running GC, which can save multiple seconds due to the large
> +    # number of object created. But flush is necessary as os._exit doesn't do
> +    # that.
> +    sys.stdout.flush()
> +    sys.stderr.flush()
> +    os._exit(ret)
> 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
> index bddbe3f4..761922b6 100644
> --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
> +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -8,13 +8,13 @@ from mojom_bindings_generator import MakeImportStackMessage
>  from mojom_bindings_generator import ScrambleMethodOrdinals
>
>
> -class FakeIface(object):
> +class FakeIface:
>    def __init__(self):
>      self.mojom_name = None
>      self.methods = None
>
>
> -class FakeMethod(object):
> +class FakeMethod:
>    def __init__(self, explicit_ordinal=None):
>      self.explicit_ordinal = explicit_ordinal
>      self.ordinal = explicit_ordinal
> diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> index f1783d59..1d940a21 100755
> --- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> +++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> @@ -1,5 +1,5 @@
>  #!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -17,7 +17,7 @@ def CheckCppTypemapConfigs(target_name, config_filename, out_filename):
>    ])
>    _SUPPORTED_TYPE_KEYS = set([
>        'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',
> -      'move_only', 'nullable_is_same_type'
> +      'move_only', 'nullable_is_same_type', 'forward_declaration'
>    ])
>    with open(config_filename, 'r') as f:
>      for config in json.load(f):
> diff --git a/utils/ipc/mojo/public/tools/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
> new file mode 100644
> index 00000000..4e456c0e
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
> @@ -0,0 +1,17 @@
> +# Copyright 2022 The Chromium Authors
> +# Use of this source code is governed by a BSD-style license that can be
> +# found in the LICENSE file.
> +
> +group("tests") {
> +  data = [
> +    "check_stable_mojom_compatibility_unittest.py",
> +    "check_stable_mojom_compatibility.py",
> +    "const_unittest.py",
> +    "enum_unittest.py",
> +    "mojom_parser_test_case.py",
> +    "mojom_parser_unittest.py",
> +    "mojom_parser.py",
> +    "stable_attribute_unittest.py",
> +    "version_compatibility_unittest.py",
> +  ]
> +}
> 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
> index 08bd672f..3c1ac42c 100755
> --- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
> +++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
> @@ -1,5 +1,5 @@
> -#!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +#!/usr/bin/env python3
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  """Verifies backward-compatibility of mojom type changes.
> @@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to prevent developers from making
>  breaking changes to stable mojoms."""
>
>  import argparse
> -import errno
>  import io
>  import json
>  import os
>  import os.path
> -import shutil
> -import six
>  import sys
> -import tempfile
>
>  from mojom.generate import module
>  from mojom.generate import translate
>  from mojom.parse import parser
>
> +# pylint: disable=raise-missing-from
> +
>
>  class ParseError(Exception):
>    pass
> @@ -41,6 +39,8 @@ def _ValidateDelta(root, delta):
>    transitive closure of a mojom's input dependencies all at once.
>    """
>
> +  translate.is_running_backwards_compatibility_check_hack = True
> +
>    # First build a map of all files covered by the delta
>    affected_files = set()
>    old_files = {}
> @@ -73,11 +73,33 @@ def _ValidateDelta(root, delta):
>      try:
>        ast = parser.Parse(contents, mojom)
>      except Exception as e:
> -      six.reraise(
> -          ParseError,
> -          'encountered exception {0} while parsing {1}'.format(e, mojom),
> -          sys.exc_info()[2])
> +      raise ParseError('encountered exception {0} while parsing {1}'.format(
> +          e, mojom))
> +
> +    # Files which are generated at compile time can't be checked by this script
> +    # (at the moment) since they may not exist in the output directory.
> +    generated_files_to_skip = {
> +        ('third_party/blink/public/mojom/runtime_feature_state/'
> +         'runtime_feature_state.mojom'),
> +    }
> +
> +    ast.import_list.items = [
> +        x for x in ast.import_list.items
> +        if x.import_filename not in generated_files_to_skip
> +    ]
> +
>      for imp in ast.import_list:
> +      if (not file_overrides.get(imp.import_filename)
> +          and not os.path.exists(os.path.join(root, imp.import_filename))):
> +        # Speculatively construct a path prefix to locate the import_filename
> +        mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep)
> +        test_prefix = ''
> +        for path_component in mojom_path:
> +          test_prefix = os.path.join(test_prefix, path_component)
> +          test_import_filename = os.path.join(test_prefix, imp.import_filename)
> +          if os.path.exists(os.path.join(root, test_import_filename)):
> +            imp.import_filename = test_import_filename
> +            break
>        parseMojom(imp.import_filename, file_overrides, override_modules)
>
>      # Now that the transitive set of dependencies has been imported and parsed
> @@ -89,10 +111,10 @@ def _ValidateDelta(root, delta):
>      modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)
>
>    old_modules = {}
> -  for mojom in old_files.keys():
> +  for mojom in old_files:
>      parseMojom(mojom, old_files, old_modules)
>    new_modules = {}
> -  for mojom in new_files.keys():
> +  for mojom in new_files:
>      parseMojom(mojom, new_files, new_modules)
>
>    # At this point we have a complete set of translated Modules from both the
> 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
> index 9f51ea77..06769c95 100755
> --- 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
> @@ -1,5 +1,5 @@
> -#!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +#!/usr/bin/env python3
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -15,7 +15,7 @@ import check_stable_mojom_compatibility
>  from mojom.generate import module
>
>
> -class Change(object):
> +class Change:
>    """Helper to clearly define a mojom file delta to be analyzed."""
>
>    def __init__(self, filename, old=None, new=None):
> @@ -28,7 +28,7 @@ class Change(object):
>
>  class UnchangedFile(Change):
>    def __init__(self, filename, contents):
> -    super(UnchangedFile, self).__init__(filename, old=contents, new=contents)
> +    super().__init__(filename, old=contents, new=contents)
>
>
>  class CheckStableMojomCompatibilityTest(unittest.TestCase):
> @@ -258,3 +258,82 @@ class CheckStableMojomCompatibilityTest(unittest.TestCase):
>                 [Stable] struct T { foo.S s; int32 x; };
>                 """)
>      ])
> +
> +  def testWithPartialImport(self):
> +    """The compatibility checking tool correctly parses imports with partial
> +    paths."""
> +    self.assertBackwardCompatible([
> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
> +        Change('foo/bar.mojom',
> +               old="""\
> +               module bar;
> +               import "foo/foo.mojom";
> +               [Stable] struct T { foo.S s; };
> +               """,
> +               new="""\
> +               module bar;
> +               import "foo.mojom";
> +               [Stable] struct T { foo.S s; };
> +               """)
> +    ])
> +
> +    self.assertBackwardCompatible([
> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
> +        Change('foo/bar.mojom',
> +               old="""\
> +               module bar;
> +               import "foo.mojom";
> +               [Stable] struct T { foo.S s; };
> +               """,
> +               new="""\
> +               module bar;
> +               import "foo/foo.mojom";
> +               [Stable] struct T { foo.S s; };
> +               """)
> +    ])
> +
> +    self.assertNotBackwardCompatible([
> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
> +        Change('bar/bar.mojom',
> +               old="""\
> +               module bar;
> +               import "foo/foo.mojom";
> +               [Stable] struct T { foo.S s; };
> +               """,
> +               new="""\
> +               module bar;
> +               import "foo.mojom";
> +               [Stable] struct T { foo.S s; };
> +               """)
> +    ])
> +
> +    self.assertNotBackwardCompatible([
> +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
> +        Change('bar/bar.mojom',
> +               old="""\
> +               module bar;
> +               import "foo.mojom";
> +               [Stable] struct T { foo.S s; };
> +               """,
> +               new="""\
> +               module bar;
> +               import "foo/foo.mojom";
> +               [Stable] struct T { foo.S s; };
> +               """)
> +    ])
> +
> +  def testNewEnumDefault(self):
> +    # Should be backwards compatible since it does not affect the wire format.
> +    # This specific case also checks that the backwards compatibility checker
> +    # does not throw an error due to the older version of the enum not
> +    # specifying [Default].
> +    self.assertBackwardCompatible([
> +        Change('foo/foo.mojom',
> +               old='[Extensible] enum E { One };',
> +               new='[Extensible] enum E { [Default] One };')
> +    ])
> +    self.assertBackwardCompatible([
> +        Change('foo/foo.mojom',
> +               old='[Extensible] enum E { [Default] One, Two, };',
> +               new='[Extensible] enum E { One, [Default] Two, };')
> +    ])
> diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> index cb42dfac..e8ed36a7 100644
> --- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> index d9005078..9269cde5 100644
> --- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase):
>      self.assertEqual('F', b.enums[0].mojom_name)
>      self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)
>      self.assertEqual(37, b.enums[0].fields[0].numeric_value)
> +
> +  def testEnumAttributesAreEnums(self):
> +    """Verifies that enum values in attributes are really enum types."""
> +    a_mojom = 'a.mojom'
> +    self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };')
> +    b_mojom = 'b.mojom'
> +    self.WriteFile(
> +        b_mojom, 'module b;'
> +        'import "a.mojom";'
> +        '[MooCow=a.E.kFoo]'
> +        'interface Foo { Foo(); };')
> +    self.ParseMojoms([a_mojom, b_mojom])
> +    b = self.LoadModule(b_mojom)
> +    self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo')
> +
> +  def testConstantAttributes(self):
> +    """Verifies that constants as attributes are translated to the constant."""
> +    a_mojom = 'a.mojom'
> +    self.WriteFile(
> +        a_mojom, 'module a;'
> +        'enum E { kFoo, kBar };'
> +        'const E kB = E.kFoo;'
> +        '[Attr=kB] interface Hello { Foo(); };')
> +    self.ParseMojoms([a_mojom])
> +    a = self.LoadModule(a_mojom)
> +    self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB')
> +    self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name,
> +                      'kFoo')
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> index 51facc0c..a0edf0eb 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -8,6 +8,7 @@ group("mojom") {
>      "error.py",
>      "fileutil.py",
>      "generate/__init__.py",
> +    "generate/check.py",
>      "generate/generator.py",
>      "generate/module.py",
>      "generate/pack.py",
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> index 8a1e03da..dd53b835 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> index bf626f54..29daec36 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> @@ -1,4 +1,4 @@
> -# Copyright 2015 The Chromium Authors. All rights reserved.
> +# Copyright 2015 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> index ff5753a2..48eaf4ec 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2015 The Chromium Authors. All rights reserved.
> +# Copyright 2015 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
> new file mode 100644
> index 00000000..1efe2022
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
> @@ -0,0 +1,26 @@
> +# Copyright 2022 The Chromium Authors
> +# Use of this source code is governed by a BSD-style license that can be
> +# found in the LICENSE file.
> +"""Code shared by the various pre-generation mojom checkers."""
> +
> +
> +class CheckException(Exception):
> +  def __init__(self, module, message):
> +    self.module = module
> +    self.message = message
> +    super().__init__(self.message)
> +
> +  def __str__(self):
> +    return "Failed mojo pre-generation check for {}:\n{}".format(
> +        self.module.path, self.message)
> +
> +
> +class Check:
> +  def __init__(self, module):
> +    self.module = module
> +
> +  def CheckModule(self):
> +    """ Subclass should return True if its Checks pass, and throw an
> +    exception otherwise. CheckModule will be called immediately before
> +    mojom.generate.Generator.GenerateFiles()"""
> +    raise NotImplementedError("Subclasses must override/implement this method")
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> index 4a1c73fc..31cacd0e 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# Copyright 2013 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  """Code shared by the various language-specific code generators."""
> @@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier):
>    return _ToSnakeCase(identifier, upper=False)
>
>
> -class Stylizer(object):
> +class Stylizer:
>    """Stylizers specify naming rules to map mojom names to names in generated
>    code. For example, if you would like method_name in mojom to be mapped to
>    MethodName in the generated code, you need to define a subclass of Stylizer
> @@ -233,7 +233,7 @@ def AddComputedData(module):
>      _AddInterfaceComputedData(interface)
>
>
> -class Generator(object):
> +class Generator:
>    # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
>    # files to stdout.
>    def __init__(self,
> @@ -243,7 +243,6 @@ class Generator(object):
>                 variant=None,
>                 bytecode_path=None,
>                 for_blink=False,
> -               js_bindings_mode="new",
>                 js_generate_struct_deserializers=False,
>                 export_attribute=None,
>                 export_header=None,
> @@ -262,7 +261,6 @@ class Generator(object):
>      self.variant = variant
>      self.bytecode_path = bytecode_path
>      self.for_blink = for_blink
> -    self.js_bindings_mode = js_bindings_mode
>      self.js_generate_struct_deserializers = js_generate_struct_deserializers
>      self.export_attribute = export_attribute
>      self.export_header = export_header
> 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
> index 32c884a8..76cda398 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> index 9bdb28e0..f0664b31 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# Copyright 2013 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -12,15 +12,14 @@
>  # method = interface.AddMethod('Tat', 0)
>  # method.AddParameter('baz', 0, mojom.INT32)
>
> -import sys
> -if sys.version_info.major == 2:
> -  import cPickle as pickle
> -else:
> -  import pickle
> +import pickle
> +from collections import OrderedDict
>  from uuid import UUID
>
> +# pylint: disable=raise-missing-from
>
> -class BackwardCompatibilityChecker(object):
> +
> +class BackwardCompatibilityChecker:
>    """Used for memoization while recursively checking two type definitions for
>    backward-compatibility."""
>
> @@ -64,23 +63,20 @@ def Repr(obj, as_ref=True):
>      return obj.Repr(as_ref=as_ref)
>    # Since we cannot implement Repr for existing container types, we
>    # handle them here.
> -  elif isinstance(obj, list):
> +  if isinstance(obj, list):
>      if not obj:
>        return '[]'
> -    else:
> -      return ('[\n%s\n]' % (',\n'.join(
> -          '    %s' % Repr(elem, as_ref).replace('\n', '\n    ')
> -          for elem in obj)))
> -  elif isinstance(obj, dict):
> +    return ('[\n%s\n]' %
> +            (',\n'.join('    %s' % Repr(elem, as_ref).replace('\n', '\n    ')
> +                        for elem in obj)))
> +  if isinstance(obj, dict):
>      if not obj:
>        return '{}'
> -    else:
> -      return ('{\n%s\n}' % (',\n'.join(
> -          '    %s: %s' % (Repr(key, as_ref).replace('\n', '\n    '),
> -                          Repr(val, as_ref).replace('\n', '\n    '))
> -          for key, val in obj.items())))
> -  else:
> -    return repr(obj)
> +    return ('{\n%s\n}' % (',\n'.join('    %s: %s' %
> +                                     (Repr(key, as_ref).replace('\n', '\n    '),
> +                                      Repr(val, as_ref).replace('\n', '\n    '))
> +                                     for key, val in obj.items())))
> +  return repr(obj)
>
>
>  def GenericRepr(obj, names):
> @@ -104,7 +100,7 @@ def GenericRepr(obj, names):
>        ReprIndent(name, as_ref) for (name, as_ref) in names.items()))
>
>
> -class Kind(object):
> +class Kind:
>    """Kind represents a type (e.g. int8, string).
>
>    Attributes:
> @@ -112,16 +108,43 @@ class Kind(object):
>      module: {Module} The defining module. Set to None for built-in types.
>      parent_kind: The enclosing type. For example, an enum defined
>          inside an interface has that interface as its parent. May be None.
> +    is_nullable: True if the type is nullable.
>    """
>
> -  def __init__(self, spec=None, module=None):
> +  def __init__(self, spec=None, is_nullable=False, module=None):
>      self.spec = spec
>      self.module = module
>      self.parent_kind = None
> +    self.is_nullable = is_nullable
> +    self.shared_definition = {}
> +
> +  @classmethod
> +  def AddSharedProperty(cls, name):
> +    """Adds a property |name| to |cls|, which accesses the corresponding item in
> +       |shared_definition|.
> +
> +       The reason of adding such indirection is to enable sharing definition
> +       between a reference kind and its nullable variation. For example:
> +         a = Struct('test_struct_1')
> +         b = a.MakeNullableKind()
> +         a.name = 'test_struct_2'
> +         print(b.name)  # Outputs 'test_struct_2'.
> +    """
> +    def Get(self):
> +      try:
> +        return self.shared_definition[name]
> +      except KeyError:  # Must raise AttributeError if property doesn't exist.
> +        raise AttributeError
> +
> +    def Set(self, value):
> +      self.shared_definition[name] = value
> +
> +    setattr(cls, name, property(Get, Set))
>
>    def Repr(self, as_ref=True):
>      # pylint: disable=unused-argument
> -    return '<%s spec=%r>' % (self.__class__.__name__, self.spec)
> +    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,
> +                                            self.is_nullable)
>
>    def __repr__(self):
>      # Gives us a decent __repr__ for all kinds.
> @@ -130,7 +153,8 @@ class Kind(object):
>    def __eq__(self, rhs):
>      # pylint: disable=unidiomatic-typecheck
>      return (type(self) == type(rhs)
> -            and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind))
> +            and (self.spec, self.parent_kind, self.is_nullable)
> +            == (rhs.spec, rhs.parent_kind, rhs.is_nullable))
>
>    def __hash__(self):
>      # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind
> @@ -138,32 +162,113 @@ class Kind(object):
>      # some primitive Kinds as dict keys. The default hash (object identity)
>      # breaks these dicts when a pickled Module instance is unpickled and used
>      # during a subsequent run of the parser.
> -    return hash((self.spec, self.parent_kind))
> +    return hash((self.spec, self.parent_kind, self.is_nullable))
>
>    # pylint: disable=unused-argument
>    def IsBackwardCompatible(self, rhs, checker):
>      return self == rhs
>
>
> +class ValueKind(Kind):
> +  """ValueKind represents values that aren't reference kinds.
> +
> +  The primary difference is the wire representation for nullable value kinds
> +  still reserves space for the value type itself, even if that value itself
> +  is logically null.
> +  """
> +  def __init__(self, spec=None, is_nullable=False, module=None):
> +    assert spec is None or is_nullable == spec.startswith('?')
> +    Kind.__init__(self, spec, is_nullable, module)
> +
> +  def MakeNullableKind(self):
> +    assert not self.is_nullable
> +
> +    if self == BOOL:
> +      return NULLABLE_BOOL
> +    if self == INT8:
> +      return NULLABLE_INT8
> +    if self == INT16:
> +      return NULLABLE_INT16
> +    if self == INT32:
> +      return NULLABLE_INT32
> +    if self == INT64:
> +      return NULLABLE_INT64
> +    if self == UINT8:
> +      return NULLABLE_UINT8
> +    if self == UINT16:
> +      return NULLABLE_UINT16
> +    if self == UINT32:
> +      return NULLABLE_UINT32
> +    if self == UINT64:
> +      return NULLABLE_UINT64
> +    if self == FLOAT:
> +      return NULLABLE_FLOAT
> +    if self == DOUBLE:
> +      return NULLABLE_DOUBLE
> +
> +    nullable_kind = type(self)()
> +    nullable_kind.shared_definition = self.shared_definition
> +    if self.spec is not None:
> +      nullable_kind.spec = '?' + self.spec
> +    nullable_kind.is_nullable = True
> +    nullable_kind.parent_kind = self.parent_kind
> +    nullable_kind.module = self.module
> +
> +    return nullable_kind
> +
> +  def MakeUnnullableKind(self):
> +    assert self.is_nullable
> +
> +    if self == NULLABLE_BOOL:
> +      return BOOL
> +    if self == NULLABLE_INT8:
> +      return INT8
> +    if self == NULLABLE_INT16:
> +      return INT16
> +    if self == NULLABLE_INT32:
> +      return INT32
> +    if self == NULLABLE_INT64:
> +      return INT64
> +    if self == NULLABLE_UINT8:
> +      return UINT8
> +    if self == NULLABLE_UINT16:
> +      return UINT16
> +    if self == NULLABLE_UINT32:
> +      return UINT32
> +    if self == NULLABLE_UINT64:
> +      return UINT64
> +    if self == NULLABLE_FLOAT:
> +      return FLOAT
> +    if self == NULLABLE_DOUBLE:
> +      return DOUBLE
> +
> +    nullable_kind = type(self)()
> +    nullable_kind.shared_definition = self.shared_definition
> +    if self.spec is not None:
> +      nullable_kind.spec = self.spec[1:]
> +    nullable_kind.is_nullable = False
> +    nullable_kind.parent_kind = self.parent_kind
> +    nullable_kind.module = self.module
> +
> +    return nullable_kind
> +
> +  def __eq__(self, rhs):
> +    return (isinstance(rhs, ValueKind) and super().__eq__(rhs))
> +
> +  def __hash__(self):  # pylint: disable=useless-super-delegation
> +    return super().__hash__()
> +
> +
>  class ReferenceKind(Kind):
>    """ReferenceKind represents pointer and handle types.
>
>    A type is nullable if null (for pointer types) or invalid handle (for handle
>    types) is a legal value for the type.
> -
> -  Attributes:
> -    is_nullable: True if the type is nullable.
>    """
>
>    def __init__(self, spec=None, is_nullable=False, module=None):
>      assert spec is None or is_nullable == spec.startswith('?')
> -    Kind.__init__(self, spec, module)
> -    self.is_nullable = is_nullable
> -    self.shared_definition = {}
> -
> -  def Repr(self, as_ref=True):
> -    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,
> -                                            self.is_nullable)
> +    Kind.__init__(self, spec, is_nullable, module)
>
>    def MakeNullableKind(self):
>      assert not self.is_nullable
> @@ -193,55 +298,36 @@ class ReferenceKind(Kind):
>
>      return nullable_kind
>
> -  @classmethod
> -  def AddSharedProperty(cls, name):
> -    """Adds a property |name| to |cls|, which accesses the corresponding item in
> -       |shared_definition|.
> -
> -       The reason of adding such indirection is to enable sharing definition
> -       between a reference kind and its nullable variation. For example:
> -         a = Struct('test_struct_1')
> -         b = a.MakeNullableKind()
> -         a.name = 'test_struct_2'
> -         print(b.name)  # Outputs 'test_struct_2'.
> -    """
> -
> -    def Get(self):
> -      try:
> -        return self.shared_definition[name]
> -      except KeyError:  # Must raise AttributeError if property doesn't exist.
> -        raise AttributeError
> -
> -    def Set(self, value):
> -      self.shared_definition[name] = value
> -
> -    setattr(cls, name, property(Get, Set))
> -
>    def __eq__(self, rhs):
> -    return (isinstance(rhs, ReferenceKind)
> -            and super(ReferenceKind, self).__eq__(rhs)
> -            and self.is_nullable == rhs.is_nullable)
> -
> -  def __hash__(self):
> -    return hash((super(ReferenceKind, self).__hash__(), self.is_nullable))
> +    return (isinstance(rhs, ReferenceKind) and super().__eq__(rhs))
>
> -  def IsBackwardCompatible(self, rhs, checker):
> -    return (super(ReferenceKind, self).IsBackwardCompatible(rhs, checker)
> -            and self.is_nullable == rhs.is_nullable)
> +  def __hash__(self):  # pylint: disable=useless-super-delegation
> +    return super().__hash__()
>
>
>  # Initialize the set of primitive types. These can be accessed by clients.
> -BOOL = Kind('b')
> -INT8 = Kind('i8')
> -INT16 = Kind('i16')
> -INT32 = Kind('i32')
> -INT64 = Kind('i64')
> -UINT8 = Kind('u8')
> -UINT16 = Kind('u16')
> -UINT32 = Kind('u32')
> -UINT64 = Kind('u64')
> -FLOAT = Kind('f')
> -DOUBLE = Kind('d')
> +BOOL = ValueKind('b')
> +INT8 = ValueKind('i8')
> +INT16 = ValueKind('i16')
> +INT32 = ValueKind('i32')
> +INT64 = ValueKind('i64')
> +UINT8 = ValueKind('u8')
> +UINT16 = ValueKind('u16')
> +UINT32 = ValueKind('u32')
> +UINT64 = ValueKind('u64')
> +FLOAT = ValueKind('f')
> +DOUBLE = ValueKind('d')
> +NULLABLE_BOOL = ValueKind('?b', True)
> +NULLABLE_INT8 = ValueKind('?i8', True)
> +NULLABLE_INT16 = ValueKind('?i16', True)
> +NULLABLE_INT32 = ValueKind('?i32', True)
> +NULLABLE_INT64 = ValueKind('?i64', True)
> +NULLABLE_UINT8 = ValueKind('?u8', True)
> +NULLABLE_UINT16 = ValueKind('?u16', True)
> +NULLABLE_UINT32 = ValueKind('?u32', True)
> +NULLABLE_UINT64 = ValueKind('?u64', True)
> +NULLABLE_FLOAT = ValueKind('?f', True)
> +NULLABLE_DOUBLE = ValueKind('?d', True)
>  STRING = ReferenceKind('s')
>  HANDLE = ReferenceKind('h')
>  DCPIPE = ReferenceKind('h:d:c')
> @@ -270,6 +356,17 @@ PRIMITIVES = (
>      UINT64,
>      FLOAT,
>      DOUBLE,
> +    NULLABLE_BOOL,
> +    NULLABLE_INT8,
> +    NULLABLE_INT16,
> +    NULLABLE_INT32,
> +    NULLABLE_INT64,
> +    NULLABLE_UINT8,
> +    NULLABLE_UINT16,
> +    NULLABLE_UINT32,
> +    NULLABLE_UINT64,
> +    NULLABLE_FLOAT,
> +    NULLABLE_DOUBLE,
>      STRING,
>      HANDLE,
>      DCPIPE,
> @@ -294,9 +391,12 @@ ATTRIBUTE_STABLE = 'Stable'
>  ATTRIBUTE_SYNC = 'Sync'
>  ATTRIBUTE_UNLIMITED_SIZE = 'UnlimitedSize'
>  ATTRIBUTE_UUID = 'Uuid'
> +ATTRIBUTE_SERVICE_SANDBOX = 'ServiceSandbox'
> +ATTRIBUTE_REQUIRE_CONTEXT = 'RequireContext'
> +ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext'
>
>
> -class NamedValue(object):
> +class NamedValue:
>    def __init__(self, module, parent_kind, mojom_name):
>      self.module = module
>      self.parent_kind = parent_kind
> @@ -316,7 +416,7 @@ class NamedValue(object):
>      return hash((self.parent_kind, self.mojom_name))
>
>
> -class BuiltinValue(object):
> +class BuiltinValue:
>    def __init__(self, value):
>      self.value = value
>
> @@ -350,7 +450,7 @@ class EnumValue(NamedValue):
>      return self.field.name
>
>
> -class Constant(object):
> +class Constant:
>    def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None):
>      self.mojom_name = mojom_name
>      self.name = None
> @@ -368,7 +468,7 @@ class Constant(object):
>                                         rhs.parent_kind))
>
>
> -class Field(object):
> +class Field:
>    def __init__(self,
>                 mojom_name=None,
>                 kind=None,
> @@ -414,7 +514,18 @@ class StructField(Field):
>
>
>  class UnionField(Field):
> -  pass
> +  def __init__(self,
> +               mojom_name=None,
> +               kind=None,
> +               ordinal=None,
> +               default=None,
> +               attributes=None):
> +    Field.__init__(self, mojom_name, kind, ordinal, default, attributes)
> +
> +  @property
> +  def is_default(self):
> +    return self.attributes.get(ATTRIBUTE_DEFAULT, False) \
> +        if self.attributes else False
>
>
>  def _IsFieldBackwardCompatible(new_field, old_field, checker):
> @@ -441,14 +552,14 @@ class Struct(ReferenceKind):
>          if it's a native struct.
>    """
>
> -  ReferenceKind.AddSharedProperty('mojom_name')
> -  ReferenceKind.AddSharedProperty('name')
> -  ReferenceKind.AddSharedProperty('native_only')
> -  ReferenceKind.AddSharedProperty('custom_serializer')
> -  ReferenceKind.AddSharedProperty('fields')
> -  ReferenceKind.AddSharedProperty('enums')
> -  ReferenceKind.AddSharedProperty('constants')
> -  ReferenceKind.AddSharedProperty('attributes')
> +  Kind.AddSharedProperty('mojom_name')
> +  Kind.AddSharedProperty('name')
> +  Kind.AddSharedProperty('native_only')
> +  Kind.AddSharedProperty('custom_serializer')
> +  Kind.AddSharedProperty('fields')
> +  Kind.AddSharedProperty('enums')
> +  Kind.AddSharedProperty('constants')
> +  Kind.AddSharedProperty('attributes')
>
>    def __init__(self, mojom_name=None, module=None, attributes=None):
>      if mojom_name is not None:
> @@ -470,12 +581,11 @@ class Struct(ReferenceKind):
>        return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__,
>                                                 self.mojom_name,
>                                                 Repr(self.module, as_ref=True))
> -    else:
> -      return GenericRepr(self, {
> -          'mojom_name': False,
> -          'fields': False,
> -          'module': True
> -      })
> +    return GenericRepr(self, {
> +        'mojom_name': False,
> +        'fields': False,
> +        'module': True
> +    })
>
>    def AddField(self,
>                 mojom_name,
> @@ -496,13 +606,13 @@ class Struct(ReferenceKind):
>      for constant in self.constants:
>        constant.Stylize(stylizer)
>
> -  def IsBackwardCompatible(self, older_struct, checker):
> -    """This struct is backward-compatible with older_struct if and only if all
> -    of the following conditions hold:
> +  def IsBackwardCompatible(self, rhs, checker):
> +    """This struct is backward-compatible with rhs (older_struct) if and only if
> +    all of the following conditions hold:
>        - Any newly added field is tagged with a [MinVersion] attribute specifying
>          a version number greater than all previously used [MinVersion]
>          attributes within the struct.
> -      - All fields present in older_struct remain present in the new struct,
> +      - All fields present in rhs remain present in the new struct,
>          with the same ordinal position, same optional or non-optional status,
>          same (or backward-compatible) type and where applicable, the same
>          [MinVersion] attribute value.
> @@ -521,7 +631,7 @@ class Struct(ReferenceKind):
>        return fields_by_ordinal
>
>      new_fields = buildOrdinalFieldMap(self)
> -    old_fields = buildOrdinalFieldMap(older_struct)
> +    old_fields = buildOrdinalFieldMap(rhs)
>      if len(new_fields) < len(old_fields):
>        # At least one field was removed, which is not OK.
>        return False
> @@ -574,11 +684,18 @@ class Struct(ReferenceKind):
>        prefix = self.module.GetNamespacePrefix()
>      return '%s%s' % (prefix, self.mojom_name)
>
> +  def _tuple(self):
> +    return (self.mojom_name, self.native_only, self.fields, self.constants,
> +            self.attributes)
> +
>    def __eq__(self, rhs):
> -    return (isinstance(rhs, Struct) and
> -            (self.mojom_name, self.native_only, self.fields, self.constants,
> -             self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields,
> -                                  rhs.constants, rhs.attributes))
> +    return isinstance(rhs, Struct) and self._tuple() == rhs._tuple()
> +
> +  def __lt__(self, rhs):
> +    if not isinstance(self, type(rhs)):
> +      return str(type(self)) < str(type(rhs))
> +
> +    return self._tuple() < rhs._tuple()
>
>    def __hash__(self):
>      return id(self)
> @@ -595,10 +712,11 @@ class Union(ReferenceKind):
>          which Java class name to use to represent it in the generated
>          bindings.
>    """
> -  ReferenceKind.AddSharedProperty('mojom_name')
> -  ReferenceKind.AddSharedProperty('name')
> -  ReferenceKind.AddSharedProperty('fields')
> -  ReferenceKind.AddSharedProperty('attributes')
> +  Kind.AddSharedProperty('mojom_name')
> +  Kind.AddSharedProperty('name')
> +  Kind.AddSharedProperty('fields')
> +  Kind.AddSharedProperty('attributes')
> +  Kind.AddSharedProperty('default_field')
>
>    def __init__(self, mojom_name=None, module=None, attributes=None):
>      if mojom_name is not None:
> @@ -610,14 +728,14 @@ class Union(ReferenceKind):
>      self.name = None
>      self.fields = []
>      self.attributes = attributes
> +    self.default_field = None
>
>    def Repr(self, as_ref=True):
>      if as_ref:
>        return '<%s spec=%r is_nullable=%r fields=%s>' % (
>            self.__class__.__name__, self.spec, self.is_nullable, Repr(
>                self.fields))
> -    else:
> -      return GenericRepr(self, {'fields': True, 'is_nullable': False})
> +    return GenericRepr(self, {'fields': True, 'is_nullable': False})
>
>    def AddField(self, mojom_name, kind, ordinal=None, attributes=None):
>      field = UnionField(mojom_name, kind, ordinal, None, attributes)
> @@ -629,13 +747,13 @@ class Union(ReferenceKind):
>      for field in self.fields:
>        field.Stylize(stylizer)
>
> -  def IsBackwardCompatible(self, older_union, checker):
> -    """This union is backward-compatible with older_union if and only if all
> -    of the following conditions hold:
> +  def IsBackwardCompatible(self, rhs, checker):
> +    """This union is backward-compatible with rhs (older_union) if and only if
> +    all of the following conditions hold:
>        - Any newly added field is tagged with a [MinVersion] attribute specifying
>          a version number greater than all previously used [MinVersion]
>          attributes within the union.
> -      - All fields present in older_union remain present in the new union,
> +      - All fields present in rhs remain present in the new union,
>          with the same ordinal value, same optional or non-optional status,
>          same (or backward-compatible) type, and where applicable, the same
>          [MinVersion] attribute value.
> @@ -651,7 +769,7 @@ class Union(ReferenceKind):
>        return fields_by_ordinal
>
>      new_fields = buildOrdinalFieldMap(self)
> -    old_fields = buildOrdinalFieldMap(older_union)
> +    old_fields = buildOrdinalFieldMap(rhs)
>      if len(new_fields) < len(old_fields):
>        # At least one field was removed, which is not OK.
>        return False
> @@ -677,6 +795,11 @@ class Union(ReferenceKind):
>
>      return True
>
> +  @property
> +  def extensible(self):
> +    return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \
> +        if self.attributes else False
> +
>    @property
>    def stable(self):
>      return self.attributes.get(ATTRIBUTE_STABLE, False) \
> @@ -690,10 +813,17 @@ class Union(ReferenceKind):
>        prefix = self.module.GetNamespacePrefix()
>      return '%s%s' % (prefix, self.mojom_name)
>
> +  def _tuple(self):
> +    return (self.mojom_name, self.fields, self.attributes)
> +
>    def __eq__(self, rhs):
> -    return (isinstance(rhs, Union) and
> -            (self.mojom_name, self.fields,
> -             self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes))
> +    return isinstance(rhs, Union) and self._tuple() == rhs._tuple()
> +
> +  def __lt__(self, rhs):
> +    if not isinstance(self, type(rhs)):
> +      return str(type(self)) < str(type(rhs))
> +
> +    return self._tuple() < rhs._tuple()
>
>    def __hash__(self):
>      return id(self)
> @@ -707,8 +837,8 @@ class Array(ReferenceKind):
>      length: The number of elements. None if unknown.
>    """
>
> -  ReferenceKind.AddSharedProperty('kind')
> -  ReferenceKind.AddSharedProperty('length')
> +  Kind.AddSharedProperty('kind')
> +  Kind.AddSharedProperty('length')
>
>    def __init__(self, kind=None, length=None):
>      if kind is not None:
> @@ -728,12 +858,11 @@ class Array(ReferenceKind):
>        return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (
>            self.__class__.__name__, self.spec, self.is_nullable, Repr(
>                self.kind), self.length)
> -    else:
> -      return GenericRepr(self, {
> -          'kind': True,
> -          'length': False,
> -          'is_nullable': False
> -      })
> +    return GenericRepr(self, {
> +        'kind': True,
> +        'length': False,
> +        'is_nullable': False
> +    })
>
>    def __eq__(self, rhs):
>      return (isinstance(rhs, Array)
> @@ -754,8 +883,8 @@ class Map(ReferenceKind):
>      key_kind: {Kind} The type of the keys. May be None.
>      value_kind: {Kind} The type of the elements. May be None.
>    """
> -  ReferenceKind.AddSharedProperty('key_kind')
> -  ReferenceKind.AddSharedProperty('value_kind')
> +  Kind.AddSharedProperty('key_kind')
> +  Kind.AddSharedProperty('value_kind')
>
>    def __init__(self, key_kind=None, value_kind=None):
>      if (key_kind is not None and value_kind is not None):
> @@ -780,8 +909,7 @@ class Map(ReferenceKind):
>        return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (
>            self.__class__.__name__, self.spec, self.is_nullable,
>            Repr(self.key_kind), Repr(self.value_kind))
> -    else:
> -      return GenericRepr(self, {'key_kind': True, 'value_kind': True})
> +    return GenericRepr(self, {'key_kind': True, 'value_kind': True})
>
>    def __eq__(self, rhs):
>      return (isinstance(rhs, Map) and
> @@ -797,7 +925,7 @@ class Map(ReferenceKind):
>
>
>  class PendingRemote(ReferenceKind):
> -  ReferenceKind.AddSharedProperty('kind')
> +  Kind.AddSharedProperty('kind')
>
>    def __init__(self, kind=None):
>      if kind is not None:
> @@ -822,7 +950,7 @@ class PendingRemote(ReferenceKind):
>
>
>  class PendingReceiver(ReferenceKind):
> -  ReferenceKind.AddSharedProperty('kind')
> +  Kind.AddSharedProperty('kind')
>
>    def __init__(self, kind=None):
>      if kind is not None:
> @@ -847,7 +975,7 @@ class PendingReceiver(ReferenceKind):
>
>
>  class PendingAssociatedRemote(ReferenceKind):
> -  ReferenceKind.AddSharedProperty('kind')
> +  Kind.AddSharedProperty('kind')
>
>    def __init__(self, kind=None):
>      if kind is not None:
> @@ -873,7 +1001,7 @@ class PendingAssociatedRemote(ReferenceKind):
>
>
>  class PendingAssociatedReceiver(ReferenceKind):
> -  ReferenceKind.AddSharedProperty('kind')
> +  Kind.AddSharedProperty('kind')
>
>    def __init__(self, kind=None):
>      if kind is not None:
> @@ -899,7 +1027,7 @@ class PendingAssociatedReceiver(ReferenceKind):
>
>
>  class InterfaceRequest(ReferenceKind):
> -  ReferenceKind.AddSharedProperty('kind')
> +  Kind.AddSharedProperty('kind')
>
>    def __init__(self, kind=None):
>      if kind is not None:
> @@ -923,7 +1051,7 @@ class InterfaceRequest(ReferenceKind):
>
>
>  class AssociatedInterfaceRequest(ReferenceKind):
> -  ReferenceKind.AddSharedProperty('kind')
> +  Kind.AddSharedProperty('kind')
>
>    def __init__(self, kind=None):
>      if kind is not None:
> @@ -949,7 +1077,7 @@ class AssociatedInterfaceRequest(ReferenceKind):
>              self.kind, rhs.kind)
>
>
> -class Parameter(object):
> +class Parameter:
>    def __init__(self,
>                 mojom_name=None,
>                 kind=None,
> @@ -983,7 +1111,7 @@ class Parameter(object):
>                                        rhs.default, rhs.attributes))
>
>
> -class Method(object):
> +class Method:
>    def __init__(self, interface, mojom_name, ordinal=None, attributes=None):
>      self.interface = interface
>      self.mojom_name = mojom_name
> @@ -999,12 +1127,11 @@ class Method(object):
>    def Repr(self, as_ref=True):
>      if as_ref:
>        return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
> -    else:
> -      return GenericRepr(self, {
> -          'mojom_name': False,
> -          'parameters': True,
> -          'response_parameters': True
> -      })
> +    return GenericRepr(self, {
> +        'mojom_name': False,
> +        'parameters': True,
> +        'response_parameters': True
> +    })
>
>    def AddParameter(self,
>                     mojom_name,
> @@ -1061,21 +1188,32 @@ class Method(object):
>      return self.attributes.get(ATTRIBUTE_UNLIMITED_SIZE) \
>          if self.attributes else False
>
> +  @property
> +  def allowed_context(self):
> +    return self.attributes.get(ATTRIBUTE_ALLOWED_CONTEXT) \
> +        if self.attributes else None
> +
> +  def _tuple(self):
> +    return (self.mojom_name, self.ordinal, self.parameters,
> +            self.response_parameters, self.attributes)
> +
>    def __eq__(self, rhs):
> -    return (isinstance(rhs, Method) and
> -            (self.mojom_name, self.ordinal, self.parameters,
> -             self.response_parameters,
> -             self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters,
> -                                  rhs.response_parameters, rhs.attributes))
> +    return isinstance(rhs, Method) and self._tuple() == rhs._tuple()
> +
> +  def __lt__(self, rhs):
> +    if not isinstance(self, type(rhs)):
> +      return str(type(self)) < str(type(rhs))
> +
> +    return self._tuple() < rhs._tuple()
>
>
>  class Interface(ReferenceKind):
> -  ReferenceKind.AddSharedProperty('mojom_name')
> -  ReferenceKind.AddSharedProperty('name')
> -  ReferenceKind.AddSharedProperty('methods')
> -  ReferenceKind.AddSharedProperty('enums')
> -  ReferenceKind.AddSharedProperty('constants')
> -  ReferenceKind.AddSharedProperty('attributes')
> +  Kind.AddSharedProperty('mojom_name')
> +  Kind.AddSharedProperty('name')
> +  Kind.AddSharedProperty('methods')
> +  Kind.AddSharedProperty('enums')
> +  Kind.AddSharedProperty('constants')
> +  Kind.AddSharedProperty('attributes')
>
>    def __init__(self, mojom_name=None, module=None, attributes=None):
>      if mojom_name is not None:
> @@ -1093,12 +1231,11 @@ class Interface(ReferenceKind):
>    def Repr(self, as_ref=True):
>      if as_ref:
>        return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
> -    else:
> -      return GenericRepr(self, {
> -          'mojom_name': False,
> -          'attributes': False,
> -          'methods': False
> -      })
> +    return GenericRepr(self, {
> +        'mojom_name': False,
> +        'attributes': False,
> +        'methods': False
> +    })
>
>    def AddMethod(self, mojom_name, ordinal=None, attributes=None):
>      method = Method(self, mojom_name, ordinal, attributes)
> @@ -1114,10 +1251,10 @@ class Interface(ReferenceKind):
>      for constant in self.constants:
>        constant.Stylize(stylizer)
>
> -  def IsBackwardCompatible(self, older_interface, checker):
> -    """This interface is backward-compatible with older_interface if and only
> -    if all of the following conditions hold:
> -      - All defined methods in older_interface (when identified by ordinal) have
> +  def IsBackwardCompatible(self, rhs, checker):
> +    """This interface is backward-compatible with rhs (older_interface) if and
> +    only if all of the following conditions hold:
> +      - All defined methods in rhs (when identified by ordinal) have
>          backward-compatible definitions in this interface. For each method this
>          means:
>            - The parameter list is backward-compatible, according to backward-
> @@ -1131,7 +1268,7 @@ class Interface(ReferenceKind):
>              rules for structs.
>        - All newly introduced methods in this interface have a [MinVersion]
>          attribute specifying a version greater than any method in
> -        older_interface.
> +        rhs.
>      """
>
>      def buildOrdinalMethodMap(interface):
> @@ -1144,7 +1281,7 @@ class Interface(ReferenceKind):
>        return methods_by_ordinal
>
>      new_methods = buildOrdinalMethodMap(self)
> -    old_methods = buildOrdinalMethodMap(older_interface)
> +    old_methods = buildOrdinalMethodMap(rhs)
>      max_old_min_version = 0
>      for ordinal, old_method in old_methods.items():
>        new_method = new_methods.get(ordinal)
> @@ -1186,6 +1323,27 @@ class Interface(ReferenceKind):
>
>      return True
>
> +  @property
> +  def service_sandbox(self):
> +    if not self.attributes:
> +      return None
> +    service_sandbox = self.attributes.get(ATTRIBUTE_SERVICE_SANDBOX, None)
> +    if service_sandbox is None:
> +      return None
> +    # Constants are only allowed to refer to an enum here, so replace.
> +    if isinstance(service_sandbox, Constant):
> +      service_sandbox = service_sandbox.value
> +    if not isinstance(service_sandbox, EnumValue):
> +      raise Exception("ServiceSandbox attribute on %s must be an enum value." %
> +                      self.module.name)
> +    return service_sandbox
> +
> +  @property
> +  def require_context(self):
> +    if not self.attributes:
> +      return None
> +    return self.attributes.get(ATTRIBUTE_REQUIRE_CONTEXT, None)
> +
>    @property
>    def stable(self):
>      return self.attributes.get(ATTRIBUTE_STABLE, False) \
> @@ -1199,11 +1357,18 @@ class Interface(ReferenceKind):
>        prefix = self.module.GetNamespacePrefix()
>      return '%s%s' % (prefix, self.mojom_name)
>
> +  def _tuple(self):
> +    return (self.mojom_name, self.methods, self.enums, self.constants,
> +            self.attributes)
> +
>    def __eq__(self, rhs):
> -    return (isinstance(rhs, Interface)
> -            and (self.mojom_name, self.methods, self.enums, self.constants,
> -                 self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums,
> -                                      rhs.constants, rhs.attributes))
> +    return isinstance(rhs, Interface) and self._tuple() == rhs._tuple()
> +
> +  def __lt__(self, rhs):
> +    if not isinstance(self, type(rhs)):
> +      return str(type(self)) < str(type(rhs))
> +
> +    return self._tuple() < rhs._tuple()
>
>    @property
>    def uuid(self):
> @@ -1224,7 +1389,7 @@ class Interface(ReferenceKind):
>
>
>  class AssociatedInterface(ReferenceKind):
> -  ReferenceKind.AddSharedProperty('kind')
> +  Kind.AddSharedProperty('kind')
>
>    def __init__(self, kind=None):
>      if kind is not None:
> @@ -1249,7 +1414,7 @@ class AssociatedInterface(ReferenceKind):
>                            self.kind, rhs.kind)
>
>
> -class EnumField(object):
> +class EnumField:
>    def __init__(self,
>                 mojom_name=None,
>                 value=None,
> @@ -1281,16 +1446,25 @@ class EnumField(object):
>                                           rhs.attributes, rhs.numeric_value))
>
>
> -class Enum(Kind):
> +class Enum(ValueKind):
> +  Kind.AddSharedProperty('mojom_name')
> +  Kind.AddSharedProperty('name')
> +  Kind.AddSharedProperty('native_only')
> +  Kind.AddSharedProperty('fields')
> +  Kind.AddSharedProperty('attributes')
> +  Kind.AddSharedProperty('min_value')
> +  Kind.AddSharedProperty('max_value')
> +  Kind.AddSharedProperty('default_field')
> +
>    def __init__(self, mojom_name=None, module=None, attributes=None):
> -    self.mojom_name = mojom_name
> -    self.name = None
> -    self.native_only = False
>      if mojom_name is not None:
>        spec = 'x:' + mojom_name
>      else:
>        spec = None
> -    Kind.__init__(self, spec, module)
> +    ValueKind.__init__(self, spec, False, module)
> +    self.mojom_name = mojom_name
> +    self.name = None
> +    self.native_only = False
>      self.fields = []
>      self.attributes = attributes
>      self.min_value = None
> @@ -1300,8 +1474,7 @@ class Enum(Kind):
>    def Repr(self, as_ref=True):
>      if as_ref:
>        return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
> -    else:
> -      return GenericRepr(self, {'mojom_name': False, 'fields': False})
> +    return GenericRepr(self, {'mojom_name': False, 'fields': False})
>
>    def Stylize(self, stylizer):
>      self.name = stylizer.StylizeEnum(self.mojom_name)
> @@ -1327,14 +1500,14 @@ class Enum(Kind):
>      return '%s%s' % (prefix, self.mojom_name)
>
>    # pylint: disable=unused-argument
> -  def IsBackwardCompatible(self, older_enum, checker):
> -    """This enum is backward-compatible with older_enum if and only if one of
> -    the following conditions holds:
> +  def IsBackwardCompatible(self, rhs, checker):
> +    """This enum is backward-compatible with rhs (older_enum) if and only if one
> +    of the following conditions holds:
>          - Neither enum is [Extensible] and both have the exact same set of valid
>            numeric values. Field names and aliases for the same numeric value do
>            not affect compatibility.
> -        - older_enum is [Extensible], and for every version defined by
> -          older_enum, this enum has the exact same set of valid numeric values.
> +        - rhs is [Extensible], and for every version defined by
> +          rhs, this enum has the exact same set of valid numeric values.
>      """
>
>      def buildVersionFieldMap(enum):
> @@ -1345,10 +1518,10 @@ class Enum(Kind):
>          fields_by_min_version[field.min_version].add(field.numeric_value)
>        return fields_by_min_version
>
> -    old_fields = buildVersionFieldMap(older_enum)
> +    old_fields = buildVersionFieldMap(rhs)
>      new_fields = buildVersionFieldMap(self)
>
> -    if new_fields.keys() != old_fields.keys() and not older_enum.extensible:
> +    if new_fields.keys() != old_fields.keys() and not rhs.extensible:
>        return False
>
>      for min_version, valid_values in old_fields.items():
> @@ -1358,19 +1531,24 @@ class Enum(Kind):
>
>      return True
>
> +  def _tuple(self):
> +    return (self.mojom_name, self.native_only, self.fields, self.attributes,
> +            self.min_value, self.max_value, self.default_field)
> +
>    def __eq__(self, rhs):
> -    return (isinstance(rhs, Enum) and
> -            (self.mojom_name, self.native_only, self.fields, self.attributes,
> -             self.min_value, self.max_value,
> -             self.default_field) == (rhs.mojom_name, rhs.native_only,
> -                                     rhs.fields, rhs.attributes, rhs.min_value,
> -                                     rhs.max_value, rhs.default_field))
> +    return isinstance(rhs, Enum) and self._tuple() == rhs._tuple()
> +
> +  def __lt__(self, rhs):
> +    if not isinstance(self, type(rhs)):
> +      return str(type(self)) < str(type(rhs))
> +
> +    return self._tuple() < rhs._tuple()
>
>    def __hash__(self):
>      return id(self)
>
>
> -class Module(object):
> +class Module:
>    def __init__(self, path=None, mojom_namespace=None, attributes=None):
>      self.path = path
>      self.mojom_namespace = mojom_namespace
> @@ -1380,11 +1558,11 @@ class Module(object):
>      self.interfaces = []
>      self.enums = []
>      self.constants = []
> -    self.kinds = {}
> +    self.kinds = OrderedDict()
>      self.attributes = attributes
>      self.imports = []
> -    self.imported_kinds = {}
> -    self.metadata = {}
> +    self.imported_kinds = OrderedDict()
> +    self.metadata = OrderedDict()
>
>    def __repr__(self):
>      # Gives us a decent __repr__ for modules.
> @@ -1405,16 +1583,15 @@ class Module(object):
>      if as_ref:
>        return '<%s path=%r mojom_namespace=%r>' % (
>            self.__class__.__name__, self.path, self.mojom_namespace)
> -    else:
> -      return GenericRepr(
> -          self, {
> -              'path': False,
> -              'mojom_namespace': False,
> -              'attributes': False,
> -              'structs': False,
> -              'interfaces': False,
> -              'unions': False
> -          })
> +    return GenericRepr(
> +        self, {
> +            'path': False,
> +            'mojom_namespace': False,
> +            'attributes': False,
> +            'structs': False,
> +            'interfaces': False,
> +            'unions': False
> +        })
>
>    def GetNamespacePrefix(self):
>      return '%s.' % self.mojom_namespace if self.mojom_namespace else ''
> @@ -1451,7 +1628,7 @@ class Module(object):
>        imported_module.Stylize(stylizer)
>
>    def Dump(self, f):
> -    pickle.dump(self, f, 2)
> +    pickle.dump(self, f)
>
>    @classmethod
>    def Load(cls, f):
> @@ -1461,15 +1638,15 @@ class Module(object):
>
>
>  def IsBoolKind(kind):
> -  return kind.spec == BOOL.spec
> +  return kind.spec == BOOL.spec or kind.spec == NULLABLE_BOOL.spec
>
>
>  def IsFloatKind(kind):
> -  return kind.spec == FLOAT.spec
> +  return kind.spec == FLOAT.spec or kind.spec == NULLABLE_FLOAT.spec
>
>
>  def IsDoubleKind(kind):
> -  return kind.spec == DOUBLE.spec
> +  return kind.spec == DOUBLE.spec or kind.spec == NULLABLE_DOUBLE.spec
>
>
>  def IsIntegralKind(kind):
> @@ -1477,7 +1654,14 @@ def IsIntegralKind(kind):
>            or kind.spec == INT16.spec or kind.spec == INT32.spec
>            or kind.spec == INT64.spec or kind.spec == UINT8.spec
>            or kind.spec == UINT16.spec or kind.spec == UINT32.spec
> -          or kind.spec == UINT64.spec)
> +          or kind.spec == UINT64.spec or kind.spec == NULLABLE_BOOL.spec
> +          or kind.spec == NULLABLE_INT8.spec or kind.spec == NULLABLE_INT16.spec
> +          or kind.spec == NULLABLE_INT32.spec
> +          or kind.spec == NULLABLE_INT64.spec
> +          or kind.spec == NULLABLE_UINT8.spec
> +          or kind.spec == NULLABLE_UINT16.spec
> +          or kind.spec == NULLABLE_UINT32.spec
> +          or kind.spec == NULLABLE_UINT64.spec)
>
>
>  def IsStringKind(kind):
> @@ -1563,7 +1747,7 @@ def IsReferenceKind(kind):
>
>
>  def IsNullableKind(kind):
> -  return IsReferenceKind(kind) and kind.is_nullable
> +  return kind.is_nullable
>
>
>  def IsMapKind(kind):
> @@ -1664,11 +1848,8 @@ def MethodPassesInterfaces(method):
>    return _AnyMethodParameterRecursive(method, IsInterfaceKind)
>
>
> -def HasSyncMethods(interface):
> -  for method in interface.methods:
> -    if method.sync:
> -      return True
> -  return False
> +def GetSyncMethodOrdinals(interface):
> +  return [method.ordinal for method in interface.methods if method.sync]
>
>
>  def HasUninterruptableMethods(interface):
> @@ -1700,18 +1881,17 @@ def ContainsHandlesOrInterfaces(kind):
>      checked.add(kind.spec)
>      if IsStructKind(kind):
>        return any(Check(field.kind) for field in kind.fields)
> -    elif IsUnionKind(kind):
> +    if IsUnionKind(kind):
>        return any(Check(field.kind) for field in kind.fields)
> -    elif IsAnyHandleKind(kind):
> +    if IsAnyHandleKind(kind):
>        return True
> -    elif IsAnyInterfaceKind(kind):
> +    if IsAnyInterfaceKind(kind):
>        return True
> -    elif IsArrayKind(kind):
> +    if IsArrayKind(kind):
>        return Check(kind.kind)
> -    elif IsMapKind(kind):
> +    if IsMapKind(kind):
>        return Check(kind.key_kind) or Check(kind.value_kind)
> -    else:
> -      return False
> +    return False
>
>    return Check(kind)
>
> @@ -1738,21 +1918,20 @@ def ContainsNativeTypes(kind):
>      checked.add(kind.spec)
>      if IsEnumKind(kind):
>        return kind.native_only
> -    elif IsStructKind(kind):
> +    if IsStructKind(kind):
>        if kind.native_only:
>          return True
>        if any(enum.native_only for enum in kind.enums):
>          return True
>        return any(Check(field.kind) for field in kind.fields)
> -    elif IsUnionKind(kind):
> +    if IsUnionKind(kind):
>        return any(Check(field.kind) for field in kind.fields)
> -    elif IsInterfaceKind(kind):
> +    if IsInterfaceKind(kind):
>        return any(enum.native_only for enum in kind.enums)
> -    elif IsArrayKind(kind):
> +    if IsArrayKind(kind):
>        return Check(kind.kind)
> -    elif IsMapKind(kind):
> +    if IsMapKind(kind):
>        return Check(kind.key_kind) or Check(kind.value_kind)
> -    else:
> -      return False
> +    return False
>
>    return Check(kind)
> 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
> index e8fd4936..2a4e852c 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> index 88b77c98..71011109 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> @@ -1,7 +1,8 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# Copyright 2013 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> +import copy
>  from mojom.generate import module as mojom
>
>  # This module provides a mechanism for determining the packed order and offsets
> @@ -15,7 +16,7 @@ from mojom.generate import module as mojom
>  HEADER_SIZE = 8
>
>
> -class PackedField(object):
> +class PackedField:
>    kind_to_size = {
>        mojom.BOOL: 1,
>        mojom.INT8: 1,
> @@ -75,18 +76,55 @@ class PackedField(object):
>        return 8
>      return cls.GetSizeForKind(kind)
>
> -  def __init__(self, field, index, ordinal):
> +  def __init__(self,
> +               field,
> +               index,
> +               ordinal,
> +               original_field=None,
> +               sub_ordinal=None,
> +               linked_value_packed_field=None):
>      """
>      Args:
>        field: the original field.
>        index: the position of the original field in the struct.
>        ordinal: the ordinal of the field for serialization.
> +      original_field: See below.
> +      sub_ordinal: See below.
> +      linked_value_packed_field: See below.
> +
> +    original_field, sub_ordinal, and linked_value_packed_field are used to
> +    support nullable ValueKind fields. For legacy reasons, nullable ValueKind
> +    fields actually generate two PackedFields. This allows:
> +
> +    - backwards compatibility prior to Mojo support for nullable ValueKinds.
> +    - correct packing of fields for the aforementioned backwards compatibility.
> +
> +    When translating Fields to PackedFields, the original field is turned into
> +    two PackedFields: the first PackedField always has type mojom.BOOL, while
> +    the second PackedField has the non-nullable version of the field's kind.
> +
> +    When constructing these PackedFields, original_field references the field
> +    as defined in the mojom; the name as defined in the mojom will be used for
> +    all layers above the wire/data layer.
> +
> +    sub_ordinal is used to sort the two PackedFields correctly with respect to
> +    each other: the first mojom.BOOL field always has sub_ordinal 0, while the
> +    second field always has sub_ordinal 1.
> +
> +    Finally, linked_value_packed_field is used by the serialization and
> +    deserialization helpers, which generally just iterate over a PackedStruct's
> +    PackedField's in ordinal order. This allows the helpers to easily reference
> +    any related PackedFields rather than having to lookup related PackedFields
> +    by index while iterating.
>      """
>      self.field = field
>      self.index = index
>      self.ordinal = ordinal
> -    self.size = self.GetSizeForKind(field.kind)
> -    self.alignment = self.GetAlignmentForKind(field.kind)
> +    self.original_field = original_field
> +    self.sub_ordinal = sub_ordinal
> +    self.linked_value_packed_field = linked_value_packed_field
> +    self.size = self.GetSizeForKind(self.field.kind)
> +    self.alignment = self.GetAlignmentForKind(self.field.kind)
>      self.offset = None
>      self.bit = None
>      self.min_version = None
> @@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field):
>    return offset + pad
>
>
> -class PackedStruct(object):
> +def IsNullableValueKindPackedField(field):
> +  """Returns true if `field` is derived from a nullable ValueKind field.
> +
> +  Nullable ValueKind fields often require special handling in the bindings due
> +  to the way the implementation is constrained for wire compatibility.
> +  """
> +  assert isinstance(field, PackedField)
> +  return field.sub_ordinal is not None
> +
> +
> +def IsPrimaryNullableValueKindPackedField(field):
> +  """Returns true if `field` is derived from a nullable ValueKind mojom field
> +  and is the "primary" field.
> +
> +  The primary field is a bool PackedField that controls if the field should be
> +  considered as present or not; it will have a reference to the PackedField that
> +  holds the actual value representation if considered present.
> +
> +  Bindings code that translates between the wire protocol and the higher layers
> +  can use this to simplify mapping multiple PackedFields to the single field
> +  that is logically exposed to bindings consumers.
> +  """
> +  assert isinstance(field, PackedField)
> +  return field.linked_value_packed_field is not None
> +
> +
> +class PackedStruct:
>    def __init__(self, struct):
>      self.struct = struct
>      # |packed_fields| contains all the fields, in increasing offset order.
> @@ -139,9 +203,41 @@ class PackedStruct(object):
>      for index, field in enumerate(struct.fields):
>        if field.ordinal is not None:
>          ordinal = field.ordinal
> -      src_fields.append(PackedField(field, index, ordinal))
> +      # Nullable value types are a bit weird: they generate two PackedFields
> +      # despite being a single ValueKind. This is for wire compatibility to
> +      # ease the transition from legacy mojom syntax where nullable value types
> +      # were not supported.
> +      if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable:
> +        # The suffixes intentionally use Unicode codepoints which are considered
> +        # valid C++/Java/JavaScript identifiers, yet are unlikely to be used in
> +        # actual user code.
> +        has_value_field = copy.copy(field)
> +        has_value_field.name = f'{field.mojom_name}_$flag'
> +        has_value_field.kind = mojom.BOOL
> +
> +        value_field = copy.copy(field)
> +        value_field.name = f'{field.mojom_name}_$value'
> +        value_field.kind = field.kind.MakeUnnullableKind()
> +
> +        value_packed_field = PackedField(value_field,
> +                                         index,
> +                                         ordinal,
> +                                         original_field=field,
> +                                         sub_ordinal=1,
> +                                         linked_value_packed_field=None)
> +        has_value_packed_field = PackedField(
> +            has_value_field,
> +            index,
> +            ordinal,
> +            original_field=field,
> +            sub_ordinal=0,
> +            linked_value_packed_field=value_packed_field)
> +        src_fields.append(has_value_packed_field)
> +        src_fields.append(value_packed_field)
> +      else:
> +        src_fields.append(PackedField(field, index, ordinal))
>        ordinal += 1
> -    src_fields.sort(key=lambda field: field.ordinal)
> +    src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal))
>
>      # Set |min_version| for each field.
>      next_min_version = 0
> @@ -156,10 +252,11 @@ class PackedStruct(object):
>        if (packed_field.min_version != 0
>            and mojom.IsReferenceKind(packed_field.field.kind)
>            and not packed_field.field.kind.is_nullable):
> -        raise Exception("Non-nullable fields are only allowed in version 0 of "
> -                        "a struct. %s.%s is defined with [MinVersion=%d]." %
> -                        (self.struct.name, packed_field.field.name,
> -                         packed_field.min_version))
> +        raise Exception(
> +            "Non-nullable reference fields are only allowed in version 0 of a "
> +            "struct. %s.%s is defined with [MinVersion=%d]." %
> +            (self.struct.name, packed_field.field.name,
> +             packed_field.min_version))
>
>      src_field = src_fields[0]
>      src_field.offset = 0
> @@ -186,7 +283,7 @@ class PackedStruct(object):
>          dst_fields.append(src_field)
>
>
> -class ByteInfo(object):
> +class ByteInfo:
>    def __init__(self):
>      self.is_padding = False
>      self.packed_fields = []
> @@ -214,7 +311,7 @@ def GetByteLayout(packed_struct):
>    return byte_info
>
>
> -class VersionInfo(object):
> +class VersionInfo:
>    def __init__(self, version, num_fields, num_bytes):
>      self.version = version
>      self.num_fields = num_fields
> 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
> index 98c705ad..5c6c36d5 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# Copyright 2013 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> 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
> index 0da90058..807e2a4f 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# Copyright 2013 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> index 7580b780..71ce3c03 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> @@ -1,4 +1,4 @@
> -# Copyright 2013 The Chromium Authors. All rights reserved.
> +# Copyright 2013 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  """Convert parse tree to AST.
> @@ -12,17 +12,296 @@ already been parsed and converted to ASTs before.
>  import itertools
>  import os
>  import re
> -import sys
>
> +from collections import OrderedDict
>  from mojom.generate import generator
>  from mojom.generate import module as mojom
>  from mojom.parse import ast
>
>
> -def _IsStrOrUnicode(x):
> -  if sys.version_info[0] < 3:
> -    return isinstance(x, (unicode, str))
> -  return isinstance(x, str)
> +is_running_backwards_compatibility_check_hack = False
> +
> +### DO NOT ADD ENTRIES TO THIS LIST. ###
> +_EXTENSIBLE_ENUMS_MISSING_DEFAULT = (
> +    'x:arc.keymaster.mojom.Algorithm',
> +    'x:arc.keymaster.mojom.Digest',
> +    'x:arc.keymaster.mojom.SignatureResult',
> +    'x:arc.mojom.AccessibilityActionType',
> +    'x:arc.mojom.AccessibilityBooleanProperty',
> +    'x:arc.mojom.AccessibilityEventIntListProperty',
> +    'x:arc.mojom.AccessibilityEventIntProperty',
> +    'x:arc.mojom.AccessibilityEventStringProperty',
> +    'x:arc.mojom.AccessibilityEventType',
> +    'x:arc.mojom.AccessibilityFilterType',
> +    'x:arc.mojom.AccessibilityIntListProperty',
> +    'x:arc.mojom.AccessibilityIntProperty',
> +    'x:arc.mojom.AccessibilityLiveRegionType',
> +    'x:arc.mojom.AccessibilityNotificationStateType',
> +    'x:arc.mojom.AccessibilityRangeType',
> +    'x:arc.mojom.AccessibilitySelectionMode',
> +    'x:arc.mojom.AccessibilityStringListProperty',
> +    'x:arc.mojom.AccessibilityStringProperty',
> +    'x:arc.mojom.AccessibilityWindowBooleanProperty',
> +    'x:arc.mojom.AccessibilityWindowIntListProperty',
> +    'x:arc.mojom.AccessibilityWindowIntProperty',
> +    'x:arc.mojom.AccessibilityWindowStringProperty',
> +    'x:arc.mojom.AccessibilityWindowType',
> +    'x:arc.mojom.AccountCheckStatus',
> +    'x:arc.mojom.AccountUpdateType',
> +    'x:arc.mojom.ActionType',
> +    'x:arc.mojom.Algorithm',
> +    'x:arc.mojom.AndroidIdSource',
> +    'x:arc.mojom.AnrSource',
> +    'x:arc.mojom.AnrType',
> +    'x:arc.mojom.AppDiscoveryRequestState',
> +    'x:arc.mojom.AppKillType',
> +    'x:arc.mojom.AppPermission',
> +    'x:arc.mojom.AppPermissionGroup',
> +    'x:arc.mojom.AppReinstallState',
> +    'x:arc.mojom.AppShortcutItemType',
> +    'x:arc.mojom.ArcAuthCodeStatus',
> +    'x:arc.mojom.ArcClipboardDragDropEvent',
> +    'x:arc.mojom.ArcCorePriAbiMigEvent',
> +    'x:arc.mojom.ArcDnsQuery',
> +    'x:arc.mojom.ArcImageCopyPasteCompatAction',
> +    'x:arc.mojom.ArcNetworkError',
> +    'x:arc.mojom.ArcNetworkEvent',
> +    'x:arc.mojom.ArcNotificationEvent',
> +    'x:arc.mojom.ArcNotificationExpandState',
> +    'x:arc.mojom.ArcNotificationPriority',
> +    'x:arc.mojom.ArcNotificationRemoteInputState',
> +    'x:arc.mojom.ArcNotificationShownContents',
> +    'x:arc.mojom.ArcNotificationStyle',
> +    'x:arc.mojom.ArcNotificationType',
> +    'x:arc.mojom.ArcPipEvent',
> +    'x:arc.mojom.ArcResizeLockState',
> +    'x:arc.mojom.ArcSignInSuccess',
> +    'x:arc.mojom.ArcTimerResult',
> +    'x:arc.mojom.AudioSwitch',
> +    'x:arc.mojom.BluetoothAclState',
> +    'x:arc.mojom.BluetoothAdapterState',
> +    'x:arc.mojom.BluetoothAdvertisingDataType',
> +    'x:arc.mojom.BluetoothBondState',
> +    'x:arc.mojom.BluetoothDeviceType',
> +    'x:arc.mojom.BluetoothDiscoveryState',
> +    'x:arc.mojom.BluetoothGattDBAttributeType',
> +    'x:arc.mojom.BluetoothGattStatus',
> +    'x:arc.mojom.BluetoothPropertyType',
> +    'x:arc.mojom.BluetoothScanMode',
> +    'x:arc.mojom.BluetoothSdpAttributeType',
> +    'x:arc.mojom.BluetoothSocketType',
> +    'x:arc.mojom.BluetoothStatus',
> +    'x:arc.mojom.BootType',
> +    'x:arc.mojom.CaptionTextShadowType',
> +    'x:arc.mojom.ChangeType',
> +    'x:arc.mojom.ChromeAccountType',
> +    'x:arc.mojom.ChromeApp',
> +    'x:arc.mojom.ChromePage',
> +    'x:arc.mojom.ClockId',
> +    'x:arc.mojom.CloudProvisionFlowError',
> +    'x:arc.mojom.CommandResultType',
> +    'x:arc.mojom.CompanionLibApiId',
> +    'x:arc.mojom.ConnectionStateType',
> +    'x:arc.mojom.ContentChangeType',
> +    'x:arc.mojom.CpuRestrictionState',
> +    'x:arc.mojom.CursorCoordinateSpace',
> +    'x:arc.mojom.DataRestoreStatus',
> +    'x:arc.mojom.DecoderStatus',
> +    'x:arc.mojom.DeviceType',
> +    'x:arc.mojom.Digest',
> +    'x:arc.mojom.DisplayWakeLockType',
> +    'x:arc.mojom.EapMethod',
> +    'x:arc.mojom.EapPhase2Method',
> +    'x:arc.mojom.FileSelectorEventType',
> +    'x:arc.mojom.GMSCheckInError',
> +    'x:arc.mojom.GMSSignInError',
> +    'x:arc.mojom.GeneralSignInError',
> +    'x:arc.mojom.GetNetworksRequestType',
> +    'x:arc.mojom.HalPixelFormat',
> +    'x:arc.mojom.IPAddressType',
> +    'x:arc.mojom.InstallErrorReason',
> +    'x:arc.mojom.KeyFormat',
> +    'x:arc.mojom.KeyManagement',
> +    'x:arc.mojom.KeyPurpose',
> +    'x:arc.mojom.KeymasterError',
> +    'x:arc.mojom.MainAccountHashMigrationStatus',
> +    'x:arc.mojom.MainAccountResolutionStatus',
> +    'x:arc.mojom.ManagementChangeStatus',
> +    'x:arc.mojom.ManagementState',
> +    'x:arc.mojom.MessageCenterVisibility',
> +    'x:arc.mojom.MetricsType',
> +    'x:arc.mojom.MountEvent',
> +    'x:arc.mojom.NativeBridgeType',
> +    'x:arc.mojom.NetworkResult',
> +    'x:arc.mojom.NetworkType',
> +    'x:arc.mojom.OemCryptoAlgorithm',
> +    'x:arc.mojom.OemCryptoCipherMode',
> +    'x:arc.mojom.OemCryptoHdcpCapability',
> +    'x:arc.mojom.OemCryptoLicenseType',
> +    'x:arc.mojom.OemCryptoPrivateKey',
> +    'x:arc.mojom.OemCryptoProvisioningMethod',
> +    'x:arc.mojom.OemCryptoResult',
> +    'x:arc.mojom.OemCryptoRsaPaddingScheme',
> +    'x:arc.mojom.OemCryptoUsageEntryStatus',
> +    'x:arc.mojom.Padding',
> +    'x:arc.mojom.PaiFlowState',
> +    'x:arc.mojom.PatternType',
> +    'x:arc.mojom.PressureLevel',
> +    'x:arc.mojom.PrintColorMode',
> +    'x:arc.mojom.PrintContentType',
> +    'x:arc.mojom.PrintDuplexMode',
> +    'x:arc.mojom.PrinterStatus',
> +    'x:arc.mojom.ProcessState',
> +    'x:arc.mojom.PurchaseState',
> +    'x:arc.mojom.ReauthReason',
> +    'x:arc.mojom.ScaleFactor',
> +    'x:arc.mojom.SecurityType',
> +    'x:arc.mojom.SegmentStyle',
> +    'x:arc.mojom.SelectFilesActionType',
> +    'x:arc.mojom.SetNativeChromeVoxResponse',
> +    'x:arc.mojom.ShareFiles',
> +    'x:arc.mojom.ShowPackageInfoPage',
> +    'x:arc.mojom.SpanType',
> +    'x:arc.mojom.SupportedLinkChangeSource',
> +    'x:arc.mojom.TetheringClientState',
> +    'x:arc.mojom.TextInputType',
> +    'x:arc.mojom.TtsEventType',
> +    'x:arc.mojom.VideoCodecProfile',
> +    'x:arc.mojom.VideoDecodeAccelerator.Result',
> +    'x:arc.mojom.VideoEncodeAccelerator.Error',
> +    'x:arc.mojom.VideoFrameStorageType',
> +    'x:arc.mojom.VideoPixelFormat',
> +    'x:arc.mojom.WakefulnessMode',
> +    'x:arc.mojom.WebApkInstallResult',
> +    'x:ash.ime.mojom.InputFieldType',
> +    'x:ash.ime.mojom.PersonalizationMode',
> +    'x:ash.language.mojom.FeatureId',
> +    'x:blink.mojom.ScrollRestorationType',
> +    'x:chrome_cleaner.mojom.PromptAcceptance',
> +    'x:chromeos.cdm.mojom.CdmKeyStatus',
> +    'x:chromeos.cdm.mojom.CdmMessageType',
> +    'x:chromeos.cdm.mojom.CdmSessionType',
> +    'x:chromeos.cdm.mojom.DecryptStatus',
> +    'x:chromeos.cdm.mojom.EmeInitDataType',
> +    'x:chromeos.cdm.mojom.EncryptionScheme',
> +    'x:chromeos.cdm.mojom.HdcpVersion',
> +    'x:chromeos.cdm.mojom.OutputProtection.LinkType',
> +    'x:chromeos.cdm.mojom.OutputProtection.ProtectionType',
> +    'x:chromeos.cdm.mojom.PromiseException',
> +    'x:chromeos.cfm.mojom.EnqueuePriority',
> +    'x:chromeos.cfm.mojom.LoggerErrorCode',
> +    'x:chromeos.cfm.mojom.LoggerState',
> +    'x:chromeos.cros_healthd.mojom.CryptoAlgorithm',
> +    'x:chromeos.cros_healthd.mojom.EncryptionState',
> +    'x:chromeos.machine_learning.mojom.AnnotationUsecase',
> +    'x:chromeos.machine_learning.mojom.BuiltinModelId',
> +    'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult',
> +    'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus',
> +    'x:chromeos.machine_learning.mojom.EndpointReason',
> +    'x:chromeos.machine_learning.mojom.EndpointerType',
> +    'x:chromeos.machine_learning.mojom.ExecuteResult',
> +    'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status',
> +    'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status',
> +    'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult',
> +    'x:chromeos.machine_learning.mojom.LoadModelResult',
> +    'x:chromeos.machine_learning.mojom.Rotation',
> +    'x:chromeos.network_config.mojom.ConnectionStateType',
> +    'x:chromeos.network_config.mojom.DeviceStateType',
> +    'x:chromeos.network_config.mojom.IPConfigType',
> +    'x:chromeos.network_config.mojom.NetworkType',
> +    'x:chromeos.network_config.mojom.OncSource',
> +    'x:chromeos.network_config.mojom.PolicySource',
> +    'x:chromeos.network_config.mojom.PortalState',
> +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent',
> +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod',
> +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus',
> +    'x:cros.mojom.CameraClientType',
> +    'x:cros.mojom.CameraMetadataSectionStart',
> +    'x:cros.mojom.CameraMetadataTag',
> +    'x:cros.mojom.HalPixelFormat',
> +    'x:crosapi.mojom.AllowedPaths',
> +    'x:crosapi.mojom.BrowserAppInstanceType',
> +    'x:crosapi.mojom.CreationResult',
> +    'x:crosapi.mojom.DeviceAccessResultCode',
> +    'x:crosapi.mojom.DeviceMode',
> +    'x:crosapi.mojom.DlpRestrictionLevel',
> +    'x:crosapi.mojom.ExoImeSupport',
> +    'x:crosapi.mojom.FullscreenVisibility',
> +    'x:crosapi.mojom.GoogleServiceAuthError.State',
> +    'x:crosapi.mojom.IsInstallableResult',
> +    'x:crosapi.mojom.KeyTag',
> +    'x:crosapi.mojom.KeystoreSigningAlgorithmName',
> +    'x:crosapi.mojom.KeystoreType',
> +    'x:crosapi.mojom.LacrosFeedbackSource',
> +    'x:crosapi.mojom.MemoryPressureLevel',
> +    'x:crosapi.mojom.MetricsReportingManaged',
> +    'x:crosapi.mojom.NotificationType',
> +    'x:crosapi.mojom.OndeviceHandwritingSupport',
> +    'x:crosapi.mojom.OpenResult',
> +    'x:crosapi.mojom.PolicyDomain',
> +    'x:crosapi.mojom.RegistrationCodeType',
> +    'x:crosapi.mojom.ScaleFactor',
> +    'x:crosapi.mojom.SearchResult.OptionalBool',
> +    'x:crosapi.mojom.SelectFileDialogType',
> +    'x:crosapi.mojom.SelectFileResult',
> +    'x:crosapi.mojom.SharesheetResult',
> +    'x:crosapi.mojom.TouchEventType',
> +    'x:crosapi.mojom.VideoRotation',
> +    'x:crosapi.mojom.WallpaperLayout',
> +    'x:crosapi.mojom.WebAppInstallResultCode',
> +    'x:crosapi.mojom.WebAppUninstallResultCode',
> +    'x:device.mojom.HidBusType',
> +    'x:device.mojom.WakeLockReason',
> +    'x:device.mojom.WakeLockType',
> +    'x:drivefs.mojom.DialogReason.Type',
> +    'x:drivefs.mojom.DriveError.Type',
> +    'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus',
> +    'x:drivefs.mojom.FileMetadata.CanPinStatus',
> +    'x:drivefs.mojom.FileMetadata.Type',
> +    'x:drivefs.mojom.ItemEventReason',
> +    'x:drivefs.mojom.MirrorPathStatus',
> +    'x:drivefs.mojom.MirrorSyncStatus',
> +    'x:drivefs.mojom.QueryParameters.SortField',
> +    'x:fuzz.mojom.FuzzEnum',
> +    'x:media.mojom.FillLightMode',
> +    'x:media.mojom.MeteringMode',
> +    'x:media.mojom.PowerLineFrequency',
> +    'x:media.mojom.RedEyeReduction',
> +    'x:media.mojom.ResolutionChangePolicy',
> +    'x:media.mojom.VideoCaptureApi',
> +    'x:media.mojom.VideoCaptureBufferType',
> +    'x:media.mojom.VideoCaptureError',
> +    'x:media.mojom.VideoCaptureFrameDropReason',
> +    'x:media.mojom.VideoCapturePixelFormat',
> +    'x:media.mojom.VideoCaptureTransportType',
> +    'x:media.mojom.VideoFacingMode',
> +    'x:media_session.mojom.AudioFocusType',
> +    'x:media_session.mojom.CameraState',
> +    'x:media_session.mojom.EnforcementMode',
> +    'x:media_session.mojom.MediaAudioVideoState',
> +    'x:media_session.mojom.MediaImageBitmapColorType',
> +    'x:media_session.mojom.MediaPictureInPictureState',
> +    'x:media_session.mojom.MediaPlaybackState',
> +    'x:media_session.mojom.MediaSession.SuspendType',
> +    'x:media_session.mojom.MediaSessionAction',
> +    'x:media_session.mojom.MediaSessionImageType',
> +    'x:media_session.mojom.MediaSessionInfo.SessionState',
> +    'x:media_session.mojom.MicrophoneState',
> +    'x:ml.model_loader.mojom.ComputeResult',
> +    'x:ml.model_loader.mojom.CreateModelLoaderResult',
> +    'x:ml.model_loader.mojom.LoadModelResult',
> +    'x:mojo.test.AnExtensibleEnum',
> +    'x:mojo.test.EnumB',
> +    'x:mojo.test.ExtensibleEmptyEnum',
> +    'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault',
> +    'x:network.mojom.WebSandboxFlags',
> +    'x:payments.mojom.BillingResponseCode',
> +    'x:payments.mojom.CreateDigitalGoodsResponseCode',
> +    'x:payments.mojom.ItemType',
> +    'x:printing.mojom.PrinterType',
> +    'x:ui.mojom.KeyboardCode',
> +)
> +### DO NOT ADD ENTRIES TO THIS LIST. ###
>
>
>  def _DuplicateName(values):
> @@ -98,12 +377,6 @@ def _MapKind(kind):
>    }
>    if kind.endswith('?'):
>      base_kind = _MapKind(kind[0:-1])
> -    # NOTE: This doesn't rule out enum types. Those will be detected later, when
> -    # cross-reference is established.
> -    reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',
> -                       'rma', 'rca')
> -    if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
> -      raise Exception('A type (spec "%s") cannot be made nullable' % base_kind)
>      return '?' + base_kind
>    if kind.endswith('}'):
>      lbracket = kind.rfind('{')
> @@ -113,8 +386,6 @@ def _MapKind(kind):
>      lbracket = kind.rfind('[')
>      typename = kind[0:lbracket]
>      return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)
> -  if kind.endswith('&'):
> -    return 'r:' + _MapKind(kind[0:-1])
>    if kind.startswith('asso<'):
>      assert kind.endswith('>')
>      return 'asso:' + _MapKind(kind[5:-1])
> @@ -135,13 +406,35 @@ def _MapKind(kind):
>    return 'x:' + kind
>
>
> -def _AttributeListToDict(attribute_list):
> +def _MapValueToEnum(module, value):
> +  # True/False/None
> +  if value is None:
> +    return value
> +  if not isinstance(value, str):
> +    return value
> +  # Otherwise try to find it.
> +  try:
> +    trial = _LookupValue(module, None, None, ('IDENTIFIER', value))
> +    if isinstance(trial, mojom.ConstantValue):
> +      return trial.constant
> +    if isinstance(trial, mojom.EnumValue):
> +      return trial
> +  except ValueError:
> +    pass
> +  # Return the string if it did not resolve to a constant or enum.
> +  return value
> +
> +
> +def _AttributeListToDict(module, attribute_list):
>    if attribute_list is None:
>      return None
>    assert isinstance(attribute_list, ast.AttributeList)
> -  # TODO(vtl): Check for duplicate keys here.
> -  return dict(
> -      [(attribute.key, attribute.value) for attribute in attribute_list])
> +  attributes = dict()
> +  for attribute in attribute_list:
> +    if attribute.key in attributes:
> +      raise Exception("Duplicate key (%s) in attribute list" % attribute.key)
> +    attributes[attribute.key] = _MapValueToEnum(module, attribute.value)
> +  return attributes
>
>
>  builtin_values = frozenset([
> @@ -257,7 +550,8 @@ def _Kind(kinds, spec, scope):
>      return kind
>
>    if spec.startswith('?'):
> -    kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
> +    kind = _Kind(kinds, spec[1:], scope)
> +    kind = kind.MakeNullableKind()
>    elif spec.startswith('a:'):
>      kind = mojom.Array(_Kind(kinds, spec[2:], scope))
>    elif spec.startswith('asso:'):
> @@ -345,7 +639,7 @@ def _Struct(module, parsed_struct):
>              struct.fields_data.append,
>          })
>
> -  struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
> +  struct.attributes = _AttributeListToDict(module, parsed_struct.attribute_list)
>
>    # Enforce that a [Native] attribute is set to make native-only struct
>    # declarations more explicit.
> @@ -377,7 +671,7 @@ def _Union(module, parsed_union):
>    union.fields_data = []
>    _ProcessElements(parsed_union.mojom_name, parsed_union.body,
>                     {ast.UnionField: union.fields_data.append})
> -  union.attributes = _AttributeListToDict(parsed_union.attribute_list)
> +  union.attributes = _AttributeListToDict(module, parsed_union.attribute_list)
>    return union
>
>
> @@ -398,7 +692,7 @@ def _StructField(module, parsed_field, struct):
>    field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
>    field.default = _LookupValue(module, struct, field.kind,
>                                 parsed_field.default_value)
> -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
> +  field.attributes = _AttributeListToDict(module, parsed_field.attribute_list)
>    return field
>
>
> @@ -414,11 +708,21 @@ def _UnionField(module, parsed_field, union):
>    """
>    field = mojom.UnionField()
>    field.mojom_name = parsed_field.mojom_name
> +  # Disallow unions from being self-recursive.
> +  parsed_typename = parsed_field.typename
> +  if parsed_typename.endswith('?'):
> +    parsed_typename = parsed_typename[:-1]
> +  assert parsed_typename != union.mojom_name
>    field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),
>                       (module.mojom_namespace, union.mojom_name))
>    field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
>    field.default = None
> -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
> +  field.attributes = _AttributeListToDict(module, parsed_field.attribute_list)
> +  if field.is_default and not mojom.IsNullableKind(field.kind) and \
> +     not mojom.IsIntegralKind(field.kind):
> +    raise Exception(
> +        '[Default] field for union %s must be nullable or integral type.' %
> +        union.mojom_name)
>    return field
>
>
> @@ -439,7 +743,8 @@ def _Parameter(module, parsed_param, interface):
>    parameter.ordinal = (parsed_param.ordinal.value
>                         if parsed_param.ordinal else None)
>    parameter.default = None  # TODO(tibell): We never have these. Remove field?
> -  parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)
> +  parameter.attributes = _AttributeListToDict(module,
> +                                              parsed_param.attribute_list)
>    return parameter
>
>
> @@ -464,7 +769,7 @@ def _Method(module, parsed_method, interface):
>      method.response_parameters = list(
>          map(lambda parameter: _Parameter(module, parameter, interface),
>              parsed_method.response_parameter_list))
> -  method.attributes = _AttributeListToDict(parsed_method.attribute_list)
> +  method.attributes = _AttributeListToDict(module, parsed_method.attribute_list)
>
>    # Enforce that only methods with response can have a [Sync] attribute.
>    if method.sync and method.response_parameters is None:
> @@ -492,7 +797,8 @@ def _Interface(module, parsed_iface):
>    interface.mojom_name = parsed_iface.mojom_name
>    interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name
>    module.kinds[interface.spec] = interface
> -  interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
> +  interface.attributes = _AttributeListToDict(module,
> +                                              parsed_iface.attribute_list)
>    interface.enums = []
>    interface.constants = []
>    interface.methods_data = []
> @@ -522,7 +828,7 @@ def _EnumField(module, enum, parsed_field):
>    field = mojom.EnumField()
>    field.mojom_name = parsed_field.mojom_name
>    field.value = _LookupValue(module, enum, None, parsed_field.value)
> -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
> +  field.attributes = _AttributeListToDict(module, parsed_field.attribute_list)
>    value = mojom.EnumValue(module, enum, field)
>    module.values[value.GetSpec()] = value
>    return field
> @@ -544,7 +850,7 @@ def _ResolveNumericEnumValues(enum):
>        prev_value += 1
>
>      # Integral value (e.g: BEGIN = -0x1).
> -    elif _IsStrOrUnicode(field.value):
> +    elif isinstance(field.value, str):
>        prev_value = int(field.value, 0)
>
>      # Reference to a previous enum value (e.g: INIT = BEGIN).
> @@ -588,7 +894,7 @@ def _Enum(module, parsed_enum, parent_kind):
>      mojom_name = parent_kind.mojom_name + '.' + mojom_name
>    enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
>    enum.parent_kind = parent_kind
> -  enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
> +  enum.attributes = _AttributeListToDict(module, parsed_enum.attribute_list)
>
>    if not enum.native_only:
>      enum.fields = list(
> @@ -600,11 +906,18 @@ def _Enum(module, parsed_enum, parent_kind):
>      for field in enum.fields:
>        if field.default:
>          if not enum.extensible:
> -          raise Exception('Non-extensible enums may not specify a default')
> -        if enum.default_field is not None:
>            raise Exception(
> -              'Only one enumerator value may be specified as the default')
> +              f'Non-extensible enum {enum.spec} may not specify a default')
> +        if enum.default_field is not None:
> +          raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}')
>          enum.default_field = field
> +    # While running the backwards compatibility check, ignore errors because the
> +    # old version of the enum might not specify [Default].
> +    if (enum.extensible and enum.default_field is None
> +        and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT
> +        and not is_running_backwards_compatibility_check_hack):
> +      raise Exception(
> +          f'Extensible enum {enum.spec} must specify a [Default] enumerator')
>
>    module.kinds[enum.spec] = enum
>
> @@ -696,6 +1009,11 @@ def _CollectReferencedKinds(module, all_defined_kinds):
>          for referenced_kind in extract_referenced_user_kinds(param.kind):
>            sanitized_kind = sanitize_kind(referenced_kind)
>            referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
> +  # Consts can reference imported enums.
> +  for const in module.constants:
> +    if not const.kind in mojom.PRIMITIVES:
> +      sanitized_kind = sanitize_kind(const.kind)
> +      referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
>
>    return referenced_user_kinds
>
> @@ -741,6 +1059,16 @@ def _AssertTypeIsStable(kind):
>            assertDependencyIsStable(response_param.kind)
>
>
> +def _AssertStructIsValid(kind):
> +  expected_ordinals = set(range(0, len(kind.fields)))
> +  ordinals = set(map(lambda field: field.ordinal, kind.fields))
> +  if ordinals != expected_ordinals:
> +    raise Exception(
> +        'Structs must use contiguous ordinals starting from 0. ' +
> +        '{} is missing the following ordinals: {}.'.format(
> +            kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals))))
> +
> +
>  def _Module(tree, path, imports):
>    """
>    Args:
> @@ -810,8 +1138,17 @@ def _Module(tree, path, imports):
>      union.fields = list(
>          map(lambda field: _UnionField(module, field, union), union.fields_data))
>      _AssignDefaultOrdinals(union.fields)
> +    for field in union.fields:
> +      if field.is_default:
> +        if union.default_field is not None:
> +          raise Exception('Multiple [Default] fields in union %s.' %
> +                          union.mojom_name)
> +        union.default_field = field
>      del union.fields_data
>      all_defined_kinds[union.spec] = union
> +    if union.extensible and union.default_field is None:
> +      raise Exception('Extensible union %s must specify a [Default] field' %
> +                      union.mojom_name)
>
>    for interface in module.interfaces:
>      interface.methods = list(
> @@ -829,8 +1166,8 @@ def _Module(tree, path, imports):
>                                                   all_defined_kinds.values())
>    imported_kind_specs = set(all_referenced_kinds.keys()).difference(
>        set(all_defined_kinds.keys()))
> -  module.imported_kinds = dict(
> -      (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)
> +  module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])
> +                                      for spec in sorted(imported_kind_specs))
>
>    generator.AddComputedData(module)
>    for iface in module.interfaces:
> @@ -847,6 +1184,9 @@ def _Module(tree, path, imports):
>        if kind.stable:
>          _AssertTypeIsStable(kind)
>
> +  for kind in module.structs:
> +    _AssertStructIsValid(kind)
> +
>    return module
>
>
> 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
> index 19905c8a..42593745 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -69,5 +69,38 @@ class TranslateTest(unittest.TestCase):
>      # pylint: disable=W0212
>      self.assertEquals(
>          translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface")
> -    self.assertEquals(
> -        translate._MapKind("asso<SomeInterface&>?"), "?asso:r:x:SomeInterface")
> +    self.assertEquals(translate._MapKind("rca<SomeInterface>?"),
> +                      "?rca:x:SomeInterface")
> +
> +  def testSelfRecursiveUnions(self):
> +    """Verifies _UnionField() raises when a union is self-recursive."""
> +    tree = ast.Mojom(None, ast.ImportList(), [
> +        ast.Union("SomeUnion", None,
> +                  ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion")]))
> +    ])
> +    with self.assertRaises(Exception):
> +      translate.OrderedModule(tree, "mojom_tree", [])
> +
> +    tree = ast.Mojom(None, ast.ImportList(), [
> +        ast.Union(
> +            "SomeUnion", None,
> +            ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion?")]))
> +    ])
> +    with self.assertRaises(Exception):
> +      translate.OrderedModule(tree, "mojom_tree", [])
> +
> +  def testDuplicateAttributesException(self):
> +    tree = ast.Mojom(None, ast.ImportList(), [
> +        ast.Union(
> +            "FakeUnion",
> +            ast.AttributeList([
> +                ast.Attribute("key1", "value"),
> +                ast.Attribute("key1", "value")
> +            ]),
> +            ast.UnionBody([
> +                ast.UnionField("a", None, None, "int32"),
> +                ast.UnionField("b", None, None, "string")
> +            ]))
> +    ])
> +    with self.assertRaises(Exception):
> +      translate.OrderedModule(tree, "mojom_tree", [])
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> index 1f0db200..80e8c657 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  """Node classes for the AST for a Mojo IDL file."""
> @@ -8,17 +8,14 @@
>  # and lineno). You may also define __repr__() to help with analyzing test
>  # failures, especially for more complex types.
>
> +import os.path
>
> -import sys
>
> +# Instance of 'NodeListBase' has no '_list_item_type' member (no-member)
> +# pylint: disable=no-member
>
> -def _IsStrOrUnicode(x):
> -  if sys.version_info[0] < 3:
> -    return isinstance(x, (unicode, str))
> -  return isinstance(x, str)
>
> -
> -class NodeBase(object):
> +class NodeBase:
>    """Base class for nodes in the AST."""
>
>    def __init__(self, filename=None, lineno=None):
> @@ -43,7 +40,7 @@ class NodeListBase(NodeBase):
>    classes, in a tuple) of the members of the list.)"""
>
>    def __init__(self, item_or_items=None, **kwargs):
> -    super(NodeListBase, self).__init__(**kwargs)
> +    super().__init__(**kwargs)
>      self.items = []
>      if item_or_items is None:
>        pass
> @@ -62,7 +59,7 @@ class NodeListBase(NodeBase):
>      return self.items.__iter__()
>
>    def __eq__(self, other):
> -    return super(NodeListBase, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.items == other.items
>
>    # Implement this so that on failure, we get slightly more sensible output.
> @@ -96,7 +93,7 @@ class Definition(NodeBase):
>    include parameter definitions.) This class is meant to be subclassed."""
>
>    def __init__(self, mojom_name, **kwargs):
> -    assert _IsStrOrUnicode(mojom_name)
> +    assert isinstance(mojom_name, str)
>      NodeBase.__init__(self, **kwargs)
>      self.mojom_name = mojom_name
>
> @@ -108,13 +105,13 @@ class Attribute(NodeBase):
>    """Represents an attribute."""
>
>    def __init__(self, key, value, **kwargs):
> -    assert _IsStrOrUnicode(key)
> -    super(Attribute, self).__init__(**kwargs)
> +    assert isinstance(key, str)
> +    super().__init__(**kwargs)
>      self.key = key
>      self.value = value
>
>    def __eq__(self, other):
> -    return super(Attribute, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.key == other.key and \
>             self.value == other.value
>
> @@ -131,17 +128,17 @@ class Const(Definition):
>    def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
>      # The typename is currently passed through as a string.
> -    assert _IsStrOrUnicode(typename)
> +    assert isinstance(typename, str)
>      # The value is either a literal (currently passed through as a string) or a
>      # "wrapped identifier".
> -    assert _IsStrOrUnicode or isinstance(value, tuple)
> -    super(Const, self).__init__(mojom_name, **kwargs)
> +    assert isinstance(value, (tuple, str))
> +    super().__init__(mojom_name, **kwargs)
>      self.attribute_list = attribute_list
>      self.typename = typename
>      self.value = value
>
>    def __eq__(self, other):
> -    return super(Const, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.typename == other.typename and \
>             self.value == other.value
> @@ -153,12 +150,12 @@ class Enum(Definition):
>    def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
>      assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)
> -    super(Enum, self).__init__(mojom_name, **kwargs)
> +    super().__init__(mojom_name, **kwargs)
>      self.attribute_list = attribute_list
>      self.enum_value_list = enum_value_list
>
>    def __eq__(self, other):
> -    return super(Enum, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.enum_value_list == other.enum_value_list
>
> @@ -170,13 +167,13 @@ class EnumValue(Definition):
>      # The optional value is either an int (which is current a string) or a
>      # "wrapped identifier".
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
> -    assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)
> -    super(EnumValue, self).__init__(mojom_name, **kwargs)
> +    assert value is None or isinstance(value, (tuple, str))
> +    super().__init__(mojom_name, **kwargs)
>      self.attribute_list = attribute_list
>      self.value = value
>
>    def __eq__(self, other):
> -    return super(EnumValue, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.value == other.value
>
> @@ -193,13 +190,14 @@ class Import(NodeBase):
>
>    def __init__(self, attribute_list, import_filename, **kwargs):
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
> -    assert _IsStrOrUnicode(import_filename)
> -    super(Import, self).__init__(**kwargs)
> +    assert isinstance(import_filename, str)
> +    super().__init__(**kwargs)
>      self.attribute_list = attribute_list
> -    self.import_filename = import_filename
> +    # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.
> +    self.import_filename = os.path.normpath(import_filename).replace('\\', '/')
>
>    def __eq__(self, other):
> -    return super(Import, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.import_filename == other.import_filename
>
> @@ -216,12 +214,12 @@ class Interface(Definition):
>    def __init__(self, mojom_name, attribute_list, body, **kwargs):
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
>      assert isinstance(body, InterfaceBody)
> -    super(Interface, self).__init__(mojom_name, **kwargs)
> +    super().__init__(mojom_name, **kwargs)
>      self.attribute_list = attribute_list
>      self.body = body
>
>    def __eq__(self, other):
> -    return super(Interface, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.body == other.body
>
> @@ -236,14 +234,14 @@ class Method(Definition):
>      assert isinstance(parameter_list, ParameterList)
>      assert response_parameter_list is None or \
>             isinstance(response_parameter_list, ParameterList)
> -    super(Method, self).__init__(mojom_name, **kwargs)
> +    super().__init__(mojom_name, **kwargs)
>      self.attribute_list = attribute_list
>      self.ordinal = ordinal
>      self.parameter_list = parameter_list
>      self.response_parameter_list = response_parameter_list
>
>    def __eq__(self, other):
> -    return super(Method, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.ordinal == other.ordinal and \
>             self.parameter_list == other.parameter_list and \
> @@ -264,12 +262,12 @@ class Module(NodeBase):
>      # |mojom_namespace| is either none or a "wrapped identifier".
>      assert mojom_namespace is None or isinstance(mojom_namespace, tuple)
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
> -    super(Module, self).__init__(**kwargs)
> +    super().__init__(**kwargs)
>      self.mojom_namespace = mojom_namespace
>      self.attribute_list = attribute_list
>
>    def __eq__(self, other):
> -    return super(Module, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.mojom_namespace == other.mojom_namespace and \
>             self.attribute_list == other.attribute_list
>
> @@ -281,13 +279,13 @@ class Mojom(NodeBase):
>      assert module is None or isinstance(module, Module)
>      assert isinstance(import_list, ImportList)
>      assert isinstance(definition_list, list)
> -    super(Mojom, self).__init__(**kwargs)
> +    super().__init__(**kwargs)
>      self.module = module
>      self.import_list = import_list
>      self.definition_list = definition_list
>
>    def __eq__(self, other):
> -    return super(Mojom, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.module == other.module and \
>             self.import_list == other.import_list and \
>             self.definition_list == other.definition_list
> @@ -302,11 +300,11 @@ class Ordinal(NodeBase):
>
>    def __init__(self, value, **kwargs):
>      assert isinstance(value, int)
> -    super(Ordinal, self).__init__(**kwargs)
> +    super().__init__(**kwargs)
>      self.value = value
>
>    def __eq__(self, other):
> -    return super(Ordinal, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.value == other.value
>
>
> @@ -314,18 +312,18 @@ class Parameter(NodeBase):
>    """Represents a method request or response parameter."""
>
>    def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
> -    assert _IsStrOrUnicode(mojom_name)
> +    assert isinstance(mojom_name, str)
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
>      assert ordinal is None or isinstance(ordinal, Ordinal)
> -    assert _IsStrOrUnicode(typename)
> -    super(Parameter, self).__init__(**kwargs)
> +    assert isinstance(typename, str)
> +    super().__init__(**kwargs)
>      self.mojom_name = mojom_name
>      self.attribute_list = attribute_list
>      self.ordinal = ordinal
>      self.typename = typename
>
>    def __eq__(self, other):
> -    return super(Parameter, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.mojom_name == other.mojom_name and \
>             self.attribute_list == other.attribute_list and \
>             self.ordinal == other.ordinal and \
> @@ -344,42 +342,51 @@ class Struct(Definition):
>    def __init__(self, mojom_name, attribute_list, body, **kwargs):
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
>      assert isinstance(body, StructBody) or body is None
> -    super(Struct, self).__init__(mojom_name, **kwargs)
> +    super().__init__(mojom_name, **kwargs)
>      self.attribute_list = attribute_list
>      self.body = body
>
>    def __eq__(self, other):
> -    return super(Struct, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.body == other.body
>
> +  def __repr__(self):
> +    return "Struct(mojom_name = %s, attribute_list = %s, body = %s)" % (
> +        self.mojom_name, self.attribute_list, self.body)
> +
>
>  class StructField(Definition):
>    """Represents a struct field definition."""
>
>    def __init__(self, mojom_name, attribute_list, ordinal, typename,
>                 default_value, **kwargs):
> -    assert _IsStrOrUnicode(mojom_name)
> +    assert isinstance(mojom_name, str)
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
>      assert ordinal is None or isinstance(ordinal, Ordinal)
> -    assert _IsStrOrUnicode(typename)
> +    assert isinstance(typename, str)
>      # The optional default value is currently either a value as a string or a
>      # "wrapped identifier".
> -    assert default_value is None or _IsStrOrUnicode(default_value) or \
> -        isinstance(default_value, tuple)
> -    super(StructField, self).__init__(mojom_name, **kwargs)
> +    assert default_value is None or isinstance(default_value, (str, tuple))
> +    super().__init__(mojom_name, **kwargs)
>      self.attribute_list = attribute_list
>      self.ordinal = ordinal
>      self.typename = typename
>      self.default_value = default_value
>
>    def __eq__(self, other):
> -    return super(StructField, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.ordinal == other.ordinal and \
>             self.typename == other.typename and \
>             self.default_value == other.default_value
>
> +  def __repr__(self):
> +    return ("StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, "
> +            "typename = %s, default_value = %s") % (
> +                self.mojom_name, self.attribute_list, self.ordinal,
> +                self.typename, self.default_value)
> +
>
>  # This needs to be declared after |StructField|.
>  class StructBody(NodeListBase):
> @@ -394,29 +401,29 @@ class Union(Definition):
>    def __init__(self, mojom_name, attribute_list, body, **kwargs):
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
>      assert isinstance(body, UnionBody)
> -    super(Union, self).__init__(mojom_name, **kwargs)
> +    super().__init__(mojom_name, **kwargs)
>      self.attribute_list = attribute_list
>      self.body = body
>
>    def __eq__(self, other):
> -    return super(Union, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.body == other.body
>
>
>  class UnionField(Definition):
>    def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
> -    assert _IsStrOrUnicode(mojom_name)
> +    assert isinstance(mojom_name, str)
>      assert attribute_list is None or isinstance(attribute_list, AttributeList)
>      assert ordinal is None or isinstance(ordinal, Ordinal)
> -    assert _IsStrOrUnicode(typename)
> -    super(UnionField, self).__init__(mojom_name, **kwargs)
> +    assert isinstance(typename, str)
> +    super().__init__(mojom_name, **kwargs)
>      self.attribute_list = attribute_list
>      self.ordinal = ordinal
>      self.typename = typename
>
>    def __eq__(self, other):
> -    return super(UnionField, self).__eq__(other) and \
> +    return super().__eq__(other) and \
>             self.attribute_list == other.attribute_list and \
>             self.ordinal == other.ordinal and \
>             self.typename == other.typename
> 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
> index 62798631..c3637671 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -14,11 +14,11 @@ class _TestNode(ast.NodeBase):
>    """Node type for tests."""
>
>    def __init__(self, value, **kwargs):
> -    super(_TestNode, self).__init__(**kwargs)
> +    super().__init__(**kwargs)
>      self.value = value
>
>    def __eq__(self, other):
> -    return super(_TestNode, self).__eq__(other) and self.value == other.value
> +    return super().__eq__(other) and self.value == other.value
>
>
>  class _TestNodeList(ast.NodeListBase):
> 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
> index 3cb73c5d..b7b06bfb 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
> @@ -1,4 +1,4 @@
> -# Copyright 2018 The Chromium Authors. All rights reserved.
> +# Copyright 2018 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  """Helpers for processing conditionally enabled features in a mojom."""
> @@ -17,8 +17,10 @@ class EnableIfError(Error):
>  def _IsEnabled(definition, enabled_features):
>    """Returns true if a definition is enabled.
>
> -  A definition is enabled if it has no EnableIf attribute, or if the value of
> -  the EnableIf attribute is in enabled_features.
> +  A definition is enabled if it has no EnableIf/EnableIfNot attribute.
> +  It is retained if it has an EnableIf attribute and the attribute is in
> +  enabled_features. It is retained if it has an EnableIfNot attribute and the
> +  attribute is not in enabled features.
>    """
>    if not hasattr(definition, "attribute_list"):
>      return True
> @@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features):
>
>    already_defined = False
>    for a in definition.attribute_list:
> -    if a.key == 'EnableIf':
> +    if a.key == 'EnableIf' or a.key == 'EnableIfNot':
>        if already_defined:
>          raise EnableIfError(
>              definition.filename,
> -            "EnableIf attribute may only be defined once per field.",
> +            "EnableIf/EnableIfNot attribute may only be set once per field.",
>              definition.lineno)
>        already_defined = True
>
>    for attribute in definition.attribute_list:
>      if attribute.key == 'EnableIf' and attribute.value not in enabled_features:
>        return False
> +    if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:
> +      return False
>    return True
>
>
> 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
> index aa609be7..5fc58202 100644
> --- 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
> @@ -1,4 +1,4 @@
> -# Copyright 2018 The Chromium Authors. All rights reserved.
> +# Copyright 2018 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -55,6 +55,48 @@ class ConditionalFeaturesTest(unittest.TestCase):
>      """
>      self.parseAndAssertEqual(const_source, expected_source)
>
> +  def testFilterIfNotConst(self):
> +    """Test that Consts are correctly filtered."""
> +    const_source = """
> +      [EnableIfNot=blue]
> +      const int kMyConst1 = 1;
> +      [EnableIfNot=orange]
> +      const double kMyConst2 = 2;
> +      [EnableIf=blue]
> +      const int kMyConst3 = 3;
> +      [EnableIfNot=blue]
> +      const int kMyConst4 = 4;
> +      [EnableIfNot=purple]
> +      const int kMyConst5 = 5;
> +    """
> +    expected_source = """
> +      [EnableIfNot=orange]
> +      const double kMyConst2 = 2;
> +      [EnableIf=blue]
> +      const int kMyConst3 = 3;
> +      [EnableIfNot=purple]
> +      const int kMyConst5 = 5;
> +    """
> +    self.parseAndAssertEqual(const_source, expected_source)
> +
> +  def testFilterIfNotMultipleConst(self):
> +    """Test that Consts are correctly filtered."""
> +    const_source = """
> +      [EnableIfNot=blue]
> +      const int kMyConst1 = 1;
> +      [EnableIfNot=orange]
> +      const double kMyConst2 = 2;
> +      [EnableIfNot=orange]
> +      const int kMyConst3 = 3;
> +    """
> +    expected_source = """
> +      [EnableIfNot=orange]
> +      const double kMyConst2 = 2;
> +      [EnableIfNot=orange]
> +      const int kMyConst3 = 3;
> +    """
> +    self.parseAndAssertEqual(const_source, expected_source)
> +
>    def testFilterEnum(self):
>      """Test that EnumValues are correctly filtered from an Enum."""
>      enum_source = """
> @@ -91,6 +133,24 @@ class ConditionalFeaturesTest(unittest.TestCase):
>      """
>      self.parseAndAssertEqual(import_source, expected_source)
>
> +  def testFilterIfNotImport(self):
> +    """Test that imports are correctly filtered from a Mojom."""
> +    import_source = """
> +      [EnableIf=blue]
> +      import "foo.mojom";
> +      [EnableIfNot=purple]
> +      import "bar.mojom";
> +      [EnableIfNot=green]
> +      import "baz.mojom";
> +    """
> +    expected_source = """
> +      [EnableIf=blue]
> +      import "foo.mojom";
> +      [EnableIfNot=purple]
> +      import "bar.mojom";
> +    """
> +    self.parseAndAssertEqual(import_source, expected_source)
> +
>    def testFilterInterface(self):
>      """Test that definitions are correctly filtered from an Interface."""
>      interface_source = """
> @@ -175,6 +235,50 @@ class ConditionalFeaturesTest(unittest.TestCase):
>      """
>      self.parseAndAssertEqual(struct_source, expected_source)
>
> +  def testFilterIfNotStruct(self):
> +    """Test that definitions are correctly filtered from a Struct."""
> +    struct_source = """
> +      struct MyStruct {
> +        [EnableIf=blue]
> +        enum MyEnum {
> +          VALUE1,
> +          [EnableIfNot=red]
> +          VALUE2,
> +        };
> +        [EnableIfNot=yellow]
> +        const double kMyConst = 1.23;
> +        [EnableIf=green]
> +        int32 a;
> +        double b;
> +        [EnableIfNot=purple]
> +        int32 c;
> +        [EnableIf=blue]
> +        double d;
> +        int32 e;
> +        [EnableIfNot=red]
> +        double f;
> +      };
> +    """
> +    expected_source = """
> +      struct MyStruct {
> +        [EnableIf=blue]
> +        enum MyEnum {
> +          VALUE1,
> +        };
> +        [EnableIfNot=yellow]
> +        const double kMyConst = 1.23;
> +        [EnableIf=green]
> +        int32 a;
> +        double b;
> +        [EnableIfNot=purple]
> +        int32 c;
> +        [EnableIf=blue]
> +        double d;
> +        int32 e;
> +      };
> +    """
> +    self.parseAndAssertEqual(struct_source, expected_source)
> +
>    def testFilterUnion(self):
>      """Test that UnionFields are correctly filtered from a Union."""
>      union_source = """
> @@ -228,6 +332,30 @@ class ConditionalFeaturesTest(unittest.TestCase):
>                        conditional_features.RemoveDisabledDefinitions,
>                        definition, ENABLED_FEATURES)
>
> +  def testMultipleEnableIfs(self):
> +    source = """
> +      enum Foo {
> +        [EnableIf=red,EnableIfNot=yellow]
> +        kBarValue = 5,
> +      };
> +    """
> +    definition = parser.Parse(source, "my_file.mojom")
> +    self.assertRaises(conditional_features.EnableIfError,
> +                      conditional_features.RemoveDisabledDefinitions,
> +                      definition, ENABLED_FEATURES)
> +
> +  def testMultipleEnableIfs(self):
> +    source = """
> +      enum Foo {
> +        [EnableIfNot=red,EnableIfNot=yellow]
> +        kBarValue = 5,
> +      };
> +    """
> +    definition = parser.Parse(source, "my_file.mojom")
> +    self.assertRaises(conditional_features.EnableIfError,
> +                      conditional_features.RemoveDisabledDefinitions,
> +                      definition, ENABLED_FEATURES)
> +
>
>  if __name__ == '__main__':
>    unittest.main()
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> index 3e084bbf..73ca15df 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -22,7 +22,7 @@ class LexError(Error):
>
>  # We have methods which look like they could be functions:
>  # pylint: disable=R0201
> -class Lexer(object):
> +class Lexer:
>    def __init__(self, filename):
>      self.filename = filename
>
> @@ -81,7 +81,6 @@ class Lexer(object):
>        # Operators
>        'MINUS',
>        'PLUS',
> -      'AMP',
>        'QSTN',
>
>        # Assignment
> @@ -168,7 +167,6 @@ class Lexer(object):
>    # Operators
>    t_MINUS = r'-'
>    t_PLUS = r'\+'
> -  t_AMP = r'&'
>    t_QSTN = r'\?'
>
>    # =
> 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
> index eadc6587..ce376da6 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -146,7 +146,6 @@ class LexerTest(unittest.TestCase):
>          self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+"))
>      self.assertEquals(
>          self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-"))
> -    self.assertEquals(self._SingleTokenForInput("&"), _MakeLexToken("AMP", "&"))
>      self.assertEquals(
>          self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?"))
>      self.assertEquals(
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> index b3b803d6..683ae757 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  """Generates a syntax tree from a Mojo IDL file."""
> @@ -33,7 +33,7 @@ class ParseError(Error):
>
>  # We have methods which look like they could be functions:
>  # pylint: disable=R0201
> -class Parser(object):
> +class Parser:
>    def __init__(self, lexer, source, filename):
>      self.tokens = lexer.tokens
>      self.source = source
> @@ -140,11 +140,18 @@ class Parser(object):
>      p[0].Append(p[3])
>
>    def p_attribute_1(self, p):
> +    """attribute : NAME EQUALS identifier_wrapped"""
> +    p[0] = ast.Attribute(p[1],
> +                         p[3][1],
> +                         filename=self.filename,
> +                         lineno=p.lineno(1))
> +
> +  def p_attribute_2(self, p):
>      """attribute : NAME EQUALS evaled_literal
>                   | NAME EQUALS NAME"""
>      p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
>
> -  def p_attribute_2(self, p):
> +  def p_attribute_3(self, p):
>      """attribute : NAME"""
>      p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))
>
> @@ -271,8 +278,7 @@ class Parser(object):
>      """nonnullable_typename : basictypename
>                              | array
>                              | fixed_array
> -                            | associative_array
> -                            | interfacerequest"""
> +                            | associative_array"""
>      p[0] = p[1]
>
>    def p_basictypename(self, p):
> @@ -342,14 +348,6 @@ class Parser(object):
>      """associative_array : MAP LANGLE identifier COMMA typename RANGLE"""
>      p[0] = p[5] + "{" + p[3] + "}"
>
> -  def p_interfacerequest(self, p):
> -    """interfacerequest : identifier AMP
> -                        | ASSOCIATED identifier AMP"""
> -    if len(p) == 3:
> -      p[0] = p[1] + "&"
> -    else:
> -      p[0] = "asso<" + p[2] + "&>"
> -
>    def p_ordinal_1(self, p):
>      """ordinal : """
>      p[0] = None
> 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
> index 6d6b7153..0513343e 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2014 The Chromium Authors. All rights reserved.
> +# Copyright 2014 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -1086,7 +1086,7 @@ class ParserTest(unittest.TestCase):
>            handle<data_pipe_producer>? k;
>            handle<message_pipe>? l;
>            handle<shared_buffer>? m;
> -          some_interface&? n;
> +          pending_receiver<some_interface>? n;
>            handle<platform>? o;
>          };
>          """
> @@ -1110,7 +1110,7 @@ class ParserTest(unittest.TestCase):
>                  ast.StructField('l', None, None, 'handle<message_pipe>?', None),
>                  ast.StructField('m', None, None, 'handle<shared_buffer>?',
>                                  None),
> -                ast.StructField('n', None, None, 'some_interface&?', None),
> +                ast.StructField('n', None, None, 'rcv<some_interface>?', None),
>                  ast.StructField('o', None, None, 'handle<platform>?', None)
>              ]))
>      ])
> @@ -1138,16 +1138,6 @@ class ParserTest(unittest.TestCase):
>          r" *handle\?<data_pipe_consumer> a;$"):
>        parser.Parse(source2, "my_file.mojom")
>
> -    source3 = """\
> -        struct MyStruct {
> -          some_interface?& a;
> -        };
> -        """
> -    with self.assertRaisesRegexp(
> -        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '&':\n"
> -        r" *some_interface\?& a;$"):
> -      parser.Parse(source3, "my_file.mojom")
> -
>    def testSimpleUnion(self):
>      """Tests a simple .mojom source that just defines a union."""
>      source = """\
> @@ -1317,9 +1307,9 @@ class ParserTest(unittest.TestCase):
>      source1 = """\
>          struct MyStruct {
>            associated MyInterface a;
> -          associated MyInterface& b;
> +          pending_associated_receiver<MyInterface> b;
>            associated MyInterface? c;
> -          associated MyInterface&? d;
> +          pending_associated_receiver<MyInterface>? d;
>          };
>          """
>      expected1 = ast.Mojom(None, ast.ImportList(), [
> @@ -1327,16 +1317,16 @@ class ParserTest(unittest.TestCase):
>              'MyStruct', None,
>              ast.StructBody([
>                  ast.StructField('a', None, None, 'asso<MyInterface>', None),
> -                ast.StructField('b', None, None, 'asso<MyInterface&>', None),
> +                ast.StructField('b', None, None, 'rca<MyInterface>', None),
>                  ast.StructField('c', None, None, 'asso<MyInterface>?', None),
> -                ast.StructField('d', None, None, 'asso<MyInterface&>?', None)
> +                ast.StructField('d', None, None, 'rca<MyInterface>?', None)
>              ]))
>      ])
>      self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
>
>      source2 = """\
>          interface MyInterface {
> -          MyMethod(associated A a) =>(associated B& b);
> +          MyMethod(associated A a) =>(pending_associated_receiver<B> b);
>          };"""
>      expected2 = ast.Mojom(None, ast.ImportList(), [
>          ast.Interface(
> @@ -1344,10 +1334,10 @@ class ParserTest(unittest.TestCase):
>              ast.InterfaceBody(
>                  ast.Method(
>                      'MyMethod', None, None,
> -                    ast.ParameterList(
> -                        ast.Parameter('a', None, None, 'asso<A>')),
> -                    ast.ParameterList(
> -                        ast.Parameter('b', None, None, 'asso<B&>')))))
> +                    ast.ParameterList(ast.Parameter('a', None, None,
> +                                                    'asso<A>')),
> +                    ast.ParameterList(ast.Parameter('b', None, None,
> +                                                    'rca<B>')))))
>      ])
>      self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> index eb90c825..9693090e 100755
> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> @@ -1,5 +1,5 @@
> -#!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +#!/usr/bin/env python3
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>  """Parses mojom IDL files.
> @@ -11,6 +11,7 @@ generate usable language bindings.
>  """
>
>  import argparse
> +import builtins
>  import codecs
>  import errno
>  import json
> @@ -19,6 +20,7 @@ import multiprocessing
>  import os
>  import os.path
>  import sys
> +import traceback
>  from collections import defaultdict
>
>  from mojom.generate import module
> @@ -28,16 +30,12 @@ from mojom.parse import conditional_features
>
>
>  # Disable this for easier debugging.
> -# In Python 2, subprocesses just hang when exceptions are thrown :(.
> -_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2
> +_ENABLE_MULTIPROCESSING = True
>
> -if sys.version_info < (3, 4):
> -  _MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux')
> -else:
> -  # https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
> -  if __name__ == '__main__' and sys.platform == 'darwin':
> -    multiprocessing.set_start_method('fork')
> -  _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
> +# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
> +if __name__ == '__main__' and sys.platform == 'darwin':
> +  multiprocessing.set_start_method('fork')
> +_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
>
>
>  def _ResolveRelativeImportPath(path, roots):
> @@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots):
>    raise ValueError('"%s" does not exist in any of %s' % (path, roots))
>
>
> -def _RebaseAbsolutePath(path, roots):
> +def RebaseAbsolutePath(path, roots):
>    """Rewrites an absolute file path as relative to an absolute directory path in
>    roots.
>
> @@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,
>      # Already done.
>      return
>
> -  for dep_abspath, dep_path in dependencies[mojom_abspath]:
> +  for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]):
>      if dep_abspath not in loaded_modules:
>        _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,
>                           loaded_modules, module_metadata)
> @@ -159,11 +157,19 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
>
>    def collect(metadata_filename):
>      processed_deps.add(metadata_filename)
> +
> +    # Paths in the metadata file are relative to the metadata file's dir.
> +    metadata_dir = os.path.abspath(os.path.dirname(metadata_filename))
> +
> +    def to_abs(s):
> +      return os.path.normpath(os.path.join(metadata_dir, s))
> +
>      with open(metadata_filename) as f:
>        metadata = json.load(f)
>        allowed_imports.update(
> -          map(os.path.normcase, map(os.path.normpath, metadata['sources'])))
> +          [os.path.normcase(to_abs(s)) for s in metadata['sources']])
>        for dep_metadata in metadata['deps']:
> +        dep_metadata = to_abs(dep_metadata)
>          if dep_metadata not in processed_deps:
>            collect(dep_metadata)
>
> @@ -172,8 +178,7 @@ def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
>
>
>  # multiprocessing helper.
> -def _ParseAstHelper(args):
> -  mojom_abspath, enabled_features = args
> +def _ParseAstHelper(mojom_abspath, enabled_features):
>    with codecs.open(mojom_abspath, encoding='utf-8') as f:
>      ast = parser.Parse(f.read(), mojom_abspath)
>      conditional_features.RemoveDisabledDefinitions(ast, enabled_features)
> @@ -181,8 +186,7 @@ def _ParseAstHelper(args):
>
>
>  # multiprocessing helper.
> -def _SerializeHelper(args):
> -  mojom_abspath, mojom_path = args
> +def _SerializeHelper(mojom_abspath, mojom_path):
>    module_path = os.path.join(_SerializeHelper.output_root_path,
>                               _GetModuleFilename(mojom_path))
>    module_dir = os.path.dirname(module_path)
> @@ -199,12 +203,33 @@ def _SerializeHelper(args):
>      _SerializeHelper.loaded_modules[mojom_abspath].Dump(f)
>
>
> -def _Shard(target_func, args, processes=None):
> -  args = list(args)
> +class _ExceptionWrapper:
> +  def __init__(self):
> +    # Do not capture exception object to ensure pickling works.
> +    self.formatted_trace = traceback.format_exc()
> +
> +
> +class _FuncWrapper:
> +  """Marshals exceptions and spreads args."""
> +
> +  def __init__(self, func):
> +    self._func = func
> +
> +  def __call__(self, args):
> +    # multiprocessing does not gracefully handle excptions.
> +    # https://crbug.com/1219044
> +    try:
> +      return self._func(*args)
> +    except:  # pylint: disable=bare-except
> +      return _ExceptionWrapper()
> +
> +
> +def _Shard(target_func, arg_list, processes=None):
> +  arg_list = list(arg_list)
>    if processes is None:
>      processes = multiprocessing.cpu_count()
>    # Seems optimal to have each process perform at least 2 tasks.
> -  processes = min(processes, len(args) // 2)
> +  processes = min(processes, len(arg_list) // 2)
>
>    if sys.platform == 'win32':
>      # TODO(crbug.com/1190269) - we can't use more than 56
> @@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None):
>
>    # Don't spin up processes unless there is enough work to merit doing so.
>    if not _ENABLE_MULTIPROCESSING or processes < 2:
> -    for result in map(target_func, args):
> -      yield result
> +    for arg_tuple in arg_list:
> +      yield target_func(*arg_tuple)
>      return
>
>    pool = multiprocessing.Pool(processes=processes)
>    try:
> -    for result in pool.imap_unordered(target_func, args):
> +    wrapped_func = _FuncWrapper(target_func)
> +    for result in pool.imap_unordered(wrapped_func, arg_list):
> +      if isinstance(result, _ExceptionWrapper):
> +        sys.stderr.write(result.formatted_trace)
> +        sys.exit(1)
>        yield result
>    finally:
>      pool.close()
> @@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None):
>  def _ParseMojoms(mojom_files,
>                   input_root_paths,
>                   output_root_path,
> +                 module_root_paths,
>                   enabled_features,
>                   module_metadata,
>                   allowed_imports=None):
> @@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files,
>          are based on the mojom's relative path, rebased onto this path.
>          Additionally, the script expects this root to contain already-generated
>          modules for any transitive dependencies not listed in mojom_files.
> +    module_root_paths: A list of absolute filesystem paths which contain
> +        already-generated modules for any non-transitive dependencies.
>      enabled_features: A list of enabled feature names, controlling which AST
> -        nodes are filtered by [EnableIf] attributes.
> +        nodes are filtered by [EnableIf] or [EnableIfNot] attributes.
>      module_metadata: A list of 2-tuples representing metadata key-value pairs to
>          attach to each compiled module output.
>
> @@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files,
>    loaded_modules = {}
>    input_dependencies = defaultdict(set)
>    mojom_files_to_parse = dict((os.path.normcase(abs_path),
> -                               _RebaseAbsolutePath(abs_path, input_root_paths))
> +                               RebaseAbsolutePath(abs_path, input_root_paths))
>                                for abs_path in mojom_files)
>    abs_paths = dict(
>        (path, abs_path) for abs_path, path in mojom_files_to_parse.items())
> @@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files,
>      loaded_mojom_asts[mojom_abspath] = ast
>
>    logging.info('Processing dependencies')
> -  for mojom_abspath, ast in loaded_mojom_asts.items():
> +  for mojom_abspath, ast in sorted(loaded_mojom_asts.items()):
>      invalid_imports = []
>      for imp in ast.import_list:
>        import_abspath = _ResolveRelativeImportPath(imp.import_filename,
> @@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files,
>          # be parsed and have a module file sitting in a corresponding output
>          # location.
>          module_path = _GetModuleFilename(imp.import_filename)
> -        module_abspath = _ResolveRelativeImportPath(module_path,
> -                                                    [output_root_path])
> +        module_abspath = _ResolveRelativeImportPath(
> +            module_path, module_root_paths + [output_root_path])
>          with open(module_abspath, 'rb') as module_file:
>            loaded_modules[import_abspath] = module.Module.Load(module_file)
>
> @@ -370,6 +402,15 @@ already present in the provided output root.""")
>        'based on the relative input path, rebased onto this root. Note that '
>        'ROOT is also searched for existing modules of any transitive imports '
>        'which were not included in the set of inputs.')
> +  arg_parser.add_argument(
> +      '--module-root',
> +      default=[],
> +      action='append',
> +      metavar='ROOT',
> +      dest='module_root_paths',
> +      help='Adds ROOT to the set of root paths to search for existing modules '
> +      'of non-transitive imports. Provided root paths are always searched in '
> +      'order from longest absolute path to shortest.')
>    arg_parser.add_argument(
>        '--mojoms',
>        nargs='+',
> @@ -396,9 +437,9 @@ already present in the provided output root.""")
>        help='Enables a named feature when parsing the given mojoms. Features '
>        'are identified by arbitrary string values. Specifying this flag with a '
>        'given FEATURE name will cause the parser to process any syntax elements '
> -      'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '
> -      'provided for a given FEATURE, such tagged elements are discarded by the '
> -      'parser and will not be present in the compiled output.')
> +      'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '
> +      'flag is not provided for a given FEATURE, such tagged elements are '
> +      'discarded by the parser and will not be present in the compiled output.')
>    arg_parser.add_argument(
>        '--check-imports',
>        dest='build_metadata_filename',
> @@ -436,6 +477,7 @@ already present in the provided output root.""")
>    mojom_files = list(map(os.path.abspath, args.mojom_files))
>    input_roots = list(map(os.path.abspath, args.input_root_paths))
>    output_root = os.path.abspath(args.output_root_path)
> +  module_roots = list(map(os.path.abspath, args.module_root_paths))
>
>    if args.build_metadata_filename:
>      allowed_imports = _CollectAllowedImportsFromBuildMetadata(
> @@ -445,13 +487,16 @@ already present in the provided output root.""")
>
>    module_metadata = list(
>        map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))
> -  _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,
> -               module_metadata, allowed_imports)
> +  _ParseMojoms(mojom_files, input_roots, output_root, module_roots,
> +               args.enabled_features, module_metadata, allowed_imports)
>    logging.info('Finished')
> -  # Exit without running GC, which can save multiple seconds due the large
> -  # number of object created.
> -  os._exit(0)
>
>
>  if __name__ == '__main__':
>    Run(sys.argv[1:])
> +  # Exit without running GC, which can save multiple seconds due to the large
> +  # number of object created. But flush is necessary as os._exit doesn't do
> +  # that.
> +  sys.stdout.flush()
> +  sys.stderr.flush()
> +  os._exit(0)
> 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
> index e213fbfa..45803ebe 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase):
>    resolution, and module serialization and deserialization."""
>
>    def __init__(self, method_name):
> -    super(MojomParserTestCase, self).__init__(method_name)
> +    super().__init__(method_name)
>      self._temp_dir = None
>
>    def setUp(self):
> diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> index a93f34ba..353a2b6e 100644
> --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> @@ -1,7 +1,9 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> +import json
> +
>  from mojom_parser_test_case import MojomParserTestCase
>
>
> @@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase):
>      c = 'c.mojom'
>      c_metadata = 'out/c.build_metadata'
>      self.WriteFile(a_metadata,
> -                   '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
> +                   json.dumps({
> +                       "sources": [self.GetPath(a)],
> +                       "deps": []
> +                   }))
>      self.WriteFile(
>          b_metadata,
> -        '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b),
> -                                                   self.GetPath(a_metadata)))
> +        json.dumps({
> +            "sources": [self.GetPath(b)],
> +            "deps": [self.GetPath(a_metadata)]
> +        }))
>      self.WriteFile(
>          c_metadata,
> -        '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c),
> -                                                   self.GetPath(b_metadata)))
> +        json.dumps({
> +            "sources": [self.GetPath(c)],
> +            "deps": [self.GetPath(b_metadata)]
> +        }))
>      self.WriteFile(a, """\
>          module a;
>          struct Bar {};""")
> @@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase):
>      b = 'b.mojom'
>      b_metadata = 'out/b.build_metadata'
>      self.WriteFile(a_metadata,
> -                   '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
> +                   json.dumps({
> +                       "sources": [self.GetPath(a)],
> +                       "deps": []
> +                   }))
>      self.WriteFile(b_metadata,
> -                   '{"sources": ["%s"], "deps": []}\n' % self.GetPath(b))
> +                   json.dumps({
> +                       "sources": [self.GetPath(b)],
> +                       "deps": []
> +                   }))
>      self.WriteFile(a, """\
>          module a;
>          struct Bar {};""")
> diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> index d45ec586..d10d69c6 100644
> --- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> diff --git a/utils/ipc/mojo/public/tools/mojom/union_unittest.py b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
> new file mode 100644
> index 00000000..6b2525e5
> --- /dev/null
> +++ b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
> @@ -0,0 +1,44 @@
> +# Copyright 2022 The Chromium Authors
> +# Use of this source code is governed by a BSD-style license that can be
> +# found in the LICENSE file.
> +
> +from mojom_parser_test_case import MojomParserTestCase
> +
> +
> +class UnionTest(MojomParserTestCase):
> +  """Tests union parsing behavior."""
> +
> +  def testExtensibleMustHaveDefault(self):
> +    """Verifies that extensible unions must have a default field."""
> +    mojom = 'foo.mojom'
> +    self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x; };')
> +    with self.assertRaisesRegexp(Exception, 'must specify a \[Default\]'):
> +      self.ParseMojoms([mojom])
> +
> +  def testExtensibleSingleDefault(self):
> +    """Verifies that extensible unions must not have multiple default fields."""
> +    mojom = 'foo.mojom'
> +    self.WriteFile(
> +        mojom, """\
> +               module foo;
> +               [Extensible] union U {
> +                 [Default] bool x;
> +                 [Default] bool y;
> +               };
> +               """)
> +    with self.assertRaisesRegexp(Exception, 'Multiple \[Default\] fields'):
> +      self.ParseMojoms([mojom])
> +
> +  def testExtensibleDefaultTypeValid(self):
> +    """Verifies that an extensible union's default field must be nullable or
> +    integral type."""
> +    mojom = 'foo.mojom'
> +    self.WriteFile(
> +        mojom, """\
> +               module foo;
> +               [Extensible] union U {
> +                 [Default] handle<message_pipe> p;
> +               };
> +               """)
> +    with self.assertRaisesRegexp(Exception, 'must be nullable or integral'):
> +      self.ParseMojoms([mojom])
> diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> index 65db4dc9..7b71ce65 100644
> --- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> +++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> @@ -1,4 +1,4 @@
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -23,7 +23,7 @@ class VersionCompatibilityTest(MojomParserTestCase):
>
>      checker = module.BackwardCompatibilityChecker()
>      compatibility_map = {}
> -    for name in old.keys():
> +    for name in old:
>        compatibility_map[name] = checker.IsBackwardCompatible(
>            new[name], old[name])
>      return compatibility_map
> @@ -60,40 +60,48 @@ class VersionCompatibilityTest(MojomParserTestCase):
>      """Adding a value to an existing version is not allowed, even if the old
>      enum was marked [Extensible]. Note that it is irrelevant whether or not the
>      new enum is marked [Extensible]."""
> -    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
> -                                     'enum E { kFoo, kBar, kBaz };')
>      self.assertNotBackwardCompatible(
> -        '[Extensible] enum E { kFoo, kBar };',
> -        '[Extensible] enum E { kFoo, kBar, kBaz };')
> +        '[Extensible] enum E { [Default] kFoo, kBar };',
> +        'enum E { kFoo, kBar, kBaz };')
> +    self.assertNotBackwardCompatible(
> +        '[Extensible] enum E { [Default] kFoo, kBar };',
> +        '[Extensible] enum E { [Default] kFoo, kBar, kBaz };')
>      self.assertNotBackwardCompatible(
> -        '[Extensible] enum E { kFoo, [MinVersion=1] kBar };',
> +        '[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };',
>          'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')
>
>    def testEnumValueRemoval(self):
>      """Removal of an enum value is never valid even for [Extensible] enums."""
>      self.assertNotBackwardCompatible('enum E { kFoo, kBar };',
>                                       'enum E { kFoo };')
> -    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
> -                                     '[Extensible] enum E { kFoo };')
>      self.assertNotBackwardCompatible(
> -        '[Extensible] enum E { kA, [MinVersion=1] kB };',
> -        '[Extensible] enum E { kA, };')
> +        '[Extensible] enum E { [Default] kFoo, kBar };',
> +        '[Extensible] enum E { [Default] kFoo };')
> +    self.assertNotBackwardCompatible(
> +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
> +        '[Extensible] enum E { [Default] kA, };')
>      self.assertNotBackwardCompatible(
> -        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };',
> -        '[Extensible] enum E { kA, [MinVersion=1] kB };')
> +        """[Extensible] enum E {
> +          [Default] kA,
> +          [MinVersion=1] kB,
> +          [MinVersion=1] kZ };""",
> +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };')
>
>    def testNewExtensibleEnumValueWithMinVersion(self):
>      """Adding a new and properly [MinVersion]'d value to an [Extensible] enum
>      is a backward-compatible change. Note that it is irrelevant whether or not
>      the new enum is marked [Extensible]."""
> -    self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',
> +    self.assertBackwardCompatible('[Extensible] enum E { [Default] kA, kB };',
>                                    'enum E { kA, kB, [MinVersion=1] kC };')
>      self.assertBackwardCompatible(
> -        '[Extensible] enum E { kA, kB };',
> -        '[Extensible] enum E { kA, kB, [MinVersion=1] kC };')
> +        '[Extensible] enum E { [Default] kA, kB };',
> +        '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };')
>      self.assertBackwardCompatible(
> -        '[Extensible] enum E { kA, [MinVersion=1] kB };',
> -        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };')
> +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
> +        """[Extensible] enum E {
> +          [Default] kA,
> +          [MinVersion=1] kB,
> +          [MinVersion=2] kC };""")
>
>    def testRenameEnumValue(self):
>      """Renaming an enum value does not affect backward-compatibility. Only
> @@ -161,14 +169,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
>          'struct S {}; struct T { S s; };',
>          'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')
>      self.assertBackwardCompatible(
> -        '[Extensible] enum E { kA }; struct S { E e; };',
> -        '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };')
> +        '[Extensible] enum E { [Default] kA }; struct S { E e; };',
> +        """[Extensible] enum E {
> +          [Default] kA,
> +          [MinVersion=1] kB };
> +          struct S { E e; };""")
>      self.assertNotBackwardCompatible(
>          'struct S {}; struct T { S s; };',
>          'struct S { int32 x; }; struct T { S s; };')
>      self.assertNotBackwardCompatible(
> -        '[Extensible] enum E { kA }; struct S { E e; };',
> -        '[Extensible] enum E { kA, kB }; struct S { E e; };')
> +        '[Extensible] enum E { [Default] kA }; struct S { E e; };',
> +        '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };')
>
>    def testNewStructFieldWithInvalidMinVersion(self):
>      """Adding a new field using an existing MinVersion breaks backward-
> @@ -305,14 +316,17 @@ class VersionCompatibilityTest(MojomParserTestCase):
>          'struct S {}; union U { S s; };',
>          'struct S { [MinVersion=1] int32 x; }; union U { S s; };')
>      self.assertBackwardCompatible(
> -        '[Extensible] enum E { kA }; union U { E e; };',
> -        '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };')
> +        '[Extensible] enum E { [Default] kA }; union U { E e; };',
> +        """[Extensible] enum E {
> +          [Default] kA,
> +          [MinVersion=1] kB };
> +          union U { E e; };""")
>      self.assertNotBackwardCompatible(
>          'struct S {}; union U { S s; };',
>          'struct S { int32 x; }; union U { S s; };')
>      self.assertNotBackwardCompatible(
> -        '[Extensible] enum E { kA }; union U { E e; };',
> -        '[Extensible] enum E { kA, kB }; union U { E e; };')
> +        '[Extensible] enum E { [Default] kA }; union U { E e; };',
> +        '[Extensible] enum E { [Default] kA, kB }; union U { E e; };')
>
>    def testNewUnionFieldWithInvalidMinVersion(self):
>      """Adding a new field using an existing MinVersion breaks backward-
> diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> index b2010958..98bce18c 100755
> --- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> +++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> @@ -1,5 +1,5 @@
> -#!/usr/bin/env python
> -# Copyright 2020 The Chromium Authors. All rights reserved.
> +#!/usr/bin/env python3
> +# Copyright 2020 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> @@ -8,11 +8,13 @@ import sys
>
>  _TOOLS_DIR = os.path.dirname(__file__)
>  _MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')
> +_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings')
>  _SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,
>                          os.path.pardir)
>
>  # Ensure that the mojom library is discoverable.
>  sys.path.append(_MOJOM_DIR)
> +sys.path.append(_BINDINGS_DIR)
>
>  # Help Python find typ in //third_party/catapult/third_party/typ/
>  sys.path.append(
> @@ -21,7 +23,7 @@ import typ
>
>
>  def Main():
> -  return typ.main(top_level_dir=_MOJOM_DIR)
> +  return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR])
>
>
>  if __name__ == '__main__':
> diff --git a/utils/ipc/tools/README b/utils/ipc/tools/README
> index d5c24fc3..9a2979d3 100644
> --- a/utils/ipc/tools/README
> +++ b/utils/ipc/tools/README
> @@ -1,4 +1,4 @@
>  # SPDX-License-Identifier: CC0-1.0
>
> -Files in this directory are imported from 9c138d992bfc of Chromium. Do not
> +Files in this directory are imported from e2b2277a00e37 of Chromium. Do not
>  modify them manually.
> diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/ipc/tools/diagnosis/crbug_1001171.py
> index 478fb8c1..40900d10 100644
> --- a/utils/ipc/tools/diagnosis/crbug_1001171.py
> +++ b/utils/ipc/tools/diagnosis/crbug_1001171.py
> @@ -1,4 +1,4 @@
> -# Copyright 2019 The Chromium Authors. All rights reserved.
> +# Copyright 2019 The Chromium Authors
>  # Use of this source code is governed by a BSD-style license that can be
>  # found in the LICENSE file.
>
> --
> 2.40.0.348.gf938b09366-goog
>
Cheng-Hao Yang March 30, 2023, 9:48 p.m. UTC | #2
Hi Jacopo,

Thanks for the quick response and sorry that I thought I passed the build
(there were some cached files in the previous build, so...). There are some
more issues to update the mojo before it works.

Han-lin pointed out though, that we should separate the system mojo library
from the libcamera local mojo library in Chrome OS build. I believe the
failure
in build was due to a recent change that the system libraries are put in a
higher priority - we need to update the python script to put the local mojo
library the first to use. I'll upload another patch for that.

Therefore, please neglect this patch for now. Sorry for the spam.

On Fri, Mar 31, 2023 at 2:05 AM Jacopo Mondi <jacopo.mondi@ideasonboard.com>
wrote:

> Hi Harvey
>
> On Thu, Mar 30, 2023 at 09:56:09AM +0000, Harvey Yang via libcamera-devel
> wrote:
> > Update mojo from the Chromium repository. The commit from which this was
> > taken is:
> > [white-space] Change DOM/HTML/SVG to set longhands of `white-space`
> >
> > The update-mojo.sh script was used for this update.
> >
> > Signed-off-by: Harvey Yang <chenghaoyang@chromium.org>
>
> Thanks,
>   I presume there are no changes in the mojom API. I've run tests but
> I didn't get it running on a platform with an IPA (so that it
> exercizes the IPC) yet.
>
>
Yes, the mojom API shouldn't change (at least it should be back-compatible).


> Have you tried that on a device or am I overly concerned ?
>
> Thanks
>   j
>
> > ---
> >  utils/ipc/mojo/README                         |   2 +-
> >  utils/ipc/mojo/public/LICENSE                 |   2 +-
> >  utils/ipc/mojo/public/tools/BUILD.gn          |   8 +-
> >  utils/ipc/mojo/public/tools/bindings/BUILD.gn |  34 +-
> >  .../ipc/mojo/public/tools/bindings/README.md  | 148 ++-
> >  .../public/tools/bindings/checks/__init__.py  |   0
> >  .../bindings/checks/mojom_attributes_check.py | 168 ++++
> >  .../checks/mojom_attributes_check_unittest.py | 186 ++++
> >  .../checks/mojom_definitions_check.py         |  34 +
> >  .../checks/mojom_restrictions_check.py        | 102 +++
> >  .../mojom_restrictions_checks_unittest.py     | 254 ++++++
> >  .../tools/bindings/concatenate-files.py       |   5 +-
> >  ...concatenate_and_replace_closure_exports.py |   8 +-
> >  .../tools/bindings/gen_data_files_list.py     |   2 +-
> >  .../tools/bindings/generate_type_mappings.py  |   3 +-
> >  .../tools/bindings/minify_with_terser.py      |  47 +
> >  .../ipc/mojo/public/tools/bindings/mojom.gni  | 860 ++++++++++--------
> >  .../bindings/mojom_bindings_generator.py      |  62 +-
> >  .../mojom_bindings_generator_unittest.py      |   6 +-
> >  .../tools/bindings/validate_typemap_config.py |   4 +-
> >  utils/ipc/mojo/public/tools/mojom/BUILD.gn    |  17 +
> >  .../mojom/check_stable_mojom_compatibility.py |  46 +-
> >  ...eck_stable_mojom_compatibility_unittest.py |  87 +-
> >  .../mojo/public/tools/mojom/const_unittest.py |   2 +-
> >  .../mojo/public/tools/mojom/enum_unittest.py  |  30 +-
> >  .../mojo/public/tools/mojom/mojom/BUILD.gn    |   3 +-
> >  .../mojo/public/tools/mojom/mojom/error.py    |   2 +-
> >  .../mojo/public/tools/mojom/mojom/fileutil.py |   2 +-
> >  .../tools/mojom/mojom/fileutil_unittest.py    |   2 +-
> >  .../tools/mojom/mojom/generate/check.py       |  26 +
> >  .../tools/mojom/mojom/generate/generator.py   |   8 +-
> >  .../mojom/generate/generator_unittest.py      |   2 +-
> >  .../tools/mojom/mojom/generate/module.py      | 649 ++++++++-----
> >  .../mojom/mojom/generate/module_unittest.py   |   2 +-
> >  .../public/tools/mojom/mojom/generate/pack.py | 125 ++-
> >  .../mojom/mojom/generate/pack_unittest.py     |   2 +-
> >  .../mojom/mojom/generate/template_expander.py |   2 +-
> >  .../tools/mojom/mojom/generate/translate.py   | 408 ++++++++-
> >  .../mojom/generate/translate_unittest.py      |  39 +-
> >  .../public/tools/mojom/mojom/parse/ast.py     | 117 +--
> >  .../tools/mojom/mojom/parse/ast_unittest.py   |   6 +-
> >  .../mojom/mojom/parse/conditional_features.py |  14 +-
> >  .../parse/conditional_features_unittest.py    | 130 ++-
> >  .../public/tools/mojom/mojom/parse/lexer.py   |   6 +-
> >  .../tools/mojom/mojom/parse/lexer_unittest.py |   3 +-
> >  .../public/tools/mojom/mojom/parse/parser.py  |  24 +-
> >  .../mojom/mojom/parse/parser_unittest.py      |  34 +-
> >  .../mojo/public/tools/mojom/mojom_parser.py   | 119 ++-
> >  .../tools/mojom/mojom_parser_test_case.py     |   4 +-
> >  .../tools/mojom/mojom_parser_unittest.py      |  31 +-
> >  .../tools/mojom/stable_attribute_unittest.py  |   2 +-
> >  .../mojo/public/tools/mojom/union_unittest.py |  44 +
> >  .../mojom/version_compatibility_unittest.py   |  66 +-
> >  .../public/tools/run_all_python_unittests.py  |   8 +-
> >  utils/ipc/tools/README                        |   2 +-
> >  utils/ipc/tools/diagnosis/crbug_1001171.py    |   2 +-
> >  56 files changed, 3061 insertions(+), 940 deletions(-)
> >  create mode 100644
> utils/ipc/mojo/public/tools/bindings/checks/__init__.py
> >  create mode 100644
> utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
> >  create mode 100644
> utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
> >  create mode 100644
> utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
> >  create mode 100644
> utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
> >  create mode 100644
> utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
> >  create mode 100755
> utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
> >  create mode 100644 utils/ipc/mojo/public/tools/mojom/BUILD.gn
> >  create mode 100644
> utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
> >  create mode 100644 utils/ipc/mojo/public/tools/mojom/union_unittest.py
> >
> > diff --git a/utils/ipc/mojo/README b/utils/ipc/mojo/README
> > index d5c24fc3..9a2979d3 100644
> > --- a/utils/ipc/mojo/README
> > +++ b/utils/ipc/mojo/README
> > @@ -1,4 +1,4 @@
> >  # SPDX-License-Identifier: CC0-1.0
> >
> > -Files in this directory are imported from 9c138d992bfc of Chromium. Do
> not
> > +Files in this directory are imported from e2b2277a00e37 of Chromium. Do
> not
> >  modify them manually.
> > diff --git a/utils/ipc/mojo/public/LICENSE
> b/utils/ipc/mojo/public/LICENSE
> > index 972bb2ed..513e8a6a 100644
> > --- a/utils/ipc/mojo/public/LICENSE
> > +++ b/utils/ipc/mojo/public/LICENSE
> > @@ -1,4 +1,4 @@
> > -// Copyright 2014 The Chromium Authors. All rights reserved.
> > +// Copyright 2014 The Chromium Authors
> >  //
> >  // Redistribution and use in source and binary forms, with or without
> >  // modification, are permitted provided that the following conditions
> are
> > diff --git a/utils/ipc/mojo/public/tools/BUILD.gn
> b/utils/ipc/mojo/public/tools/BUILD.gn
> > index eb6391a6..5328a34a 100644
> > --- a/utils/ipc/mojo/public/tools/BUILD.gn
> > +++ b/utils/ipc/mojo/public/tools/BUILD.gn
> > @@ -1,4 +1,4 @@
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -10,7 +10,11 @@ group("mojo_python_unittests") {
> >      "run_all_python_unittests.py",
> >      "//testing/scripts/run_isolated_script_test.py",
> >    ]
> > -  deps = [ "//mojo/public/tools/mojom/mojom:tests" ]
> > +  deps = [
> > +    "//mojo/public/tools/bindings:tests",
> > +    "//mojo/public/tools/mojom:tests",
> > +    "//mojo/public/tools/mojom/mojom:tests",
> > +  ]
> >    data_deps = [
> >      "//testing:test_scripts_shared",
> >      "//third_party/catapult/third_party/typ/",
> > diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> > index 3e242532..203e476c 100644
> > --- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> > +++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
> > @@ -1,13 +1,11 @@
> > -# Copyright 2016 The Chromium Authors. All rights reserved.
> > +# Copyright 2016 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > -import("//build/config/python.gni")
> >  import("//mojo/public/tools/bindings/mojom.gni")
> >  import("//third_party/jinja2/jinja2.gni")
> >
> > -# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
> > -python2_action("precompile_templates") {
> > +action("precompile_templates") {
> >    sources = mojom_generator_sources
> >    sources += [
> >      "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl",
> > @@ -26,7 +24,6 @@ python2_action("precompile_templates") {
> >
> "$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl",
> >
> "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl",
> >
> "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
> > -
> "$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl",
> >
> "$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl",
> >      "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
> >      "$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
> > @@ -65,9 +62,6 @@ python2_action("precompile_templates") {
> >      "$mojom_generator_root/generators/java_templates/struct.java.tmpl",
> >      "$mojom_generator_root/generators/java_templates/union.java.tmpl",
> >
> "$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
> > -
> "$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl",
> > -
> "$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl",
> > -
> "$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl",
> >      "$mojom_generator_root/generators/js_templates/fuzzing.tmpl",
> >
> "$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
> >
> "$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl",
> > @@ -93,8 +87,14 @@ python2_action("precompile_templates") {
> >
> "$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
> >
> "$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
> >
> "$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
> > +
> "$mojom_generator_root/generators/ts_templates/enum_definition.tmpl",
> > +
> "$mojom_generator_root/generators/ts_templates/interface_definition.tmpl",
> >
> "$mojom_generator_root/generators/ts_templates/module_definition.tmpl",
> > -    "$mojom_generator_root/generators/ts_templates/mojom.tmpl",
> > +
> "$mojom_generator_root/generators/ts_templates/struct_definition.tmpl",
> > +
> "$mojom_generator_root/generators/ts_templates/union_definition.tmpl",
> > +
> "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_impl.cc.tmpl",
> > +
> "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_impl.h.tmpl",
> > +
> "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_macros.tmpl",
> >    ]
> >    script = mojom_generator_script
> >
> > @@ -102,8 +102,9 @@ python2_action("precompile_templates") {
> >    outputs = [
> >      "$target_gen_dir/cpp_templates.zip",
> >      "$target_gen_dir/java_templates.zip",
> > -    "$target_gen_dir/mojolpm_templates.zip",
> > +    "$target_gen_dir/webui_js_bridge_templates.zip",
> >      "$target_gen_dir/js_templates.zip",
> > +    "$target_gen_dir/mojolpm_templates.zip",
> >      "$target_gen_dir/ts_templates.zip",
> >    ]
> >    args = [
> > @@ -113,3 +114,16 @@ python2_action("precompile_templates") {
> >      "precompile",
> >    ]
> >  }
> > +
> > +group("tests") {
> > +  data = [
> > +    mojom_generator_script,
> > +    "checks/mojom_attributes_check_unittest.py",
> > +    "checks/mojom_restrictions_checks_unittest.py",
> > +    "mojom_bindings_generator_unittest.py",
> > +    "//tools/diagnosis/crbug_1001171.py",
> > +    "//third_party/markupsafe/",
> > +  ]
> > +  data += mojom_generator_sources
> > +  data += jinja2_sources
> > +}
> > diff --git a/utils/ipc/mojo/public/tools/bindings/README.md
> b/utils/ipc/mojo/public/tools/bindings/README.md
> > index 43882450..683aa2f0 100644
> > --- a/utils/ipc/mojo/public/tools/bindings/README.md
> > +++ b/utils/ipc/mojo/public/tools/bindings/README.md
> > @@ -188,8 +188,8 @@ struct StringPair {
> >  };
> >
> >  enum AnEnum {
> > -  YES,
> > -  NO
> > +  kYes,
> > +  kNo
> >  };
> >
> >  interface SampleInterface {
> > @@ -209,7 +209,7 @@ struct AllTheThings {
> >    uint64 unsigned_64bit_value;
> >    float float_value_32bit;
> >    double float_value_64bit;
> > -  AnEnum enum_value = AnEnum.YES;
> > +  AnEnum enum_value = AnEnum.kYes;
> >
> >    // Strings may be nullable.
> >    string? maybe_a_string_maybe_not;
> > @@ -300,14 +300,14 @@ within a module or nested within the namespace of
> some struct or interface:
> >  module business.mojom;
> >
> >  enum Department {
> > -  SALES = 0,
> > -  DEV,
> > +  kSales = 0,
> > +  kDev,
> >  };
> >
> >  struct Employee {
> >    enum Type {
> > -    FULL_TIME,
> > -    PART_TIME,
> > +    kFullTime,
> > +    kPartTime,
> >    };
> >
> >    Type type;
> > @@ -315,6 +315,9 @@ struct Employee {
> >  };
> >  ```
> >
> > +C++ constant-style enum value names are preferred as specified in the
> > +[Google C++ Style Guide](
> https://google.github.io/styleguide/cppguide.html#Enumerator_Names).
> > +
> >  Similar to C-style enums, individual values may be explicitly assigned
> within an
> >  enum definition. By default, values are based at zero and increment by
> >  1 sequentially.
> > @@ -336,8 +339,8 @@ struct Employee {
> >    const uint64 kInvalidId = 0;
> >
> >    enum Type {
> > -    FULL_TIME,
> > -    PART_TIME,
> > +    kFullTime,
> > +    kPartTime,
> >    };
> >
> >    uint64 id = kInvalidId;
> > @@ -396,20 +399,33 @@ interesting attributes supported today.
> >    extreme caution, because it can lead to deadlocks otherwise.
> >
> >  * **`[Default]`**:
> > -  The `Default` attribute may be used to specify an enumerator value
> that
> > -  will be used if an `Extensible` enumeration does not deserialize to a
> known
> > -  value on the receiver side, i.e. the sender is using a newer version
> of the
> > -  enum. This allows unknown values to be mapped to a well-defined value
> that can
> > -  be appropriately handled.
> > +  The `Default` attribute may be used to specify an enumerator value or
> union
> > +  field that will be used if an `Extensible` enumeration or union does
> not
> > +  deserialize to a known value on the receiver side, i.e. the sender is
> using a
> > +  newer version of the enum or union. This allows unknown values to be
> mapped to
> > +  a well-defined value that can be appropriately handled.
> > +
> > +  Note: The `Default` field for a union must be of nullable or integral
> type.
> > +  When a union is defaulted to this field, the field takes on the
> default value
> > +  for its type: null for nullable types, and zero/false for integral
> types.
> >
> >  * **`[Extensible]`**:
> > -  The `Extensible` attribute may be specified for any enum definition.
> This
> > -  essentially disables builtin range validation when receiving values
> of the
> > -  enum type in a message, allowing older bindings to tolerate
> unrecognized
> > -  values from newer versions of the enum.
> > +  The `Extensible` attribute may be specified for any enum or union
> definition.
> > +  For enums, this essentially disables builtin range validation when
> receiving
> > +  values of the enum type in a message, allowing older bindings to
> tolerate
> > +  unrecognized values from newer versions of the enum.
> > +
> > +  If an enum value within an extensible enum definition is affixed with
> the
> > +  `Default` attribute, out-of-range values for the enum will
> deserialize to that
> > +  default value. Only one enum value may be designated as the `Default`.
> >
> > -  Note: in the future, an `Extensible` enumeration will require that a
> `Default`
> > -  enumerator value also be specified.
> > +  Similarly, a union marked `Extensible` will deserialize to its
> `Default` field
> > +  when an unrecognized field is received. Extensible unions MUST
> specify exactly
> > +  one `Default` field, and the field must be of nullable or integral
> type. When
> > +  defaulted to this field, the value is always null/zero/false as
> appropriate.
> > +
> > +  An `Extensible` enumeration REQUIRES that a `Default` value be
> specified,
> > +  so all new extensible enums should specify one.
> >
> >  * **`[Native]`**:
> >    The `Native` attribute may be specified for an empty struct
> declaration to
> > @@ -422,7 +438,10 @@ interesting attributes supported today.
> >  * **`[MinVersion=N]`**:
> >    The `MinVersion` attribute is used to specify the version at which a
> given
> >    field, enum value, interface method, or method parameter was
> introduced.
> > -  See [Versioning](#Versioning) for more details.
> > +  See [Versioning](#Versioning) for more details. `MinVersion` does not
> apply
> > +  to interfaces, structs or enums, but to the fields of those types.
> > +  `MinVersion` is not a module-global value, but it is ok to pretend it
> is by
> > +  skipping versions when adding fields or parameters.
> >
> >  * **`[Stable]`**:
> >    The `Stable` attribute specifies that a given mojom type or interface
> > @@ -448,7 +467,50 @@ interesting attributes supported today.
> >    matching `value` in the list of `enabled_features`, the definition
> will be
> >    disabled. This is useful for mojom definitions that only make sense
> on one
> >    platform. Note that the `EnableIf` attribute can only be set once per
> > -  definition.
> > +  definition and cannot be set at the same time as `EnableIfNot`. Also
> be aware
> > +  that only one condition can be tested, `EnableIf=value,xyz`
> introduces a new
> > +  `xyz` attribute. `xyz` is not part of the `EnableIf` condition that
> depends
> > +  only on the feature `value`. Complex conditions can be introduced via
> > +  enabled_features in `build.gn` files.
> > +
> > +* **`[EnableIfNot=value]`**:
> > +  The `EnableIfNot` attribute is used to conditionally enable
> definitions when
> > +  the mojom is parsed. If the `mojom` target in the GN file includes the
> > +  matching `value` in the list of `enabled_features`, the definition
> will be
> > +  disabled. This is useful for mojom definitions that only make sense
> on all but
> > +  one platform. Note that the `EnableIfNot` attribute can only be set
> once per
> > +  definition and cannot be set at the same time as `EnableIf`.
> > +
> > +* **`[ServiceSandbox=value]`**:
> > +  The `ServiceSandbox` attribute is used in Chromium to tag which
> sandbox a
> > +  service hosting an implementation of interface will be launched in.
> This only
> > +  applies to `C++` bindings. `value` should match a constant defined in
> an
> > +  imported `sandbox.mojom.Sandbox` enum (for Chromium this is
> > +  `//sandbox/policy/mojom/sandbox.mojom`), such as `kService`.
> > +
> > +* **`[RequireContext=enum]`**:
> > +  The `RequireContext` attribute is used in Chromium to tag interfaces
> that
> > +  should be passed (as remotes or receivers) only to privileged process
> > +  contexts. The process context must be an enum that is imported into
> the
> > +  mojom that defines the tagged interface. `RequireContext` may be used
> in
> > +  future to DCHECK or CHECK if remotes are made available in contexts
> that
> > +  conflict with the one provided in the interface definition. Process
> contexts
> > +  are not the same as the sandbox a process is running in, but will
> reflect
> > +  the set of capabilities provided to the service.
> > +
> > +* **`[AllowedContext=enum]`**:
> > +  The `AllowedContext` attribute is used in Chromium to tag methods
> that pass
> > +  remotes or receivers of interfaces that are marked with a
> `RequireContext`
> > +  attribute. The enum provided on the method must be equal or better
> (lower
> > +  numerically) than the one required on the interface being passed. At
> present
> > +  failing to specify an adequate `AllowedContext` value will cause mojom
> > +  generation to fail at compile time. In future DCHECKs or CHECKs might
> be
> > +  added to enforce that method is only called from a process context
> that meets
> > +  the given `AllowedContext` value. The enum must of the same type as
> that
> > +  specified in the interface's `RequireContext` attribute. Adding an
> > +  `AllowedContext` attribute to a method is a strong indication that
> you need
> > +   a detailed security review of your design - please reach out to the
> security
> > +   team.
> >
> >  ## Generated Code For Target Languages
> >
> > @@ -495,9 +557,9 @@ values. For example if a Mojom declares the enum:
> >
> >  ``` cpp
> >  enum AdvancedBoolean {
> > -  TRUE = 0,
> > -  FALSE = 1,
> > -  FILE_NOT_FOUND = 2,
> > +  kTrue = 0,
> > +  kFalse = 1,
> > +  kFileNotFound = 2,
> >  };
> >  ```
> >
> > @@ -550,10 +612,16 @@ See the documentation for
> >
> >  *** note
> >  **NOTE:** You don't need to worry about versioning if you don't care
> about
> > -backwards compatibility. Specifically, all parts of Chrome are updated
> > -atomically today and there is not yet any possibility of any two Chrome
> > -processes communicating with two different versions of any given Mojom
> > -interface.
> > +backwards compatibility. Today, all parts of the Chrome browser are
> > +updated atomically and there is not yet any possibility of any two
> > +Chrome processes communicating with two different versions of any given
> Mojom
> > +interface. On Chrome OS, there are several places where versioning is
> required.
> > +For example,
> > +[ARC++](https://developer.android.com/chrome-os/intro)
> > +uses versioned mojo to send IPC to the Android container.
> > +Likewise, the
> > +[Lacros](/docs/lacros.md)
> > +browser uses versioned mojo to talk to the ash system UI.
> >  ***
> >
> >  Services extend their interfaces to support new features over time, and
> clients
> > @@ -593,8 +661,8 @@ struct Employee {
> >
> >  *** note
> >  **NOTE:** Mojo object or handle types added with a `MinVersion`
> **MUST** be
> > -optional (nullable). See [Primitive Types](#Primitive-Types) for
> details on
> > -nullable values.
> > +optional (nullable) or primitive. See [Primitive
> Types](#Primitive-Types) for
> > +details on nullable values.
> >  ***
> >
> >  By default, fields belong to version 0. New fields must be appended to
> the
> > @@ -624,10 +692,10 @@ the following hard constraints:
> >  * For any given struct or interface, if any field or method explicitly
> specifies
> >      an ordinal value, all fields or methods must explicitly specify an
> ordinal
> >      value.
> > -* For an *N*-field struct or *N*-method interface, the set of explicitly
> > -    assigned ordinal values must be limited to the range *[0, N-1]*.
> Interfaces
> > -    should include placeholder methods to fill the ordinal positions of
> removed
> > -    methods (for example "Unused_Message_7@7()" or "RemovedMessage@42()",
> etc).
> > +* For an *N*-field struct, the set of explicitly assigned ordinal
> values must be
> > +    limited to the range *[0, N-1]*. Structs should include placeholder
> fields
> > +    to fill the ordinal positions of removed fields (for example
> "Unused_Field"
> > +    or "RemovedField", etc).
> >
> >  You may reorder fields, but you must ensure that the ordinal values of
> existing
> >  fields remain unchanged. For example, the following struct remains
> > @@ -712,8 +780,8 @@ If you want an enum to be extensible in the future,
> you can apply the
> >  ``` cpp
> >  [Extensible]
> >  enum Department {
> > -  SALES,
> > -  DEV,
> > +  kSales,
> > +  kDev,
> >  };
> >  ```
> >
> > @@ -722,9 +790,9 @@ And later you can extend this enum without breaking
> backwards compatibility:
> >  ``` cpp
> >  [Extensible]
> >  enum Department {
> > -  SALES,
> > -  DEV,
> > -  [MinVersion=1] RESEARCH,
> > +  kSales,
> > +  kDev,
> > +  [MinVersion=1] kResearch,
> >  };
> >  ```
> >
> > diff --git a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py
> b/utils/ipc/mojo/public/tools/bindings/checks/__init__.py
> > new file mode 100644
> > index 00000000..e69de29b
> > 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
> > new file mode 100644
> > index 00000000..3a2d2a3b
> > --- /dev/null
> > +++
> b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
> > @@ -0,0 +1,168 @@
> > +# Copyright 2022 The Chromium Authors
> > +# Use of this source code is governed by a BSD-style license that can be
> > +# found in the LICENSE file.
> > +"""Validate mojo attributes are allowed in Chrome before generation."""
> > +
> > +import mojom.generate.check as check
> > +import mojom.generate.module as module
> > +
> > +_COMMON_ATTRIBUTES = {
> > +    'EnableIf',
> > +    'EnableIfNot',
> > +}
> > +
> > +# For struct, union & parameter lists.
> > +_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> > +    'MinVersion',
> > +    'RenamedFrom',
> > +}
> > +
> > +# Note: `Default`` goes on the default _value_, not on the enum.
> > +# Note: [Stable] without [Extensible] is not allowed.
> > +_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> > +    'Extensible',
> > +    'Native',
> > +    'Stable',
> > +    'RenamedFrom',
> > +    'Uuid',
> > +}
> > +
> > +# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal.
> > +_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> > +    'Default',
> > +    'MinVersion',
> > +}
> > +
> > +_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> > +    'WebUIJsBridge',
> > +    'RenamedFrom',
> > +    'RequireContext',
> > +    'ServiceSandbox',
> > +    'Stable',
> > +    'Uuid',
> > +}
> > +
> > +_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> > +    'AllowedContext',
> > +    'MinVersion',
> > +    'NoInterrupt',
> > +    'Sync',
> > +    'UnlimitedSize',
> > +}
> > +
> > +_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> > +    'JavaConstantsClassName',
> > +    'JavaPackage',
> > +}
> > +
> > +_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
> > +
> > +_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> > +    'CustomSerializer',
> > +    'JavaClassName',
> > +    'Native',
> > +    'Stable',
> > +    'RenamedFrom',
> > +    'Uuid',
> > +}
> > +
> > +_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
> > +
> > +_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | {
> > +    'Extensible',
> > +    'Stable',
> > +    'RenamedFrom',
> > +    'Uuid',
> > +}
> > +
> > +_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | {
> > +    'Default',
> > +}
> > +
> > +# TODO(https://crbug.com/1193875) empty this set and remove the
> allowlist.
> > +_STABLE_ONLY_ALLOWLISTED_ENUMS = {
> > +    'crosapi.mojom.OptionalBool',
> > +    'crosapi.mojom.TriState',
> > +}
> > +
> > +
> > +class Check(check.Check):
> > +  def __init__(self, *args, **kwargs):
> > +    super(Check, self).__init__(*args, **kwargs)
> > +
> > +  def _Respell(self, allowed, attribute):
> > +    for a in allowed:
> > +      if a.lower() == attribute.lower():
> > +        return f" - Did you mean: {a}?"
> > +    return ""
> > +
> > +  def _CheckAttributes(self, context, allowed, attributes):
> > +    if not attributes:
> > +      return
> > +    for attribute in attributes:
> > +      if not attribute in allowed:
> > +        # Is there a close misspelling?
> > +        hint = self._Respell(allowed, attribute)
> > +        raise check.CheckException(
> > +            self.module,
> > +            f"attribute {attribute} not allowed on {context}{hint}")
> > +
> > +  def _CheckEnumAttributes(self, enum):
> > +    if enum.attributes:
> > +      self._CheckAttributes("enum", _ENUM_ATTRIBUTES, enum.attributes)
> > +      if 'Stable' in enum.attributes and not 'Extensible' in
> enum.attributes:
> > +        full_name = f"{self.module.mojom_namespace}.{enum.mojom_name}"
> > +        if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS:
> > +          raise check.CheckException(
> > +              self.module,
> > +              f"[Extensible] required on [Stable] enum {full_name}")
> > +    for enumval in enum.fields:
> > +      self._CheckAttributes("enum value", _ENUMVAL_ATTRIBUTES,
> > +                            enumval.attributes)
> > +
> > +  def _CheckInterfaceAttributes(self, interface):
> > +    self._CheckAttributes("interface", _INTERFACE_ATTRIBUTES,
> > +                          interface.attributes)
> > +    for method in interface.methods:
> > +      self._CheckAttributes("method", _METHOD_ATTRIBUTES,
> method.attributes)
> > +      for param in method.parameters:
> > +        self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
> > +                              param.attributes)
> > +      if method.response_parameters:
> > +        for param in method.response_parameters:
> > +          self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
> > +                                param.attributes)
> > +    for enum in interface.enums:
> > +      self._CheckEnumAttributes(enum)
> > +
> > +  def _CheckModuleAttributes(self):
> > +    self._CheckAttributes("module", _MODULE_ATTRIBUTES,
> self.module.attributes)
> > +
> > +  def _CheckStructAttributes(self, struct):
> > +    self._CheckAttributes("struct", _STRUCT_ATTRIBUTES,
> struct.attributes)
> > +    for field in struct.fields:
> > +      self._CheckAttributes("struct field", _STRUCT_FIELD_ATTRIBUTES,
> > +                            field.attributes)
> > +    for enum in struct.enums:
> > +      self._CheckEnumAttributes(enum)
> > +
> > +  def _CheckUnionAttributes(self, union):
> > +    self._CheckAttributes("union", _UNION_ATTRIBUTES, union.attributes)
> > +    for field in union.fields:
> > +      self._CheckAttributes("union field", _UNION_FIELD_ATTRIBUTES,
> > +                            field.attributes)
> > +
> > +  def CheckModule(self):
> > +    """Note that duplicate attributes are forbidden at the parse phase.
> > +    We also do not need to look at the types of any parameters, as they
> will be
> > +    checked where they are defined. Consts do not have attributes so
> can be
> > +    skipped."""
> > +    self._CheckModuleAttributes()
> > +    for interface in self.module.interfaces:
> > +      self._CheckInterfaceAttributes(interface)
> > +    for enum in self.module.enums:
> > +      self._CheckEnumAttributes(enum)
> > +    for struct in self.module.structs:
> > +      self._CheckStructAttributes(struct)
> > +    for union in self.module.unions:
> > +      self._CheckUnionAttributes(union)
> > 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
> > new file mode 100644
> > index 00000000..8c7f3c2c
> > --- /dev/null
> > +++
> b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
> > @@ -0,0 +1,186 @@
> > +# Copyright 2022 The Chromium Authors
> > +# Use of this source code is governed by a BSD-style license that can be
> > +# found in the LICENSE file.
> > +
> > +import unittest
> > +
> > +import mojom.generate.check as check
> > +from mojom_bindings_generator import LoadChecks, _Generate
> > +from mojom_parser_test_case import MojomParserTestCase
> > +
> > +
> > +class FakeArgs:
> > +  """Fakes args to _Generate - intention is to do just enough to run
> checks"""
> > +
> > +  def __init__(self, tester, files=None):
> > +    """ `tester` is MojomParserTestCase for paths.
> > +        `files` will have tester path added."""
> > +    self.checks_string = 'attributes'
> > +    self.depth = tester.GetPath('')
> > +    self.filelist = None
> > +    self.filename = [tester.GetPath(x) for x in files]
> > +    self.gen_directories = tester.GetPath('gen')
> > +    self.generators_string = ''
> > +    self.import_directories = []
> > +    self.output_dir = tester.GetPath('out')
> > +    self.scrambled_message_id_salt_paths = None
> > +    self.typemaps = []
> > +    self.variant = 'none'
> > +
> > +
> > +class MojoBindingsCheckTest(MojomParserTestCase):
> > +  def _ParseAndGenerate(self, mojoms):
> > +    self.ParseMojoms(mojoms)
> > +    args = FakeArgs(self, files=mojoms)
> > +    _Generate(args, {})
> > +
> > +  def _testValid(self, filename, content):
> > +    self.WriteFile(filename, content)
> > +    self._ParseAndGenerate([filename])
> > +
> > +  def _testThrows(self, filename, content, regexp):
> > +    mojoms = []
> > +    self.WriteFile(filename, content)
> > +    mojoms.append(filename)
> > +    with self.assertRaisesRegexp(check.CheckException, regexp):
> > +      self._ParseAndGenerate(mojoms)
> > +
> > +  def testLoads(self):
> > +    """Validate that the check is registered under the expected name."""
> > +    check_modules = LoadChecks('attributes')
> > +    self.assertTrue(check_modules['attributes'])
> > +
> > +  def testNoAnnotations(self):
> > +    # Undecorated mojom should be fine.
> > +    self._testValid(
> > +        "a.mojom", """
> > +      module a;
> > +      struct Bar { int32 a; };
> > +      enum Hello { kValue };
> > +      union Thingy { Bar b; Hello hi; };
> > +      interface Foo {
> > +        Foo(int32 a, Hello hi, Thingy t) => (Bar b);
> > +      };
> > +    """)
> > +
> > +  def testValidAnnotations(self):
> > +    # Obviously this is meaningless and won't generate, but it should
> pass
> > +    # the attribute check's validation.
> > +    self._testValid(
> > +        "a.mojom", """
> > +
> [JavaConstantsClassName="FakeClass",JavaPackage="org.chromium.Fake"]
> > +      module a;
> > +      [Stable, Extensible]
> > +      enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 };
> > +      [Native]
> > +      enum NativeEnum {};
> > +      [Stable,Extensible]
> > +      union Thingy { Bar b; [Default]int32 c; Hello hi; };
> > +
> > +      [Stable,RenamedFrom="module.other.Foo",
> > +       Uuid="4C178401-4B07-4C2E-9255-5401A943D0C7"]
> > +      struct Structure { Hello hi; };
> > +
> > +      [ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable,
> > +       Uuid="2F17D7DD-865A-4B1C-9394-9C94E035E82F"]
> > +      interface Foo {
> > +        [AllowedContext=Hello.kValue]
> > +        Foo@0(int32 a) => (int32 b);
> > +        [MinVersion=2,Sync,UnlimitedSize,NoInterrupt]
> > +        Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c);
> > +      };
> > +    """)
> > +
> > +  def testWrongModuleStable(self):
> > +    contents = """
> > +      // err: module cannot be Stable
> > +      [Stable]
> > +      module a;
> > +      enum Hello { kValue, kValue2, kValue3 };
> > +      enum NativeEnum {};
> > +      struct Structure { Hello hi; };
> > +
> > +      interface Foo {
> > +        Foo(int32 a) => (int32 b);
> > +        Bar(int32 b, Structure? s) => (bool c);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents,
> > +                     'attribute Stable not allowed on module')
> > +
> > +  def testWrongEnumDefault(self):
> > +    contents = """
> > +      module a;
> > +      // err: default should go on EnumValue not Enum.
> > +      [Default=kValue]
> > +      enum Hello { kValue, kValue2, kValue3 };
> > +      enum NativeEnum {};
> > +      struct Structure { Hello hi; };
> > +
> > +      interface Foo {
> > +        Foo(int32 a) => (int32 b);
> > +        Bar(int32 b, Structure? s) => (bool c);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents,
> > +                     'attribute Default not allowed on enum')
> > +
> > +  def testWrongStructMinVersion(self):
> > +    contents = """
> > +      module a;
> > +      enum Hello { kValue, kValue2, kValue3 };
> > +      enum NativeEnum {};
> > +      // err: struct cannot have MinVersion.
> > +      [MinVersion=2]
> > +      struct Structure { Hello hi; };
> > +
> > +      interface Foo {
> > +        Foo(int32 a) => (int32 b);
> > +        Bar(int32 b, Structure? s) => (bool c);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents,
> > +                     'attribute MinVersion not allowed on struct')
> > +
> > +  def testWrongMethodRequireContext(self):
> > +    contents = """
> > +      module a;
> > +      enum Hello { kValue, kValue2, kValue3 };
> > +      enum NativeEnum {};
> > +      struct Structure { Hello hi; };
> > +
> > +      interface Foo {
> > +        // err: RequireContext is for interfaces.
> > +        [RequireContext=Hello.kValue]
> > +        Foo(int32 a) => (int32 b);
> > +        Bar(int32 b, Structure? s) => (bool c);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents,
> > +                     'RequireContext not allowed on method')
> > +
> > +  def testWrongMethodRequireContext(self):
> > +    # crbug.com/1230122
> > +    contents = """
> > +      module a;
> > +      interface Foo {
> > +        // err: sync not Sync.
> > +        [sync]
> > +        Foo(int32 a) => (int32 b);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents,
> > +                     'attribute sync not allowed.*Did you mean: Sync')
> > +
> > +  def testStableExtensibleEnum(self):
> > +    # crbug.com/1193875
> > +    contents = """
> > +      module a;
> > +      [Stable]
> > +      enum Foo {
> > +        kDefaultVal,
> > +        kOtherVal = 2,
> > +      };
> > +    """
> > +    self._testThrows('a.mojom', contents,
> > +                     'Extensible.*?required.*?Stable.*?enum')
> > 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
> > new file mode 100644
> > index 00000000..702d41c3
> > --- /dev/null
> > +++
> b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
> > @@ -0,0 +1,34 @@
> > +# Copyright 2022 The Chromium Authors
> > +# Use of this source code is governed by a BSD-style license that can be
> > +# found in the LICENSE file.
> > +"""Ensure no duplicate type definitions before generation."""
> > +
> > +import mojom.generate.check as check
> > +import mojom.generate.module as module
> > +
> > +
> > +class Check(check.Check):
> > +  def __init__(self, *args, **kwargs):
> > +    super(Check, self).__init__(*args, **kwargs)
> > +
> > +  def CheckModule(self):
> > +    kinds = dict()
> > +    for module in self.module.imports:
> > +      for kind in module.enums + module.structs + module.unions:
> > +        kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
> > +        if kind_name in kinds:
> > +          previous_module = kinds[kind_name]
> > +          if previous_module.path != module.path:
> > +            raise check.CheckException(
> > +                self.module, f"multiple-definition for type
> {kind_name}" +
> > +                f"(defined in both {previous_module} and {module})")
> > +        kinds[kind_name] = kind.module
> > +
> > +    for kind in self.module.enums + self.module.structs +
> self.module.unions:
> > +      kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
> > +      if kind_name in kinds:
> > +        previous_module = kinds[kind_name]
> > +        raise check.CheckException(
> > +            self.module, f"multiple-definition for type {kind_name}" +
> > +            f"(previous definition in {previous_module})")
> > +    return True
> > 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
> > new file mode 100644
> > index 00000000..d570e26c
> > --- /dev/null
> > +++
> b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
> > @@ -0,0 +1,102 @@
> > +# Copyright 2022 The Chromium Authors
> > +# Use of this source code is governed by a BSD-style license that can be
> > +# found in the LICENSE file.
> > +"""Validate RequireContext and AllowedContext annotations before
> generation."""
> > +
> > +import mojom.generate.check as check
> > +import mojom.generate.module as module
> > +
> > +
> > +class Check(check.Check):
> > +  def __init__(self, *args, **kwargs):
> > +    self.kind_to_interfaces = dict()
> > +    super(Check, self).__init__(*args, **kwargs)
> > +
> > +  def _IsPassedInterface(self, candidate):
> > +    if isinstance(
> > +        candidate.kind,
> > +        (module.PendingReceiver, module.PendingRemote,
> > +         module.PendingAssociatedReceiver,
> module.PendingAssociatedRemote)):
> > +      return True
> > +    return False
> > +
> > +  def _CheckInterface(self, method, param):
> > +    # |param| is a pending_x<Interface> so need .kind.kind to get
> Interface.
> > +    interface = param.kind.kind
> > +    if interface.require_context:
> > +      if method.allowed_context is None:
> > +        raise check.CheckException(
> > +            self.module, "method `{}` has parameter `{}` which passes
> interface"
> > +            " `{}` that requires an AllowedContext annotation but none
> exists.".
> > +            format(
> > +                method.mojom_name,
> > +                param.mojom_name,
> > +                interface.mojom_name,
> > +            ))
> > +      # If a string was provided, or if an enum was not imported, this
> will
> > +      # be a string and we cannot validate that it is in range.
> > +      if not isinstance(method.allowed_context, module.EnumValue):
> > +        raise check.CheckException(
> > +            self.module,
> > +            "method `{}` has AllowedContext={} which is not a valid
> enum value."
> > +            .format(method.mojom_name, method.allowed_context))
> > +      # EnumValue must be from the same enum to be compared.
> > +      if interface.require_context.enum != method.allowed_context.enum:
> > +        raise check.CheckException(
> > +            self.module, "method `{}` has parameter `{}` which passes
> interface"
> > +            " `{}` that requires AllowedContext={} but one of kind `{}`
> was "
> > +            "provided.".format(
> > +                method.mojom_name,
> > +                param.mojom_name,
> > +                interface.mojom_name,
> > +                interface.require_context.enum,
> > +                method.allowed_context.enum,
> > +            ))
> > +      # RestrictContext enums have most privileged field first (lowest
> value).
> > +      interface_value = interface.require_context.field.numeric_value
> > +      method_value = method.allowed_context.field.numeric_value
> > +      if interface_value < method_value:
> > +        raise check.CheckException(
> > +            self.module, "RequireContext={} > AllowedContext={} for
> method "
> > +            "`{}` which passes interface `{}`.".format(
> > +                interface.require_context.GetSpec(),
> > +                method.allowed_context.GetSpec(), method.mojom_name,
> > +                interface.mojom_name))
> > +      return True
> > +
> > +  def _GatherReferencedInterfaces(self, field):
> > +    key = field.kind.spec
> > +    # structs/unions can nest themselves so we need to bookkeep.
> > +    if not key in self.kind_to_interfaces:
> > +      # Might reference ourselves so have to create the list first.
> > +      self.kind_to_interfaces[key] = set()
> > +      for param in field.kind.fields:
> > +        if self._IsPassedInterface(param):
> > +          self.kind_to_interfaces[key].add(param)
> > +        elif isinstance(param.kind, (module.Struct, module.Union)):
> > +          for iface in self._GatherReferencedInterfaces(param):
> > +            self.kind_to_interfaces[key].add(iface)
> > +    return self.kind_to_interfaces[key]
> > +
> > +  def _CheckParams(self, method, params):
> > +    # Note: we have to repeat _CheckParams for each method as each
> might have
> > +    # different AllowedContext= attributes. We cannot memoize this
> function,
> > +    # but can do so for gathering referenced interfaces as their
> RequireContext
> > +    # attributes do not change.
> > +    for param in params:
> > +      if self._IsPassedInterface(param):
> > +        self._CheckInterface(method, param)
> > +      elif isinstance(param.kind, (module.Struct, module.Union)):
> > +        for interface in self._GatherReferencedInterfaces(param):
> > +          self._CheckInterface(method, interface)
> > +
> > +  def _CheckMethod(self, method):
> > +    if method.parameters:
> > +      self._CheckParams(method, method.parameters)
> > +    if method.response_parameters:
> > +      self._CheckParams(method, method.response_parameters)
> > +
> > +  def CheckModule(self):
> > +    for interface in self.module.interfaces:
> > +      for method in interface.methods:
> > +        self._CheckMethod(method)
> > 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
> > new file mode 100644
> > index 00000000..a6cd71e2
> > --- /dev/null
> > +++
> b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
> > @@ -0,0 +1,254 @@
> > +# Copyright 2022 The Chromium Authors
> > +# Use of this source code is governed by a BSD-style license that can be
> > +# found in the LICENSE file.
> > +
> > +import unittest
> > +
> > +import mojom.generate.check as check
> > +from mojom_bindings_generator import LoadChecks, _Generate
> > +from mojom_parser_test_case import MojomParserTestCase
> > +
> > +# Mojoms that we will use in multiple tests.
> > +basic_mojoms = {
> > +    'level.mojom':
> > +    """
> > +  module level;
> > +  enum Level {
> > +    kHighest,
> > +    kMiddle,
> > +    kLowest,
> > +  };
> > +  """,
> > +    'interfaces.mojom':
> > +    """
> > +  module interfaces;
> > +  import "level.mojom";
> > +  struct Foo {int32 bar;};
> > +  [RequireContext=level.Level.kHighest]
> > +  interface High {
> > +    DoFoo(Foo foo);
> > +  };
> > +  [RequireContext=level.Level.kMiddle]
> > +  interface Mid {
> > +    DoFoo(Foo foo);
> > +  };
> > +  [RequireContext=level.Level.kLowest]
> > +  interface Low {
> > +    DoFoo(Foo foo);
> > +  };
> > +  """
> > +}
> > +
> > +
> > +class FakeArgs:
> > +  """Fakes args to _Generate - intention is to do just enough to run
> checks"""
> > +
> > +  def __init__(self, tester, files=None):
> > +    """ `tester` is MojomParserTestCase for paths.
> > +        `files` will have tester path added."""
> > +    self.checks_string = 'restrictions'
> > +    self.depth = tester.GetPath('')
> > +    self.filelist = None
> > +    self.filename = [tester.GetPath(x) for x in files]
> > +    self.gen_directories = tester.GetPath('gen')
> > +    self.generators_string = ''
> > +    self.import_directories = []
> > +    self.output_dir = tester.GetPath('out')
> > +    self.scrambled_message_id_salt_paths = None
> > +    self.typemaps = []
> > +    self.variant = 'none'
> > +
> > +
> > +class MojoBindingsCheckTest(MojomParserTestCase):
> > +  def _WriteBasicMojoms(self):
> > +    for filename, contents in basic_mojoms.items():
> > +      self.WriteFile(filename, contents)
> > +    return list(basic_mojoms.keys())
> > +
> > +  def _ParseAndGenerate(self, mojoms):
> > +    self.ParseMojoms(mojoms)
> > +    args = FakeArgs(self, files=mojoms)
> > +    _Generate(args, {})
> > +
> > +  def testLoads(self):
> > +    """Validate that the check is registered under the expected name."""
> > +    check_modules = LoadChecks('restrictions')
> > +    self.assertTrue(check_modules['restrictions'])
> > +
> > +  def testValidAnnotations(self):
> > +    mojoms = self._WriteBasicMojoms()
> > +
> > +    a = 'a.mojom'
> > +    self.WriteFile(
> > +        a, """
> > +      module a;
> > +      import "level.mojom";
> > +      import "interfaces.mojom";
> > +
> > +      interface PassesHigh {
> > +        [AllowedContext=level.Level.kHighest]
> > +        DoHigh(pending_receiver<interfaces.High> hi);
> > +      };
> > +      interface PassesMedium {
> > +        [AllowedContext=level.Level.kMiddle]
> > +        DoMedium(pending_receiver<interfaces.Mid> hi);
> > +        [AllowedContext=level.Level.kMiddle]
> > +        DoMediumRem(pending_remote<interfaces.Mid> hi);
> > +        [AllowedContext=level.Level.kMiddle]
> > +        DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);
> > +        [AllowedContext=level.Level.kMiddle]
> > +        DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);
> > +      };
> > +      interface PassesLow {
> > +        [AllowedContext=level.Level.kLowest]
> > +        DoLow(pending_receiver<interfaces.Low> hi);
> > +      };
> > +
> > +      struct One { pending_receiver<interfaces.High> hi; };
> > +      struct Two { One one; };
> > +      interface PassesNestedHigh {
> > +        [AllowedContext=level.Level.kHighest]
> > +        DoNestedHigh(Two two);
> > +      };
> > +
> > +      // Allowed as PassesHigh is not itself restricted.
> > +      interface PassesPassesHigh {
> > +        DoPass(pending_receiver<PassesHigh> hiho);
> > +      };
> > +    """)
> > +    mojoms.append(a)
> > +    self._ParseAndGenerate(mojoms)
> > +
> > +  def _testThrows(self, filename, content, regexp):
> > +    mojoms = self._WriteBasicMojoms()
> > +    self.WriteFile(filename, content)
> > +    mojoms.append(filename)
> > +    with self.assertRaisesRegexp(check.CheckException, regexp):
> > +      self._ParseAndGenerate(mojoms)
> > +
> > +  def testMissingAnnotation(self):
> > +    contents = """
> > +      module b;
> > +      import "level.mojom";
> > +      import "interfaces.mojom";
> > +
> > +      interface PassesHigh {
> > +        // err: missing annotation.
> > +        DoHigh(pending_receiver<interfaces.High> hi);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
> > +
> > +  def testAllowTooLow(self):
> > +    contents = """
> > +      module b;
> > +      import "level.mojom";
> > +      import "interfaces.mojom";
> > +
> > +      interface PassesHigh {
> > +        // err: level is worse than required.
> > +        [AllowedContext=level.Level.kMiddle]
> > +        DoHigh(pending_receiver<interfaces.High> hi);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents,
> > +                     'RequireContext=.*?kHighest >
> AllowedContext=.*?kMiddle')
> > +
> > +  def testWrongEnumInAllow(self):
> > +    contents = """
> > +      module b;
> > +      import "level.mojom";
> > +      import "interfaces.mojom";
> > +      enum Blah {
> > +        kZero,
> > +      };
> > +      interface PassesHigh {
> > +        // err: different enums.
> > +        [AllowedContext=Blah.kZero]
> > +        DoHigh(pending_receiver<interfaces.High> hi);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents, 'but one of kind')
> > +
> > +  def testNotAnEnumInAllow(self):
> > +    contents = """
> > +      module b;
> > +      import "level.mojom";
> > +      import "interfaces.mojom";
> > +      interface PassesHigh {
> > +        // err: not an enum.
> > +        [AllowedContext=doopdedoo.mojom.kWhatever]
> > +        DoHigh(pending_receiver<interfaces.High> hi);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents, 'not a valid enum value')
> > +
> > +  def testMissingAllowedForNestedStructs(self):
> > +    contents = """
> > +      module b;
> > +      import "level.mojom";
> > +      import "interfaces.mojom";
> > +      struct One { pending_receiver<interfaces.High> hi; };
> > +      struct Two { One one; };
> > +      interface PassesNestedHigh {
> > +        // err: missing annotation.
> > +        DoNestedHigh(Two two);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
> > +
> > +  def testMissingAllowedForNestedUnions(self):
> > +    contents = """
> > +      module b;
> > +      import "level.mojom";
> > +      import "interfaces.mojom";
> > +      struct One { pending_receiver<interfaces.High> hi; };
> > +      struct Two { One one; };
> > +      union Three {One one; Two two; };
> > +      interface PassesNestedHigh {
> > +        // err: missing annotation.
> > +        DoNestedHigh(Three three);
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
> > +
> > +  def testMultipleInterfacesThrows(self):
> > +    contents = """
> > +      module b;
> > +      import "level.mojom";
> > +      import "interfaces.mojom";
> > +      struct One { pending_receiver<interfaces.High> hi; };
> > +      interface PassesMultipleInterfaces {
> > +        [AllowedContext=level.Level.kMiddle]
> > +        DoMultiple(
> > +          pending_remote<interfaces.Mid> mid,
> > +          pending_receiver<interfaces.High> hi,
> > +          One one
> > +        );
> > +      };
> > +    """
> > +    self._testThrows('b.mojom', contents,
> > +                     'RequireContext=.*?kHighest >
> AllowedContext=.*?kMiddle')
> > +
> > +  def testMultipleInterfacesAllowed(self):
> > +    """Multiple interfaces can be passed, all satisfy the level."""
> > +    mojoms = self._WriteBasicMojoms()
> > +
> > +    b = "b.mojom"
> > +    self.WriteFile(
> > +        b, """
> > +      module b;
> > +      import "level.mojom";
> > +      import "interfaces.mojom";
> > +      struct One { pending_receiver<interfaces.High> hi; };
> > +      interface PassesMultipleInterfaces {
> > +        [AllowedContext=level.Level.kHighest]
> > +        DoMultiple(
> > +          pending_receiver<interfaces.High> hi,
> > +          pending_remote<interfaces.Mid> mid,
> > +          One one
> > +        );
> > +      };
> > +    """)
> > +    mojoms.append(b)
> > +    self._ParseAndGenerate(mojoms)
> > diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> > index 48bc66fd..4dd26d4a 100755
> > --- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> > +++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
> > @@ -1,5 +1,5 @@
> >  #!/usr/bin/env python
> > -# Copyright 2019 The Chromium Authors. All rights reserved.
> > +# Copyright 2019 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  #
> > @@ -15,6 +15,7 @@
> >  from __future__ import print_function
> >
> >  import optparse
> > +import sys
> >
> >
> >  def Concatenate(filenames):
> > @@ -47,7 +48,7 @@ def main():
> >    parser.set_usage("""Concatenate several files into one.
> >        Equivalent to: cat file1 ... > target.""")
> >    (_options, args) = parser.parse_args()
> > -  exit(0 if Concatenate(args) else 1)
> > +  sys.exit(0 if Concatenate(args) else 1)
> >
> >
> >  if __name__ == "__main__":
> > 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
> > index be8985ce..770081e1 100755
> > ---
> 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
> > @@ -1,5 +1,5 @@
> >  #!/usr/bin/env python
> > -# Copyright 2018 The Chromium Authors. All rights reserved.
> > +# Copyright 2018 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -20,6 +20,7 @@ from __future__ import print_function
> >
> >  import optparse
> >  import re
> > +import sys
> >
> >
> >  _MOJO_INTERNAL_MODULE_NAME = "mojo.internal"
> > @@ -34,7 +35,7 @@ def FilterLine(filename, line, output):
> >      match = re.match("goog.provide\('([^']+)'\);", line)
> >      if not match:
> >        print("Invalid goog.provide line in %s:\n%s" % (filename, line))
> > -      exit(1)
> > +      sys.exit(1)
> >
> >      module_name = match.group(1)
> >      if module_name == _MOJO_INTERNAL_MODULE_NAME:
> > @@ -67,7 +68,8 @@ def main():
> >      Concatenate several files into one, stripping Closure provide and
> >      require directives along the way.""")
> >    (_, args) = parser.parse_args()
> > -  exit(0 if ConcatenateAndReplaceExports(args) else 1)
> > +  sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)
> > +
> >
> >  if __name__ == "__main__":
> >    main()
> > 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
> > index 8b78d092..c6daff03 100644
> > --- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
> > +++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2017 The Chromium Authors. All rights reserved.
> > +# Copyright 2017 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  """Generates a list of all files in a directory.
> > diff --git
> a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> > index a0096649..d73c6ea4 100755
> > --- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> > +++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
> > @@ -1,5 +1,5 @@
> >  #!/usr/bin/env python
> > -# Copyright 2016 The Chromium Authors. All rights reserved.
> > +# Copyright 2016 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  """Generates a JSON typemap from its command-line arguments and
> dependencies.
> > @@ -82,6 +82,7 @@ def LoadCppTypemapConfig(path):
> >        for entry in config['types']:
> >          configs[entry['mojom']] = {
> >              'typename': entry['cpp'],
> > +            'forward_declaration': entry.get('forward_declaration',
> None),
> >              'public_headers': config.get('traits_headers', []),
> >              'traits_headers': config.get('traits_private_headers', []),
> >              'copyable_pass_by_value':
> entry.get('copyable_pass_by_value',
> > diff --git a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
> b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
> > new file mode 100755
> > index 00000000..cefee7a4
> > --- /dev/null
> > +++ b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
> > @@ -0,0 +1,47 @@
> > +#!/usr/bin/env python3
> > +# Copyright 2023 The Chromium Authors
> > +# Use of this source code is governed by a BSD-style license that can be
> > +# found in the LICENSE file.
> > +#
> > +# This utility minifies JS files with terser.
> > +#
> > +# Instance of 'node' has no 'RunNode' member (no-member)
> > +# pylint: disable=no-member
> > +
> > +import argparse
> > +import os
> > +import sys
> > +
> > +_HERE_PATH = os.path.dirname(__file__)
> > +_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..',
> '..'))
> > +_CWD = os.getcwd()
> > +sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
> > +import node
> > +import node_modules
> > +
> > +
> > +def MinifyFile(input_file, output_file):
> > +  node.RunNode([
> > +      node_modules.PathToTerser(), input_file, '--mangle', '--compress',
> > +      '--comments', 'false', '--output', output_file
> > +  ])
> > +
> > +
> > +def main(argv):
> > +  parser = argparse.ArgumentParser()
> > +  parser.add_argument('--input', required=True)
> > +  parser.add_argument('--output', required=True)
> > +  args = parser.parse_args(argv)
> > +
> > +  # Delete the output file if it already exists. It may be a sym link
> to the
> > +  # input, because in non-optimized/pre-Terser builds the input file is
> copied
> > +  # to the output location with gn copy().
> > +  out_path = os.path.join(_CWD, args.output)
> > +  if (os.path.exists(out_path)):
> > +    os.remove(out_path)
> > +
> > +  MinifyFile(os.path.join(_CWD, args.input), out_path)
> > +
> > +
> > +if __name__ == '__main__':
> > +  main(sys.argv[1:])
> > diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni
> b/utils/ipc/mojo/public/tools/bindings/mojom.gni
> > index fe2a1da3..ded53259 100644
> > --- a/utils/ipc/mojo/public/tools/bindings/mojom.gni
> > +++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni
> > @@ -1,11 +1,11 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > -import("//build/config/python.gni")
> >  import("//third_party/closure_compiler/closure_args.gni")
> >  import("//third_party/closure_compiler/compile_js.gni")
> >  import("//third_party/protobuf/proto_library.gni")
> > +import("//ui/webui/resources/tools/generate_grd.gni")
> >  import("//ui/webui/webui_features.gni")
> >
> >  # TODO(rockot): Maybe we can factor these dependencies out of //mojo.
> They're
> > @@ -16,10 +16,12 @@ import("//ui/webui/webui_features.gni")
> >  import("//build/config/chrome_build.gni")
> >  import("//build/config/chromecast_build.gni")
> >  import("//build/config/chromeos/ui_mode.gni")
> > +import("//build/config/features.gni")
> >  import("//build/config/nacl/config.gni")
> >  import("//build/toolchain/kythe.gni")
> >  import("//components/nacl/features.gni")
> >  import("//third_party/jinja2/jinja2.gni")
> > +import("//third_party/ply/ply.gni")
> >  import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
> >  declare_args() {
> >    # Indicates whether typemapping should be supported in this build
> > @@ -38,17 +40,25 @@ declare_args() {
> >    # scrambling on all platforms.
> >    enable_mojom_message_id_scrambling = true
> >
> > +  # Enables generating javascript fuzzing-related code and the bindings
> for the
> > +  # MojoLPM fuzzer targets. Off by default.
> > +  enable_mojom_fuzzer = false
> > +
> >    # Enables Closure compilation of generated JS lite bindings. In
> environments
> >    # where compilation is supported, any mojom target "foo" will also
> have a
> >    # corresponding "foo_js_library_for_compile" target generated.
> > -  enable_mojom_closure_compile = enable_js_type_check && optimize_webui
> > -
> > -  # Enables generating Typescript bindings and compiling them to JS
> bindings.
> > -  enable_typescript_bindings = false
> > +  if (is_chromeos_ash) {
> > +    enable_mojom_closure_compile = enable_js_type_check &&
> optimize_webui
> > +  }
> > +}
> >
> > -  # Enables generating javascript fuzzing-related code and the bindings
> for the
> > -  # MojoLPM fuzzer targets. Off by default.
> > -  enable_mojom_fuzzer = false
> > +# Closure libraries are needed for mojom_closure_compile, and when
> > +# js_type_check is enabled on Ash.
> > +if (is_chromeos_ash) {
> > +  generate_mojom_closure_libraries =
> > +      enable_mojom_closure_compile || enable_js_type_check
> > +} else {
> > +  generate_mojom_closure_libraries = false
> >  }
> >
> >  # NOTE: We would like to avoid scrambling message IDs where it doesn't
> add
> > @@ -69,9 +79,8 @@ declare_args() {
> >  # lacros-chrome switches to target_os="chromeos"
> >  enable_scrambled_message_ids =
> >      enable_mojom_message_id_scrambling &&
> > -    (is_mac || is_win ||
> > -     (is_linux && !is_chromeos_ash && !is_chromecast &&
> !is_chromeos_lacros) ||
> > -     ((enable_nacl || is_nacl || is_nacl_nonsfi) &&
> > +    (is_mac || is_win || (is_linux && !is_castos) ||
> > +     ((enable_nacl || is_nacl) &&
> >        (target_os != "chromeos" && !chromeos_is_browser_only)))
> >
> >  _mojom_tools_root = "//mojo/public/tools"
> > @@ -80,7 +89,9 @@ mojom_parser_script =
> "$_mojom_tools_root/mojom/mojom_parser.py"
> >  mojom_parser_sources = [
> >    "$_mojom_library_root/__init__.py",
> >    "$_mojom_library_root/error.py",
> > +  "$_mojom_library_root/fileutil.py",
> >    "$_mojom_library_root/generate/__init__.py",
> > +  "$_mojom_library_root/generate/check.py",
> >    "$_mojom_library_root/generate/generator.py",
> >    "$_mojom_library_root/generate/module.py",
> >    "$_mojom_library_root/generate/pack.py",
> > @@ -88,20 +99,28 @@ mojom_parser_sources = [
> >    "$_mojom_library_root/generate/translate.py",
> >    "$_mojom_library_root/parse/__init__.py",
> >    "$_mojom_library_root/parse/ast.py",
> > +  "$_mojom_library_root/parse/conditional_features.py",
> >    "$_mojom_library_root/parse/lexer.py",
> >    "$_mojom_library_root/parse/parser.py",
> > +  "//tools/diagnosis/crbug_1001171.py",
> >  ]
> >
> >  mojom_generator_root = "$_mojom_tools_root/bindings"
> >  mojom_generator_script =
> "$mojom_generator_root/mojom_bindings_generator.py"
> >  mojom_generator_sources =
> >      mojom_parser_sources + [
> > +      "$mojom_generator_root/checks/__init__.py",
> > +      "$mojom_generator_root/checks/mojom_attributes_check.py",
> > +      "$mojom_generator_root/checks/mojom_definitions_check.py",
> > +      "$mojom_generator_root/checks/mojom_restrictions_check.py",
> > +      "$mojom_generator_root/generators/__init__.py",
> >        "$mojom_generator_root/generators/cpp_util.py",
> >        "$mojom_generator_root/generators/mojom_cpp_generator.py",
> >        "$mojom_generator_root/generators/mojom_java_generator.py",
> > -      "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
> >        "$mojom_generator_root/generators/mojom_js_generator.py",
> > +      "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
> >        "$mojom_generator_root/generators/mojom_ts_generator.py",
> > +
> "$mojom_generator_root/generators/mojom_webui_js_bridge_generator.py",
> >        "$mojom_generator_script",
> >      ]
> >
> > @@ -243,12 +262,16 @@ if (enable_scrambled_message_ids) {
> >  #       |cpp_only| is set to true, it overrides this to prevent
> generation of
> >  #       Java bindings.
> >  #
> > -#   enable_fuzzing (optional)
> > +#   enable_js_fuzzing (optional)
> > +#       Enables generation of javascript fuzzing sources for the target
> if the
> > +#       global build arg |enable_mojom_fuzzer| is also set to |true|.
> > +#       Defaults to |true|. If JS fuzzing generation is enabled for a
> target,
> > +#       the target will always generate JS bindings even if |cpp_only|
> is set to
> > +#       |true|. See note above.
> > +#
> > +#   enable_mojolpm_fuzzing (optional)
> >  #       Enables generation of fuzzing sources for the target if the
> global build
> > -#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to
> |true|. If
> > -#       fuzzing generation is enabled for a target, the target will
> always
> > -#       generate JS bindings even if |cpp_only| is set to |true|. See
> note
> > -#       above.
> > +#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to
> |true|.
> >  #
> >  #   support_lazy_serialization (optional)
> >  #       If set to |true|, generated C++ bindings will effectively
> prefer to
> > @@ -313,6 +336,16 @@ if (enable_scrambled_message_ids) {
> >  #   use_typescript_sources (optional)
> >  #       Uses the Typescript generator to generate JavaScript bindings.
> >  #
> > +#   generate_legacy_js_bindings (optional)
> > +#       Generate js_data_deps target containing legacy JavaScript
> bindings files
> > +#       for Blink tests and other non-WebUI users when generating
> TypeScript
> > +#       bindings for WebUI. Ignored if use_typescript_sources is not
> set to
> > +#       true.
> > +#
> > +#   webui_js_bridge_config (optional)
> > +#       Prefer to use the `mojom_with_webui_js_bridge` target below
> instead
> > +#       of using this argument directly.
> > +#
> >  #   js_generate_struct_deserializers (optional)
> >  #       Generates JS deerialize methods for structs.
> >  #
> > @@ -402,6 +435,12 @@ if (enable_scrambled_message_ids) {
> >  #             should be mapped in generated bindings. This is a string
> like
> >  #             "::base::Value" or "std::vector<::base::Value>".
> >  #
> > +#         forward_declaration (optional)
> > +#             A forward declaration of the C++ type, which bindings
> that don't
> > +#             need the full type definition can use to reduce the size
> of
> > +#             the generated code. This is a string like
> > +#             "namespace base { class Value; }".
> > +#
> >  #         move_only (optional)
> >  #             A boolean value (default false) which indicates whether
> the C++
> >  #             type is move-only. If true, generated bindings will pass
> the type
> > @@ -621,20 +660,26 @@ template("mojom") {
> >    build_metadata_filename =
> "$target_gen_dir/$target_name.build_metadata"
> >    build_metadata = {
> >    }
> > -  build_metadata.sources = rebase_path(sources_list)
> > +  build_metadata.sources = rebase_path(sources_list, target_gen_dir)
> >    build_metadata.deps = []
> >    foreach(dep, all_deps) {
> >      dep_target_gen_dir = get_label_info(dep, "target_gen_dir")
> >      dep_name = get_label_info(dep, "name")
> >      build_metadata.deps +=
> > -        [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata") ]
> > +        [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata",
> > +                      target_gen_dir) ]
> >    }
> >    write_file(build_metadata_filename, build_metadata, "json")
> >
> > -  generate_fuzzing =
> > -      (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) &&
> > +  generate_js_fuzzing =
> > +      (!defined(invoker.enable_js_fuzzing) ||
> invoker.enable_js_fuzzing) &&
> >        enable_mojom_fuzzer && (!defined(invoker.testonly) ||
> !invoker.testonly)
> >
> > +  generate_mojolpm_fuzzing =
> > +      (!defined(invoker.enable_mojolpm_fuzzing) ||
> > +       invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer &&
> > +      (!defined(invoker.testonly) || !invoker.testonly)
> > +
> >    parser_target_name = "${target_name}__parser"
> >    parser_deps = []
> >    foreach(dep, all_deps) {
> > @@ -683,12 +728,17 @@ template("mojom") {
> >        enabled_features += [ "is_win" ]
> >      }
> >
> > -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -    python2_action(parser_target_name) {
> > +    if (is_apple) {
> > +      enabled_features += [ "is_apple" ]
> > +    }
> > +
> > +    action(parser_target_name) {
> > +      allow_remote = true
> > +      custom_processor = "mojom_parser"
> >        script = mojom_parser_script
> > -      inputs = mojom_parser_sources + [ build_metadata_filename ]
> > +      inputs = mojom_parser_sources + ply_sources + [
> build_metadata_filename ]
> >        sources = sources_list
> > -      deps = parser_deps
> > +      public_deps = parser_deps
> >        outputs = []
> >        foreach(base_path, output_file_base_paths) {
> >          filename = get_path_info(base_path, "file")
> > @@ -698,31 +748,35 @@ template("mojom") {
> >
> >        filelist = []
> >        foreach(source, sources_list) {
> > -        filelist += [ rebase_path(source) ]
> > +        filelist += [ rebase_path(source, root_build_dir) ]
> >        }
> > -      response_file_contents = filelist
> > +
> > +      # Workaround for https://github.com/ninja-build/ninja/issues/1966
> .
> > +      rsp_file = "$target_gen_dir/${target_name}.rsp"
> > +      write_file(rsp_file, filelist)
> > +      inputs += [ rsp_file ]
> >
> >        args = [
> >          # Resolve relative input mojom paths against both the root src
> dir and
> >          # the root gen dir.
> >          "--input-root",
> > -        rebase_path("//."),
> > +        rebase_path("//.", root_build_dir),
> >          "--input-root",
> > -        rebase_path(root_gen_dir),
> > +        rebase_path(root_gen_dir, root_build_dir),
> >
> >          "--output-root",
> > -        rebase_path(root_gen_dir),
> > +        rebase_path(root_gen_dir, root_build_dir),
> >
> > -        "--mojom-file-list={{response_file_name}}",
> > +        "--mojom-file-list=" + rebase_path(rsp_file, root_build_dir),
> >
> >          "--check-imports",
> > -        rebase_path(build_metadata_filename),
> > +        rebase_path(build_metadata_filename, root_build_dir),
> >        ]
> >
> >        if (defined(invoker.input_root_override)) {
> >          args += [
> >            "--input-root",
> > -          rebase_path(invoker.input_root_override),
> > +          rebase_path(invoker.input_root_override, root_build_dir),
> >          ]
> >        }
> >
> > @@ -738,6 +792,9 @@ template("mojom") {
> >            "--add-module-metadata",
> >            "webui_module_path=${invoker.webui_module_path}",
> >          ]
> > +        if (defined(invoker.generate_legacy_js_bindings)) {
> > +          args += [
> "legacy_js_only=${invoker.generate_legacy_js_bindings}" ]
> > +        }
> >        }
> >      }
> >    }
> > @@ -819,11 +876,12 @@ template("mojom") {
> >        }
> >      }
> >
> > -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -    python2_action(generator_cpp_message_ids_target_name) {
> > +    action(generator_cpp_message_ids_target_name) {
> > +      allow_remote = true
> >        script = mojom_generator_script
> >        inputs = mojom_generator_sources + jinja2_sources
> > -      sources = sources_list
> > +      sources = sources_list +
> > +                [
> "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
> >        deps = [
> >          ":$parser_target_name",
> >          "//mojo/public/tools/bindings:precompile_templates",
> > @@ -835,16 +893,22 @@ template("mojom") {
> >        args = common_generator_args
> >        filelist = []
> >        foreach(source, sources_list) {
> > -        filelist += [ rebase_path("$source", root_build_dir) ]
> > +        filelist += [ rebase_path(source, root_build_dir) ]
> >        }
> >        foreach(base_path, output_file_base_paths) {
> > +        filename = get_path_info(base_path, "file")
> > +        dirname = get_path_info(base_path, "dir")
> > +        inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
> >          outputs += [ "$root_gen_dir/$base_path-shared-message-ids.h" ]
> >        }
> >
> > -      response_file_contents = filelist
> > +      # Workaround for https://github.com/ninja-build/ninja/issues/1966
> .
> > +      rsp_file = "$target_gen_dir/${target_name}.rsp"
> > +      write_file(rsp_file, filelist)
> > +      inputs += [ rsp_file ]
> >
> >        args += [
> > -        "--filelist={{response_file_name}}",
> > +        "--filelist=" + rebase_path(rsp_file, root_build_dir),
> >          "--generate_non_variant_code",
> >          "--generate_message_ids",
> >          "-g",
> > @@ -860,12 +924,13 @@ template("mojom") {
> >
> >      generator_shared_target_name = "${target_name}_shared__generator"
> >
> > -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -    python2_action(generator_shared_target_name) {
> > +    action(generator_shared_target_name) {
> > +      allow_remote = true
> >        visibility = [ ":*" ]
> >        script = mojom_generator_script
> >        inputs = mojom_generator_sources + jinja2_sources
> > -      sources = sources_list
> > +      sources = sources_list +
> > +                [
> "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
> >        deps = [
> >          ":$parser_target_name",
> >          "//mojo/public/tools/bindings:precompile_templates",
> > @@ -878,9 +943,14 @@ template("mojom") {
> >        args = common_generator_args
> >        filelist = []
> >        foreach(source, sources_list) {
> > -        filelist += [ rebase_path("$source", root_build_dir) ]
> > +        filelist += [ rebase_path(source, root_build_dir) ]
> >        }
> >        foreach(base_path, output_file_base_paths) {
> > +        # Need the mojom-module as an input to this action.
> > +        filename = get_path_info(base_path, "file")
> > +        dirname = get_path_info(base_path, "dir")
> > +        inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
> > +
> >          outputs += [
> >            "$root_gen_dir/$base_path-params-data.h",
> >            "$root_gen_dir/$base_path-shared-internal.h",
> > @@ -889,10 +959,13 @@ template("mojom") {
> >          ]
> >        }
> >
> > -      response_file_contents = filelist
> > +      # Workaround for https://github.com/ninja-build/ninja/issues/1966
> .
> > +      rsp_file = "$target_gen_dir/${target_name}.rsp"
> > +      write_file(rsp_file, filelist)
> > +      inputs += [ rsp_file ]
> >
> >        args += [
> > -        "--filelist={{response_file_name}}",
> > +        "--filelist=" + rebase_path(rsp_file, root_build_dir),
> >          "--generate_non_variant_code",
> >          "-g",
> >          "c++",
> > @@ -972,7 +1045,7 @@ template("mojom") {
> >      }
> >    }
> >
> > -  if (generate_fuzzing) {
> > +  if (generate_mojolpm_fuzzing) {
> >      # This block generates the proto files used for the MojoLPM fuzzer,
> >      # and the corresponding proto targets that will be linked in the
> fuzzer
> >      # targets. These are independent of the typemappings, and can be
> done
> > @@ -981,11 +1054,15 @@ template("mojom") {
> >      generator_mojolpm_proto_target_name =
> >          "${target_name}_mojolpm_proto_generator"
> >
> > -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -    python2_action(generator_mojolpm_proto_target_name) {
> > +    action(generator_mojolpm_proto_target_name) {
> > +      allow_remote = true
> >        script = mojom_generator_script
> >        inputs = mojom_generator_sources + jinja2_sources
> > -      sources = invoker.sources
> > +      sources =
> > +          invoker.sources + [
> > +
> "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
> > +
> "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
> > +          ]
> >        deps = [
> >          ":$parser_target_name",
> >          "//mojo/public/tools/bindings:precompile_templates",
> > @@ -994,15 +1071,37 @@ template("mojom") {
> >        outputs = []
> >        args = common_generator_args
> >        filelist = []
> > -      foreach(source, invoker.sources) {
> > -        filelist += [ rebase_path("$source", root_build_dir) ]
> > +
> > +      # Split the input into generated and non-generated source files.
> They
> > +      # need to be processed separately.
> > +      gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
> > +      non_gen_sources =
> > +          filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
> > +      gen_sources = filter_include(invoker.sources, [
> gen_dir_path_wildcard ])
> > +
> > +      foreach(source, non_gen_sources) {
> > +        filelist += [ rebase_path(source, root_build_dir) ]
> > +        inputs += [ "$target_gen_dir/$source-module" ]
> >          outputs += [ "$target_gen_dir/$source.mojolpm.proto" ]
> >        }
> >
> > -      response_file_contents = filelist
> > +      foreach(source, gen_sources) {
> > +        filelist += [ rebase_path(source, root_build_dir) ]
> > +
> > +        # For generated files, we assume they're in the target_gen_dir
> or a
> > +        # sub-folder of it. Rebase the path so we can get the relative
> location.
> > +        source_file = rebase_path(source, target_gen_dir)
> > +        inputs += [ "$target_gen_dir/$source_file-module" ]
> > +        outputs += [ "$target_gen_dir/$source_file.mojolpm.proto" ]
> > +      }
> > +
> > +      # Workaround for https://github.com/ninja-build/ninja/issues/1966
> .
> > +      rsp_file = "$target_gen_dir/${target_name}.rsp"
> > +      write_file(rsp_file, filelist)
> > +      inputs += [ rsp_file ]
> >
> >        args += [
> > -        "--filelist={{response_file_name}}",
> > +        "--filelist=" + rebase_path(rsp_file, root_build_dir),
> >          "--generate_non_variant_code",
> >          "-g",
> >          "mojolpm",
> > @@ -1014,9 +1113,20 @@ template("mojom") {
> >        proto_library(mojolpm_proto_target_name) {
> >          testonly = true
> >          generate_python = false
> > +
> > +        # Split the input into generated and non-generated source
> files. They
> > +        # need to be processed separately.
> > +        gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
> > +        non_gen_sources =
> > +            filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
> > +        gen_sources = filter_include(invoker.sources, [
> gen_dir_path_wildcard ])
> >          sources = process_file_template(
> > -                invoker.sources,
> > +                non_gen_sources,
> >                  [
> "{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto" ])
> > +        sources += process_file_template(
> > +                gen_sources,
> > +                [ "{{source_dir}}/{{source_file_part}}.mojolpm.proto" ])
> > +
> >          import_dirs = [ "//" ]
> >          proto_in_dir = "${root_gen_dir}"
> >          proto_out_dir = "."
> > @@ -1055,7 +1165,7 @@ template("mojom") {
> >      component_macro_suffix = ""
> >    }
> >    if ((!defined(invoker.disable_variants) || !invoker.disable_variants)
> &&
> > -      !is_ios) {
> > +      use_blink) {
> >      blink_variant = {
> >        variant = "blink"
> >        component_macro_suffix = "_BLINK"
> > @@ -1149,39 +1259,6 @@ template("mojom") {
> >              "${bindings_configuration.component_macro_suffix}_IMPL" ]
> >      }
> >
> > -    export_args = []
> > -    export_args_overridden = false
> > -    if (defined(bindings_configuration.for_blink) &&
> > -        bindings_configuration.for_blink) {
> > -      if (defined(invoker.export_class_attribute_blink)) {
> > -        export_args_overridden = true
> > -        export_args += [
> > -          "--export_attribute",
> > -          invoker.export_class_attribute_blink,
> > -          "--export_header",
> > -          invoker.export_header_blink,
> > -        ]
> > -      }
> > -    } else if (defined(invoker.export_class_attribute)) {
> > -      export_args_overridden = true
> > -      export_args += [
> > -        "--export_attribute",
> > -        invoker.export_class_attribute,
> > -        "--export_header",
> > -        invoker.export_header,
> > -      ]
> > -    }
> > -
> > -    if (!export_args_overridden &&
> defined(invoker.component_macro_prefix)) {
> > -      export_args += [
> > -        "--export_attribute",
> > -        "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
> > -            "${bindings_configuration.component_macro_suffix})",
> > -        "--export_header",
> > -        "base/component_export.h",
> > -      ]
> > -    }
> > -
> >      generate_java = false
> >      if (!cpp_only && defined(invoker.generate_java)) {
> >        generate_java = invoker.generate_java
> > @@ -1190,6 +1267,38 @@ template("mojom") {
> >      type_mappings_path =
> >          "$target_gen_dir/${target_name}${variant_suffix}__type_mappings"
> >      if (sources_list != []) {
> > +      export_args = []
> > +      export_args_overridden = false
> > +      if (defined(bindings_configuration.for_blink) &&
> > +          bindings_configuration.for_blink) {
> > +        if (defined(invoker.export_class_attribute_blink)) {
> > +          export_args_overridden = true
> > +          export_args += [
> > +            "--export_attribute",
> > +            invoker.export_class_attribute_blink,
> > +            "--export_header",
> > +            invoker.export_header_blink,
> > +          ]
> > +        }
> > +      } else if (defined(invoker.export_class_attribute)) {
> > +        export_args_overridden = true
> > +        export_args += [
> > +          "--export_attribute",
> > +          invoker.export_class_attribute,
> > +          "--export_header",
> > +          invoker.export_header,
> > +        ]
> > +      }
> > +      if (!export_args_overridden &&
> defined(invoker.component_macro_prefix)) {
> > +        export_args += [
> > +          "--export_attribute",
> > +          "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
> > +              "${bindings_configuration.component_macro_suffix})",
> > +          "--export_header",
> > +          "base/component_export.h",
> > +        ]
> > +      }
> > +
> >        generator_cpp_output_suffixes = []
> >        variant_dash_suffix = ""
> >        if (defined(variant)) {
> > @@ -1198,7 +1307,6 @@ template("mojom") {
> >        generator_cpp_output_suffixes += [
> >          "${variant_dash_suffix}-forward.h",
> >          "${variant_dash_suffix}-import-headers.h",
> > -        "${variant_dash_suffix}-test-utils.cc",
> >          "${variant_dash_suffix}-test-utils.h",
> >          "${variant_dash_suffix}.cc",
> >          "${variant_dash_suffix}.h",
> > @@ -1207,16 +1315,28 @@ template("mojom") {
> >        generator_target_name =
> "${target_name}${variant_suffix}__generator"
> >
> >        # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -      python2_action(generator_target_name) {
> > +      action(generator_target_name) {
> > +        allow_remote = true
> >          visibility = [ ":*" ]
> >          script = mojom_generator_script
> >          inputs = mojom_generator_sources + jinja2_sources
> > -        sources = sources_list
> > +        sources =
> > +            sources_list + [
> > +
> "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
> > +              type_mappings_path,
> > +            ]
> > +        if (generate_mojolpm_fuzzing &&
> > +            !defined(bindings_configuration.variant)) {
> > +          sources += [
> > +
> "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
> > +          ]
> > +        }
> >          deps = [
> >            ":$parser_target_name",
> >            ":$type_mappings_target_name",
> >            "//mojo/public/tools/bindings:precompile_templates",
> >          ]
> > +
> >          if (defined(invoker.parser_deps)) {
> >            deps += invoker.parser_deps
> >          }
> > @@ -1224,18 +1344,22 @@ template("mojom") {
> >          args = common_generator_args + export_args
> >          filelist = []
> >          foreach(source, sources_list) {
> > -          filelist += [ rebase_path("$source", root_build_dir) ]
> > +          filelist += [ rebase_path(source, root_build_dir) ]
> >          }
> >          foreach(base_path, output_file_base_paths) {
> > +          filename = get_path_info(base_path, "file")
> > +          dirname = get_path_info(base_path, "dir")
> > +          inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
> > +
> >            outputs += [
> >
> "$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h",
> >
> "$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h",
> > -
> "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc",
> >
> "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h",
> >              "$root_gen_dir/${base_path}${variant_dash_suffix}.cc",
> >              "$root_gen_dir/${base_path}${variant_dash_suffix}.h",
> >            ]
> > -          if (generate_fuzzing &&
> !defined(bindings_configuration.variant)) {
> > +          if (generate_mojolpm_fuzzing &&
> > +              !defined(bindings_configuration.variant)) {
> >              outputs += [
> >
> "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc",
> >
> "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h",
> > @@ -1243,14 +1367,17 @@ template("mojom") {
> >            }
> >          }
> >
> > -        response_file_contents = filelist
> > -
> > +        # Workaround for
> https://github.com/ninja-build/ninja/issues/1966.
> > +        rsp_file = "$target_gen_dir/${target_name}.rsp"
> > +        write_file(rsp_file, filelist)
> > +        inputs += [ rsp_file ]
> >          args += [
> > -          "--filelist={{response_file_name}}",
> > +          "--filelist=" + rebase_path("$rsp_file", root_build_dir),
> >            "-g",
> >          ]
> >
> > -        if (generate_fuzzing &&
> !defined(bindings_configuration.variant)) {
> > +        if (generate_mojolpm_fuzzing &&
> > +            !defined(bindings_configuration.variant)) {
> >            args += [ "c++,mojolpm" ]
> >          } else {
> >            args += [ "c++" ]
> > @@ -1294,6 +1421,8 @@ template("mojom") {
> >                "--extra_cpp_template_paths",
> >                rebase_path(extra_cpp_template, root_build_dir),
> >              ]
> > +            inputs += [ extra_cpp_template ]
> > +
> >              assert(
> >                  get_path_info(extra_cpp_template, "extension") ==
> "tmpl",
> >                  "--extra_cpp_template_paths only accepts template files
> ending in extension .tmpl")
> > @@ -1306,62 +1435,6 @@ template("mojom") {
> >        }
> >      }
> >
> > -    if (generate_fuzzing && !defined(variant)) {
> > -      # This block contains the C++ targets for the MojoLPM fuzzer, we
> need to
> > -      # do this here so that we can use the typemap configuration for
> the
> > -      # empty-variant Mojo target.
> > -
> > -      mojolpm_target_name = "${target_name}_mojolpm"
> > -      mojolpm_generator_target_name = "${target_name}__generator"
> > -      source_set(mojolpm_target_name) {
> > -        # There are still a few missing header dependencies between
> mojo targets
> > -        # with typemaps and the dependencies of their typemap headers.
> It would
> > -        # be good to enable include checking for these in the future
> though.
> > -        check_includes = false
> > -        testonly = true
> > -        if (defined(invoker.sources)) {
> > -          sources = process_file_template(
> > -                  invoker.sources,
> > -                  [
> > -
> "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
> > -                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
> > -                  ])
> > -          deps = []
> > -        } else {
> > -          sources = []
> > -          deps = []
> > -        }
> > -
> > -        public_deps = [
> > -          ":$generator_shared_target_name",
> > -
> > -          # NB: hardcoded dependency on the no-variant variant
> generator, since
> > -          # mojolpm only uses the no-variant type.
> > -          ":$mojolpm_generator_target_name",
> > -          ":$mojolpm_proto_target_name",
> > -          "//base",
> > -          "//mojo/public/tools/fuzzers:mojolpm",
> > -        ]
> > -
> > -        foreach(d, all_deps) {
> > -          # Resolve the name, so that a target //mojo/something becomes
> > -          # //mojo/something:something and we can append variant_suffix
> to
> > -          # get the cpp dependency name.
> > -          full_name = get_label_info("$d", "label_no_toolchain")
> > -          public_deps += [ "${full_name}_mojolpm" ]
> > -        }
> > -
> > -        foreach(config, cpp_typemap_configs) {
> > -          if (defined(config.traits_deps)) {
> > -            deps += config.traits_deps
> > -          }
> > -          if (defined(config.traits_public_deps)) {
> > -            public_deps += config.traits_public_deps
> > -          }
> > -        }
> > -      }
> > -    }
> > -
> >      # Write the typemapping configuration for this target out to a file
> to be
> >      # validated by a Python script. This helps catch mistakes that can't
> >      # be caught by logic in GN.
> > @@ -1389,20 +1462,20 @@ template("mojom") {
> >      write_file(_typemap_config_filename, _rebased_typemap_configs,
> "json")
> >      _mojom_target_name = target_name
> >
> > -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -    python2_action(_typemap_validator_target_name) {
> > +    action(_typemap_validator_target_name) {
> > +      allow_remote = true
> >        script = "$mojom_generator_root/validate_typemap_config.py"
> >        inputs = [ _typemap_config_filename ]
> >        outputs = [ _typemap_stamp_filename ]
> >        args = [
> >          get_label_info(_mojom_target_name, "label_no_toolchain"),
> > -        rebase_path(_typemap_config_filename),
> > -        rebase_path(_typemap_stamp_filename),
> > +        rebase_path(_typemap_config_filename, root_build_dir),
> > +        rebase_path(_typemap_stamp_filename, root_build_dir),
> >        ]
> >      }
> >
> > -    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -    python2_action(type_mappings_target_name) {
> > +    action(type_mappings_target_name) {
> > +      allow_remote = true
> >        inputs =
> >            mojom_generator_sources + jinja2_sources + [
> _typemap_stamp_filename ]
> >        outputs = [ type_mappings_path ]
> > @@ -1413,6 +1486,7 @@ template("mojom") {
> >          rebase_path(type_mappings_path, root_build_dir),
> >        ]
> >
> > +      sources = []
> >        foreach(d, all_deps) {
> >          name = get_label_info(d, "label_no_toolchain")
> >          toolchain = get_label_info(d, "toolchain")
> > @@ -1422,12 +1496,11 @@ template("mojom") {
> >          dependency_output_dir =
> >              get_label_info(dependency_output, "target_gen_dir")
> >          dependency_name = get_label_info(dependency_output, "name")
> > -        dependency_path =
> > -            rebase_path("$dependency_output_dir/${dependency_name}",
> > -                        root_build_dir)
> > +        dependency_path = "$dependency_output_dir/${dependency_name}"
> > +        sources += [ dependency_path ]
> >          args += [
> >            "--dependency",
> > -          dependency_path,
> > +          rebase_path(dependency_path, root_build_dir),
> >          ]
> >        }
> >
> > @@ -1485,11 +1558,15 @@ template("mojom") {
> >        if (defined(output_name_override)) {
> >          output_name = output_name_override
> >        }
> > -      visibility = output_visibility + [ ":$output_target_name" ]
> > +      visibility = output_visibility + [
> > +                     ":$output_target_name",
> > +                     ":${target_name}_mojolpm",
> > +                   ]
> >        if (defined(invoker.testonly)) {
> >          testonly = invoker.testonly
> >        }
> >        defines = export_defines
> > +      configs += [ "//build/config/compiler:wexit_time_destructors" ]
> >        configs += extra_configs
> >        if (output_file_base_paths != []) {
> >          sources = []
> > @@ -1578,13 +1655,81 @@ template("mojom") {
> >        }
> >      }
> >
> > +    if (generate_mojolpm_fuzzing && !defined(variant)) {
> > +      # This block contains the C++ targets for the MojoLPM fuzzer, we
> need to
> > +      # do this here so that we can use the typemap configuration for
> the
> > +      # empty-variant Mojo target.
> > +
> > +      mojolpm_target_name = "${target_name}_mojolpm"
> > +      mojolpm_generator_target_name = "${target_name}__generator"
> > +      source_set(mojolpm_target_name) {
> > +        # There are still a few missing header dependencies between
> mojo targets
> > +        # with typemaps and the dependencies of their typemap headers.
> It would
> > +        # be good to enable include checking for these in the future
> though.
> > +        check_includes = false
> > +        testonly = true
> > +        if (defined(invoker.sources)) {
> > +          # Split the input into generated and non-generated source
> files. They
> > +          # need to be processed separately.
> > +          gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
> > +          non_gen_sources =
> > +              filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
> > +          gen_sources =
> > +              filter_include(invoker.sources, [ gen_dir_path_wildcard ])
> > +          sources = process_file_template(
> > +                  non_gen_sources,
> > +                  [
> > +
> "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
> > +                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
> > +                  ])
> > +          sources += process_file_template(
> > +                  gen_sources,
> > +                  [
> > +                    "{{source_dir}}/{{source_file_part}}-mojolpm.cc",
> > +                    "{{source_dir}}/{{source_file_part}}-mojolpm.h",
> > +                  ])
> > +          deps = [ ":$output_target_name" ]
> > +        } else {
> > +          sources = []
> > +          deps = []
> > +        }
> > +
> > +        public_deps = [
> > +          ":$generator_shared_target_name",
> > +
> > +          # NB: hardcoded dependency on the no-variant variant
> generator, since
> > +          # mojolpm only uses the no-variant type.
> > +          ":$mojolpm_generator_target_name",
> > +          ":$mojolpm_proto_target_name",
> > +          "//base",
> > +          "//mojo/public/tools/fuzzers:mojolpm",
> > +        ]
> > +
> > +        foreach(d, all_deps) {
> > +          # Resolve the name, so that a target //mojo/something becomes
> > +          # //mojo/something:something and we can append variant_suffix
> to
> > +          # get the cpp dependency name.
> > +          full_name = get_label_info("$d", "label_no_toolchain")
> > +          public_deps += [ "${full_name}_mojolpm" ]
> > +        }
> > +
> > +        foreach(config, cpp_typemap_configs) {
> > +          if (defined(config.traits_deps)) {
> > +            deps += config.traits_deps
> > +          }
> > +          if (defined(config.traits_public_deps)) {
> > +            public_deps += config.traits_public_deps
> > +          }
> > +        }
> > +      }
> > +    }
> > +
> >      if (generate_java && is_android) {
> >        import("//build/config/android/rules.gni")
> >
> >        java_generator_target_name = target_name + "_java__generator"
> >        if (sources_list != []) {
> > -        # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -        python2_action(java_generator_target_name) {
> > +        action(java_generator_target_name) {
> >            script = mojom_generator_script
> >            inputs = mojom_generator_sources + jinja2_sources
> >            sources = sources_list
> > @@ -1597,7 +1742,7 @@ template("mojom") {
> >            args = common_generator_args
> >            filelist = []
> >            foreach(source, sources_list) {
> > -            filelist += [ rebase_path("$source", root_build_dir) ]
> > +            filelist += [ rebase_path(source, root_build_dir) ]
> >            }
> >            foreach(base_path, output_file_base_paths) {
> >              outputs += [ "$root_gen_dir/$base_path.srcjar" ]
> > @@ -1624,8 +1769,7 @@ template("mojom") {
> >
> >        java_srcjar_target_name = target_name + "_java_sources"
> >
> > -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -      python2_action(java_srcjar_target_name) {
> > +      action(java_srcjar_target_name) {
> >          script = "//build/android/gyp/zip.py"
> >          inputs = []
> >          if (output_file_base_paths != []) {
> > @@ -1651,7 +1795,6 @@ template("mojom") {
> >        android_library(java_target_name) {
> >          forward_variables_from(invoker, [ "enable_bytecode_checks" ])
> >          deps = [
> > -          "//base:base_java",
> >            "//mojo/public/java:bindings_java",
> >            "//mojo/public/java:system_java",
> >            "//third_party/androidx:androidx_annotation_annotation_java",
> > @@ -1673,21 +1816,103 @@ template("mojom") {
> >      }
> >    }
> >
> > +  if (defined(invoker.webui_js_bridge_config)) {
> > +    if (sources_list != []) {
> > +      bridge_config = invoker.webui_js_bridge_config
> > +
> > +      generator_webui_js_bridge_impl_target_name =
> > +          "${target_name}_webui_js_bridge_impl__generator"
> > +      action(generator_webui_js_bridge_impl_target_name) {
> > +        visibility = [ ":*" ]
> > +        script = mojom_generator_script
> > +        inputs = mojom_generator_sources + jinja2_sources
> > +        sources = sources_list
> > +
> > +        deps = [
> > +          ":$parser_target_name",
> > +          "//mojo/public/tools/bindings:precompile_templates",
> > +        ]
> > +
> > +        filelist = []
> > +        foreach(source, sources_list) {
> > +          filelist += [ rebase_path(source, root_build_dir) ]
> > +        }
> > +
> > +        outputs = []
> > +        foreach(base_path, output_file_base_paths) {
> > +          outputs += [
> > +            "$root_gen_dir/${base_path}-webui-js-bridge-impl.cc",
> > +            "$root_gen_dir/${base_path}-webui-js-bridge-impl.h",
> > +          ]
> > +        }
> > +
> > +        response_file_contents = filelist
> > +
> > +        args = common_generator_args + export_args
> > +        args += [
> > +          "--filelist={{response_file_name}}",
> > +          "-g",
> > +          "webui_js_bridge",
> > +        ]
> > +
> > +        rebased_webui_controller_header =
> > +            rebase_path(bridge_config.webui_controller_header, "//")
> > +
> > +        # Use a single string for the argument to avoid it being parsed
> as a
> > +        # filename by the mojom_bindings_generator.py argument parser.
> > +        config_arg =
> > +            "--webui_js_bridge_config=" +
> bridge_config.webui_controller + "=" +
> > +            rebased_webui_controller_header
> > +
> > +        args += [ config_arg ]
> > +      }
> > +
> > +      cpp_target_name = target_name
> > +      source_set("${target_name}_webui_js_bridge_impl") {
> > +        if (defined(invoker.testonly)) {
> > +          testonly = invoker.testonly
> > +        }
> > +
> > +        sources = []
> > +        foreach(base_path, output_file_base_paths) {
> > +          sources += [
> > +            "$root_gen_dir/${base_path}-webui-js-bridge-impl.cc",
> > +            "$root_gen_dir/${base_path}-webui-js-bridge-impl.h",
> > +          ]
> > +        }
> > +        deps = [
> > +          ":$cpp_target_name",
> > +          ":$generator_webui_js_bridge_impl_target_name",
> > +        ]
> > +
> > +        deps += bridge_config.webui_controller_deps
> > +      }
> > +    }
> > +  }
> > +
> >    use_typescript_for_target =
> > -      enable_typescript_bindings &&
> defined(invoker.use_typescript_sources) &&
> > -      invoker.use_typescript_sources
> > +      defined(invoker.use_typescript_sources) &&
> > +      invoker.use_typescript_sources &&
> defined(invoker.webui_module_path)
> >
> >    if (!use_typescript_for_target &&
> defined(invoker.use_typescript_sources)) {
> >      not_needed(invoker, [ "use_typescript_sources" ])
> >    }
> >
> > -  if ((generate_fuzzing || !defined(invoker.cpp_only) ||
> !invoker.cpp_only) &&
> > -      !use_typescript_for_target) {
> > +  generate_legacy_js = !use_typescript_for_target ||
> > +                       (defined(invoker.generate_legacy_js_bindings) &&
> > +                        invoker.generate_legacy_js_bindings)
> > +
> > +  # Targets needed by both TS and JS bindings targets. These are needed
> > +  # unconditionally for JS bindings targets, and are needed for TS
> bindings
> > +  # targets when generate_legacy_js_bindings is true. This option is
> provided
> > +  # since the legacy bindings are needed by Blink tests and
> non-Chromium users,
> > +  # which are not expected to migrate to modules or TypeScript.
> > +  if (generate_legacy_js && (generate_js_fuzzing ||
> > +                             !defined(invoker.cpp_only) ||
> !invoker.cpp_only)) {
> >      if (sources_list != []) {
> >        generator_js_target_name = "${target_name}_js__generator"
> >
> > -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -      python2_action(generator_js_target_name) {
> > +      action(generator_js_target_name) {
> >          script = mojom_generator_script
> >          inputs = mojom_generator_sources + jinja2_sources
> >          sources = sources_list
> > @@ -1702,19 +1927,18 @@ template("mojom") {
> >          args = common_generator_args
> >          filelist = []
> >          foreach(source, sources_list) {
> > -          filelist += [ rebase_path("$source", root_build_dir) ]
> > +          filelist += [ rebase_path(source, root_build_dir) ]
> >          }
> >          foreach(base_path, output_file_base_paths) {
> >            outputs += [
> >              "$root_gen_dir/$base_path.js",
> > -            "$root_gen_dir/$base_path.externs.js",
> >              "$root_gen_dir/$base_path.m.js",
> >              "$root_gen_dir/$base_path-lite.js",
> > -            "$root_gen_dir/$base_path.html",
> >              "$root_gen_dir/$base_path-lite-for-compile.js",
> >            ]
> >
> > -          if (defined(invoker.webui_module_path)) {
> > +          if (defined(invoker.webui_module_path) &&
> > +              !use_typescript_for_target) {
> >              outputs += [
> "$root_gen_dir/mojom-webui/$base_path-webui.js" ]
> >            }
> >          }
> > @@ -1725,7 +1949,6 @@ template("mojom") {
> >            "--filelist={{response_file_name}}",
> >            "-g",
> >            "javascript",
> > -          "--js_bindings_mode=new",
> >          ]
> >
> >          if (defined(invoker.js_generate_struct_deserializers) &&
> > @@ -1739,7 +1962,7 @@ template("mojom") {
> >            args += message_scrambling_args
> >          }
> >
> > -        if (generate_fuzzing) {
> > +        if (generate_js_fuzzing) {
> >            args += [ "--generate_fuzzing" ]
> >          }
> >        }
> > @@ -1783,31 +2006,13 @@ template("mojom") {
> >          data_deps += [ "${full_name}_js_data_deps" ]
> >        }
> >      }
> > +  }
> >
> > -    js_library_target_name = "${target_name}_js_library"
> > -    if (sources_list != []) {
> > -      js_library(js_library_target_name) {
> > -        extra_public_deps = [ ":$generator_js_target_name" ]
> > -        sources = []
> > -        foreach(base_path, output_file_base_paths) {
> > -          sources += [ "$root_gen_dir/${base_path}-lite.js" ]
> > -        }
> > -        externs_list = [
> > -          "${externs_path}/mojo_core.js",
> > -          "${externs_path}/pending.js",
> > -        ]
> > -
> > -        deps = []
> > -        foreach(d, all_deps) {
> > -          full_name = get_label_info(d, "label_no_toolchain")
> > -          deps += [ "${full_name}_js_library" ]
> > -        }
> > -      }
> > -    } else {
> > -      group(js_library_target_name) {
> > -      }
> > -    }
> > -
> > +  # js_library() closure compiler targets, primarily used on ChromeOS.
> Only
> > +  # generate these targets if the mojom target is not C++ only and is
> not using
> > +  # TypeScript.
> > +  if (generate_mojom_closure_libraries &&
> > +      (!defined(invoker.cpp_only) || !invoker.cpp_only) &&
> generate_legacy_js) {
> >      js_library_for_compile_target_name =
> "${target_name}_js_library_for_compile"
> >      if (sources_list != []) {
> >        js_library(js_library_for_compile_target_name) {
> > @@ -1834,35 +2039,9 @@ template("mojom") {
> >        }
> >      }
> >
> > -    js_modules_target_name = "${target_name}_js_modules"
> > -    if (sources_list != []) {
> > -      js_library(js_modules_target_name) {
> > -        extra_public_deps = [ ":$generator_js_target_name" ]
> > -        sources = []
> > -        foreach(base_path, output_file_base_paths) {
> > -          sources += [ "$root_gen_dir/${base_path}.m.js" ]
> > -        }
> > -        externs_list = [
> > -          "${externs_path}/mojo_core.js",
> > -          "${externs_path}/pending.js",
> > -        ]
> > -        if (defined(invoker.disallow_native_types) &&
> > -            invoker.disallow_native_types) {
> > -          deps = []
> > -        } else {
> > -          deps = [ "//mojo/public/js:bindings_uncompiled" ]
> > -        }
> > -        foreach(d, all_deps) {
> > -          full_name = get_label_info(d, "label_no_toolchain")
> > -          deps += [ "${full_name}_js_modules" ]
> > -        }
> > -      }
> > -    } else {
> > -      group(js_modules_target_name) {
> > -      }
> > -    }
> > -
> > -    if (defined(invoker.webui_module_path)) {
> > +    # WebUI specific closure targets, not needed by targets that are
> generating
> > +    # TypeScript WebUI bindings or by legacy-only targets.
> > +    if (defined(invoker.webui_module_path) &&
> !use_typescript_for_target) {
> >        webui_js_target_name = "${target_name}_webui_js"
> >        if (sources_list != []) {
> >          js_library(webui_js_target_name) {
> > @@ -1890,46 +2069,38 @@ template("mojom") {
> >          group(webui_js_target_name) {
> >          }
> >        }
> > -    }
> > -  }
> > -  if ((generate_fuzzing || !defined(invoker.cpp_only) ||
> !invoker.cpp_only) &&
> > -      use_typescript_for_target) {
> > -    generator_js_target_names = []
> > -    source_filelist = []
> > -    foreach(source, sources_list) {
> > -      source_filelist += [ rebase_path("$source", root_build_dir) ]
> > -    }
> >
> > -    dependency_types = [
> > -      {
> > -        name = "regular"
> > -        ts_extension = ".ts"
> > -        js_extension = ".js"
> > -      },
> > -      {
> > -        name = "es_modules"
> > -        ts_extension = ".m.ts"
> > -        js_extension = ".m.js"
> > -      },
> > -    ]
> > +      webui_grdp_target_name = "${target_name}_webui_grdp"
> > +      out_grd = "$target_gen_dir/${target_name}_webui_resources.grdp"
> > +      grd_prefix = "${target_name}_webui"
> > +      generate_grd(webui_grdp_target_name) {
> > +        grd_prefix = grd_prefix
> > +        out_grd = out_grd
> >
> > -    foreach(dependency_type, dependency_types) {
> > -      ts_outputs = []
> > -      js_outputs = []
> > +        deps = [ ":$webui_js_target_name" ]
> >
> > -      foreach(base_path, output_file_base_paths) {
> > -        ts_outputs +=
> > -            [
> "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}" ]
> > -        js_outputs +=
> > -            [
> "$root_gen_dir/$base_path-lite${dependency_type.js_extension}" ]
> > +        input_files = []
> > +        foreach(base_path, output_file_base_paths) {
> > +          input_files += [ "${base_path}-webui.js" ]
> > +        }
> > +
> > +        input_files_base_dir =
> > +            rebase_path("$root_gen_dir/mojom-webui", "$root_build_dir")
> > +      }
> > +    }
> > +  }
> > +  if ((generate_js_fuzzing || !defined(invoker.cpp_only) ||
> > +       !invoker.cpp_only) && use_typescript_for_target) {
> > +    if (sources_list != []) {
> > +      source_filelist = []
> > +      foreach(source, sources_list) {
> > +        source_filelist += [ rebase_path(source, root_build_dir) ]
> >        }
> >
> >        # Generate Typescript bindings.
> > -      generator_ts_target_name =
> > -          "${target_name}_${dependency_type.name}__ts__generator"
> > +      generator_ts_target_name = "${target_name}_ts__generator"
> >
> > -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -      python2_action(generator_ts_target_name) {
> > +      action(generator_ts_target_name) {
> >          script = mojom_generator_script
> >          inputs = mojom_generator_sources + jinja2_sources
> >          sources = sources_list
> > @@ -1938,7 +2109,10 @@ template("mojom") {
> >            "//mojo/public/tools/bindings:precompile_templates",
> >          ]
> >
> > -        outputs = ts_outputs
> > +        outputs = []
> > +        foreach(base_path, output_file_base_paths) {
> > +          outputs += [ "$root_gen_dir/$base_path-webui.ts" ]
> > +        }
> >          args = common_generator_args
> >          response_file_contents = source_filelist
> >
> > @@ -1948,98 +2122,16 @@ template("mojom") {
> >            "typescript",
> >          ]
> >
> > -        if (dependency_type.name == "es_modules") {
> > -          args += [ "--ts_use_es_modules" ]
> > +        if (!defined(invoker.scramble_message_ids) ||
> > +            invoker.scramble_message_ids) {
> > +          inputs += message_scrambling_inputs
> > +          args += message_scrambling_args
> >          }
> >
> > -        # TODO(crbug.com/1007587): Support scramble_message_ids.
> > +        # TODO(crbug.com/1007587): Support scramble_message_ids if
> above is
> > +        # insufficient.
> >          # TODO(crbug.com/1007591): Support generate_fuzzing.
> >        }
> > -
> > -      # Create tsconfig.json for the generated Typescript.
> > -      tsconfig_filename =
> > -          "$target_gen_dir/$target_name-${dependency_type.name
> }-tsconfig.json"
> > -      tsconfig = {
> > -      }
> > -      tsconfig.compilerOptions = {
> > -        composite = true
> > -        target = "es6"
> > -        module = "es6"
> > -        lib = [
> > -          "es6",
> > -          "esnext.bigint",
> > -        ]
> > -        strict = true
> > -      }
> > -      tsconfig.files = []
> > -      foreach(base_path, output_file_base_paths) {
> > -        tsconfig.files += [ rebase_path(
> > -
> "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}",
> > -                target_gen_dir,
> > -                root_gen_dir) ]
> > -      }
> > -      tsconfig.references = []
> > -
> > -      # Get tsconfigs for deps.
> > -      foreach(d, all_deps) {
> > -        dep_target_gen_dir = rebase_path(get_label_info(d,
> "target_gen_dir"))
> > -        dep_name = get_label_info(d, "name")
> > -        reference = {
> > -        }
> > -        reference.path = "$dep_target_gen_dir/$dep_name-${
> dependency_type.name}-tsconfig.json"
> > -        tsconfig.references += [ reference ]
> > -      }
> > -      write_file(tsconfig_filename, tsconfig, "json")
> > -
> > -      # Compile previously generated Typescript to Javascript.
> > -      generator_js_target_name =
> > -          "${target_name}_${dependency_type.name}__js__generator"
> > -      generator_js_target_names += [ generator_js_target_name ]
> > -
> > -      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3
> builds.
> > -      python2_action(generator_js_target_name) {
> > -        script = "$mojom_generator_root/compile_typescript.py"
> > -        sources = ts_outputs
> > -        outputs = js_outputs
> > -        public_deps = [ ":$generator_ts_target_name" ]
> > -        foreach(d, all_deps) {
> > -          full_name = get_label_info(d, "label_no_toolchain")
> > -          public_deps +=
> > -              [ "${full_name}_${dependency_type.name}__js__generator" ]
> > -        }
> > -
> > -        absolute_tsconfig_path =
> > -            rebase_path(tsconfig_filename, "", target_gen_dir)
> > -        args = [ "--tsconfig_path=$absolute_tsconfig_path" ]
> > -      }
> > -    }
> > -
> > -    js_target_name = target_name + "_js"
> > -    group(js_target_name) {
> > -      public_deps = []
> > -      if (sources_list != []) {
> > -        foreach(generator_js_target_name, generator_js_target_names) {
> > -          public_deps += [ ":$generator_js_target_name" ]
> > -        }
> > -      }
> > -
> > -      foreach(d, all_deps) {
> > -        full_name = get_label_info(d, "label_no_toolchain")
> > -        public_deps += [ "${full_name}_js" ]
> > -      }
> > -    }
> > -
> > -    group(js_data_deps_target_name) {
> > -      data = js_outputs
> > -      deps = []
> > -      foreach(generator_js_target_name, generator_js_target_names) {
> > -        deps += [ ":$generator_js_target_name" ]
> > -      }
> > -      data_deps = []
> > -      foreach(d, all_deps) {
> > -        full_name = get_label_info(d, "label_no_toolchain")
> > -        data_deps += [ "${full_name}_js_data_deps" ]
> > -      }
> >      }
> >    }
> >  }
> > @@ -2063,3 +2155,53 @@ template("mojom_component") {
> >      component_macro_prefix = invoker.macro_prefix
> >    }
> >  }
> > +
> > +# A helper for the mojom() template above for defining a WebUIJsBridge.
> > +# Supports all the same arguments as mojom() except for `sources` and
> adds
> > +# `source`, `webui_controller`, `webui_controller_header`, and
> > +# `webui_controller_deps` which are *required*. See below for more
> details.
> > +#
> > +# A WebUIJsBridge is a Mojo interface that contains binders for all
> interfaces
> > +# that can be bound from a WebUI's JavaScript. This taret
> > +# will generate a C++ implementation of the WebUIJsBridge interface
> > +# and a {target_name}_webui_js_bridge_impl target that can be depended
> > +# on.
> > +#
> > +# The following arguments are required:
> > +#  - `source`: The mojom file that defines the WebUIJsBridge interface.
> It's
> > +#    allowed to define other interfaces.
> > +#  - `webui_controller`: The name of the WebUIController associated
> with the
> > +#    WebUIJsBridge.
> > +#  - `webui_controller_header`: The header where the WebUIController
> > +#    is declared.
> > +#  - `webui_controller_deps`: The target containing the WebUIController.
> > +template("mojom_with_webui_js_bridge") {
> > +  assert(defined(invoker.source))
> > +  assert(defined(invoker.webui_controller),
> > +         "mojom_with_webui_js_bridge should have a " +
> > +             "`webui_controller` parameter e.g. `foo::FooUI`.")
> > +  assert(defined(invoker.webui_controller_header),
> > +         "mojom_with_webui_js_bridge should have a " +
> > +             "`webui_controller_header` parameter.")
> > +  assert(defined(invoker.webui_controller_deps),
> > +         "mojom_with_webui_js_bridge should have a " +
> > +             "`webui_controller_deps` parameter.")
> > +
> > +  mojom(target_name) {
> > +    forward_variables_from(invoker,
> > +                           "*",
> > +                           [
> > +                             "source",
> > +                             "webui_controller",
> > +                             "webui_controller_header",
> > +                             "webui_controller_deps",
> > +                           ])
> > +    sources = [ invoker.source ]
> > +
> > +    webui_js_bridge_config = {
> > +      webui_controller = invoker.webui_controller
> > +      webui_controller_header = invoker.webui_controller_header
> > +      webui_controller_deps = invoker.webui_controller_deps
> > +    }
> > +  }
> > +}
> > diff --git
> a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> > index da9efc71..98cac149 100755
> > --- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> > +++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
> > @@ -1,5 +1,5 @@
> >  #!/usr/bin/env python
> > -# Copyright 2013 The Chromium Authors. All rights reserved.
> > +# Copyright 2013 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -51,16 +51,23 @@ import crbug_1001171
> >
> >  _BUILTIN_GENERATORS = {
> >      "c++": "mojom_cpp_generator",
> > +    "webui_js_bridge": "mojom_webui_js_bridge_generator",
> >      "javascript": "mojom_js_generator",
> >      "java": "mojom_java_generator",
> >      "mojolpm": "mojom_mojolpm_generator",
> >      "typescript": "mojom_ts_generator",
> >  }
> >
> > +_BUILTIN_CHECKS = {
> > +    "attributes": "mojom_attributes_check",
> > +    "definitions": "mojom_definitions_check",
> > +    "restrictions": "mojom_restrictions_check",
> > +}
> > +
> >
> >  def LoadGenerators(generators_string):
> >    if not generators_string:
> > -    return []  # No generators.
> > +    return {}  # No generators.
> >
> >    generators = {}
> >    for generator_name in [s.strip() for s in
> generators_string.split(",")]:
> > @@ -74,6 +81,21 @@ def LoadGenerators(generators_string):
> >    return generators
> >
> >
> > +def LoadChecks(checks_string):
> > +  if not checks_string:
> > +    return {}  # No checks.
> > +
> > +  checks = {}
> > +  for check_name in [s.strip() for s in checks_string.split(",")]:
> > +    check = check_name.lower()
> > +    if check not in _BUILTIN_CHECKS:
> > +      print("Unknown check name %s" % check_name)
> > +      sys.exit(1)
> > +    check_module = importlib.import_module("checks.%s" %
> _BUILTIN_CHECKS[check])
> > +    checks[check] = check_module
> > +  return checks
> > +
> > +
> >  def MakeImportStackMessage(imported_filename_stack):
> >    """Make a (human-readable) message listing a chain of imports.
> (Returned
> >    string begins with a newline (if nonempty) and does not end with
> one.)"""
> > @@ -82,7 +104,7 @@ def MakeImportStackMessage(imported_filename_stack):
> >                      zip(imported_filename_stack[1:],
> imported_filename_stack)]))
> >
> >
> > -class RelativePath(object):
> > +class RelativePath:
> >    """Represents a path relative to the source tree or generated output
> dir."""
> >
> >    def __init__(self, path, source_root, output_dir):
> > @@ -142,7 +164,7 @@ def ReadFileContents(filename):
> >      return f.read()
> >
> >
> > -class MojomProcessor(object):
> > +class MojomProcessor:
> >    """Takes parsed mojom modules and generates language bindings from
> them.
> >
> >    Attributes:
> > @@ -169,8 +191,8 @@ class MojomProcessor(object):
> >      if 'c++' in self._typemap:
> >        self._typemap['mojolpm'] = self._typemap['c++']
> >
> > -  def _GenerateModule(self, args, remaining_args, generator_modules,
> > -                      rel_filename, imported_filename_stack):
> > +  def _GenerateModule(self, args, remaining_args, check_modules,
> > +                      generator_modules, rel_filename,
> imported_filename_stack):
> >      # Return the already-generated module.
> >      if rel_filename.path in self._processed_files:
> >        return self._processed_files[rel_filename.path]
> > @@ -190,12 +212,16 @@ class MojomProcessor(object):
> >        ScrambleMethodOrdinals(module.interfaces, salt)
> >
> >      if self._should_generate(rel_filename.path):
> > +      # Run checks on module first.
> > +      for check_module in check_modules.values():
> > +        checker = check_module.Check(module)
> > +        checker.CheckModule()
> > +      # Then run generation.
> >        for language, generator_module in generator_modules.items():
> >          generator = generator_module.Generator(
> >              module, args.output_dir,
> typemap=self._typemap.get(language, {}),
> >              variant=args.variant, bytecode_path=args.bytecode_path,
> >              for_blink=args.for_blink,
> > -            js_bindings_mode=args.js_bindings_mode,
> >              js_generate_struct_deserializers=\
> >                      args.js_generate_struct_deserializers,
> >              export_attribute=args.export_attribute,
> > @@ -234,6 +260,7 @@ def _Generate(args, remaining_args):
> >        args.import_directories[idx] = RelativePath(tokens[0], args.depth,
> >                                                    args.output_dir)
> >    generator_modules = LoadGenerators(args.generators_string)
> > +  check_modules = LoadChecks(args.checks_string)
> >
> >    fileutil.EnsureDirectoryExists(args.output_dir)
> >
> > @@ -246,7 +273,7 @@ def _Generate(args, remaining_args):
> >
> >    for filename in args.filename:
> >      processor._GenerateModule(
> > -        args, remaining_args, generator_modules,
> > +        args, remaining_args, check_modules, generator_modules,
> >          RelativePath(filename, args.depth, args.output_dir), [])
> >
> >    return 0
> > @@ -286,6 +313,12 @@ def main():
> >                                 metavar="GENERATORS",
> >                                 default="c++,javascript,java,mojolpm",
> >                                 help="comma-separated list of
> generators")
> > +  generate_parser.add_argument("-c",
> > +                               "--checks",
> > +                               dest="checks_string",
> > +                               metavar="CHECKS",
> > +
>  default="attributes,definitions,restrictions",
> > +                               help="comma-separated list of checks")
> >    generate_parser.add_argument(
> >        "--gen_dir", dest="gen_directories", action="append",
> metavar="directory",
> >        default=[], help="add a directory to be searched for the syntax
> trees.")
> > @@ -308,11 +341,6 @@ def main():
> >    generate_parser.add_argument("--for_blink", action="store_true",
> >                                 help="Use WTF types as generated types
> for mojo "
> >                                 "string/array/map.")
> > -  generate_parser.add_argument(
> > -      "--js_bindings_mode", choices=["new", "old"], default="old",
> > -      help="This option only affects the JavaScript bindings. The value
> could "
> > -      "be \"new\" to generate new-style lite JS bindings in addition to
> the "
> > -      "old, or \"old\" to only generate old bindings.")
> >    generate_parser.add_argument(
> >        "--js_generate_struct_deserializers", action="store_true",
> >        help="Generate javascript deserialize methods for structs in "
> > @@ -387,4 +415,10 @@ def main():
> >
> >  if __name__ == "__main__":
> >    with crbug_1001171.DumpStateOnLookupError():
> > -    sys.exit(main())
> > +    ret = main()
> > +    # Exit without running GC, which can save multiple seconds due to
> the large
> > +    # number of object created. But flush is necessary as os._exit
> doesn't do
> > +    # that.
> > +    sys.stdout.flush()
> > +    sys.stderr.flush()
> > +    os._exit(ret)
> > 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
> > index bddbe3f4..761922b6 100644
> > ---
> a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
> > +++
> b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -8,13 +8,13 @@ from mojom_bindings_generator import
> MakeImportStackMessage
> >  from mojom_bindings_generator import ScrambleMethodOrdinals
> >
> >
> > -class FakeIface(object):
> > +class FakeIface:
> >    def __init__(self):
> >      self.mojom_name = None
> >      self.methods = None
> >
> >
> > -class FakeMethod(object):
> > +class FakeMethod:
> >    def __init__(self, explicit_ordinal=None):
> >      self.explicit_ordinal = explicit_ordinal
> >      self.ordinal = explicit_ordinal
> > diff --git
> a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> > index f1783d59..1d940a21 100755
> > --- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> > +++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
> > @@ -1,5 +1,5 @@
> >  #!/usr/bin/env python
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -17,7 +17,7 @@ def CheckCppTypemapConfigs(target_name,
> config_filename, out_filename):
> >    ])
> >    _SUPPORTED_TYPE_KEYS = set([
> >        'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize',
> 'hashable',
> > -      'move_only', 'nullable_is_same_type'
> > +      'move_only', 'nullable_is_same_type', 'forward_declaration'
> >    ])
> >    with open(config_filename, 'r') as f:
> >      for config in json.load(f):
> > diff --git a/utils/ipc/mojo/public/tools/mojom/BUILD.gn
> b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
> > new file mode 100644
> > index 00000000..4e456c0e
> > --- /dev/null
> > +++ b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
> > @@ -0,0 +1,17 @@
> > +# Copyright 2022 The Chromium Authors
> > +# Use of this source code is governed by a BSD-style license that can be
> > +# found in the LICENSE file.
> > +
> > +group("tests") {
> > +  data = [
> > +    "check_stable_mojom_compatibility_unittest.py",
> > +    "check_stable_mojom_compatibility.py",
> > +    "const_unittest.py",
> > +    "enum_unittest.py",
> > +    "mojom_parser_test_case.py",
> > +    "mojom_parser_unittest.py",
> > +    "mojom_parser.py",
> > +    "stable_attribute_unittest.py",
> > +    "version_compatibility_unittest.py",
> > +  ]
> > +}
> > 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
> > index 08bd672f..3c1ac42c 100755
> > ---
> a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
> > +++
> b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
> > @@ -1,5 +1,5 @@
> > -#!/usr/bin/env python
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +#!/usr/bin/env python3
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  """Verifies backward-compatibility of mojom type changes.
> > @@ -12,20 +12,18 @@ This can be used e.g. by a presubmit check to
> prevent developers from making
> >  breaking changes to stable mojoms."""
> >
> >  import argparse
> > -import errno
> >  import io
> >  import json
> >  import os
> >  import os.path
> > -import shutil
> > -import six
> >  import sys
> > -import tempfile
> >
> >  from mojom.generate import module
> >  from mojom.generate import translate
> >  from mojom.parse import parser
> >
> > +# pylint: disable=raise-missing-from
> > +
> >
> >  class ParseError(Exception):
> >    pass
> > @@ -41,6 +39,8 @@ def _ValidateDelta(root, delta):
> >    transitive closure of a mojom's input dependencies all at once.
> >    """
> >
> > +  translate.is_running_backwards_compatibility_check_hack = True
> > +
> >    # First build a map of all files covered by the delta
> >    affected_files = set()
> >    old_files = {}
> > @@ -73,11 +73,33 @@ def _ValidateDelta(root, delta):
> >      try:
> >        ast = parser.Parse(contents, mojom)
> >      except Exception as e:
> > -      six.reraise(
> > -          ParseError,
> > -          'encountered exception {0} while parsing {1}'.format(e,
> mojom),
> > -          sys.exc_info()[2])
> > +      raise ParseError('encountered exception {0} while parsing
> {1}'.format(
> > +          e, mojom))
> > +
> > +    # Files which are generated at compile time can't be checked by
> this script
> > +    # (at the moment) since they may not exist in the output directory.
> > +    generated_files_to_skip = {
> > +        ('third_party/blink/public/mojom/runtime_feature_state/'
> > +         'runtime_feature_state.mojom'),
> > +    }
> > +
> > +    ast.import_list.items = [
> > +        x for x in ast.import_list.items
> > +        if x.import_filename not in generated_files_to_skip
> > +    ]
> > +
> >      for imp in ast.import_list:
> > +      if (not file_overrides.get(imp.import_filename)
> > +          and not os.path.exists(os.path.join(root,
> imp.import_filename))):
> > +        # Speculatively construct a path prefix to locate the
> import_filename
> > +        mojom_path =
> os.path.dirname(os.path.normpath(mojom)).split(os.sep)
> > +        test_prefix = ''
> > +        for path_component in mojom_path:
> > +          test_prefix = os.path.join(test_prefix, path_component)
> > +          test_import_filename = os.path.join(test_prefix,
> imp.import_filename)
> > +          if os.path.exists(os.path.join(root, test_import_filename)):
> > +            imp.import_filename = test_import_filename
> > +            break
> >        parseMojom(imp.import_filename, file_overrides, override_modules)
> >
> >      # Now that the transitive set of dependencies has been imported and
> parsed
> > @@ -89,10 +111,10 @@ def _ValidateDelta(root, delta):
> >      modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)
> >
> >    old_modules = {}
> > -  for mojom in old_files.keys():
> > +  for mojom in old_files:
> >      parseMojom(mojom, old_files, old_modules)
> >    new_modules = {}
> > -  for mojom in new_files.keys():
> > +  for mojom in new_files:
> >      parseMojom(mojom, new_files, new_modules)
> >
> >    # At this point we have a complete set of translated Modules from
> both the
> > 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
> > index 9f51ea77..06769c95 100755
> > ---
> 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
> > @@ -1,5 +1,5 @@
> > -#!/usr/bin/env python
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +#!/usr/bin/env python3
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -15,7 +15,7 @@ import check_stable_mojom_compatibility
> >  from mojom.generate import module
> >
> >
> > -class Change(object):
> > +class Change:
> >    """Helper to clearly define a mojom file delta to be analyzed."""
> >
> >    def __init__(self, filename, old=None, new=None):
> > @@ -28,7 +28,7 @@ class Change(object):
> >
> >  class UnchangedFile(Change):
> >    def __init__(self, filename, contents):
> > -    super(UnchangedFile, self).__init__(filename, old=contents,
> new=contents)
> > +    super().__init__(filename, old=contents, new=contents)
> >
> >
> >  class CheckStableMojomCompatibilityTest(unittest.TestCase):
> > @@ -258,3 +258,82 @@ class
> CheckStableMojomCompatibilityTest(unittest.TestCase):
> >                 [Stable] struct T { foo.S s; int32 x; };
> >                 """)
> >      ])
> > +
> > +  def testWithPartialImport(self):
> > +    """The compatibility checking tool correctly parses imports with
> partial
> > +    paths."""
> > +    self.assertBackwardCompatible([
> > +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S
> {};'),
> > +        Change('foo/bar.mojom',
> > +               old="""\
> > +               module bar;
> > +               import "foo/foo.mojom";
> > +               [Stable] struct T { foo.S s; };
> > +               """,
> > +               new="""\
> > +               module bar;
> > +               import "foo.mojom";
> > +               [Stable] struct T { foo.S s; };
> > +               """)
> > +    ])
> > +
> > +    self.assertBackwardCompatible([
> > +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S
> {};'),
> > +        Change('foo/bar.mojom',
> > +               old="""\
> > +               module bar;
> > +               import "foo.mojom";
> > +               [Stable] struct T { foo.S s; };
> > +               """,
> > +               new="""\
> > +               module bar;
> > +               import "foo/foo.mojom";
> > +               [Stable] struct T { foo.S s; };
> > +               """)
> > +    ])
> > +
> > +    self.assertNotBackwardCompatible([
> > +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S
> {};'),
> > +        Change('bar/bar.mojom',
> > +               old="""\
> > +               module bar;
> > +               import "foo/foo.mojom";
> > +               [Stable] struct T { foo.S s; };
> > +               """,
> > +               new="""\
> > +               module bar;
> > +               import "foo.mojom";
> > +               [Stable] struct T { foo.S s; };
> > +               """)
> > +    ])
> > +
> > +    self.assertNotBackwardCompatible([
> > +        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S
> {};'),
> > +        Change('bar/bar.mojom',
> > +               old="""\
> > +               module bar;
> > +               import "foo.mojom";
> > +               [Stable] struct T { foo.S s; };
> > +               """,
> > +               new="""\
> > +               module bar;
> > +               import "foo/foo.mojom";
> > +               [Stable] struct T { foo.S s; };
> > +               """)
> > +    ])
> > +
> > +  def testNewEnumDefault(self):
> > +    # Should be backwards compatible since it does not affect the wire
> format.
> > +    # This specific case also checks that the backwards compatibility
> checker
> > +    # does not throw an error due to the older version of the enum not
> > +    # specifying [Default].
> > +    self.assertBackwardCompatible([
> > +        Change('foo/foo.mojom',
> > +               old='[Extensible] enum E { One };',
> > +               new='[Extensible] enum E { [Default] One };')
> > +    ])
> > +    self.assertBackwardCompatible([
> > +        Change('foo/foo.mojom',
> > +               old='[Extensible] enum E { [Default] One, Two, };',
> > +               new='[Extensible] enum E { One, [Default] Two, };')
> > +    ])
> > diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> > index cb42dfac..e8ed36a7 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> > index d9005078..9269cde5 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -90,3 +90,31 @@ class EnumTest(MojomParserTestCase):
> >      self.assertEqual('F', b.enums[0].mojom_name)
> >      self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)
> >      self.assertEqual(37, b.enums[0].fields[0].numeric_value)
> > +
> > +  def testEnumAttributesAreEnums(self):
> > +    """Verifies that enum values in attributes are really enum types."""
> > +    a_mojom = 'a.mojom'
> > +    self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };')
> > +    b_mojom = 'b.mojom'
> > +    self.WriteFile(
> > +        b_mojom, 'module b;'
> > +        'import "a.mojom";'
> > +        '[MooCow=a.E.kFoo]'
> > +        'interface Foo { Foo(); };')
> > +    self.ParseMojoms([a_mojom, b_mojom])
> > +    b = self.LoadModule(b_mojom)
> > +    self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name,
> 'kFoo')
> > +
> > +  def testConstantAttributes(self):
> > +    """Verifies that constants as attributes are translated to the
> constant."""
> > +    a_mojom = 'a.mojom'
> > +    self.WriteFile(
> > +        a_mojom, 'module a;'
> > +        'enum E { kFoo, kBar };'
> > +        'const E kB = E.kFoo;'
> > +        '[Attr=kB] interface Hello { Foo(); };')
> > +    self.ParseMojoms([a_mojom])
> > +    a = self.LoadModule(a_mojom)
> > +    self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name,
> 'kB')
> > +
> self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name,
> > +                      'kFoo')
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> > index 51facc0c..a0edf0eb 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
> > @@ -1,4 +1,4 @@
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -8,6 +8,7 @@ group("mojom") {
> >      "error.py",
> >      "fileutil.py",
> >      "generate/__init__.py",
> > +    "generate/check.py",
> >      "generate/generator.py",
> >      "generate/module.py",
> >      "generate/pack.py",
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> > index 8a1e03da..dd53b835 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> > index bf626f54..29daec36 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2015 The Chromium Authors. All rights reserved.
> > +# Copyright 2015 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > diff --git
> a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> > index ff5753a2..48eaf4ec 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2015 The Chromium Authors. All rights reserved.
> > +# Copyright 2015 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
> > new file mode 100644
> > index 00000000..1efe2022
> > --- /dev/null
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
> > @@ -0,0 +1,26 @@
> > +# Copyright 2022 The Chromium Authors
> > +# Use of this source code is governed by a BSD-style license that can be
> > +# found in the LICENSE file.
> > +"""Code shared by the various pre-generation mojom checkers."""
> > +
> > +
> > +class CheckException(Exception):
> > +  def __init__(self, module, message):
> > +    self.module = module
> > +    self.message = message
> > +    super().__init__(self.message)
> > +
> > +  def __str__(self):
> > +    return "Failed mojo pre-generation check for {}:\n{}".format(
> > +        self.module.path, self.message)
> > +
> > +
> > +class Check:
> > +  def __init__(self, module):
> > +    self.module = module
> > +
> > +  def CheckModule(self):
> > +    """ Subclass should return True if its Checks pass, and throw an
> > +    exception otherwise. CheckModule will be called immediately before
> > +    mojom.generate.Generator.GenerateFiles()"""
> > +    raise NotImplementedError("Subclasses must override/implement this
> method")
> > diff --git
> a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> > index 4a1c73fc..31cacd0e 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2013 The Chromium Authors. All rights reserved.
> > +# Copyright 2013 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  """Code shared by the various language-specific code generators."""
> > @@ -97,7 +97,7 @@ def ToLowerSnakeCase(identifier):
> >    return _ToSnakeCase(identifier, upper=False)
> >
> >
> > -class Stylizer(object):
> > +class Stylizer:
> >    """Stylizers specify naming rules to map mojom names to names in
> generated
> >    code. For example, if you would like method_name in mojom to be
> mapped to
> >    MethodName in the generated code, you need to define a subclass of
> Stylizer
> > @@ -233,7 +233,7 @@ def AddComputedData(module):
> >      _AddInterfaceComputedData(interface)
> >
> >
> > -class Generator(object):
> > +class Generator:
> >    # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo
> all
> >    # files to stdout.
> >    def __init__(self,
> > @@ -243,7 +243,6 @@ class Generator(object):
> >                 variant=None,
> >                 bytecode_path=None,
> >                 for_blink=False,
> > -               js_bindings_mode="new",
> >                 js_generate_struct_deserializers=False,
> >                 export_attribute=None,
> >                 export_header=None,
> > @@ -262,7 +261,6 @@ class Generator(object):
> >      self.variant = variant
> >      self.bytecode_path = bytecode_path
> >      self.for_blink = for_blink
> > -    self.js_bindings_mode = js_bindings_mode
> >      self.js_generate_struct_deserializers =
> js_generate_struct_deserializers
> >      self.export_attribute = export_attribute
> >      self.export_header = export_header
> > 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
> > index 32c884a8..76cda398 100644
> > ---
> a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
> > +++
> b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> > index 9bdb28e0..f0664b31 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2013 The Chromium Authors. All rights reserved.
> > +# Copyright 2013 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -12,15 +12,14 @@
> >  # method = interface.AddMethod('Tat', 0)
> >  # method.AddParameter('baz', 0, mojom.INT32)
> >
> > -import sys
> > -if sys.version_info.major == 2:
> > -  import cPickle as pickle
> > -else:
> > -  import pickle
> > +import pickle
> > +from collections import OrderedDict
> >  from uuid import UUID
> >
> > +# pylint: disable=raise-missing-from
> >
> > -class BackwardCompatibilityChecker(object):
> > +
> > +class BackwardCompatibilityChecker:
> >    """Used for memoization while recursively checking two type
> definitions for
> >    backward-compatibility."""
> >
> > @@ -64,23 +63,20 @@ def Repr(obj, as_ref=True):
> >      return obj.Repr(as_ref=as_ref)
> >    # Since we cannot implement Repr for existing container types, we
> >    # handle them here.
> > -  elif isinstance(obj, list):
> > +  if isinstance(obj, list):
> >      if not obj:
> >        return '[]'
> > -    else:
> > -      return ('[\n%s\n]' % (',\n'.join(
> > -          '    %s' % Repr(elem, as_ref).replace('\n', '\n    ')
> > -          for elem in obj)))
> > -  elif isinstance(obj, dict):
> > +    return ('[\n%s\n]' %
> > +            (',\n'.join('    %s' % Repr(elem, as_ref).replace('\n',
> '\n    ')
> > +                        for elem in obj)))
> > +  if isinstance(obj, dict):
> >      if not obj:
> >        return '{}'
> > -    else:
> > -      return ('{\n%s\n}' % (',\n'.join(
> > -          '    %s: %s' % (Repr(key, as_ref).replace('\n', '\n    '),
> > -                          Repr(val, as_ref).replace('\n', '\n    '))
> > -          for key, val in obj.items())))
> > -  else:
> > -    return repr(obj)
> > +    return ('{\n%s\n}' % (',\n'.join('    %s: %s' %
> > +                                     (Repr(key, as_ref).replace('\n',
> '\n    '),
> > +                                      Repr(val, as_ref).replace('\n',
> '\n    '))
> > +                                     for key, val in obj.items())))
> > +  return repr(obj)
> >
> >
> >  def GenericRepr(obj, names):
> > @@ -104,7 +100,7 @@ def GenericRepr(obj, names):
> >        ReprIndent(name, as_ref) for (name, as_ref) in names.items()))
> >
> >
> > -class Kind(object):
> > +class Kind:
> >    """Kind represents a type (e.g. int8, string).
> >
> >    Attributes:
> > @@ -112,16 +108,43 @@ class Kind(object):
> >      module: {Module} The defining module. Set to None for built-in
> types.
> >      parent_kind: The enclosing type. For example, an enum defined
> >          inside an interface has that interface as its parent. May be
> None.
> > +    is_nullable: True if the type is nullable.
> >    """
> >
> > -  def __init__(self, spec=None, module=None):
> > +  def __init__(self, spec=None, is_nullable=False, module=None):
> >      self.spec = spec
> >      self.module = module
> >      self.parent_kind = None
> > +    self.is_nullable = is_nullable
> > +    self.shared_definition = {}
> > +
> > +  @classmethod
> > +  def AddSharedProperty(cls, name):
> > +    """Adds a property |name| to |cls|, which accesses the
> corresponding item in
> > +       |shared_definition|.
> > +
> > +       The reason of adding such indirection is to enable sharing
> definition
> > +       between a reference kind and its nullable variation. For example:
> > +         a = Struct('test_struct_1')
> > +         b = a.MakeNullableKind()
> > +         a.name = 'test_struct_2'
> > +         print(b.name)  # Outputs 'test_struct_2'.
> > +    """
> > +    def Get(self):
> > +      try:
> > +        return self.shared_definition[name]
> > +      except KeyError:  # Must raise AttributeError if property doesn't
> exist.
> > +        raise AttributeError
> > +
> > +    def Set(self, value):
> > +      self.shared_definition[name] = value
> > +
> > +    setattr(cls, name, property(Get, Set))
> >
> >    def Repr(self, as_ref=True):
> >      # pylint: disable=unused-argument
> > -    return '<%s spec=%r>' % (self.__class__.__name__, self.spec)
> > +    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__,
> self.spec,
> > +                                            self.is_nullable)
> >
> >    def __repr__(self):
> >      # Gives us a decent __repr__ for all kinds.
> > @@ -130,7 +153,8 @@ class Kind(object):
> >    def __eq__(self, rhs):
> >      # pylint: disable=unidiomatic-typecheck
> >      return (type(self) == type(rhs)
> > -            and (self.spec, self.parent_kind) == (rhs.spec,
> rhs.parent_kind))
> > +            and (self.spec, self.parent_kind, self.is_nullable)
> > +            == (rhs.spec, rhs.parent_kind, rhs.is_nullable))
> >
> >    def __hash__(self):
> >      # TODO(crbug.com/1060471): Remove this and other __hash__ methods
> on Kind
> > @@ -138,32 +162,113 @@ class Kind(object):
> >      # some primitive Kinds as dict keys. The default hash (object
> identity)
> >      # breaks these dicts when a pickled Module instance is unpickled
> and used
> >      # during a subsequent run of the parser.
> > -    return hash((self.spec, self.parent_kind))
> > +    return hash((self.spec, self.parent_kind, self.is_nullable))
> >
> >    # pylint: disable=unused-argument
> >    def IsBackwardCompatible(self, rhs, checker):
> >      return self == rhs
> >
> >
> > +class ValueKind(Kind):
> > +  """ValueKind represents values that aren't reference kinds.
> > +
> > +  The primary difference is the wire representation for nullable value
> kinds
> > +  still reserves space for the value type itself, even if that value
> itself
> > +  is logically null.
> > +  """
> > +  def __init__(self, spec=None, is_nullable=False, module=None):
> > +    assert spec is None or is_nullable == spec.startswith('?')
> > +    Kind.__init__(self, spec, is_nullable, module)
> > +
> > +  def MakeNullableKind(self):
> > +    assert not self.is_nullable
> > +
> > +    if self == BOOL:
> > +      return NULLABLE_BOOL
> > +    if self == INT8:
> > +      return NULLABLE_INT8
> > +    if self == INT16:
> > +      return NULLABLE_INT16
> > +    if self == INT32:
> > +      return NULLABLE_INT32
> > +    if self == INT64:
> > +      return NULLABLE_INT64
> > +    if self == UINT8:
> > +      return NULLABLE_UINT8
> > +    if self == UINT16:
> > +      return NULLABLE_UINT16
> > +    if self == UINT32:
> > +      return NULLABLE_UINT32
> > +    if self == UINT64:
> > +      return NULLABLE_UINT64
> > +    if self == FLOAT:
> > +      return NULLABLE_FLOAT
> > +    if self == DOUBLE:
> > +      return NULLABLE_DOUBLE
> > +
> > +    nullable_kind = type(self)()
> > +    nullable_kind.shared_definition = self.shared_definition
> > +    if self.spec is not None:
> > +      nullable_kind.spec = '?' + self.spec
> > +    nullable_kind.is_nullable = True
> > +    nullable_kind.parent_kind = self.parent_kind
> > +    nullable_kind.module = self.module
> > +
> > +    return nullable_kind
> > +
> > +  def MakeUnnullableKind(self):
> > +    assert self.is_nullable
> > +
> > +    if self == NULLABLE_BOOL:
> > +      return BOOL
> > +    if self == NULLABLE_INT8:
> > +      return INT8
> > +    if self == NULLABLE_INT16:
> > +      return INT16
> > +    if self == NULLABLE_INT32:
> > +      return INT32
> > +    if self == NULLABLE_INT64:
> > +      return INT64
> > +    if self == NULLABLE_UINT8:
> > +      return UINT8
> > +    if self == NULLABLE_UINT16:
> > +      return UINT16
> > +    if self == NULLABLE_UINT32:
> > +      return UINT32
> > +    if self == NULLABLE_UINT64:
> > +      return UINT64
> > +    if self == NULLABLE_FLOAT:
> > +      return FLOAT
> > +    if self == NULLABLE_DOUBLE:
> > +      return DOUBLE
> > +
> > +    nullable_kind = type(self)()
> > +    nullable_kind.shared_definition = self.shared_definition
> > +    if self.spec is not None:
> > +      nullable_kind.spec = self.spec[1:]
> > +    nullable_kind.is_nullable = False
> > +    nullable_kind.parent_kind = self.parent_kind
> > +    nullable_kind.module = self.module
> > +
> > +    return nullable_kind
> > +
> > +  def __eq__(self, rhs):
> > +    return (isinstance(rhs, ValueKind) and super().__eq__(rhs))
> > +
> > +  def __hash__(self):  # pylint: disable=useless-super-delegation
> > +    return super().__hash__()
> > +
> > +
> >  class ReferenceKind(Kind):
> >    """ReferenceKind represents pointer and handle types.
> >
> >    A type is nullable if null (for pointer types) or invalid handle (for
> handle
> >    types) is a legal value for the type.
> > -
> > -  Attributes:
> > -    is_nullable: True if the type is nullable.
> >    """
> >
> >    def __init__(self, spec=None, is_nullable=False, module=None):
> >      assert spec is None or is_nullable == spec.startswith('?')
> > -    Kind.__init__(self, spec, module)
> > -    self.is_nullable = is_nullable
> > -    self.shared_definition = {}
> > -
> > -  def Repr(self, as_ref=True):
> > -    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__,
> self.spec,
> > -                                            self.is_nullable)
> > +    Kind.__init__(self, spec, is_nullable, module)
> >
> >    def MakeNullableKind(self):
> >      assert not self.is_nullable
> > @@ -193,55 +298,36 @@ class ReferenceKind(Kind):
> >
> >      return nullable_kind
> >
> > -  @classmethod
> > -  def AddSharedProperty(cls, name):
> > -    """Adds a property |name| to |cls|, which accesses the
> corresponding item in
> > -       |shared_definition|.
> > -
> > -       The reason of adding such indirection is to enable sharing
> definition
> > -       between a reference kind and its nullable variation. For example:
> > -         a = Struct('test_struct_1')
> > -         b = a.MakeNullableKind()
> > -         a.name = 'test_struct_2'
> > -         print(b.name)  # Outputs 'test_struct_2'.
> > -    """
> > -
> > -    def Get(self):
> > -      try:
> > -        return self.shared_definition[name]
> > -      except KeyError:  # Must raise AttributeError if property doesn't
> exist.
> > -        raise AttributeError
> > -
> > -    def Set(self, value):
> > -      self.shared_definition[name] = value
> > -
> > -    setattr(cls, name, property(Get, Set))
> > -
> >    def __eq__(self, rhs):
> > -    return (isinstance(rhs, ReferenceKind)
> > -            and super(ReferenceKind, self).__eq__(rhs)
> > -            and self.is_nullable == rhs.is_nullable)
> > -
> > -  def __hash__(self):
> > -    return hash((super(ReferenceKind, self).__hash__(),
> self.is_nullable))
> > +    return (isinstance(rhs, ReferenceKind) and super().__eq__(rhs))
> >
> > -  def IsBackwardCompatible(self, rhs, checker):
> > -    return (super(ReferenceKind, self).IsBackwardCompatible(rhs,
> checker)
> > -            and self.is_nullable == rhs.is_nullable)
> > +  def __hash__(self):  # pylint: disable=useless-super-delegation
> > +    return super().__hash__()
> >
> >
> >  # Initialize the set of primitive types. These can be accessed by
> clients.
> > -BOOL = Kind('b')
> > -INT8 = Kind('i8')
> > -INT16 = Kind('i16')
> > -INT32 = Kind('i32')
> > -INT64 = Kind('i64')
> > -UINT8 = Kind('u8')
> > -UINT16 = Kind('u16')
> > -UINT32 = Kind('u32')
> > -UINT64 = Kind('u64')
> > -FLOAT = Kind('f')
> > -DOUBLE = Kind('d')
> > +BOOL = ValueKind('b')
> > +INT8 = ValueKind('i8')
> > +INT16 = ValueKind('i16')
> > +INT32 = ValueKind('i32')
> > +INT64 = ValueKind('i64')
> > +UINT8 = ValueKind('u8')
> > +UINT16 = ValueKind('u16')
> > +UINT32 = ValueKind('u32')
> > +UINT64 = ValueKind('u64')
> > +FLOAT = ValueKind('f')
> > +DOUBLE = ValueKind('d')
> > +NULLABLE_BOOL = ValueKind('?b', True)
> > +NULLABLE_INT8 = ValueKind('?i8', True)
> > +NULLABLE_INT16 = ValueKind('?i16', True)
> > +NULLABLE_INT32 = ValueKind('?i32', True)
> > +NULLABLE_INT64 = ValueKind('?i64', True)
> > +NULLABLE_UINT8 = ValueKind('?u8', True)
> > +NULLABLE_UINT16 = ValueKind('?u16', True)
> > +NULLABLE_UINT32 = ValueKind('?u32', True)
> > +NULLABLE_UINT64 = ValueKind('?u64', True)
> > +NULLABLE_FLOAT = ValueKind('?f', True)
> > +NULLABLE_DOUBLE = ValueKind('?d', True)
> >  STRING = ReferenceKind('s')
> >  HANDLE = ReferenceKind('h')
> >  DCPIPE = ReferenceKind('h:d:c')
> > @@ -270,6 +356,17 @@ PRIMITIVES = (
> >      UINT64,
> >      FLOAT,
> >      DOUBLE,
> > +    NULLABLE_BOOL,
> > +    NULLABLE_INT8,
> > +    NULLABLE_INT16,
> > +    NULLABLE_INT32,
> > +    NULLABLE_INT64,
> > +    NULLABLE_UINT8,
> > +    NULLABLE_UINT16,
> > +    NULLABLE_UINT32,
> > +    NULLABLE_UINT64,
> > +    NULLABLE_FLOAT,
> > +    NULLABLE_DOUBLE,
> >      STRING,
> >      HANDLE,
> >      DCPIPE,
> > @@ -294,9 +391,12 @@ ATTRIBUTE_STABLE = 'Stable'
> >  ATTRIBUTE_SYNC = 'Sync'
> >  ATTRIBUTE_UNLIMITED_SIZE = 'UnlimitedSize'
> >  ATTRIBUTE_UUID = 'Uuid'
> > +ATTRIBUTE_SERVICE_SANDBOX = 'ServiceSandbox'
> > +ATTRIBUTE_REQUIRE_CONTEXT = 'RequireContext'
> > +ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext'
> >
> >
> > -class NamedValue(object):
> > +class NamedValue:
> >    def __init__(self, module, parent_kind, mojom_name):
> >      self.module = module
> >      self.parent_kind = parent_kind
> > @@ -316,7 +416,7 @@ class NamedValue(object):
> >      return hash((self.parent_kind, self.mojom_name))
> >
> >
> > -class BuiltinValue(object):
> > +class BuiltinValue:
> >    def __init__(self, value):
> >      self.value = value
> >
> > @@ -350,7 +450,7 @@ class EnumValue(NamedValue):
> >      return self.field.name
> >
> >
> > -class Constant(object):
> > +class Constant:
> >    def __init__(self, mojom_name=None, kind=None, value=None,
> parent_kind=None):
> >      self.mojom_name = mojom_name
> >      self.name = None
> > @@ -368,7 +468,7 @@ class Constant(object):
> >                                         rhs.parent_kind))
> >
> >
> > -class Field(object):
> > +class Field:
> >    def __init__(self,
> >                 mojom_name=None,
> >                 kind=None,
> > @@ -414,7 +514,18 @@ class StructField(Field):
> >
> >
> >  class UnionField(Field):
> > -  pass
> > +  def __init__(self,
> > +               mojom_name=None,
> > +               kind=None,
> > +               ordinal=None,
> > +               default=None,
> > +               attributes=None):
> > +    Field.__init__(self, mojom_name, kind, ordinal, default, attributes)
> > +
> > +  @property
> > +  def is_default(self):
> > +    return self.attributes.get(ATTRIBUTE_DEFAULT, False) \
> > +        if self.attributes else False
> >
> >
> >  def _IsFieldBackwardCompatible(new_field, old_field, checker):
> > @@ -441,14 +552,14 @@ class Struct(ReferenceKind):
> >          if it's a native struct.
> >    """
> >
> > -  ReferenceKind.AddSharedProperty('mojom_name')
> > -  ReferenceKind.AddSharedProperty('name')
> > -  ReferenceKind.AddSharedProperty('native_only')
> > -  ReferenceKind.AddSharedProperty('custom_serializer')
> > -  ReferenceKind.AddSharedProperty('fields')
> > -  ReferenceKind.AddSharedProperty('enums')
> > -  ReferenceKind.AddSharedProperty('constants')
> > -  ReferenceKind.AddSharedProperty('attributes')
> > +  Kind.AddSharedProperty('mojom_name')
> > +  Kind.AddSharedProperty('name')
> > +  Kind.AddSharedProperty('native_only')
> > +  Kind.AddSharedProperty('custom_serializer')
> > +  Kind.AddSharedProperty('fields')
> > +  Kind.AddSharedProperty('enums')
> > +  Kind.AddSharedProperty('constants')
> > +  Kind.AddSharedProperty('attributes')
> >
> >    def __init__(self, mojom_name=None, module=None, attributes=None):
> >      if mojom_name is not None:
> > @@ -470,12 +581,11 @@ class Struct(ReferenceKind):
> >        return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__,
> >                                                 self.mojom_name,
> >                                                 Repr(self.module,
> as_ref=True))
> > -    else:
> > -      return GenericRepr(self, {
> > -          'mojom_name': False,
> > -          'fields': False,
> > -          'module': True
> > -      })
> > +    return GenericRepr(self, {
> > +        'mojom_name': False,
> > +        'fields': False,
> > +        'module': True
> > +    })
> >
> >    def AddField(self,
> >                 mojom_name,
> > @@ -496,13 +606,13 @@ class Struct(ReferenceKind):
> >      for constant in self.constants:
> >        constant.Stylize(stylizer)
> >
> > -  def IsBackwardCompatible(self, older_struct, checker):
> > -    """This struct is backward-compatible with older_struct if and only
> if all
> > -    of the following conditions hold:
> > +  def IsBackwardCompatible(self, rhs, checker):
> > +    """This struct is backward-compatible with rhs (older_struct) if
> and only if
> > +    all of the following conditions hold:
> >        - Any newly added field is tagged with a [MinVersion] attribute
> specifying
> >          a version number greater than all previously used [MinVersion]
> >          attributes within the struct.
> > -      - All fields present in older_struct remain present in the new
> struct,
> > +      - All fields present in rhs remain present in the new struct,
> >          with the same ordinal position, same optional or non-optional
> status,
> >          same (or backward-compatible) type and where applicable, the
> same
> >          [MinVersion] attribute value.
> > @@ -521,7 +631,7 @@ class Struct(ReferenceKind):
> >        return fields_by_ordinal
> >
> >      new_fields = buildOrdinalFieldMap(self)
> > -    old_fields = buildOrdinalFieldMap(older_struct)
> > +    old_fields = buildOrdinalFieldMap(rhs)
> >      if len(new_fields) < len(old_fields):
> >        # At least one field was removed, which is not OK.
> >        return False
> > @@ -574,11 +684,18 @@ class Struct(ReferenceKind):
> >        prefix = self.module.GetNamespacePrefix()
> >      return '%s%s' % (prefix, self.mojom_name)
> >
> > +  def _tuple(self):
> > +    return (self.mojom_name, self.native_only, self.fields,
> self.constants,
> > +            self.attributes)
> > +
> >    def __eq__(self, rhs):
> > -    return (isinstance(rhs, Struct) and
> > -            (self.mojom_name, self.native_only, self.fields,
> self.constants,
> > -             self.attributes) == (rhs.mojom_name, rhs.native_only,
> rhs.fields,
> > -                                  rhs.constants, rhs.attributes))
> > +    return isinstance(rhs, Struct) and self._tuple() == rhs._tuple()
> > +
> > +  def __lt__(self, rhs):
> > +    if not isinstance(self, type(rhs)):
> > +      return str(type(self)) < str(type(rhs))
> > +
> > +    return self._tuple() < rhs._tuple()
> >
> >    def __hash__(self):
> >      return id(self)
> > @@ -595,10 +712,11 @@ class Union(ReferenceKind):
> >          which Java class name to use to represent it in the generated
> >          bindings.
> >    """
> > -  ReferenceKind.AddSharedProperty('mojom_name')
> > -  ReferenceKind.AddSharedProperty('name')
> > -  ReferenceKind.AddSharedProperty('fields')
> > -  ReferenceKind.AddSharedProperty('attributes')
> > +  Kind.AddSharedProperty('mojom_name')
> > +  Kind.AddSharedProperty('name')
> > +  Kind.AddSharedProperty('fields')
> > +  Kind.AddSharedProperty('attributes')
> > +  Kind.AddSharedProperty('default_field')
> >
> >    def __init__(self, mojom_name=None, module=None, attributes=None):
> >      if mojom_name is not None:
> > @@ -610,14 +728,14 @@ class Union(ReferenceKind):
> >      self.name = None
> >      self.fields = []
> >      self.attributes = attributes
> > +    self.default_field = None
> >
> >    def Repr(self, as_ref=True):
> >      if as_ref:
> >        return '<%s spec=%r is_nullable=%r fields=%s>' % (
> >            self.__class__.__name__, self.spec, self.is_nullable, Repr(
> >                self.fields))
> > -    else:
> > -      return GenericRepr(self, {'fields': True, 'is_nullable': False})
> > +    return GenericRepr(self, {'fields': True, 'is_nullable': False})
> >
> >    def AddField(self, mojom_name, kind, ordinal=None, attributes=None):
> >      field = UnionField(mojom_name, kind, ordinal, None, attributes)
> > @@ -629,13 +747,13 @@ class Union(ReferenceKind):
> >      for field in self.fields:
> >        field.Stylize(stylizer)
> >
> > -  def IsBackwardCompatible(self, older_union, checker):
> > -    """This union is backward-compatible with older_union if and only
> if all
> > -    of the following conditions hold:
> > +  def IsBackwardCompatible(self, rhs, checker):
> > +    """This union is backward-compatible with rhs (older_union) if and
> only if
> > +    all of the following conditions hold:
> >        - Any newly added field is tagged with a [MinVersion] attribute
> specifying
> >          a version number greater than all previously used [MinVersion]
> >          attributes within the union.
> > -      - All fields present in older_union remain present in the new
> union,
> > +      - All fields present in rhs remain present in the new union,
> >          with the same ordinal value, same optional or non-optional
> status,
> >          same (or backward-compatible) type, and where applicable, the
> same
> >          [MinVersion] attribute value.
> > @@ -651,7 +769,7 @@ class Union(ReferenceKind):
> >        return fields_by_ordinal
> >
> >      new_fields = buildOrdinalFieldMap(self)
> > -    old_fields = buildOrdinalFieldMap(older_union)
> > +    old_fields = buildOrdinalFieldMap(rhs)
> >      if len(new_fields) < len(old_fields):
> >        # At least one field was removed, which is not OK.
> >        return False
> > @@ -677,6 +795,11 @@ class Union(ReferenceKind):
> >
> >      return True
> >
> > +  @property
> > +  def extensible(self):
> > +    return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \
> > +        if self.attributes else False
> > +
> >    @property
> >    def stable(self):
> >      return self.attributes.get(ATTRIBUTE_STABLE, False) \
> > @@ -690,10 +813,17 @@ class Union(ReferenceKind):
> >        prefix = self.module.GetNamespacePrefix()
> >      return '%s%s' % (prefix, self.mojom_name)
> >
> > +  def _tuple(self):
> > +    return (self.mojom_name, self.fields, self.attributes)
> > +
> >    def __eq__(self, rhs):
> > -    return (isinstance(rhs, Union) and
> > -            (self.mojom_name, self.fields,
> > -             self.attributes) == (rhs.mojom_name, rhs.fields,
> rhs.attributes))
> > +    return isinstance(rhs, Union) and self._tuple() == rhs._tuple()
> > +
> > +  def __lt__(self, rhs):
> > +    if not isinstance(self, type(rhs)):
> > +      return str(type(self)) < str(type(rhs))
> > +
> > +    return self._tuple() < rhs._tuple()
> >
> >    def __hash__(self):
> >      return id(self)
> > @@ -707,8 +837,8 @@ class Array(ReferenceKind):
> >      length: The number of elements. None if unknown.
> >    """
> >
> > -  ReferenceKind.AddSharedProperty('kind')
> > -  ReferenceKind.AddSharedProperty('length')
> > +  Kind.AddSharedProperty('kind')
> > +  Kind.AddSharedProperty('length')
> >
> >    def __init__(self, kind=None, length=None):
> >      if kind is not None:
> > @@ -728,12 +858,11 @@ class Array(ReferenceKind):
> >        return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (
> >            self.__class__.__name__, self.spec, self.is_nullable, Repr(
> >                self.kind), self.length)
> > -    else:
> > -      return GenericRepr(self, {
> > -          'kind': True,
> > -          'length': False,
> > -          'is_nullable': False
> > -      })
> > +    return GenericRepr(self, {
> > +        'kind': True,
> > +        'length': False,
> > +        'is_nullable': False
> > +    })
> >
> >    def __eq__(self, rhs):
> >      return (isinstance(rhs, Array)
> > @@ -754,8 +883,8 @@ class Map(ReferenceKind):
> >      key_kind: {Kind} The type of the keys. May be None.
> >      value_kind: {Kind} The type of the elements. May be None.
> >    """
> > -  ReferenceKind.AddSharedProperty('key_kind')
> > -  ReferenceKind.AddSharedProperty('value_kind')
> > +  Kind.AddSharedProperty('key_kind')
> > +  Kind.AddSharedProperty('value_kind')
> >
> >    def __init__(self, key_kind=None, value_kind=None):
> >      if (key_kind is not None and value_kind is not None):
> > @@ -780,8 +909,7 @@ class Map(ReferenceKind):
> >        return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (
> >            self.__class__.__name__, self.spec, self.is_nullable,
> >            Repr(self.key_kind), Repr(self.value_kind))
> > -    else:
> > -      return GenericRepr(self, {'key_kind': True, 'value_kind': True})
> > +    return GenericRepr(self, {'key_kind': True, 'value_kind': True})
> >
> >    def __eq__(self, rhs):
> >      return (isinstance(rhs, Map) and
> > @@ -797,7 +925,7 @@ class Map(ReferenceKind):
> >
> >
> >  class PendingRemote(ReferenceKind):
> > -  ReferenceKind.AddSharedProperty('kind')
> > +  Kind.AddSharedProperty('kind')
> >
> >    def __init__(self, kind=None):
> >      if kind is not None:
> > @@ -822,7 +950,7 @@ class PendingRemote(ReferenceKind):
> >
> >
> >  class PendingReceiver(ReferenceKind):
> > -  ReferenceKind.AddSharedProperty('kind')
> > +  Kind.AddSharedProperty('kind')
> >
> >    def __init__(self, kind=None):
> >      if kind is not None:
> > @@ -847,7 +975,7 @@ class PendingReceiver(ReferenceKind):
> >
> >
> >  class PendingAssociatedRemote(ReferenceKind):
> > -  ReferenceKind.AddSharedProperty('kind')
> > +  Kind.AddSharedProperty('kind')
> >
> >    def __init__(self, kind=None):
> >      if kind is not None:
> > @@ -873,7 +1001,7 @@ class PendingAssociatedRemote(ReferenceKind):
> >
> >
> >  class PendingAssociatedReceiver(ReferenceKind):
> > -  ReferenceKind.AddSharedProperty('kind')
> > +  Kind.AddSharedProperty('kind')
> >
> >    def __init__(self, kind=None):
> >      if kind is not None:
> > @@ -899,7 +1027,7 @@ class PendingAssociatedReceiver(ReferenceKind):
> >
> >
> >  class InterfaceRequest(ReferenceKind):
> > -  ReferenceKind.AddSharedProperty('kind')
> > +  Kind.AddSharedProperty('kind')
> >
> >    def __init__(self, kind=None):
> >      if kind is not None:
> > @@ -923,7 +1051,7 @@ class InterfaceRequest(ReferenceKind):
> >
> >
> >  class AssociatedInterfaceRequest(ReferenceKind):
> > -  ReferenceKind.AddSharedProperty('kind')
> > +  Kind.AddSharedProperty('kind')
> >
> >    def __init__(self, kind=None):
> >      if kind is not None:
> > @@ -949,7 +1077,7 @@ class AssociatedInterfaceRequest(ReferenceKind):
> >              self.kind, rhs.kind)
> >
> >
> > -class Parameter(object):
> > +class Parameter:
> >    def __init__(self,
> >                 mojom_name=None,
> >                 kind=None,
> > @@ -983,7 +1111,7 @@ class Parameter(object):
> >                                        rhs.default, rhs.attributes))
> >
> >
> > -class Method(object):
> > +class Method:
> >    def __init__(self, interface, mojom_name, ordinal=None,
> attributes=None):
> >      self.interface = interface
> >      self.mojom_name = mojom_name
> > @@ -999,12 +1127,11 @@ class Method(object):
> >    def Repr(self, as_ref=True):
> >      if as_ref:
> >        return '<%s mojom_name=%r>' % (self.__class__.__name__,
> self.mojom_name)
> > -    else:
> > -      return GenericRepr(self, {
> > -          'mojom_name': False,
> > -          'parameters': True,
> > -          'response_parameters': True
> > -      })
> > +    return GenericRepr(self, {
> > +        'mojom_name': False,
> > +        'parameters': True,
> > +        'response_parameters': True
> > +    })
> >
> >    def AddParameter(self,
> >                     mojom_name,
> > @@ -1061,21 +1188,32 @@ class Method(object):
> >      return self.attributes.get(ATTRIBUTE_UNLIMITED_SIZE) \
> >          if self.attributes else False
> >
> > +  @property
> > +  def allowed_context(self):
> > +    return self.attributes.get(ATTRIBUTE_ALLOWED_CONTEXT) \
> > +        if self.attributes else None
> > +
> > +  def _tuple(self):
> > +    return (self.mojom_name, self.ordinal, self.parameters,
> > +            self.response_parameters, self.attributes)
> > +
> >    def __eq__(self, rhs):
> > -    return (isinstance(rhs, Method) and
> > -            (self.mojom_name, self.ordinal, self.parameters,
> > -             self.response_parameters,
> > -             self.attributes) == (rhs.mojom_name, rhs.ordinal,
> rhs.parameters,
> > -                                  rhs.response_parameters,
> rhs.attributes))
> > +    return isinstance(rhs, Method) and self._tuple() == rhs._tuple()
> > +
> > +  def __lt__(self, rhs):
> > +    if not isinstance(self, type(rhs)):
> > +      return str(type(self)) < str(type(rhs))
> > +
> > +    return self._tuple() < rhs._tuple()
> >
> >
> >  class Interface(ReferenceKind):
> > -  ReferenceKind.AddSharedProperty('mojom_name')
> > -  ReferenceKind.AddSharedProperty('name')
> > -  ReferenceKind.AddSharedProperty('methods')
> > -  ReferenceKind.AddSharedProperty('enums')
> > -  ReferenceKind.AddSharedProperty('constants')
> > -  ReferenceKind.AddSharedProperty('attributes')
> > +  Kind.AddSharedProperty('mojom_name')
> > +  Kind.AddSharedProperty('name')
> > +  Kind.AddSharedProperty('methods')
> > +  Kind.AddSharedProperty('enums')
> > +  Kind.AddSharedProperty('constants')
> > +  Kind.AddSharedProperty('attributes')
> >
> >    def __init__(self, mojom_name=None, module=None, attributes=None):
> >      if mojom_name is not None:
> > @@ -1093,12 +1231,11 @@ class Interface(ReferenceKind):
> >    def Repr(self, as_ref=True):
> >      if as_ref:
> >        return '<%s mojom_name=%r>' % (self.__class__.__name__,
> self.mojom_name)
> > -    else:
> > -      return GenericRepr(self, {
> > -          'mojom_name': False,
> > -          'attributes': False,
> > -          'methods': False
> > -      })
> > +    return GenericRepr(self, {
> > +        'mojom_name': False,
> > +        'attributes': False,
> > +        'methods': False
> > +    })
> >
> >    def AddMethod(self, mojom_name, ordinal=None, attributes=None):
> >      method = Method(self, mojom_name, ordinal, attributes)
> > @@ -1114,10 +1251,10 @@ class Interface(ReferenceKind):
> >      for constant in self.constants:
> >        constant.Stylize(stylizer)
> >
> > -  def IsBackwardCompatible(self, older_interface, checker):
> > -    """This interface is backward-compatible with older_interface if
> and only
> > -    if all of the following conditions hold:
> > -      - All defined methods in older_interface (when identified by
> ordinal) have
> > +  def IsBackwardCompatible(self, rhs, checker):
> > +    """This interface is backward-compatible with rhs (older_interface)
> if and
> > +    only if all of the following conditions hold:
> > +      - All defined methods in rhs (when identified by ordinal) have
> >          backward-compatible definitions in this interface. For each
> method this
> >          means:
> >            - The parameter list is backward-compatible, according to
> backward-
> > @@ -1131,7 +1268,7 @@ class Interface(ReferenceKind):
> >              rules for structs.
> >        - All newly introduced methods in this interface have a
> [MinVersion]
> >          attribute specifying a version greater than any method in
> > -        older_interface.
> > +        rhs.
> >      """
> >
> >      def buildOrdinalMethodMap(interface):
> > @@ -1144,7 +1281,7 @@ class Interface(ReferenceKind):
> >        return methods_by_ordinal
> >
> >      new_methods = buildOrdinalMethodMap(self)
> > -    old_methods = buildOrdinalMethodMap(older_interface)
> > +    old_methods = buildOrdinalMethodMap(rhs)
> >      max_old_min_version = 0
> >      for ordinal, old_method in old_methods.items():
> >        new_method = new_methods.get(ordinal)
> > @@ -1186,6 +1323,27 @@ class Interface(ReferenceKind):
> >
> >      return True
> >
> > +  @property
> > +  def service_sandbox(self):
> > +    if not self.attributes:
> > +      return None
> > +    service_sandbox = self.attributes.get(ATTRIBUTE_SERVICE_SANDBOX,
> None)
> > +    if service_sandbox is None:
> > +      return None
> > +    # Constants are only allowed to refer to an enum here, so replace.
> > +    if isinstance(service_sandbox, Constant):
> > +      service_sandbox = service_sandbox.value
> > +    if not isinstance(service_sandbox, EnumValue):
> > +      raise Exception("ServiceSandbox attribute on %s must be an enum
> value." %
> > +                      self.module.name)
> > +    return service_sandbox
> > +
> > +  @property
> > +  def require_context(self):
> > +    if not self.attributes:
> > +      return None
> > +    return self.attributes.get(ATTRIBUTE_REQUIRE_CONTEXT, None)
> > +
> >    @property
> >    def stable(self):
> >      return self.attributes.get(ATTRIBUTE_STABLE, False) \
> > @@ -1199,11 +1357,18 @@ class Interface(ReferenceKind):
> >        prefix = self.module.GetNamespacePrefix()
> >      return '%s%s' % (prefix, self.mojom_name)
> >
> > +  def _tuple(self):
> > +    return (self.mojom_name, self.methods, self.enums, self.constants,
> > +            self.attributes)
> > +
> >    def __eq__(self, rhs):
> > -    return (isinstance(rhs, Interface)
> > -            and (self.mojom_name, self.methods, self.enums,
> self.constants,
> > -                 self.attributes) == (rhs.mojom_name, rhs.methods,
> rhs.enums,
> > -                                      rhs.constants, rhs.attributes))
> > +    return isinstance(rhs, Interface) and self._tuple() == rhs._tuple()
> > +
> > +  def __lt__(self, rhs):
> > +    if not isinstance(self, type(rhs)):
> > +      return str(type(self)) < str(type(rhs))
> > +
> > +    return self._tuple() < rhs._tuple()
> >
> >    @property
> >    def uuid(self):
> > @@ -1224,7 +1389,7 @@ class Interface(ReferenceKind):
> >
> >
> >  class AssociatedInterface(ReferenceKind):
> > -  ReferenceKind.AddSharedProperty('kind')
> > +  Kind.AddSharedProperty('kind')
> >
> >    def __init__(self, kind=None):
> >      if kind is not None:
> > @@ -1249,7 +1414,7 @@ class AssociatedInterface(ReferenceKind):
> >                            self.kind, rhs.kind)
> >
> >
> > -class EnumField(object):
> > +class EnumField:
> >    def __init__(self,
> >                 mojom_name=None,
> >                 value=None,
> > @@ -1281,16 +1446,25 @@ class EnumField(object):
> >                                           rhs.attributes,
> rhs.numeric_value))
> >
> >
> > -class Enum(Kind):
> > +class Enum(ValueKind):
> > +  Kind.AddSharedProperty('mojom_name')
> > +  Kind.AddSharedProperty('name')
> > +  Kind.AddSharedProperty('native_only')
> > +  Kind.AddSharedProperty('fields')
> > +  Kind.AddSharedProperty('attributes')
> > +  Kind.AddSharedProperty('min_value')
> > +  Kind.AddSharedProperty('max_value')
> > +  Kind.AddSharedProperty('default_field')
> > +
> >    def __init__(self, mojom_name=None, module=None, attributes=None):
> > -    self.mojom_name = mojom_name
> > -    self.name = None
> > -    self.native_only = False
> >      if mojom_name is not None:
> >        spec = 'x:' + mojom_name
> >      else:
> >        spec = None
> > -    Kind.__init__(self, spec, module)
> > +    ValueKind.__init__(self, spec, False, module)
> > +    self.mojom_name = mojom_name
> > +    self.name = None
> > +    self.native_only = False
> >      self.fields = []
> >      self.attributes = attributes
> >      self.min_value = None
> > @@ -1300,8 +1474,7 @@ class Enum(Kind):
> >    def Repr(self, as_ref=True):
> >      if as_ref:
> >        return '<%s mojom_name=%r>' % (self.__class__.__name__,
> self.mojom_name)
> > -    else:
> > -      return GenericRepr(self, {'mojom_name': False, 'fields': False})
> > +    return GenericRepr(self, {'mojom_name': False, 'fields': False})
> >
> >    def Stylize(self, stylizer):
> >      self.name = stylizer.StylizeEnum(self.mojom_name)
> > @@ -1327,14 +1500,14 @@ class Enum(Kind):
> >      return '%s%s' % (prefix, self.mojom_name)
> >
> >    # pylint: disable=unused-argument
> > -  def IsBackwardCompatible(self, older_enum, checker):
> > -    """This enum is backward-compatible with older_enum if and only if
> one of
> > -    the following conditions holds:
> > +  def IsBackwardCompatible(self, rhs, checker):
> > +    """This enum is backward-compatible with rhs (older_enum) if and
> only if one
> > +    of the following conditions holds:
> >          - Neither enum is [Extensible] and both have the exact same set
> of valid
> >            numeric values. Field names and aliases for the same numeric
> value do
> >            not affect compatibility.
> > -        - older_enum is [Extensible], and for every version defined by
> > -          older_enum, this enum has the exact same set of valid numeric
> values.
> > +        - rhs is [Extensible], and for every version defined by
> > +          rhs, this enum has the exact same set of valid numeric values.
> >      """
> >
> >      def buildVersionFieldMap(enum):
> > @@ -1345,10 +1518,10 @@ class Enum(Kind):
> >
> fields_by_min_version[field.min_version].add(field.numeric_value)
> >        return fields_by_min_version
> >
> > -    old_fields = buildVersionFieldMap(older_enum)
> > +    old_fields = buildVersionFieldMap(rhs)
> >      new_fields = buildVersionFieldMap(self)
> >
> > -    if new_fields.keys() != old_fields.keys() and not
> older_enum.extensible:
> > +    if new_fields.keys() != old_fields.keys() and not rhs.extensible:
> >        return False
> >
> >      for min_version, valid_values in old_fields.items():
> > @@ -1358,19 +1531,24 @@ class Enum(Kind):
> >
> >      return True
> >
> > +  def _tuple(self):
> > +    return (self.mojom_name, self.native_only, self.fields,
> self.attributes,
> > +            self.min_value, self.max_value, self.default_field)
> > +
> >    def __eq__(self, rhs):
> > -    return (isinstance(rhs, Enum) and
> > -            (self.mojom_name, self.native_only, self.fields,
> self.attributes,
> > -             self.min_value, self.max_value,
> > -             self.default_field) == (rhs.mojom_name, rhs.native_only,
> > -                                     rhs.fields, rhs.attributes,
> rhs.min_value,
> > -                                     rhs.max_value, rhs.default_field))
> > +    return isinstance(rhs, Enum) and self._tuple() == rhs._tuple()
> > +
> > +  def __lt__(self, rhs):
> > +    if not isinstance(self, type(rhs)):
> > +      return str(type(self)) < str(type(rhs))
> > +
> > +    return self._tuple() < rhs._tuple()
> >
> >    def __hash__(self):
> >      return id(self)
> >
> >
> > -class Module(object):
> > +class Module:
> >    def __init__(self, path=None, mojom_namespace=None, attributes=None):
> >      self.path = path
> >      self.mojom_namespace = mojom_namespace
> > @@ -1380,11 +1558,11 @@ class Module(object):
> >      self.interfaces = []
> >      self.enums = []
> >      self.constants = []
> > -    self.kinds = {}
> > +    self.kinds = OrderedDict()
> >      self.attributes = attributes
> >      self.imports = []
> > -    self.imported_kinds = {}
> > -    self.metadata = {}
> > +    self.imported_kinds = OrderedDict()
> > +    self.metadata = OrderedDict()
> >
> >    def __repr__(self):
> >      # Gives us a decent __repr__ for modules.
> > @@ -1405,16 +1583,15 @@ class Module(object):
> >      if as_ref:
> >        return '<%s path=%r mojom_namespace=%r>' % (
> >            self.__class__.__name__, self.path, self.mojom_namespace)
> > -    else:
> > -      return GenericRepr(
> > -          self, {
> > -              'path': False,
> > -              'mojom_namespace': False,
> > -              'attributes': False,
> > -              'structs': False,
> > -              'interfaces': False,
> > -              'unions': False
> > -          })
> > +    return GenericRepr(
> > +        self, {
> > +            'path': False,
> > +            'mojom_namespace': False,
> > +            'attributes': False,
> > +            'structs': False,
> > +            'interfaces': False,
> > +            'unions': False
> > +        })
> >
> >    def GetNamespacePrefix(self):
> >      return '%s.' % self.mojom_namespace if self.mojom_namespace else ''
> > @@ -1451,7 +1628,7 @@ class Module(object):
> >        imported_module.Stylize(stylizer)
> >
> >    def Dump(self, f):
> > -    pickle.dump(self, f, 2)
> > +    pickle.dump(self, f)
> >
> >    @classmethod
> >    def Load(cls, f):
> > @@ -1461,15 +1638,15 @@ class Module(object):
> >
> >
> >  def IsBoolKind(kind):
> > -  return kind.spec == BOOL.spec
> > +  return kind.spec == BOOL.spec or kind.spec == NULLABLE_BOOL.spec
> >
> >
> >  def IsFloatKind(kind):
> > -  return kind.spec == FLOAT.spec
> > +  return kind.spec == FLOAT.spec or kind.spec == NULLABLE_FLOAT.spec
> >
> >
> >  def IsDoubleKind(kind):
> > -  return kind.spec == DOUBLE.spec
> > +  return kind.spec == DOUBLE.spec or kind.spec == NULLABLE_DOUBLE.spec
> >
> >
> >  def IsIntegralKind(kind):
> > @@ -1477,7 +1654,14 @@ def IsIntegralKind(kind):
> >            or kind.spec == INT16.spec or kind.spec == INT32.spec
> >            or kind.spec == INT64.spec or kind.spec == UINT8.spec
> >            or kind.spec == UINT16.spec or kind.spec == UINT32.spec
> > -          or kind.spec == UINT64.spec)
> > +          or kind.spec == UINT64.spec or kind.spec == NULLABLE_BOOL.spec
> > +          or kind.spec == NULLABLE_INT8.spec or kind.spec ==
> NULLABLE_INT16.spec
> > +          or kind.spec == NULLABLE_INT32.spec
> > +          or kind.spec == NULLABLE_INT64.spec
> > +          or kind.spec == NULLABLE_UINT8.spec
> > +          or kind.spec == NULLABLE_UINT16.spec
> > +          or kind.spec == NULLABLE_UINT32.spec
> > +          or kind.spec == NULLABLE_UINT64.spec)
> >
> >
> >  def IsStringKind(kind):
> > @@ -1563,7 +1747,7 @@ def IsReferenceKind(kind):
> >
> >
> >  def IsNullableKind(kind):
> > -  return IsReferenceKind(kind) and kind.is_nullable
> > +  return kind.is_nullable
> >
> >
> >  def IsMapKind(kind):
> > @@ -1664,11 +1848,8 @@ def MethodPassesInterfaces(method):
> >    return _AnyMethodParameterRecursive(method, IsInterfaceKind)
> >
> >
> > -def HasSyncMethods(interface):
> > -  for method in interface.methods:
> > -    if method.sync:
> > -      return True
> > -  return False
> > +def GetSyncMethodOrdinals(interface):
> > +  return [method.ordinal for method in interface.methods if method.sync]
> >
> >
> >  def HasUninterruptableMethods(interface):
> > @@ -1700,18 +1881,17 @@ def ContainsHandlesOrInterfaces(kind):
> >      checked.add(kind.spec)
> >      if IsStructKind(kind):
> >        return any(Check(field.kind) for field in kind.fields)
> > -    elif IsUnionKind(kind):
> > +    if IsUnionKind(kind):
> >        return any(Check(field.kind) for field in kind.fields)
> > -    elif IsAnyHandleKind(kind):
> > +    if IsAnyHandleKind(kind):
> >        return True
> > -    elif IsAnyInterfaceKind(kind):
> > +    if IsAnyInterfaceKind(kind):
> >        return True
> > -    elif IsArrayKind(kind):
> > +    if IsArrayKind(kind):
> >        return Check(kind.kind)
> > -    elif IsMapKind(kind):
> > +    if IsMapKind(kind):
> >        return Check(kind.key_kind) or Check(kind.value_kind)
> > -    else:
> > -      return False
> > +    return False
> >
> >    return Check(kind)
> >
> > @@ -1738,21 +1918,20 @@ def ContainsNativeTypes(kind):
> >      checked.add(kind.spec)
> >      if IsEnumKind(kind):
> >        return kind.native_only
> > -    elif IsStructKind(kind):
> > +    if IsStructKind(kind):
> >        if kind.native_only:
> >          return True
> >        if any(enum.native_only for enum in kind.enums):
> >          return True
> >        return any(Check(field.kind) for field in kind.fields)
> > -    elif IsUnionKind(kind):
> > +    if IsUnionKind(kind):
> >        return any(Check(field.kind) for field in kind.fields)
> > -    elif IsInterfaceKind(kind):
> > +    if IsInterfaceKind(kind):
> >        return any(enum.native_only for enum in kind.enums)
> > -    elif IsArrayKind(kind):
> > +    if IsArrayKind(kind):
> >        return Check(kind.kind)
> > -    elif IsMapKind(kind):
> > +    if IsMapKind(kind):
> >        return Check(kind.key_kind) or Check(kind.value_kind)
> > -    else:
> > -      return False
> > +    return False
> >
> >    return Check(kind)
> > 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
> > index e8fd4936..2a4e852c 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> > index 88b77c98..71011109 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
> > @@ -1,7 +1,8 @@
> > -# Copyright 2013 The Chromium Authors. All rights reserved.
> > +# Copyright 2013 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > +import copy
> >  from mojom.generate import module as mojom
> >
> >  # This module provides a mechanism for determining the packed order and
> offsets
> > @@ -15,7 +16,7 @@ from mojom.generate import module as mojom
> >  HEADER_SIZE = 8
> >
> >
> > -class PackedField(object):
> > +class PackedField:
> >    kind_to_size = {
> >        mojom.BOOL: 1,
> >        mojom.INT8: 1,
> > @@ -75,18 +76,55 @@ class PackedField(object):
> >        return 8
> >      return cls.GetSizeForKind(kind)
> >
> > -  def __init__(self, field, index, ordinal):
> > +  def __init__(self,
> > +               field,
> > +               index,
> > +               ordinal,
> > +               original_field=None,
> > +               sub_ordinal=None,
> > +               linked_value_packed_field=None):
> >      """
> >      Args:
> >        field: the original field.
> >        index: the position of the original field in the struct.
> >        ordinal: the ordinal of the field for serialization.
> > +      original_field: See below.
> > +      sub_ordinal: See below.
> > +      linked_value_packed_field: See below.
> > +
> > +    original_field, sub_ordinal, and linked_value_packed_field are used
> to
> > +    support nullable ValueKind fields. For legacy reasons, nullable
> ValueKind
> > +    fields actually generate two PackedFields. This allows:
> > +
> > +    - backwards compatibility prior to Mojo support for nullable
> ValueKinds.
> > +    - correct packing of fields for the aforementioned backwards
> compatibility.
> > +
> > +    When translating Fields to PackedFields, the original field is
> turned into
> > +    two PackedFields: the first PackedField always has type mojom.BOOL,
> while
> > +    the second PackedField has the non-nullable version of the field's
> kind.
> > +
> > +    When constructing these PackedFields, original_field references the
> field
> > +    as defined in the mojom; the name as defined in the mojom will be
> used for
> > +    all layers above the wire/data layer.
> > +
> > +    sub_ordinal is used to sort the two PackedFields correctly with
> respect to
> > +    each other: the first mojom.BOOL field always has sub_ordinal 0,
> while the
> > +    second field always has sub_ordinal 1.
> > +
> > +    Finally, linked_value_packed_field is used by the serialization and
> > +    deserialization helpers, which generally just iterate over a
> PackedStruct's
> > +    PackedField's in ordinal order. This allows the helpers to easily
> reference
> > +    any related PackedFields rather than having to lookup related
> PackedFields
> > +    by index while iterating.
> >      """
> >      self.field = field
> >      self.index = index
> >      self.ordinal = ordinal
> > -    self.size = self.GetSizeForKind(field.kind)
> > -    self.alignment = self.GetAlignmentForKind(field.kind)
> > +    self.original_field = original_field
> > +    self.sub_ordinal = sub_ordinal
> > +    self.linked_value_packed_field = linked_value_packed_field
> > +    self.size = self.GetSizeForKind(self.field.kind)
> > +    self.alignment = self.GetAlignmentForKind(self.field.kind)
> >      self.offset = None
> >      self.bit = None
> >      self.min_version = None
> > @@ -120,7 +158,33 @@ def GetPayloadSizeUpToField(field):
> >    return offset + pad
> >
> >
> > -class PackedStruct(object):
> > +def IsNullableValueKindPackedField(field):
> > +  """Returns true if `field` is derived from a nullable ValueKind field.
> > +
> > +  Nullable ValueKind fields often require special handling in the
> bindings due
> > +  to the way the implementation is constrained for wire compatibility.
> > +  """
> > +  assert isinstance(field, PackedField)
> > +  return field.sub_ordinal is not None
> > +
> > +
> > +def IsPrimaryNullableValueKindPackedField(field):
> > +  """Returns true if `field` is derived from a nullable ValueKind mojom
> field
> > +  and is the "primary" field.
> > +
> > +  The primary field is a bool PackedField that controls if the field
> should be
> > +  considered as present or not; it will have a reference to the
> PackedField that
> > +  holds the actual value representation if considered present.
> > +
> > +  Bindings code that translates between the wire protocol and the
> higher layers
> > +  can use this to simplify mapping multiple PackedFields to the single
> field
> > +  that is logically exposed to bindings consumers.
> > +  """
> > +  assert isinstance(field, PackedField)
> > +  return field.linked_value_packed_field is not None
> > +
> > +
> > +class PackedStruct:
> >    def __init__(self, struct):
> >      self.struct = struct
> >      # |packed_fields| contains all the fields, in increasing offset
> order.
> > @@ -139,9 +203,41 @@ class PackedStruct(object):
> >      for index, field in enumerate(struct.fields):
> >        if field.ordinal is not None:
> >          ordinal = field.ordinal
> > -      src_fields.append(PackedField(field, index, ordinal))
> > +      # Nullable value types are a bit weird: they generate two
> PackedFields
> > +      # despite being a single ValueKind. This is for wire
> compatibility to
> > +      # ease the transition from legacy mojom syntax where nullable
> value types
> > +      # were not supported.
> > +      if isinstance(field.kind, mojom.ValueKind) and
> field.kind.is_nullable:
> > +        # The suffixes intentionally use Unicode codepoints which are
> considered
> > +        # valid C++/Java/JavaScript identifiers, yet are unlikely to be
> used in
> > +        # actual user code.
> > +        has_value_field = copy.copy(field)
> > +        has_value_field.name = f'{field.mojom_name}_$flag'
> > +        has_value_field.kind = mojom.BOOL
> > +
> > +        value_field = copy.copy(field)
> > +        value_field.name = f'{field.mojom_name}_$value'
> > +        value_field.kind = field.kind.MakeUnnullableKind()
> > +
> > +        value_packed_field = PackedField(value_field,
> > +                                         index,
> > +                                         ordinal,
> > +                                         original_field=field,
> > +                                         sub_ordinal=1,
> > +                                         linked_value_packed_field=None)
> > +        has_value_packed_field = PackedField(
> > +            has_value_field,
> > +            index,
> > +            ordinal,
> > +            original_field=field,
> > +            sub_ordinal=0,
> > +            linked_value_packed_field=value_packed_field)
> > +        src_fields.append(has_value_packed_field)
> > +        src_fields.append(value_packed_field)
> > +      else:
> > +        src_fields.append(PackedField(field, index, ordinal))
> >        ordinal += 1
> > -    src_fields.sort(key=lambda field: field.ordinal)
> > +    src_fields.sort(key=lambda field: (field.ordinal,
> field.sub_ordinal))
> >
> >      # Set |min_version| for each field.
> >      next_min_version = 0
> > @@ -156,10 +252,11 @@ class PackedStruct(object):
> >        if (packed_field.min_version != 0
> >            and mojom.IsReferenceKind(packed_field.field.kind)
> >            and not packed_field.field.kind.is_nullable):
> > -        raise Exception("Non-nullable fields are only allowed in
> version 0 of "
> > -                        "a struct. %s.%s is defined with
> [MinVersion=%d]." %
> > -                        (self.struct.name, packed_field.field.name,
> > -                         packed_field.min_version))
> > +        raise Exception(
> > +            "Non-nullable reference fields are only allowed in version
> 0 of a "
> > +            "struct. %s.%s is defined with [MinVersion=%d]." %
> > +            (self.struct.name, packed_field.field.name,
> > +             packed_field.min_version))
> >
> >      src_field = src_fields[0]
> >      src_field.offset = 0
> > @@ -186,7 +283,7 @@ class PackedStruct(object):
> >          dst_fields.append(src_field)
> >
> >
> > -class ByteInfo(object):
> > +class ByteInfo:
> >    def __init__(self):
> >      self.is_padding = False
> >      self.packed_fields = []
> > @@ -214,7 +311,7 @@ def GetByteLayout(packed_struct):
> >    return byte_info
> >
> >
> > -class VersionInfo(object):
> > +class VersionInfo:
> >    def __init__(self, version, num_fields, num_bytes):
> >      self.version = version
> >      self.num_fields = num_fields
> > 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
> > index 98c705ad..5c6c36d5 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2013 The Chromium Authors. All rights reserved.
> > +# Copyright 2013 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > 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
> > index 0da90058..807e2a4f 100644
> > ---
> a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
> > +++
> b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2013 The Chromium Authors. All rights reserved.
> > +# Copyright 2013 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > diff --git
> a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> > index 7580b780..71ce3c03 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2013 The Chromium Authors. All rights reserved.
> > +# Copyright 2013 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  """Convert parse tree to AST.
> > @@ -12,17 +12,296 @@ already been parsed and converted to ASTs before.
> >  import itertools
> >  import os
> >  import re
> > -import sys
> >
> > +from collections import OrderedDict
> >  from mojom.generate import generator
> >  from mojom.generate import module as mojom
> >  from mojom.parse import ast
> >
> >
> > -def _IsStrOrUnicode(x):
> > -  if sys.version_info[0] < 3:
> > -    return isinstance(x, (unicode, str))
> > -  return isinstance(x, str)
> > +is_running_backwards_compatibility_check_hack = False
> > +
> > +### DO NOT ADD ENTRIES TO THIS LIST. ###
> > +_EXTENSIBLE_ENUMS_MISSING_DEFAULT = (
> > +    'x:arc.keymaster.mojom.Algorithm',
> > +    'x:arc.keymaster.mojom.Digest',
> > +    'x:arc.keymaster.mojom.SignatureResult',
> > +    'x:arc.mojom.AccessibilityActionType',
> > +    'x:arc.mojom.AccessibilityBooleanProperty',
> > +    'x:arc.mojom.AccessibilityEventIntListProperty',
> > +    'x:arc.mojom.AccessibilityEventIntProperty',
> > +    'x:arc.mojom.AccessibilityEventStringProperty',
> > +    'x:arc.mojom.AccessibilityEventType',
> > +    'x:arc.mojom.AccessibilityFilterType',
> > +    'x:arc.mojom.AccessibilityIntListProperty',
> > +    'x:arc.mojom.AccessibilityIntProperty',
> > +    'x:arc.mojom.AccessibilityLiveRegionType',
> > +    'x:arc.mojom.AccessibilityNotificationStateType',
> > +    'x:arc.mojom.AccessibilityRangeType',
> > +    'x:arc.mojom.AccessibilitySelectionMode',
> > +    'x:arc.mojom.AccessibilityStringListProperty',
> > +    'x:arc.mojom.AccessibilityStringProperty',
> > +    'x:arc.mojom.AccessibilityWindowBooleanProperty',
> > +    'x:arc.mojom.AccessibilityWindowIntListProperty',
> > +    'x:arc.mojom.AccessibilityWindowIntProperty',
> > +    'x:arc.mojom.AccessibilityWindowStringProperty',
> > +    'x:arc.mojom.AccessibilityWindowType',
> > +    'x:arc.mojom.AccountCheckStatus',
> > +    'x:arc.mojom.AccountUpdateType',
> > +    'x:arc.mojom.ActionType',
> > +    'x:arc.mojom.Algorithm',
> > +    'x:arc.mojom.AndroidIdSource',
> > +    'x:arc.mojom.AnrSource',
> > +    'x:arc.mojom.AnrType',
> > +    'x:arc.mojom.AppDiscoveryRequestState',
> > +    'x:arc.mojom.AppKillType',
> > +    'x:arc.mojom.AppPermission',
> > +    'x:arc.mojom.AppPermissionGroup',
> > +    'x:arc.mojom.AppReinstallState',
> > +    'x:arc.mojom.AppShortcutItemType',
> > +    'x:arc.mojom.ArcAuthCodeStatus',
> > +    'x:arc.mojom.ArcClipboardDragDropEvent',
> > +    'x:arc.mojom.ArcCorePriAbiMigEvent',
> > +    'x:arc.mojom.ArcDnsQuery',
> > +    'x:arc.mojom.ArcImageCopyPasteCompatAction',
> > +    'x:arc.mojom.ArcNetworkError',
> > +    'x:arc.mojom.ArcNetworkEvent',
> > +    'x:arc.mojom.ArcNotificationEvent',
> > +    'x:arc.mojom.ArcNotificationExpandState',
> > +    'x:arc.mojom.ArcNotificationPriority',
> > +    'x:arc.mojom.ArcNotificationRemoteInputState',
> > +    'x:arc.mojom.ArcNotificationShownContents',
> > +    'x:arc.mojom.ArcNotificationStyle',
> > +    'x:arc.mojom.ArcNotificationType',
> > +    'x:arc.mojom.ArcPipEvent',
> > +    'x:arc.mojom.ArcResizeLockState',
> > +    'x:arc.mojom.ArcSignInSuccess',
> > +    'x:arc.mojom.ArcTimerResult',
> > +    'x:arc.mojom.AudioSwitch',
> > +    'x:arc.mojom.BluetoothAclState',
> > +    'x:arc.mojom.BluetoothAdapterState',
> > +    'x:arc.mojom.BluetoothAdvertisingDataType',
> > +    'x:arc.mojom.BluetoothBondState',
> > +    'x:arc.mojom.BluetoothDeviceType',
> > +    'x:arc.mojom.BluetoothDiscoveryState',
> > +    'x:arc.mojom.BluetoothGattDBAttributeType',
> > +    'x:arc.mojom.BluetoothGattStatus',
> > +    'x:arc.mojom.BluetoothPropertyType',
> > +    'x:arc.mojom.BluetoothScanMode',
> > +    'x:arc.mojom.BluetoothSdpAttributeType',
> > +    'x:arc.mojom.BluetoothSocketType',
> > +    'x:arc.mojom.BluetoothStatus',
> > +    'x:arc.mojom.BootType',
> > +    'x:arc.mojom.CaptionTextShadowType',
> > +    'x:arc.mojom.ChangeType',
> > +    'x:arc.mojom.ChromeAccountType',
> > +    'x:arc.mojom.ChromeApp',
> > +    'x:arc.mojom.ChromePage',
> > +    'x:arc.mojom.ClockId',
> > +    'x:arc.mojom.CloudProvisionFlowError',
> > +    'x:arc.mojom.CommandResultType',
> > +    'x:arc.mojom.CompanionLibApiId',
> > +    'x:arc.mojom.ConnectionStateType',
> > +    'x:arc.mojom.ContentChangeType',
> > +    'x:arc.mojom.CpuRestrictionState',
> > +    'x:arc.mojom.CursorCoordinateSpace',
> > +    'x:arc.mojom.DataRestoreStatus',
> > +    'x:arc.mojom.DecoderStatus',
> > +    'x:arc.mojom.DeviceType',
> > +    'x:arc.mojom.Digest',
> > +    'x:arc.mojom.DisplayWakeLockType',
> > +    'x:arc.mojom.EapMethod',
> > +    'x:arc.mojom.EapPhase2Method',
> > +    'x:arc.mojom.FileSelectorEventType',
> > +    'x:arc.mojom.GMSCheckInError',
> > +    'x:arc.mojom.GMSSignInError',
> > +    'x:arc.mojom.GeneralSignInError',
> > +    'x:arc.mojom.GetNetworksRequestType',
> > +    'x:arc.mojom.HalPixelFormat',
> > +    'x:arc.mojom.IPAddressType',
> > +    'x:arc.mojom.InstallErrorReason',
> > +    'x:arc.mojom.KeyFormat',
> > +    'x:arc.mojom.KeyManagement',
> > +    'x:arc.mojom.KeyPurpose',
> > +    'x:arc.mojom.KeymasterError',
> > +    'x:arc.mojom.MainAccountHashMigrationStatus',
> > +    'x:arc.mojom.MainAccountResolutionStatus',
> > +    'x:arc.mojom.ManagementChangeStatus',
> > +    'x:arc.mojom.ManagementState',
> > +    'x:arc.mojom.MessageCenterVisibility',
> > +    'x:arc.mojom.MetricsType',
> > +    'x:arc.mojom.MountEvent',
> > +    'x:arc.mojom.NativeBridgeType',
> > +    'x:arc.mojom.NetworkResult',
> > +    'x:arc.mojom.NetworkType',
> > +    'x:arc.mojom.OemCryptoAlgorithm',
> > +    'x:arc.mojom.OemCryptoCipherMode',
> > +    'x:arc.mojom.OemCryptoHdcpCapability',
> > +    'x:arc.mojom.OemCryptoLicenseType',
> > +    'x:arc.mojom.OemCryptoPrivateKey',
> > +    'x:arc.mojom.OemCryptoProvisioningMethod',
> > +    'x:arc.mojom.OemCryptoResult',
> > +    'x:arc.mojom.OemCryptoRsaPaddingScheme',
> > +    'x:arc.mojom.OemCryptoUsageEntryStatus',
> > +    'x:arc.mojom.Padding',
> > +    'x:arc.mojom.PaiFlowState',
> > +    'x:arc.mojom.PatternType',
> > +    'x:arc.mojom.PressureLevel',
> > +    'x:arc.mojom.PrintColorMode',
> > +    'x:arc.mojom.PrintContentType',
> > +    'x:arc.mojom.PrintDuplexMode',
> > +    'x:arc.mojom.PrinterStatus',
> > +    'x:arc.mojom.ProcessState',
> > +    'x:arc.mojom.PurchaseState',
> > +    'x:arc.mojom.ReauthReason',
> > +    'x:arc.mojom.ScaleFactor',
> > +    'x:arc.mojom.SecurityType',
> > +    'x:arc.mojom.SegmentStyle',
> > +    'x:arc.mojom.SelectFilesActionType',
> > +    'x:arc.mojom.SetNativeChromeVoxResponse',
> > +    'x:arc.mojom.ShareFiles',
> > +    'x:arc.mojom.ShowPackageInfoPage',
> > +    'x:arc.mojom.SpanType',
> > +    'x:arc.mojom.SupportedLinkChangeSource',
> > +    'x:arc.mojom.TetheringClientState',
> > +    'x:arc.mojom.TextInputType',
> > +    'x:arc.mojom.TtsEventType',
> > +    'x:arc.mojom.VideoCodecProfile',
> > +    'x:arc.mojom.VideoDecodeAccelerator.Result',
> > +    'x:arc.mojom.VideoEncodeAccelerator.Error',
> > +    'x:arc.mojom.VideoFrameStorageType',
> > +    'x:arc.mojom.VideoPixelFormat',
> > +    'x:arc.mojom.WakefulnessMode',
> > +    'x:arc.mojom.WebApkInstallResult',
> > +    'x:ash.ime.mojom.InputFieldType',
> > +    'x:ash.ime.mojom.PersonalizationMode',
> > +    'x:ash.language.mojom.FeatureId',
> > +    'x:blink.mojom.ScrollRestorationType',
> > +    'x:chrome_cleaner.mojom.PromptAcceptance',
> > +    'x:chromeos.cdm.mojom.CdmKeyStatus',
> > +    'x:chromeos.cdm.mojom.CdmMessageType',
> > +    'x:chromeos.cdm.mojom.CdmSessionType',
> > +    'x:chromeos.cdm.mojom.DecryptStatus',
> > +    'x:chromeos.cdm.mojom.EmeInitDataType',
> > +    'x:chromeos.cdm.mojom.EncryptionScheme',
> > +    'x:chromeos.cdm.mojom.HdcpVersion',
> > +    'x:chromeos.cdm.mojom.OutputProtection.LinkType',
> > +    'x:chromeos.cdm.mojom.OutputProtection.ProtectionType',
> > +    'x:chromeos.cdm.mojom.PromiseException',
> > +    'x:chromeos.cfm.mojom.EnqueuePriority',
> > +    'x:chromeos.cfm.mojom.LoggerErrorCode',
> > +    'x:chromeos.cfm.mojom.LoggerState',
> > +    'x:chromeos.cros_healthd.mojom.CryptoAlgorithm',
> > +    'x:chromeos.cros_healthd.mojom.EncryptionState',
> > +    'x:chromeos.machine_learning.mojom.AnnotationUsecase',
> > +    'x:chromeos.machine_learning.mojom.BuiltinModelId',
> > +    'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult',
> > +    'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus',
> > +    'x:chromeos.machine_learning.mojom.EndpointReason',
> > +    'x:chromeos.machine_learning.mojom.EndpointerType',
> > +    'x:chromeos.machine_learning.mojom.ExecuteResult',
> > +    'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status',
> > +
> 'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status',
> > +    'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult',
> > +    'x:chromeos.machine_learning.mojom.LoadModelResult',
> > +    'x:chromeos.machine_learning.mojom.Rotation',
> > +    'x:chromeos.network_config.mojom.ConnectionStateType',
> > +    'x:chromeos.network_config.mojom.DeviceStateType',
> > +    'x:chromeos.network_config.mojom.IPConfigType',
> > +    'x:chromeos.network_config.mojom.NetworkType',
> > +    'x:chromeos.network_config.mojom.OncSource',
> > +    'x:chromeos.network_config.mojom.PolicySource',
> > +    'x:chromeos.network_config.mojom.PortalState',
> > +    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent',
> > +
> 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod',
> > +
> 'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus',
> > +    'x:cros.mojom.CameraClientType',
> > +    'x:cros.mojom.CameraMetadataSectionStart',
> > +    'x:cros.mojom.CameraMetadataTag',
> > +    'x:cros.mojom.HalPixelFormat',
> > +    'x:crosapi.mojom.AllowedPaths',
> > +    'x:crosapi.mojom.BrowserAppInstanceType',
> > +    'x:crosapi.mojom.CreationResult',
> > +    'x:crosapi.mojom.DeviceAccessResultCode',
> > +    'x:crosapi.mojom.DeviceMode',
> > +    'x:crosapi.mojom.DlpRestrictionLevel',
> > +    'x:crosapi.mojom.ExoImeSupport',
> > +    'x:crosapi.mojom.FullscreenVisibility',
> > +    'x:crosapi.mojom.GoogleServiceAuthError.State',
> > +    'x:crosapi.mojom.IsInstallableResult',
> > +    'x:crosapi.mojom.KeyTag',
> > +    'x:crosapi.mojom.KeystoreSigningAlgorithmName',
> > +    'x:crosapi.mojom.KeystoreType',
> > +    'x:crosapi.mojom.LacrosFeedbackSource',
> > +    'x:crosapi.mojom.MemoryPressureLevel',
> > +    'x:crosapi.mojom.MetricsReportingManaged',
> > +    'x:crosapi.mojom.NotificationType',
> > +    'x:crosapi.mojom.OndeviceHandwritingSupport',
> > +    'x:crosapi.mojom.OpenResult',
> > +    'x:crosapi.mojom.PolicyDomain',
> > +    'x:crosapi.mojom.RegistrationCodeType',
> > +    'x:crosapi.mojom.ScaleFactor',
> > +    'x:crosapi.mojom.SearchResult.OptionalBool',
> > +    'x:crosapi.mojom.SelectFileDialogType',
> > +    'x:crosapi.mojom.SelectFileResult',
> > +    'x:crosapi.mojom.SharesheetResult',
> > +    'x:crosapi.mojom.TouchEventType',
> > +    'x:crosapi.mojom.VideoRotation',
> > +    'x:crosapi.mojom.WallpaperLayout',
> > +    'x:crosapi.mojom.WebAppInstallResultCode',
> > +    'x:crosapi.mojom.WebAppUninstallResultCode',
> > +    'x:device.mojom.HidBusType',
> > +    'x:device.mojom.WakeLockReason',
> > +    'x:device.mojom.WakeLockType',
> > +    'x:drivefs.mojom.DialogReason.Type',
> > +    'x:drivefs.mojom.DriveError.Type',
> > +    'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus',
> > +    'x:drivefs.mojom.FileMetadata.CanPinStatus',
> > +    'x:drivefs.mojom.FileMetadata.Type',
> > +    'x:drivefs.mojom.ItemEventReason',
> > +    'x:drivefs.mojom.MirrorPathStatus',
> > +    'x:drivefs.mojom.MirrorSyncStatus',
> > +    'x:drivefs.mojom.QueryParameters.SortField',
> > +    'x:fuzz.mojom.FuzzEnum',
> > +    'x:media.mojom.FillLightMode',
> > +    'x:media.mojom.MeteringMode',
> > +    'x:media.mojom.PowerLineFrequency',
> > +    'x:media.mojom.RedEyeReduction',
> > +    'x:media.mojom.ResolutionChangePolicy',
> > +    'x:media.mojom.VideoCaptureApi',
> > +    'x:media.mojom.VideoCaptureBufferType',
> > +    'x:media.mojom.VideoCaptureError',
> > +    'x:media.mojom.VideoCaptureFrameDropReason',
> > +    'x:media.mojom.VideoCapturePixelFormat',
> > +    'x:media.mojom.VideoCaptureTransportType',
> > +    'x:media.mojom.VideoFacingMode',
> > +    'x:media_session.mojom.AudioFocusType',
> > +    'x:media_session.mojom.CameraState',
> > +    'x:media_session.mojom.EnforcementMode',
> > +    'x:media_session.mojom.MediaAudioVideoState',
> > +    'x:media_session.mojom.MediaImageBitmapColorType',
> > +    'x:media_session.mojom.MediaPictureInPictureState',
> > +    'x:media_session.mojom.MediaPlaybackState',
> > +    'x:media_session.mojom.MediaSession.SuspendType',
> > +    'x:media_session.mojom.MediaSessionAction',
> > +    'x:media_session.mojom.MediaSessionImageType',
> > +    'x:media_session.mojom.MediaSessionInfo.SessionState',
> > +    'x:media_session.mojom.MicrophoneState',
> > +    'x:ml.model_loader.mojom.ComputeResult',
> > +    'x:ml.model_loader.mojom.CreateModelLoaderResult',
> > +    'x:ml.model_loader.mojom.LoadModelResult',
> > +    'x:mojo.test.AnExtensibleEnum',
> > +    'x:mojo.test.EnumB',
> > +    'x:mojo.test.ExtensibleEmptyEnum',
> > +
> 'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault',
> > +    'x:network.mojom.WebSandboxFlags',
> > +    'x:payments.mojom.BillingResponseCode',
> > +    'x:payments.mojom.CreateDigitalGoodsResponseCode',
> > +    'x:payments.mojom.ItemType',
> > +    'x:printing.mojom.PrinterType',
> > +    'x:ui.mojom.KeyboardCode',
> > +)
> > +### DO NOT ADD ENTRIES TO THIS LIST. ###
> >
> >
> >  def _DuplicateName(values):
> > @@ -98,12 +377,6 @@ def _MapKind(kind):
> >    }
> >    if kind.endswith('?'):
> >      base_kind = _MapKind(kind[0:-1])
> > -    # NOTE: This doesn't rule out enum types. Those will be detected
> later, when
> > -    # cross-reference is established.
> > -    reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt',
> 'rcv',
> > -                       'rma', 'rca')
> > -    if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
> > -      raise Exception('A type (spec "%s") cannot be made nullable' %
> base_kind)
> >      return '?' + base_kind
> >    if kind.endswith('}'):
> >      lbracket = kind.rfind('{')
> > @@ -113,8 +386,6 @@ def _MapKind(kind):
> >      lbracket = kind.rfind('[')
> >      typename = kind[0:lbracket]
> >      return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)
> > -  if kind.endswith('&'):
> > -    return 'r:' + _MapKind(kind[0:-1])
> >    if kind.startswith('asso<'):
> >      assert kind.endswith('>')
> >      return 'asso:' + _MapKind(kind[5:-1])
> > @@ -135,13 +406,35 @@ def _MapKind(kind):
> >    return 'x:' + kind
> >
> >
> > -def _AttributeListToDict(attribute_list):
> > +def _MapValueToEnum(module, value):
> > +  # True/False/None
> > +  if value is None:
> > +    return value
> > +  if not isinstance(value, str):
> > +    return value
> > +  # Otherwise try to find it.
> > +  try:
> > +    trial = _LookupValue(module, None, None, ('IDENTIFIER', value))
> > +    if isinstance(trial, mojom.ConstantValue):
> > +      return trial.constant
> > +    if isinstance(trial, mojom.EnumValue):
> > +      return trial
> > +  except ValueError:
> > +    pass
> > +  # Return the string if it did not resolve to a constant or enum.
> > +  return value
> > +
> > +
> > +def _AttributeListToDict(module, attribute_list):
> >    if attribute_list is None:
> >      return None
> >    assert isinstance(attribute_list, ast.AttributeList)
> > -  # TODO(vtl): Check for duplicate keys here.
> > -  return dict(
> > -      [(attribute.key, attribute.value) for attribute in
> attribute_list])
> > +  attributes = dict()
> > +  for attribute in attribute_list:
> > +    if attribute.key in attributes:
> > +      raise Exception("Duplicate key (%s) in attribute list" %
> attribute.key)
> > +    attributes[attribute.key] = _MapValueToEnum(module, attribute.value)
> > +  return attributes
> >
> >
> >  builtin_values = frozenset([
> > @@ -257,7 +550,8 @@ def _Kind(kinds, spec, scope):
> >      return kind
> >
> >    if spec.startswith('?'):
> > -    kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
> > +    kind = _Kind(kinds, spec[1:], scope)
> > +    kind = kind.MakeNullableKind()
> >    elif spec.startswith('a:'):
> >      kind = mojom.Array(_Kind(kinds, spec[2:], scope))
> >    elif spec.startswith('asso:'):
> > @@ -345,7 +639,7 @@ def _Struct(module, parsed_struct):
> >              struct.fields_data.append,
> >          })
> >
> > -  struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
> > +  struct.attributes = _AttributeListToDict(module,
> parsed_struct.attribute_list)
> >
> >    # Enforce that a [Native] attribute is set to make native-only struct
> >    # declarations more explicit.
> > @@ -377,7 +671,7 @@ def _Union(module, parsed_union):
> >    union.fields_data = []
> >    _ProcessElements(parsed_union.mojom_name, parsed_union.body,
> >                     {ast.UnionField: union.fields_data.append})
> > -  union.attributes = _AttributeListToDict(parsed_union.attribute_list)
> > +  union.attributes = _AttributeListToDict(module,
> parsed_union.attribute_list)
> >    return union
> >
> >
> > @@ -398,7 +692,7 @@ def _StructField(module, parsed_field, struct):
> >    field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal
> else None
> >    field.default = _LookupValue(module, struct, field.kind,
> >                                 parsed_field.default_value)
> > -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
> > +  field.attributes = _AttributeListToDict(module,
> parsed_field.attribute_list)
> >    return field
> >
> >
> > @@ -414,11 +708,21 @@ def _UnionField(module, parsed_field, union):
> >    """
> >    field = mojom.UnionField()
> >    field.mojom_name = parsed_field.mojom_name
> > +  # Disallow unions from being self-recursive.
> > +  parsed_typename = parsed_field.typename
> > +  if parsed_typename.endswith('?'):
> > +    parsed_typename = parsed_typename[:-1]
> > +  assert parsed_typename != union.mojom_name
> >    field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),
> >                       (module.mojom_namespace, union.mojom_name))
> >    field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal
> else None
> >    field.default = None
> > -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
> > +  field.attributes = _AttributeListToDict(module,
> parsed_field.attribute_list)
> > +  if field.is_default and not mojom.IsNullableKind(field.kind) and \
> > +     not mojom.IsIntegralKind(field.kind):
> > +    raise Exception(
> > +        '[Default] field for union %s must be nullable or integral
> type.' %
> > +        union.mojom_name)
> >    return field
> >
> >
> > @@ -439,7 +743,8 @@ def _Parameter(module, parsed_param, interface):
> >    parameter.ordinal = (parsed_param.ordinal.value
> >                         if parsed_param.ordinal else None)
> >    parameter.default = None  # TODO(tibell): We never have these. Remove
> field?
> > -  parameter.attributes =
> _AttributeListToDict(parsed_param.attribute_list)
> > +  parameter.attributes = _AttributeListToDict(module,
> > +
> parsed_param.attribute_list)
> >    return parameter
> >
> >
> > @@ -464,7 +769,7 @@ def _Method(module, parsed_method, interface):
> >      method.response_parameters = list(
> >          map(lambda parameter: _Parameter(module, parameter, interface),
> >              parsed_method.response_parameter_list))
> > -  method.attributes = _AttributeListToDict(parsed_method.attribute_list)
> > +  method.attributes = _AttributeListToDict(module,
> parsed_method.attribute_list)
> >
> >    # Enforce that only methods with response can have a [Sync] attribute.
> >    if method.sync and method.response_parameters is None:
> > @@ -492,7 +797,8 @@ def _Interface(module, parsed_iface):
> >    interface.mojom_name = parsed_iface.mojom_name
> >    interface.spec = 'x:' + module.GetNamespacePrefix() +
> interface.mojom_name
> >    module.kinds[interface.spec] = interface
> > -  interface.attributes =
> _AttributeListToDict(parsed_iface.attribute_list)
> > +  interface.attributes = _AttributeListToDict(module,
> > +
> parsed_iface.attribute_list)
> >    interface.enums = []
> >    interface.constants = []
> >    interface.methods_data = []
> > @@ -522,7 +828,7 @@ def _EnumField(module, enum, parsed_field):
> >    field = mojom.EnumField()
> >    field.mojom_name = parsed_field.mojom_name
> >    field.value = _LookupValue(module, enum, None, parsed_field.value)
> > -  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
> > +  field.attributes = _AttributeListToDict(module,
> parsed_field.attribute_list)
> >    value = mojom.EnumValue(module, enum, field)
> >    module.values[value.GetSpec()] = value
> >    return field
> > @@ -544,7 +850,7 @@ def _ResolveNumericEnumValues(enum):
> >        prev_value += 1
> >
> >      # Integral value (e.g: BEGIN = -0x1).
> > -    elif _IsStrOrUnicode(field.value):
> > +    elif isinstance(field.value, str):
> >        prev_value = int(field.value, 0)
> >
> >      # Reference to a previous enum value (e.g: INIT = BEGIN).
> > @@ -588,7 +894,7 @@ def _Enum(module, parsed_enum, parent_kind):
> >      mojom_name = parent_kind.mojom_name + '.' + mojom_name
> >    enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
> >    enum.parent_kind = parent_kind
> > -  enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
> > +  enum.attributes = _AttributeListToDict(module,
> parsed_enum.attribute_list)
> >
> >    if not enum.native_only:
> >      enum.fields = list(
> > @@ -600,11 +906,18 @@ def _Enum(module, parsed_enum, parent_kind):
> >      for field in enum.fields:
> >        if field.default:
> >          if not enum.extensible:
> > -          raise Exception('Non-extensible enums may not specify a
> default')
> > -        if enum.default_field is not None:
> >            raise Exception(
> > -              'Only one enumerator value may be specified as the
> default')
> > +              f'Non-extensible enum {enum.spec} may not specify a
> default')
> > +        if enum.default_field is not None:
> > +          raise Exception(f'Multiple [Default] enumerators in enum
> {enum.spec}')
> >          enum.default_field = field
> > +    # While running the backwards compatibility check, ignore errors
> because the
> > +    # old version of the enum might not specify [Default].
> > +    if (enum.extensible and enum.default_field is None
> > +        and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT
> > +        and not is_running_backwards_compatibility_check_hack):
> > +      raise Exception(
> > +          f'Extensible enum {enum.spec} must specify a [Default]
> enumerator')
> >
> >    module.kinds[enum.spec] = enum
> >
> > @@ -696,6 +1009,11 @@ def _CollectReferencedKinds(module,
> all_defined_kinds):
> >          for referenced_kind in
> extract_referenced_user_kinds(param.kind):
> >            sanitized_kind = sanitize_kind(referenced_kind)
> >            referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
> > +  # Consts can reference imported enums.
> > +  for const in module.constants:
> > +    if not const.kind in mojom.PRIMITIVES:
> > +      sanitized_kind = sanitize_kind(const.kind)
> > +      referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
> >
> >    return referenced_user_kinds
> >
> > @@ -741,6 +1059,16 @@ def _AssertTypeIsStable(kind):
> >            assertDependencyIsStable(response_param.kind)
> >
> >
> > +def _AssertStructIsValid(kind):
> > +  expected_ordinals = set(range(0, len(kind.fields)))
> > +  ordinals = set(map(lambda field: field.ordinal, kind.fields))
> > +  if ordinals != expected_ordinals:
> > +    raise Exception(
> > +        'Structs must use contiguous ordinals starting from 0. ' +
> > +        '{} is missing the following ordinals: {}.'.format(
> > +            kind.mojom_name, ', '.join(map(str, expected_ordinals -
> ordinals))))
> > +
> > +
> >  def _Module(tree, path, imports):
> >    """
> >    Args:
> > @@ -810,8 +1138,17 @@ def _Module(tree, path, imports):
> >      union.fields = list(
> >          map(lambda field: _UnionField(module, field, union),
> union.fields_data))
> >      _AssignDefaultOrdinals(union.fields)
> > +    for field in union.fields:
> > +      if field.is_default:
> > +        if union.default_field is not None:
> > +          raise Exception('Multiple [Default] fields in union %s.' %
> > +                          union.mojom_name)
> > +        union.default_field = field
> >      del union.fields_data
> >      all_defined_kinds[union.spec] = union
> > +    if union.extensible and union.default_field is None:
> > +      raise Exception('Extensible union %s must specify a [Default]
> field' %
> > +                      union.mojom_name)
> >
> >    for interface in module.interfaces:
> >      interface.methods = list(
> > @@ -829,8 +1166,8 @@ def _Module(tree, path, imports):
> >
>  all_defined_kinds.values())
> >    imported_kind_specs = set(all_referenced_kinds.keys()).difference(
> >        set(all_defined_kinds.keys()))
> > -  module.imported_kinds = dict(
> > -      (spec, all_referenced_kinds[spec]) for spec in
> imported_kind_specs)
> > +  module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])
> > +                                      for spec in
> sorted(imported_kind_specs))
> >
> >    generator.AddComputedData(module)
> >    for iface in module.interfaces:
> > @@ -847,6 +1184,9 @@ def _Module(tree, path, imports):
> >        if kind.stable:
> >          _AssertTypeIsStable(kind)
> >
> > +  for kind in module.structs:
> > +    _AssertStructIsValid(kind)
> > +
> >    return module
> >
> >
> > 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
> > index 19905c8a..42593745 100644
> > ---
> a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
> > +++
> b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -69,5 +69,38 @@ class TranslateTest(unittest.TestCase):
> >      # pylint: disable=W0212
> >      self.assertEquals(
> >          translate._MapKind("asso<SomeInterface>?"),
> "?asso:x:SomeInterface")
> > -    self.assertEquals(
> > -        translate._MapKind("asso<SomeInterface&>?"),
> "?asso:r:x:SomeInterface")
> > +    self.assertEquals(translate._MapKind("rca<SomeInterface>?"),
> > +                      "?rca:x:SomeInterface")
> > +
> > +  def testSelfRecursiveUnions(self):
> > +    """Verifies _UnionField() raises when a union is self-recursive."""
> > +    tree = ast.Mojom(None, ast.ImportList(), [
> > +        ast.Union("SomeUnion", None,
> > +                  ast.UnionBody([ast.UnionField("a", None, None,
> "SomeUnion")]))
> > +    ])
> > +    with self.assertRaises(Exception):
> > +      translate.OrderedModule(tree, "mojom_tree", [])
> > +
> > +    tree = ast.Mojom(None, ast.ImportList(), [
> > +        ast.Union(
> > +            "SomeUnion", None,
> > +            ast.UnionBody([ast.UnionField("a", None, None,
> "SomeUnion?")]))
> > +    ])
> > +    with self.assertRaises(Exception):
> > +      translate.OrderedModule(tree, "mojom_tree", [])
> > +
> > +  def testDuplicateAttributesException(self):
> > +    tree = ast.Mojom(None, ast.ImportList(), [
> > +        ast.Union(
> > +            "FakeUnion",
> > +            ast.AttributeList([
> > +                ast.Attribute("key1", "value"),
> > +                ast.Attribute("key1", "value")
> > +            ]),
> > +            ast.UnionBody([
> > +                ast.UnionField("a", None, None, "int32"),
> > +                ast.UnionField("b", None, None, "string")
> > +            ]))
> > +    ])
> > +    with self.assertRaises(Exception):
> > +      translate.OrderedModule(tree, "mojom_tree", [])
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> > index 1f0db200..80e8c657 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  """Node classes for the AST for a Mojo IDL file."""
> > @@ -8,17 +8,14 @@
> >  # and lineno). You may also define __repr__() to help with analyzing
> test
> >  # failures, especially for more complex types.
> >
> > +import os.path
> >
> > -import sys
> >
> > +# Instance of 'NodeListBase' has no '_list_item_type' member (no-member)
> > +# pylint: disable=no-member
> >
> > -def _IsStrOrUnicode(x):
> > -  if sys.version_info[0] < 3:
> > -    return isinstance(x, (unicode, str))
> > -  return isinstance(x, str)
> >
> > -
> > -class NodeBase(object):
> > +class NodeBase:
> >    """Base class for nodes in the AST."""
> >
> >    def __init__(self, filename=None, lineno=None):
> > @@ -43,7 +40,7 @@ class NodeListBase(NodeBase):
> >    classes, in a tuple) of the members of the list.)"""
> >
> >    def __init__(self, item_or_items=None, **kwargs):
> > -    super(NodeListBase, self).__init__(**kwargs)
> > +    super().__init__(**kwargs)
> >      self.items = []
> >      if item_or_items is None:
> >        pass
> > @@ -62,7 +59,7 @@ class NodeListBase(NodeBase):
> >      return self.items.__iter__()
> >
> >    def __eq__(self, other):
> > -    return super(NodeListBase, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.items == other.items
> >
> >    # Implement this so that on failure, we get slightly more sensible
> output.
> > @@ -96,7 +93,7 @@ class Definition(NodeBase):
> >    include parameter definitions.) This class is meant to be
> subclassed."""
> >
> >    def __init__(self, mojom_name, **kwargs):
> > -    assert _IsStrOrUnicode(mojom_name)
> > +    assert isinstance(mojom_name, str)
> >      NodeBase.__init__(self, **kwargs)
> >      self.mojom_name = mojom_name
> >
> > @@ -108,13 +105,13 @@ class Attribute(NodeBase):
> >    """Represents an attribute."""
> >
> >    def __init__(self, key, value, **kwargs):
> > -    assert _IsStrOrUnicode(key)
> > -    super(Attribute, self).__init__(**kwargs)
> > +    assert isinstance(key, str)
> > +    super().__init__(**kwargs)
> >      self.key = key
> >      self.value = value
> >
> >    def __eq__(self, other):
> > -    return super(Attribute, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.key == other.key and \
> >             self.value == other.value
> >
> > @@ -131,17 +128,17 @@ class Const(Definition):
> >    def __init__(self, mojom_name, attribute_list, typename, value,
> **kwargs):
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> >      # The typename is currently passed through as a string.
> > -    assert _IsStrOrUnicode(typename)
> > +    assert isinstance(typename, str)
> >      # The value is either a literal (currently passed through as a
> string) or a
> >      # "wrapped identifier".
> > -    assert _IsStrOrUnicode or isinstance(value, tuple)
> > -    super(Const, self).__init__(mojom_name, **kwargs)
> > +    assert isinstance(value, (tuple, str))
> > +    super().__init__(mojom_name, **kwargs)
> >      self.attribute_list = attribute_list
> >      self.typename = typename
> >      self.value = value
> >
> >    def __eq__(self, other):
> > -    return super(Const, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.typename == other.typename and \
> >             self.value == other.value
> > @@ -153,12 +150,12 @@ class Enum(Definition):
> >    def __init__(self, mojom_name, attribute_list, enum_value_list,
> **kwargs):
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> >      assert enum_value_list is None or isinstance(enum_value_list,
> EnumValueList)
> > -    super(Enum, self).__init__(mojom_name, **kwargs)
> > +    super().__init__(mojom_name, **kwargs)
> >      self.attribute_list = attribute_list
> >      self.enum_value_list = enum_value_list
> >
> >    def __eq__(self, other):
> > -    return super(Enum, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.enum_value_list == other.enum_value_list
> >
> > @@ -170,13 +167,13 @@ class EnumValue(Definition):
> >      # The optional value is either an int (which is current a string)
> or a
> >      # "wrapped identifier".
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> > -    assert value is None or _IsStrOrUnicode(value) or isinstance(value,
> tuple)
> > -    super(EnumValue, self).__init__(mojom_name, **kwargs)
> > +    assert value is None or isinstance(value, (tuple, str))
> > +    super().__init__(mojom_name, **kwargs)
> >      self.attribute_list = attribute_list
> >      self.value = value
> >
> >    def __eq__(self, other):
> > -    return super(EnumValue, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.value == other.value
> >
> > @@ -193,13 +190,14 @@ class Import(NodeBase):
> >
> >    def __init__(self, attribute_list, import_filename, **kwargs):
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> > -    assert _IsStrOrUnicode(import_filename)
> > -    super(Import, self).__init__(**kwargs)
> > +    assert isinstance(import_filename, str)
> > +    super().__init__(**kwargs)
> >      self.attribute_list = attribute_list
> > -    self.import_filename = import_filename
> > +    # TODO(crbug.com/953884): Use pathlib once we're migrated fully to
> Python 3.
> > +    self.import_filename =
> os.path.normpath(import_filename).replace('\\', '/')
> >
> >    def __eq__(self, other):
> > -    return super(Import, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.import_filename == other.import_filename
> >
> > @@ -216,12 +214,12 @@ class Interface(Definition):
> >    def __init__(self, mojom_name, attribute_list, body, **kwargs):
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> >      assert isinstance(body, InterfaceBody)
> > -    super(Interface, self).__init__(mojom_name, **kwargs)
> > +    super().__init__(mojom_name, **kwargs)
> >      self.attribute_list = attribute_list
> >      self.body = body
> >
> >    def __eq__(self, other):
> > -    return super(Interface, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.body == other.body
> >
> > @@ -236,14 +234,14 @@ class Method(Definition):
> >      assert isinstance(parameter_list, ParameterList)
> >      assert response_parameter_list is None or \
> >             isinstance(response_parameter_list, ParameterList)
> > -    super(Method, self).__init__(mojom_name, **kwargs)
> > +    super().__init__(mojom_name, **kwargs)
> >      self.attribute_list = attribute_list
> >      self.ordinal = ordinal
> >      self.parameter_list = parameter_list
> >      self.response_parameter_list = response_parameter_list
> >
> >    def __eq__(self, other):
> > -    return super(Method, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.ordinal == other.ordinal and \
> >             self.parameter_list == other.parameter_list and \
> > @@ -264,12 +262,12 @@ class Module(NodeBase):
> >      # |mojom_namespace| is either none or a "wrapped identifier".
> >      assert mojom_namespace is None or isinstance(mojom_namespace, tuple)
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> > -    super(Module, self).__init__(**kwargs)
> > +    super().__init__(**kwargs)
> >      self.mojom_namespace = mojom_namespace
> >      self.attribute_list = attribute_list
> >
> >    def __eq__(self, other):
> > -    return super(Module, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.mojom_namespace == other.mojom_namespace and \
> >             self.attribute_list == other.attribute_list
> >
> > @@ -281,13 +279,13 @@ class Mojom(NodeBase):
> >      assert module is None or isinstance(module, Module)
> >      assert isinstance(import_list, ImportList)
> >      assert isinstance(definition_list, list)
> > -    super(Mojom, self).__init__(**kwargs)
> > +    super().__init__(**kwargs)
> >      self.module = module
> >      self.import_list = import_list
> >      self.definition_list = definition_list
> >
> >    def __eq__(self, other):
> > -    return super(Mojom, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.module == other.module and \
> >             self.import_list == other.import_list and \
> >             self.definition_list == other.definition_list
> > @@ -302,11 +300,11 @@ class Ordinal(NodeBase):
> >
> >    def __init__(self, value, **kwargs):
> >      assert isinstance(value, int)
> > -    super(Ordinal, self).__init__(**kwargs)
> > +    super().__init__(**kwargs)
> >      self.value = value
> >
> >    def __eq__(self, other):
> > -    return super(Ordinal, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.value == other.value
> >
> >
> > @@ -314,18 +312,18 @@ class Parameter(NodeBase):
> >    """Represents a method request or response parameter."""
> >
> >    def __init__(self, mojom_name, attribute_list, ordinal, typename,
> **kwargs):
> > -    assert _IsStrOrUnicode(mojom_name)
> > +    assert isinstance(mojom_name, str)
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> >      assert ordinal is None or isinstance(ordinal, Ordinal)
> > -    assert _IsStrOrUnicode(typename)
> > -    super(Parameter, self).__init__(**kwargs)
> > +    assert isinstance(typename, str)
> > +    super().__init__(**kwargs)
> >      self.mojom_name = mojom_name
> >      self.attribute_list = attribute_list
> >      self.ordinal = ordinal
> >      self.typename = typename
> >
> >    def __eq__(self, other):
> > -    return super(Parameter, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.mojom_name == other.mojom_name and \
> >             self.attribute_list == other.attribute_list and \
> >             self.ordinal == other.ordinal and \
> > @@ -344,42 +342,51 @@ class Struct(Definition):
> >    def __init__(self, mojom_name, attribute_list, body, **kwargs):
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> >      assert isinstance(body, StructBody) or body is None
> > -    super(Struct, self).__init__(mojom_name, **kwargs)
> > +    super().__init__(mojom_name, **kwargs)
> >      self.attribute_list = attribute_list
> >      self.body = body
> >
> >    def __eq__(self, other):
> > -    return super(Struct, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.body == other.body
> >
> > +  def __repr__(self):
> > +    return "Struct(mojom_name = %s, attribute_list = %s, body = %s)" % (
> > +        self.mojom_name, self.attribute_list, self.body)
> > +
> >
> >  class StructField(Definition):
> >    """Represents a struct field definition."""
> >
> >    def __init__(self, mojom_name, attribute_list, ordinal, typename,
> >                 default_value, **kwargs):
> > -    assert _IsStrOrUnicode(mojom_name)
> > +    assert isinstance(mojom_name, str)
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> >      assert ordinal is None or isinstance(ordinal, Ordinal)
> > -    assert _IsStrOrUnicode(typename)
> > +    assert isinstance(typename, str)
> >      # The optional default value is currently either a value as a
> string or a
> >      # "wrapped identifier".
> > -    assert default_value is None or _IsStrOrUnicode(default_value) or \
> > -        isinstance(default_value, tuple)
> > -    super(StructField, self).__init__(mojom_name, **kwargs)
> > +    assert default_value is None or isinstance(default_value, (str,
> tuple))
> > +    super().__init__(mojom_name, **kwargs)
> >      self.attribute_list = attribute_list
> >      self.ordinal = ordinal
> >      self.typename = typename
> >      self.default_value = default_value
> >
> >    def __eq__(self, other):
> > -    return super(StructField, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.ordinal == other.ordinal and \
> >             self.typename == other.typename and \
> >             self.default_value == other.default_value
> >
> > +  def __repr__(self):
> > +    return ("StructField(mojom_name = %s, attribute_list = %s, ordinal
> = %s, "
> > +            "typename = %s, default_value = %s") % (
> > +                self.mojom_name, self.attribute_list, self.ordinal,
> > +                self.typename, self.default_value)
> > +
> >
> >  # This needs to be declared after |StructField|.
> >  class StructBody(NodeListBase):
> > @@ -394,29 +401,29 @@ class Union(Definition):
> >    def __init__(self, mojom_name, attribute_list, body, **kwargs):
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> >      assert isinstance(body, UnionBody)
> > -    super(Union, self).__init__(mojom_name, **kwargs)
> > +    super().__init__(mojom_name, **kwargs)
> >      self.attribute_list = attribute_list
> >      self.body = body
> >
> >    def __eq__(self, other):
> > -    return super(Union, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.body == other.body
> >
> >
> >  class UnionField(Definition):
> >    def __init__(self, mojom_name, attribute_list, ordinal, typename,
> **kwargs):
> > -    assert _IsStrOrUnicode(mojom_name)
> > +    assert isinstance(mojom_name, str)
> >      assert attribute_list is None or isinstance(attribute_list,
> AttributeList)
> >      assert ordinal is None or isinstance(ordinal, Ordinal)
> > -    assert _IsStrOrUnicode(typename)
> > -    super(UnionField, self).__init__(mojom_name, **kwargs)
> > +    assert isinstance(typename, str)
> > +    super().__init__(mojom_name, **kwargs)
> >      self.attribute_list = attribute_list
> >      self.ordinal = ordinal
> >      self.typename = typename
> >
> >    def __eq__(self, other):
> > -    return super(UnionField, self).__eq__(other) and \
> > +    return super().__eq__(other) and \
> >             self.attribute_list == other.attribute_list and \
> >             self.ordinal == other.ordinal and \
> >             self.typename == other.typename
> > 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
> > index 62798631..c3637671 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -14,11 +14,11 @@ class _TestNode(ast.NodeBase):
> >    """Node type for tests."""
> >
> >    def __init__(self, value, **kwargs):
> > -    super(_TestNode, self).__init__(**kwargs)
> > +    super().__init__(**kwargs)
> >      self.value = value
> >
> >    def __eq__(self, other):
> > -    return super(_TestNode, self).__eq__(other) and self.value ==
> other.value
> > +    return super().__eq__(other) and self.value == other.value
> >
> >
> >  class _TestNodeList(ast.NodeListBase):
> > 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
> > index 3cb73c5d..b7b06bfb 100644
> > ---
> a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
> > +++
> b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2018 The Chromium Authors. All rights reserved.
> > +# Copyright 2018 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  """Helpers for processing conditionally enabled features in a mojom."""
> > @@ -17,8 +17,10 @@ class EnableIfError(Error):
> >  def _IsEnabled(definition, enabled_features):
> >    """Returns true if a definition is enabled.
> >
> > -  A definition is enabled if it has no EnableIf attribute, or if the
> value of
> > -  the EnableIf attribute is in enabled_features.
> > +  A definition is enabled if it has no EnableIf/EnableIfNot attribute.
> > +  It is retained if it has an EnableIf attribute and the attribute is in
> > +  enabled_features. It is retained if it has an EnableIfNot attribute
> and the
> > +  attribute is not in enabled features.
> >    """
> >    if not hasattr(definition, "attribute_list"):
> >      return True
> > @@ -27,17 +29,19 @@ def _IsEnabled(definition, enabled_features):
> >
> >    already_defined = False
> >    for a in definition.attribute_list:
> > -    if a.key == 'EnableIf':
> > +    if a.key == 'EnableIf' or a.key == 'EnableIfNot':
> >        if already_defined:
> >          raise EnableIfError(
> >              definition.filename,
> > -            "EnableIf attribute may only be defined once per field.",
> > +            "EnableIf/EnableIfNot attribute may only be set once per
> field.",
> >              definition.lineno)
> >        already_defined = True
> >
> >    for attribute in definition.attribute_list:
> >      if attribute.key == 'EnableIf' and attribute.value not in
> enabled_features:
> >        return False
> > +    if attribute.key == 'EnableIfNot' and attribute.value in
> enabled_features:
> > +      return False
> >    return True
> >
> >
> > 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
> > index aa609be7..5fc58202 100644
> > ---
> 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
> > @@ -1,4 +1,4 @@
> > -# Copyright 2018 The Chromium Authors. All rights reserved.
> > +# Copyright 2018 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -55,6 +55,48 @@ class ConditionalFeaturesTest(unittest.TestCase):
> >      """
> >      self.parseAndAssertEqual(const_source, expected_source)
> >
> > +  def testFilterIfNotConst(self):
> > +    """Test that Consts are correctly filtered."""
> > +    const_source = """
> > +      [EnableIfNot=blue]
> > +      const int kMyConst1 = 1;
> > +      [EnableIfNot=orange]
> > +      const double kMyConst2 = 2;
> > +      [EnableIf=blue]
> > +      const int kMyConst3 = 3;
> > +      [EnableIfNot=blue]
> > +      const int kMyConst4 = 4;
> > +      [EnableIfNot=purple]
> > +      const int kMyConst5 = 5;
> > +    """
> > +    expected_source = """
> > +      [EnableIfNot=orange]
> > +      const double kMyConst2 = 2;
> > +      [EnableIf=blue]
> > +      const int kMyConst3 = 3;
> > +      [EnableIfNot=purple]
> > +      const int kMyConst5 = 5;
> > +    """
> > +    self.parseAndAssertEqual(const_source, expected_source)
> > +
> > +  def testFilterIfNotMultipleConst(self):
> > +    """Test that Consts are correctly filtered."""
> > +    const_source = """
> > +      [EnableIfNot=blue]
> > +      const int kMyConst1 = 1;
> > +      [EnableIfNot=orange]
> > +      const double kMyConst2 = 2;
> > +      [EnableIfNot=orange]
> > +      const int kMyConst3 = 3;
> > +    """
> > +    expected_source = """
> > +      [EnableIfNot=orange]
> > +      const double kMyConst2 = 2;
> > +      [EnableIfNot=orange]
> > +      const int kMyConst3 = 3;
> > +    """
> > +    self.parseAndAssertEqual(const_source, expected_source)
> > +
> >    def testFilterEnum(self):
> >      """Test that EnumValues are correctly filtered from an Enum."""
> >      enum_source = """
> > @@ -91,6 +133,24 @@ class ConditionalFeaturesTest(unittest.TestCase):
> >      """
> >      self.parseAndAssertEqual(import_source, expected_source)
> >
> > +  def testFilterIfNotImport(self):
> > +    """Test that imports are correctly filtered from a Mojom."""
> > +    import_source = """
> > +      [EnableIf=blue]
> > +      import "foo.mojom";
> > +      [EnableIfNot=purple]
> > +      import "bar.mojom";
> > +      [EnableIfNot=green]
> > +      import "baz.mojom";
> > +    """
> > +    expected_source = """
> > +      [EnableIf=blue]
> > +      import "foo.mojom";
> > +      [EnableIfNot=purple]
> > +      import "bar.mojom";
> > +    """
> > +    self.parseAndAssertEqual(import_source, expected_source)
> > +
> >    def testFilterInterface(self):
> >      """Test that definitions are correctly filtered from an
> Interface."""
> >      interface_source = """
> > @@ -175,6 +235,50 @@ class ConditionalFeaturesTest(unittest.TestCase):
> >      """
> >      self.parseAndAssertEqual(struct_source, expected_source)
> >
> > +  def testFilterIfNotStruct(self):
> > +    """Test that definitions are correctly filtered from a Struct."""
> > +    struct_source = """
> > +      struct MyStruct {
> > +        [EnableIf=blue]
> > +        enum MyEnum {
> > +          VALUE1,
> > +          [EnableIfNot=red]
> > +          VALUE2,
> > +        };
> > +        [EnableIfNot=yellow]
> > +        const double kMyConst = 1.23;
> > +        [EnableIf=green]
> > +        int32 a;
> > +        double b;
> > +        [EnableIfNot=purple]
> > +        int32 c;
> > +        [EnableIf=blue]
> > +        double d;
> > +        int32 e;
> > +        [EnableIfNot=red]
> > +        double f;
> > +      };
> > +    """
> > +    expected_source = """
> > +      struct MyStruct {
> > +        [EnableIf=blue]
> > +        enum MyEnum {
> > +          VALUE1,
> > +        };
> > +        [EnableIfNot=yellow]
> > +        const double kMyConst = 1.23;
> > +        [EnableIf=green]
> > +        int32 a;
> > +        double b;
> > +        [EnableIfNot=purple]
> > +        int32 c;
> > +        [EnableIf=blue]
> > +        double d;
> > +        int32 e;
> > +      };
> > +    """
> > +    self.parseAndAssertEqual(struct_source, expected_source)
> > +
> >    def testFilterUnion(self):
> >      """Test that UnionFields are correctly filtered from a Union."""
> >      union_source = """
> > @@ -228,6 +332,30 @@ class ConditionalFeaturesTest(unittest.TestCase):
> >                        conditional_features.RemoveDisabledDefinitions,
> >                        definition, ENABLED_FEATURES)
> >
> > +  def testMultipleEnableIfs(self):
> > +    source = """
> > +      enum Foo {
> > +        [EnableIf=red,EnableIfNot=yellow]
> > +        kBarValue = 5,
> > +      };
> > +    """
> > +    definition = parser.Parse(source, "my_file.mojom")
> > +    self.assertRaises(conditional_features.EnableIfError,
> > +                      conditional_features.RemoveDisabledDefinitions,
> > +                      definition, ENABLED_FEATURES)
> > +
> > +  def testMultipleEnableIfs(self):
> > +    source = """
> > +      enum Foo {
> > +        [EnableIfNot=red,EnableIfNot=yellow]
> > +        kBarValue = 5,
> > +      };
> > +    """
> > +    definition = parser.Parse(source, "my_file.mojom")
> > +    self.assertRaises(conditional_features.EnableIfError,
> > +                      conditional_features.RemoveDisabledDefinitions,
> > +                      definition, ENABLED_FEATURES)
> > +
> >
> >  if __name__ == '__main__':
> >    unittest.main()
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> > index 3e084bbf..73ca15df 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -22,7 +22,7 @@ class LexError(Error):
> >
> >  # We have methods which look like they could be functions:
> >  # pylint: disable=R0201
> > -class Lexer(object):
> > +class Lexer:
> >    def __init__(self, filename):
> >      self.filename = filename
> >
> > @@ -81,7 +81,6 @@ class Lexer(object):
> >        # Operators
> >        'MINUS',
> >        'PLUS',
> > -      'AMP',
> >        'QSTN',
> >
> >        # Assignment
> > @@ -168,7 +167,6 @@ class Lexer(object):
> >    # Operators
> >    t_MINUS = r'-'
> >    t_PLUS = r'\+'
> > -  t_AMP = r'&'
> >    t_QSTN = r'\?'
> >
> >    # =
> > 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
> > index eadc6587..ce376da6 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -146,7 +146,6 @@ class LexerTest(unittest.TestCase):
> >          self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+"))
> >      self.assertEquals(
> >          self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-"))
> > -    self.assertEquals(self._SingleTokenForInput("&"),
> _MakeLexToken("AMP", "&"))
> >      self.assertEquals(
> >          self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?"))
> >      self.assertEquals(
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> > index b3b803d6..683ae757 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  """Generates a syntax tree from a Mojo IDL file."""
> > @@ -33,7 +33,7 @@ class ParseError(Error):
> >
> >  # We have methods which look like they could be functions:
> >  # pylint: disable=R0201
> > -class Parser(object):
> > +class Parser:
> >    def __init__(self, lexer, source, filename):
> >      self.tokens = lexer.tokens
> >      self.source = source
> > @@ -140,11 +140,18 @@ class Parser(object):
> >      p[0].Append(p[3])
> >
> >    def p_attribute_1(self, p):
> > +    """attribute : NAME EQUALS identifier_wrapped"""
> > +    p[0] = ast.Attribute(p[1],
> > +                         p[3][1],
> > +                         filename=self.filename,
> > +                         lineno=p.lineno(1))
> > +
> > +  def p_attribute_2(self, p):
> >      """attribute : NAME EQUALS evaled_literal
> >                   | NAME EQUALS NAME"""
> >      p[0] = ast.Attribute(p[1], p[3], filename=self.filename,
> lineno=p.lineno(1))
> >
> > -  def p_attribute_2(self, p):
> > +  def p_attribute_3(self, p):
> >      """attribute : NAME"""
> >      p[0] = ast.Attribute(p[1], True, filename=self.filename,
> lineno=p.lineno(1))
> >
> > @@ -271,8 +278,7 @@ class Parser(object):
> >      """nonnullable_typename : basictypename
> >                              | array
> >                              | fixed_array
> > -                            | associative_array
> > -                            | interfacerequest"""
> > +                            | associative_array"""
> >      p[0] = p[1]
> >
> >    def p_basictypename(self, p):
> > @@ -342,14 +348,6 @@ class Parser(object):
> >      """associative_array : MAP LANGLE identifier COMMA typename
> RANGLE"""
> >      p[0] = p[5] + "{" + p[3] + "}"
> >
> > -  def p_interfacerequest(self, p):
> > -    """interfacerequest : identifier AMP
> > -                        | ASSOCIATED identifier AMP"""
> > -    if len(p) == 3:
> > -      p[0] = p[1] + "&"
> > -    else:
> > -      p[0] = "asso<" + p[2] + "&>"
> > -
> >    def p_ordinal_1(self, p):
> >      """ordinal : """
> >      p[0] = None
> > 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
> > index 6d6b7153..0513343e 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2014 The Chromium Authors. All rights reserved.
> > +# Copyright 2014 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -1086,7 +1086,7 @@ class ParserTest(unittest.TestCase):
> >            handle<data_pipe_producer>? k;
> >            handle<message_pipe>? l;
> >            handle<shared_buffer>? m;
> > -          some_interface&? n;
> > +          pending_receiver<some_interface>? n;
> >            handle<platform>? o;
> >          };
> >          """
> > @@ -1110,7 +1110,7 @@ class ParserTest(unittest.TestCase):
> >                  ast.StructField('l', None, None,
> 'handle<message_pipe>?', None),
> >                  ast.StructField('m', None, None,
> 'handle<shared_buffer>?',
> >                                  None),
> > -                ast.StructField('n', None, None, 'some_interface&?',
> None),
> > +                ast.StructField('n', None, None,
> 'rcv<some_interface>?', None),
> >                  ast.StructField('o', None, None, 'handle<platform>?',
> None)
> >              ]))
> >      ])
> > @@ -1138,16 +1138,6 @@ class ParserTest(unittest.TestCase):
> >          r" *handle\?<data_pipe_consumer> a;$"):
> >        parser.Parse(source2, "my_file.mojom")
> >
> > -    source3 = """\
> > -        struct MyStruct {
> > -          some_interface?& a;
> > -        };
> > -        """
> > -    with self.assertRaisesRegexp(
> > -        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected
> '&':\n"
> > -        r" *some_interface\?& a;$"):
> > -      parser.Parse(source3, "my_file.mojom")
> > -
> >    def testSimpleUnion(self):
> >      """Tests a simple .mojom source that just defines a union."""
> >      source = """\
> > @@ -1317,9 +1307,9 @@ class ParserTest(unittest.TestCase):
> >      source1 = """\
> >          struct MyStruct {
> >            associated MyInterface a;
> > -          associated MyInterface& b;
> > +          pending_associated_receiver<MyInterface> b;
> >            associated MyInterface? c;
> > -          associated MyInterface&? d;
> > +          pending_associated_receiver<MyInterface>? d;
> >          };
> >          """
> >      expected1 = ast.Mojom(None, ast.ImportList(), [
> > @@ -1327,16 +1317,16 @@ class ParserTest(unittest.TestCase):
> >              'MyStruct', None,
> >              ast.StructBody([
> >                  ast.StructField('a', None, None, 'asso<MyInterface>',
> None),
> > -                ast.StructField('b', None, None, 'asso<MyInterface&>',
> None),
> > +                ast.StructField('b', None, None, 'rca<MyInterface>',
> None),
> >                  ast.StructField('c', None, None, 'asso<MyInterface>?',
> None),
> > -                ast.StructField('d', None, None, 'asso<MyInterface&>?',
> None)
> > +                ast.StructField('d', None, None, 'rca<MyInterface>?',
> None)
> >              ]))
> >      ])
> >      self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
> >
> >      source2 = """\
> >          interface MyInterface {
> > -          MyMethod(associated A a) =>(associated B& b);
> > +          MyMethod(associated A a) =>(pending_associated_receiver<B> b);
> >          };"""
> >      expected2 = ast.Mojom(None, ast.ImportList(), [
> >          ast.Interface(
> > @@ -1344,10 +1334,10 @@ class ParserTest(unittest.TestCase):
> >              ast.InterfaceBody(
> >                  ast.Method(
> >                      'MyMethod', None, None,
> > -                    ast.ParameterList(
> > -                        ast.Parameter('a', None, None, 'asso<A>')),
> > -                    ast.ParameterList(
> > -                        ast.Parameter('b', None, None, 'asso<B&>')))))
> > +                    ast.ParameterList(ast.Parameter('a', None, None,
> > +                                                    'asso<A>')),
> > +                    ast.ParameterList(ast.Parameter('b', None, None,
> > +                                                    'rca<B>')))))
> >      ])
> >      self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
> >
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> > index eb90c825..9693090e 100755
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
> > @@ -1,5 +1,5 @@
> > -#!/usr/bin/env python
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +#!/usr/bin/env python3
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >  """Parses mojom IDL files.
> > @@ -11,6 +11,7 @@ generate usable language bindings.
> >  """
> >
> >  import argparse
> > +import builtins
> >  import codecs
> >  import errno
> >  import json
> > @@ -19,6 +20,7 @@ import multiprocessing
> >  import os
> >  import os.path
> >  import sys
> > +import traceback
> >  from collections import defaultdict
> >
> >  from mojom.generate import module
> > @@ -28,16 +30,12 @@ from mojom.parse import conditional_features
> >
> >
> >  # Disable this for easier debugging.
> > -# In Python 2, subprocesses just hang when exceptions are thrown :(.
> > -_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2
> > +_ENABLE_MULTIPROCESSING = True
> >
> > -if sys.version_info < (3, 4):
> > -  _MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux')
> > -else:
> > -  #
> https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
> > -  if __name__ == '__main__' and sys.platform == 'darwin':
> > -    multiprocessing.set_start_method('fork')
> > -  _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() ==
> 'fork'
> > +#
> https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
> > +if __name__ == '__main__' and sys.platform == 'darwin':
> > +  multiprocessing.set_start_method('fork')
> > +_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() ==
> 'fork'
> >
> >
> >  def _ResolveRelativeImportPath(path, roots):
> > @@ -63,7 +61,7 @@ def _ResolveRelativeImportPath(path, roots):
> >    raise ValueError('"%s" does not exist in any of %s' % (path, roots))
> >
> >
> > -def _RebaseAbsolutePath(path, roots):
> > +def RebaseAbsolutePath(path, roots):
> >    """Rewrites an absolute file path as relative to an absolute
> directory path in
> >    roots.
> >
> > @@ -139,7 +137,7 @@ def _EnsureInputLoaded(mojom_abspath, module_path,
> abs_paths, asts,
> >      # Already done.
> >      return
> >
> > -  for dep_abspath, dep_path in dependencies[mojom_abspath]:
> > +  for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]):
> >      if dep_abspath not in loaded_modules:
> >        _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts,
> dependencies,
> >                           loaded_modules, module_metadata)
> > @@ -159,11 +157,19 @@ def
> _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
> >
> >    def collect(metadata_filename):
> >      processed_deps.add(metadata_filename)
> > +
> > +    # Paths in the metadata file are relative to the metadata file's
> dir.
> > +    metadata_dir = os.path.abspath(os.path.dirname(metadata_filename))
> > +
> > +    def to_abs(s):
> > +      return os.path.normpath(os.path.join(metadata_dir, s))
> > +
> >      with open(metadata_filename) as f:
> >        metadata = json.load(f)
> >        allowed_imports.update(
> > -          map(os.path.normcase, map(os.path.normpath,
> metadata['sources'])))
> > +          [os.path.normcase(to_abs(s)) for s in metadata['sources']])
> >        for dep_metadata in metadata['deps']:
> > +        dep_metadata = to_abs(dep_metadata)
> >          if dep_metadata not in processed_deps:
> >            collect(dep_metadata)
> >
> > @@ -172,8 +178,7 @@ def
> _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
> >
> >
> >  # multiprocessing helper.
> > -def _ParseAstHelper(args):
> > -  mojom_abspath, enabled_features = args
> > +def _ParseAstHelper(mojom_abspath, enabled_features):
> >    with codecs.open(mojom_abspath, encoding='utf-8') as f:
> >      ast = parser.Parse(f.read(), mojom_abspath)
> >      conditional_features.RemoveDisabledDefinitions(ast,
> enabled_features)
> > @@ -181,8 +186,7 @@ def _ParseAstHelper(args):
> >
> >
> >  # multiprocessing helper.
> > -def _SerializeHelper(args):
> > -  mojom_abspath, mojom_path = args
> > +def _SerializeHelper(mojom_abspath, mojom_path):
> >    module_path = os.path.join(_SerializeHelper.output_root_path,
> >                               _GetModuleFilename(mojom_path))
> >    module_dir = os.path.dirname(module_path)
> > @@ -199,12 +203,33 @@ def _SerializeHelper(args):
> >      _SerializeHelper.loaded_modules[mojom_abspath].Dump(f)
> >
> >
> > -def _Shard(target_func, args, processes=None):
> > -  args = list(args)
> > +class _ExceptionWrapper:
> > +  def __init__(self):
> > +    # Do not capture exception object to ensure pickling works.
> > +    self.formatted_trace = traceback.format_exc()
> > +
> > +
> > +class _FuncWrapper:
> > +  """Marshals exceptions and spreads args."""
> > +
> > +  def __init__(self, func):
> > +    self._func = func
> > +
> > +  def __call__(self, args):
> > +    # multiprocessing does not gracefully handle excptions.
> > +    # https://crbug.com/1219044
> > +    try:
> > +      return self._func(*args)
> > +    except:  # pylint: disable=bare-except
> > +      return _ExceptionWrapper()
> > +
> > +
> > +def _Shard(target_func, arg_list, processes=None):
> > +  arg_list = list(arg_list)
> >    if processes is None:
> >      processes = multiprocessing.cpu_count()
> >    # Seems optimal to have each process perform at least 2 tasks.
> > -  processes = min(processes, len(args) // 2)
> > +  processes = min(processes, len(arg_list) // 2)
> >
> >    if sys.platform == 'win32':
> >      # TODO(crbug.com/1190269) - we can't use more than 56
> > @@ -213,13 +238,17 @@ def _Shard(target_func, args, processes=None):
> >
> >    # Don't spin up processes unless there is enough work to merit doing
> so.
> >    if not _ENABLE_MULTIPROCESSING or processes < 2:
> > -    for result in map(target_func, args):
> > -      yield result
> > +    for arg_tuple in arg_list:
> > +      yield target_func(*arg_tuple)
> >      return
> >
> >    pool = multiprocessing.Pool(processes=processes)
> >    try:
> > -    for result in pool.imap_unordered(target_func, args):
> > +    wrapped_func = _FuncWrapper(target_func)
> > +    for result in pool.imap_unordered(wrapped_func, arg_list):
> > +      if isinstance(result, _ExceptionWrapper):
> > +        sys.stderr.write(result.formatted_trace)
> > +        sys.exit(1)
> >        yield result
> >    finally:
> >      pool.close()
> > @@ -230,6 +259,7 @@ def _Shard(target_func, args, processes=None):
> >  def _ParseMojoms(mojom_files,
> >                   input_root_paths,
> >                   output_root_path,
> > +                 module_root_paths,
> >                   enabled_features,
> >                   module_metadata,
> >                   allowed_imports=None):
> > @@ -245,8 +275,10 @@ def _ParseMojoms(mojom_files,
> >          are based on the mojom's relative path, rebased onto this path.
> >          Additionally, the script expects this root to contain
> already-generated
> >          modules for any transitive dependencies not listed in
> mojom_files.
> > +    module_root_paths: A list of absolute filesystem paths which contain
> > +        already-generated modules for any non-transitive dependencies.
> >      enabled_features: A list of enabled feature names, controlling
> which AST
> > -        nodes are filtered by [EnableIf] attributes.
> > +        nodes are filtered by [EnableIf] or [EnableIfNot] attributes.
> >      module_metadata: A list of 2-tuples representing metadata key-value
> pairs to
> >          attach to each compiled module output.
> >
> > @@ -262,7 +294,7 @@ def _ParseMojoms(mojom_files,
> >    loaded_modules = {}
> >    input_dependencies = defaultdict(set)
> >    mojom_files_to_parse = dict((os.path.normcase(abs_path),
> > -                               _RebaseAbsolutePath(abs_path,
> input_root_paths))
> > +                               RebaseAbsolutePath(abs_path,
> input_root_paths))
> >                                for abs_path in mojom_files)
> >    abs_paths = dict(
> >        (path, abs_path) for abs_path, path in
> mojom_files_to_parse.items())
> > @@ -274,7 +306,7 @@ def _ParseMojoms(mojom_files,
> >      loaded_mojom_asts[mojom_abspath] = ast
> >
> >    logging.info('Processing dependencies')
> > -  for mojom_abspath, ast in loaded_mojom_asts.items():
> > +  for mojom_abspath, ast in sorted(loaded_mojom_asts.items()):
> >      invalid_imports = []
> >      for imp in ast.import_list:
> >        import_abspath = _ResolveRelativeImportPath(imp.import_filename,
> > @@ -295,8 +327,8 @@ def _ParseMojoms(mojom_files,
> >          # be parsed and have a module file sitting in a corresponding
> output
> >          # location.
> >          module_path = _GetModuleFilename(imp.import_filename)
> > -        module_abspath = _ResolveRelativeImportPath(module_path,
> > -                                                    [output_root_path])
> > +        module_abspath = _ResolveRelativeImportPath(
> > +            module_path, module_root_paths + [output_root_path])
> >          with open(module_abspath, 'rb') as module_file:
> >            loaded_modules[import_abspath] =
> module.Module.Load(module_file)
> >
> > @@ -370,6 +402,15 @@ already present in the provided output root.""")
> >        'based on the relative input path, rebased onto this root. Note
> that '
> >        'ROOT is also searched for existing modules of any transitive
> imports '
> >        'which were not included in the set of inputs.')
> > +  arg_parser.add_argument(
> > +      '--module-root',
> > +      default=[],
> > +      action='append',
> > +      metavar='ROOT',
> > +      dest='module_root_paths',
> > +      help='Adds ROOT to the set of root paths to search for existing
> modules '
> > +      'of non-transitive imports. Provided root paths are always
> searched in '
> > +      'order from longest absolute path to shortest.')
> >    arg_parser.add_argument(
> >        '--mojoms',
> >        nargs='+',
> > @@ -396,9 +437,9 @@ already present in the provided output root.""")
> >        help='Enables a named feature when parsing the given mojoms.
> Features '
> >        'are identified by arbitrary string values. Specifying this flag
> with a '
> >        'given FEATURE name will cause the parser to process any syntax
> elements '
> > -      'tagged with an [EnableIf=FEATURE] attribute. If this flag is not
> '
> > -      'provided for a given FEATURE, such tagged elements are discarded
> by the '
> > -      'parser and will not be present in the compiled output.')
> > +      'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If
> this '
> > +      'flag is not provided for a given FEATURE, such tagged elements
> are '
> > +      'discarded by the parser and will not be present in the compiled
> output.')
> >    arg_parser.add_argument(
> >        '--check-imports',
> >        dest='build_metadata_filename',
> > @@ -436,6 +477,7 @@ already present in the provided output root.""")
> >    mojom_files = list(map(os.path.abspath, args.mojom_files))
> >    input_roots = list(map(os.path.abspath, args.input_root_paths))
> >    output_root = os.path.abspath(args.output_root_path)
> > +  module_roots = list(map(os.path.abspath, args.module_root_paths))
> >
> >    if args.build_metadata_filename:
> >      allowed_imports = _CollectAllowedImportsFromBuildMetadata(
> > @@ -445,13 +487,16 @@ already present in the provided output root.""")
> >
> >    module_metadata = list(
> >        map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))
> > -  _ParseMojoms(mojom_files, input_roots, output_root,
> args.enabled_features,
> > -               module_metadata, allowed_imports)
> > +  _ParseMojoms(mojom_files, input_roots, output_root, module_roots,
> > +               args.enabled_features, module_metadata, allowed_imports)
> >    logging.info('Finished')
> > -  # Exit without running GC, which can save multiple seconds due the
> large
> > -  # number of object created.
> > -  os._exit(0)
> >
> >
> >  if __name__ == '__main__':
> >    Run(sys.argv[1:])
> > +  # Exit without running GC, which can save multiple seconds due to the
> large
> > +  # number of object created. But flush is necessary as os._exit
> doesn't do
> > +  # that.
> > +  sys.stdout.flush()
> > +  sys.stderr.flush()
> > +  os._exit(0)
> > 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
> > index e213fbfa..45803ebe 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -20,7 +20,7 @@ class MojomParserTestCase(unittest.TestCase):
> >    resolution, and module serialization and deserialization."""
> >
> >    def __init__(self, method_name):
> > -    super(MojomParserTestCase, self).__init__(method_name)
> > +    super().__init__(method_name)
> >      self._temp_dir = None
> >
> >    def setUp(self):
> > diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> > index a93f34ba..353a2b6e 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
> > @@ -1,7 +1,9 @@
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > +import json
> > +
> >  from mojom_parser_test_case import MojomParserTestCase
> >
> >
> > @@ -119,15 +121,22 @@ class MojomParserTest(MojomParserTestCase):
> >      c = 'c.mojom'
> >      c_metadata = 'out/c.build_metadata'
> >      self.WriteFile(a_metadata,
> > -                   '{"sources": ["%s"], "deps": []}\n' %
> self.GetPath(a))
> > +                   json.dumps({
> > +                       "sources": [self.GetPath(a)],
> > +                       "deps": []
> > +                   }))
> >      self.WriteFile(
> >          b_metadata,
> > -        '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b),
> > -
>  self.GetPath(a_metadata)))
> > +        json.dumps({
> > +            "sources": [self.GetPath(b)],
> > +            "deps": [self.GetPath(a_metadata)]
> > +        }))
> >      self.WriteFile(
> >          c_metadata,
> > -        '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c),
> > -
>  self.GetPath(b_metadata)))
> > +        json.dumps({
> > +            "sources": [self.GetPath(c)],
> > +            "deps": [self.GetPath(b_metadata)]
> > +        }))
> >      self.WriteFile(a, """\
> >          module a;
> >          struct Bar {};""")
> > @@ -154,9 +163,15 @@ class MojomParserTest(MojomParserTestCase):
> >      b = 'b.mojom'
> >      b_metadata = 'out/b.build_metadata'
> >      self.WriteFile(a_metadata,
> > -                   '{"sources": ["%s"], "deps": []}\n' %
> self.GetPath(a))
> > +                   json.dumps({
> > +                       "sources": [self.GetPath(a)],
> > +                       "deps": []
> > +                   }))
> >      self.WriteFile(b_metadata,
> > -                   '{"sources": ["%s"], "deps": []}\n' %
> self.GetPath(b))
> > +                   json.dumps({
> > +                       "sources": [self.GetPath(b)],
> > +                       "deps": []
> > +                   }))
> >      self.WriteFile(a, """\
> >          module a;
> >          struct Bar {};""")
> > diff --git
> a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> > index d45ec586..d10d69c6 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > diff --git a/utils/ipc/mojo/public/tools/mojom/union_unittest.py
> b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
> > new file mode 100644
> > index 00000000..6b2525e5
> > --- /dev/null
> > +++ b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
> > @@ -0,0 +1,44 @@
> > +# Copyright 2022 The Chromium Authors
> > +# Use of this source code is governed by a BSD-style license that can be
> > +# found in the LICENSE file.
> > +
> > +from mojom_parser_test_case import MojomParserTestCase
> > +
> > +
> > +class UnionTest(MojomParserTestCase):
> > +  """Tests union parsing behavior."""
> > +
> > +  def testExtensibleMustHaveDefault(self):
> > +    """Verifies that extensible unions must have a default field."""
> > +    mojom = 'foo.mojom'
> > +    self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x;
> };')
> > +    with self.assertRaisesRegexp(Exception, 'must specify a
> \[Default\]'):
> > +      self.ParseMojoms([mojom])
> > +
> > +  def testExtensibleSingleDefault(self):
> > +    """Verifies that extensible unions must not have multiple default
> fields."""
> > +    mojom = 'foo.mojom'
> > +    self.WriteFile(
> > +        mojom, """\
> > +               module foo;
> > +               [Extensible] union U {
> > +                 [Default] bool x;
> > +                 [Default] bool y;
> > +               };
> > +               """)
> > +    with self.assertRaisesRegexp(Exception, 'Multiple \[Default\]
> fields'):
> > +      self.ParseMojoms([mojom])
> > +
> > +  def testExtensibleDefaultTypeValid(self):
> > +    """Verifies that an extensible union's default field must be
> nullable or
> > +    integral type."""
> > +    mojom = 'foo.mojom'
> > +    self.WriteFile(
> > +        mojom, """\
> > +               module foo;
> > +               [Extensible] union U {
> > +                 [Default] handle<message_pipe> p;
> > +               };
> > +               """)
> > +    with self.assertRaisesRegexp(Exception, 'must be nullable or
> integral'):
> > +      self.ParseMojoms([mojom])
> > diff --git
> a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> > index 65db4dc9..7b71ce65 100644
> > --- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> > +++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -23,7 +23,7 @@ class VersionCompatibilityTest(MojomParserTestCase):
> >
> >      checker = module.BackwardCompatibilityChecker()
> >      compatibility_map = {}
> > -    for name in old.keys():
> > +    for name in old:
> >        compatibility_map[name] = checker.IsBackwardCompatible(
> >            new[name], old[name])
> >      return compatibility_map
> > @@ -60,40 +60,48 @@ class VersionCompatibilityTest(MojomParserTestCase):
> >      """Adding a value to an existing version is not allowed, even if
> the old
> >      enum was marked [Extensible]. Note that it is irrelevant whether or
> not the
> >      new enum is marked [Extensible]."""
> > -    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar
> };',
> > -                                     'enum E { kFoo, kBar, kBaz };')
> >      self.assertNotBackwardCompatible(
> > -        '[Extensible] enum E { kFoo, kBar };',
> > -        '[Extensible] enum E { kFoo, kBar, kBaz };')
> > +        '[Extensible] enum E { [Default] kFoo, kBar };',
> > +        'enum E { kFoo, kBar, kBaz };')
> > +    self.assertNotBackwardCompatible(
> > +        '[Extensible] enum E { [Default] kFoo, kBar };',
> > +        '[Extensible] enum E { [Default] kFoo, kBar, kBaz };')
> >      self.assertNotBackwardCompatible(
> > -        '[Extensible] enum E { kFoo, [MinVersion=1] kBar };',
> > +        '[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };',
> >          'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')
> >
> >    def testEnumValueRemoval(self):
> >      """Removal of an enum value is never valid even for [Extensible]
> enums."""
> >      self.assertNotBackwardCompatible('enum E { kFoo, kBar };',
> >                                       'enum E { kFoo };')
> > -    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar
> };',
> > -                                     '[Extensible] enum E { kFoo };')
> >      self.assertNotBackwardCompatible(
> > -        '[Extensible] enum E { kA, [MinVersion=1] kB };',
> > -        '[Extensible] enum E { kA, };')
> > +        '[Extensible] enum E { [Default] kFoo, kBar };',
> > +        '[Extensible] enum E { [Default] kFoo };')
> > +    self.assertNotBackwardCompatible(
> > +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
> > +        '[Extensible] enum E { [Default] kA, };')
> >      self.assertNotBackwardCompatible(
> > -        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ
> };',
> > -        '[Extensible] enum E { kA, [MinVersion=1] kB };')
> > +        """[Extensible] enum E {
> > +          [Default] kA,
> > +          [MinVersion=1] kB,
> > +          [MinVersion=1] kZ };""",
> > +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };')
> >
> >    def testNewExtensibleEnumValueWithMinVersion(self):
> >      """Adding a new and properly [MinVersion]'d value to an
> [Extensible] enum
> >      is a backward-compatible change. Note that it is irrelevant whether
> or not
> >      the new enum is marked [Extensible]."""
> > -    self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',
> > +    self.assertBackwardCompatible('[Extensible] enum E { [Default] kA,
> kB };',
> >                                    'enum E { kA, kB, [MinVersion=1] kC
> };')
> >      self.assertBackwardCompatible(
> > -        '[Extensible] enum E { kA, kB };',
> > -        '[Extensible] enum E { kA, kB, [MinVersion=1] kC };')
> > +        '[Extensible] enum E { [Default] kA, kB };',
> > +        '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };')
> >      self.assertBackwardCompatible(
> > -        '[Extensible] enum E { kA, [MinVersion=1] kB };',
> > -        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC
> };')
> > +        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
> > +        """[Extensible] enum E {
> > +          [Default] kA,
> > +          [MinVersion=1] kB,
> > +          [MinVersion=2] kC };""")
> >
> >    def testRenameEnumValue(self):
> >      """Renaming an enum value does not affect backward-compatibility.
> Only
> > @@ -161,14 +169,17 @@ class
> VersionCompatibilityTest(MojomParserTestCase):
> >          'struct S {}; struct T { S s; };',
> >          'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')
> >      self.assertBackwardCompatible(
> > -        '[Extensible] enum E { kA }; struct S { E e; };',
> > -        '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e;
> };')
> > +        '[Extensible] enum E { [Default] kA }; struct S { E e; };',
> > +        """[Extensible] enum E {
> > +          [Default] kA,
> > +          [MinVersion=1] kB };
> > +          struct S { E e; };""")
> >      self.assertNotBackwardCompatible(
> >          'struct S {}; struct T { S s; };',
> >          'struct S { int32 x; }; struct T { S s; };')
> >      self.assertNotBackwardCompatible(
> > -        '[Extensible] enum E { kA }; struct S { E e; };',
> > -        '[Extensible] enum E { kA, kB }; struct S { E e; };')
> > +        '[Extensible] enum E { [Default] kA }; struct S { E e; };',
> > +        '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };')
> >
> >    def testNewStructFieldWithInvalidMinVersion(self):
> >      """Adding a new field using an existing MinVersion breaks backward-
> > @@ -305,14 +316,17 @@ class
> VersionCompatibilityTest(MojomParserTestCase):
> >          'struct S {}; union U { S s; };',
> >          'struct S { [MinVersion=1] int32 x; }; union U { S s; };')
> >      self.assertBackwardCompatible(
> > -        '[Extensible] enum E { kA }; union U { E e; };',
> > -        '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e;
> };')
> > +        '[Extensible] enum E { [Default] kA }; union U { E e; };',
> > +        """[Extensible] enum E {
> > +          [Default] kA,
> > +          [MinVersion=1] kB };
> > +          union U { E e; };""")
> >      self.assertNotBackwardCompatible(
> >          'struct S {}; union U { S s; };',
> >          'struct S { int32 x; }; union U { S s; };')
> >      self.assertNotBackwardCompatible(
> > -        '[Extensible] enum E { kA }; union U { E e; };',
> > -        '[Extensible] enum E { kA, kB }; union U { E e; };')
> > +        '[Extensible] enum E { [Default] kA }; union U { E e; };',
> > +        '[Extensible] enum E { [Default] kA, kB }; union U { E e; };')
> >
> >    def testNewUnionFieldWithInvalidMinVersion(self):
> >      """Adding a new field using an existing MinVersion breaks backward-
> > diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> > index b2010958..98bce18c 100755
> > --- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> > +++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
> > @@ -1,5 +1,5 @@
> > -#!/usr/bin/env python
> > -# Copyright 2020 The Chromium Authors. All rights reserved.
> > +#!/usr/bin/env python3
> > +# Copyright 2020 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > @@ -8,11 +8,13 @@ import sys
> >
> >  _TOOLS_DIR = os.path.dirname(__file__)
> >  _MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')
> > +_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings')
> >  _SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,
> >                          os.path.pardir)
> >
> >  # Ensure that the mojom library is discoverable.
> >  sys.path.append(_MOJOM_DIR)
> > +sys.path.append(_BINDINGS_DIR)
> >
> >  # Help Python find typ in //third_party/catapult/third_party/typ/
> >  sys.path.append(
> > @@ -21,7 +23,7 @@ import typ
> >
> >
> >  def Main():
> > -  return typ.main(top_level_dir=_MOJOM_DIR)
> > +  return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR])
> >
> >
> >  if __name__ == '__main__':
> > diff --git a/utils/ipc/tools/README b/utils/ipc/tools/README
> > index d5c24fc3..9a2979d3 100644
> > --- a/utils/ipc/tools/README
> > +++ b/utils/ipc/tools/README
> > @@ -1,4 +1,4 @@
> >  # SPDX-License-Identifier: CC0-1.0
> >
> > -Files in this directory are imported from 9c138d992bfc of Chromium. Do
> not
> > +Files in this directory are imported from e2b2277a00e37 of Chromium. Do
> not
> >  modify them manually.
> > diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py
> b/utils/ipc/tools/diagnosis/crbug_1001171.py
> > index 478fb8c1..40900d10 100644
> > --- a/utils/ipc/tools/diagnosis/crbug_1001171.py
> > +++ b/utils/ipc/tools/diagnosis/crbug_1001171.py
> > @@ -1,4 +1,4 @@
> > -# Copyright 2019 The Chromium Authors. All rights reserved.
> > +# Copyright 2019 The Chromium Authors
> >  # Use of this source code is governed by a BSD-style license that can be
> >  # found in the LICENSE file.
> >
> > --
> > 2.40.0.348.gf938b09366-goog
> >
>

Patch
diff mbox series

diff --git a/utils/ipc/mojo/README b/utils/ipc/mojo/README
index d5c24fc3..9a2979d3 100644
--- a/utils/ipc/mojo/README
+++ b/utils/ipc/mojo/README
@@ -1,4 +1,4 @@ 
 # SPDX-License-Identifier: CC0-1.0
 
-Files in this directory are imported from 9c138d992bfc of Chromium. Do not
+Files in this directory are imported from e2b2277a00e37 of Chromium. Do not
 modify them manually.
diff --git a/utils/ipc/mojo/public/LICENSE b/utils/ipc/mojo/public/LICENSE
index 972bb2ed..513e8a6a 100644
--- a/utils/ipc/mojo/public/LICENSE
+++ b/utils/ipc/mojo/public/LICENSE
@@ -1,4 +1,4 @@ 
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2014 The Chromium Authors
 //
 // Redistribution and use in source and binary forms, with or without
 // modification, are permitted provided that the following conditions are
diff --git a/utils/ipc/mojo/public/tools/BUILD.gn b/utils/ipc/mojo/public/tools/BUILD.gn
index eb6391a6..5328a34a 100644
--- a/utils/ipc/mojo/public/tools/BUILD.gn
+++ b/utils/ipc/mojo/public/tools/BUILD.gn
@@ -1,4 +1,4 @@ 
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -10,7 +10,11 @@  group("mojo_python_unittests") {
     "run_all_python_unittests.py",
     "//testing/scripts/run_isolated_script_test.py",
   ]
-  deps = [ "//mojo/public/tools/mojom/mojom:tests" ]
+  deps = [
+    "//mojo/public/tools/bindings:tests",
+    "//mojo/public/tools/mojom:tests",
+    "//mojo/public/tools/mojom/mojom:tests",
+  ]
   data_deps = [
     "//testing:test_scripts_shared",
     "//third_party/catapult/third_party/typ/",
diff --git a/utils/ipc/mojo/public/tools/bindings/BUILD.gn b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
index 3e242532..203e476c 100644
--- a/utils/ipc/mojo/public/tools/bindings/BUILD.gn
+++ b/utils/ipc/mojo/public/tools/bindings/BUILD.gn
@@ -1,13 +1,11 @@ 
-# Copyright 2016 The Chromium Authors. All rights reserved.
+# Copyright 2016 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/config/python.gni")
 import("//mojo/public/tools/bindings/mojom.gni")
 import("//third_party/jinja2/jinja2.gni")
 
-# TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-python2_action("precompile_templates") {
+action("precompile_templates") {
   sources = mojom_generator_sources
   sources += [
     "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl",
@@ -26,7 +24,6 @@  python2_action("precompile_templates") {
     "$mojom_generator_root/generators/cpp_templates/module-shared-message-ids.h.tmpl",
     "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl",
     "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl",
-    "$mojom_generator_root/generators/cpp_templates/module-test-utils.cc.tmpl",
     "$mojom_generator_root/generators/cpp_templates/module-test-utils.h.tmpl",
     "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl",
     "$mojom_generator_root/generators/cpp_templates/module.h.tmpl",
@@ -65,9 +62,6 @@  python2_action("precompile_templates") {
     "$mojom_generator_root/generators/java_templates/struct.java.tmpl",
     "$mojom_generator_root/generators/java_templates/union.java.tmpl",
     "$mojom_generator_root/generators/js_templates/enum_definition.tmpl",
-    "$mojom_generator_root/generators/js_templates/externs/interface_definition.tmpl",
-    "$mojom_generator_root/generators/js_templates/externs/module.externs.tmpl",
-    "$mojom_generator_root/generators/js_templates/externs/struct_definition.tmpl",
     "$mojom_generator_root/generators/js_templates/fuzzing.tmpl",
     "$mojom_generator_root/generators/js_templates/interface_definition.tmpl",
     "$mojom_generator_root/generators/js_templates/lite/enum_definition.tmpl",
@@ -93,8 +87,14 @@  python2_action("precompile_templates") {
     "$mojom_generator_root/generators/mojolpm_templates/mojolpm_macros.tmpl",
     "$mojom_generator_root/generators/mojolpm_templates/mojolpm_to_proto_macros.tmpl",
     "$mojom_generator_root/generators/mojolpm_templates/mojolpm_traits_specialization_macros.tmpl",
+    "$mojom_generator_root/generators/ts_templates/enum_definition.tmpl",
+    "$mojom_generator_root/generators/ts_templates/interface_definition.tmpl",
     "$mojom_generator_root/generators/ts_templates/module_definition.tmpl",
-    "$mojom_generator_root/generators/ts_templates/mojom.tmpl",
+    "$mojom_generator_root/generators/ts_templates/struct_definition.tmpl",
+    "$mojom_generator_root/generators/ts_templates/union_definition.tmpl",
+    "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_impl.cc.tmpl",
+    "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_impl.h.tmpl",
+    "$mojom_generator_root/generators/webui_js_bridge_templates/webui_js_bridge_macros.tmpl",
   ]
   script = mojom_generator_script
 
@@ -102,8 +102,9 @@  python2_action("precompile_templates") {
   outputs = [
     "$target_gen_dir/cpp_templates.zip",
     "$target_gen_dir/java_templates.zip",
-    "$target_gen_dir/mojolpm_templates.zip",
+    "$target_gen_dir/webui_js_bridge_templates.zip",
     "$target_gen_dir/js_templates.zip",
+    "$target_gen_dir/mojolpm_templates.zip",
     "$target_gen_dir/ts_templates.zip",
   ]
   args = [
@@ -113,3 +114,16 @@  python2_action("precompile_templates") {
     "precompile",
   ]
 }
+
+group("tests") {
+  data = [
+    mojom_generator_script,
+    "checks/mojom_attributes_check_unittest.py",
+    "checks/mojom_restrictions_checks_unittest.py",
+    "mojom_bindings_generator_unittest.py",
+    "//tools/diagnosis/crbug_1001171.py",
+    "//third_party/markupsafe/",
+  ]
+  data += mojom_generator_sources
+  data += jinja2_sources
+}
diff --git a/utils/ipc/mojo/public/tools/bindings/README.md b/utils/ipc/mojo/public/tools/bindings/README.md
index 43882450..683aa2f0 100644
--- a/utils/ipc/mojo/public/tools/bindings/README.md
+++ b/utils/ipc/mojo/public/tools/bindings/README.md
@@ -188,8 +188,8 @@  struct StringPair {
 };
 
 enum AnEnum {
-  YES,
-  NO
+  kYes,
+  kNo
 };
 
 interface SampleInterface {
@@ -209,7 +209,7 @@  struct AllTheThings {
   uint64 unsigned_64bit_value;
   float float_value_32bit;
   double float_value_64bit;
-  AnEnum enum_value = AnEnum.YES;
+  AnEnum enum_value = AnEnum.kYes;
 
   // Strings may be nullable.
   string? maybe_a_string_maybe_not;
@@ -300,14 +300,14 @@  within a module or nested within the namespace of some struct or interface:
 module business.mojom;
 
 enum Department {
-  SALES = 0,
-  DEV,
+  kSales = 0,
+  kDev,
 };
 
 struct Employee {
   enum Type {
-    FULL_TIME,
-    PART_TIME,
+    kFullTime,
+    kPartTime,
   };
 
   Type type;
@@ -315,6 +315,9 @@  struct Employee {
 };
 ```
 
+C++ constant-style enum value names are preferred as specified in the
+[Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html#Enumerator_Names).
+
 Similar to C-style enums, individual values may be explicitly assigned within an
 enum definition. By default, values are based at zero and increment by
 1 sequentially.
@@ -336,8 +339,8 @@  struct Employee {
   const uint64 kInvalidId = 0;
 
   enum Type {
-    FULL_TIME,
-    PART_TIME,
+    kFullTime,
+    kPartTime,
   };
 
   uint64 id = kInvalidId;
@@ -396,20 +399,33 @@  interesting attributes supported today.
   extreme caution, because it can lead to deadlocks otherwise.
 
 * **`[Default]`**:
-  The `Default` attribute may be used to specify an enumerator value that
-  will be used if an `Extensible` enumeration does not deserialize to a known
-  value on the receiver side, i.e. the sender is using a newer version of the
-  enum. This allows unknown values to be mapped to a well-defined value that can
-  be appropriately handled.
+  The `Default` attribute may be used to specify an enumerator value or union
+  field that will be used if an `Extensible` enumeration or union does not
+  deserialize to a known value on the receiver side, i.e. the sender is using a
+  newer version of the enum or union. This allows unknown values to be mapped to
+  a well-defined value that can be appropriately handled.
+
+  Note: The `Default` field for a union must be of nullable or integral type.
+  When a union is defaulted to this field, the field takes on the default value
+  for its type: null for nullable types, and zero/false for integral types.
 
 * **`[Extensible]`**:
-  The `Extensible` attribute may be specified for any enum definition. This
-  essentially disables builtin range validation when receiving values of the
-  enum type in a message, allowing older bindings to tolerate unrecognized
-  values from newer versions of the enum.
+  The `Extensible` attribute may be specified for any enum or union definition.
+  For enums, this essentially disables builtin range validation when receiving
+  values of the enum type in a message, allowing older bindings to tolerate
+  unrecognized values from newer versions of the enum.
+
+  If an enum value within an extensible enum definition is affixed with the
+  `Default` attribute, out-of-range values for the enum will deserialize to that
+  default value. Only one enum value may be designated as the `Default`.
 
-  Note: in the future, an `Extensible` enumeration will require that a `Default`
-  enumerator value also be specified.
+  Similarly, a union marked `Extensible` will deserialize to its `Default` field
+  when an unrecognized field is received. Extensible unions MUST specify exactly
+  one `Default` field, and the field must be of nullable or integral type. When
+  defaulted to this field, the value is always null/zero/false as appropriate.
+
+  An `Extensible` enumeration REQUIRES that a `Default` value be specified,
+  so all new extensible enums should specify one.
 
 * **`[Native]`**:
   The `Native` attribute may be specified for an empty struct declaration to
@@ -422,7 +438,10 @@  interesting attributes supported today.
 * **`[MinVersion=N]`**:
   The `MinVersion` attribute is used to specify the version at which a given
   field, enum value, interface method, or method parameter was introduced.
-  See [Versioning](#Versioning) for more details.
+  See [Versioning](#Versioning) for more details. `MinVersion` does not apply
+  to interfaces, structs or enums, but to the fields of those types.
+  `MinVersion` is not a module-global value, but it is ok to pretend it is by
+  skipping versions when adding fields or parameters.
 
 * **`[Stable]`**:
   The `Stable` attribute specifies that a given mojom type or interface
@@ -448,7 +467,50 @@  interesting attributes supported today.
   matching `value` in the list of `enabled_features`, the definition will be
   disabled. This is useful for mojom definitions that only make sense on one
   platform. Note that the `EnableIf` attribute can only be set once per
-  definition.
+  definition and cannot be set at the same time as `EnableIfNot`. Also be aware
+  that only one condition can be tested, `EnableIf=value,xyz` introduces a new
+  `xyz` attribute. `xyz` is not part of the `EnableIf` condition that depends
+  only on the feature `value`. Complex conditions can be introduced via
+  enabled_features in `build.gn` files.
+
+* **`[EnableIfNot=value]`**:
+  The `EnableIfNot` attribute is used to conditionally enable definitions when
+  the mojom is parsed. If the `mojom` target in the GN file includes the
+  matching `value` in the list of `enabled_features`, the definition will be
+  disabled. This is useful for mojom definitions that only make sense on all but
+  one platform. Note that the `EnableIfNot` attribute can only be set once per
+  definition and cannot be set at the same time as `EnableIf`.
+
+* **`[ServiceSandbox=value]`**:
+  The `ServiceSandbox` attribute is used in Chromium to tag which sandbox a
+  service hosting an implementation of interface will be launched in. This only
+  applies to `C++` bindings. `value` should match a constant defined in an
+  imported `sandbox.mojom.Sandbox` enum (for Chromium this is
+  `//sandbox/policy/mojom/sandbox.mojom`), such as `kService`.
+
+* **`[RequireContext=enum]`**:
+  The `RequireContext` attribute is used in Chromium to tag interfaces that
+  should be passed (as remotes or receivers) only to privileged process
+  contexts. The process context must be an enum that is imported into the
+  mojom that defines the tagged interface. `RequireContext` may be used in
+  future to DCHECK or CHECK if remotes are made available in contexts that
+  conflict with the one provided in the interface definition. Process contexts
+  are not the same as the sandbox a process is running in, but will reflect
+  the set of capabilities provided to the service.
+
+* **`[AllowedContext=enum]`**:
+  The `AllowedContext` attribute is used in Chromium to tag methods that pass
+  remotes or receivers of interfaces that are marked with a `RequireContext`
+  attribute. The enum provided on the method must be equal or better (lower
+  numerically) than the one required on the interface being passed. At present
+  failing to specify an adequate `AllowedContext` value will cause mojom
+  generation to fail at compile time. In future DCHECKs or CHECKs might be
+  added to enforce that method is only called from a process context that meets
+  the given `AllowedContext` value. The enum must of the same type as that
+  specified in the interface's `RequireContext` attribute. Adding an
+  `AllowedContext` attribute to a method is a strong indication that you need
+   a detailed security review of your design - please reach out to the security
+   team.
 
 ## Generated Code For Target Languages
 
@@ -495,9 +557,9 @@  values. For example if a Mojom declares the enum:
 
 ``` cpp
 enum AdvancedBoolean {
-  TRUE = 0,
-  FALSE = 1,
-  FILE_NOT_FOUND = 2,
+  kTrue = 0,
+  kFalse = 1,
+  kFileNotFound = 2,
 };
 ```
 
@@ -550,10 +612,16 @@  See the documentation for
 
 *** note
 **NOTE:** You don't need to worry about versioning if you don't care about
-backwards compatibility. Specifically, all parts of Chrome are updated
-atomically today and there is not yet any possibility of any two Chrome
-processes communicating with two different versions of any given Mojom
-interface.
+backwards compatibility. Today, all parts of the Chrome browser are
+updated atomically and there is not yet any possibility of any two
+Chrome processes communicating with two different versions of any given Mojom
+interface. On Chrome OS, there are several places where versioning is required.
+For example,
+[ARC++](https://developer.android.com/chrome-os/intro)
+uses versioned mojo to send IPC to the Android container.
+Likewise, the
+[Lacros](/docs/lacros.md)
+browser uses versioned mojo to talk to the ash system UI.
 ***
 
 Services extend their interfaces to support new features over time, and clients
@@ -593,8 +661,8 @@  struct Employee {
 
 *** note
 **NOTE:** Mojo object or handle types added with a `MinVersion` **MUST** be
-optional (nullable). See [Primitive Types](#Primitive-Types) for details on
-nullable values.
+optional (nullable) or primitive. See [Primitive Types](#Primitive-Types) for
+details on nullable values.
 ***
 
 By default, fields belong to version 0. New fields must be appended to the
@@ -624,10 +692,10 @@  the following hard constraints:
 * For any given struct or interface, if any field or method explicitly specifies
     an ordinal value, all fields or methods must explicitly specify an ordinal
     value.
-* For an *N*-field struct or *N*-method interface, the set of explicitly
-    assigned ordinal values must be limited to the range *[0, N-1]*. Interfaces
-    should include placeholder methods to fill the ordinal positions of removed
-    methods (for example "Unused_Message_7@7()" or "RemovedMessage@42()", etc).
+* For an *N*-field struct, the set of explicitly assigned ordinal values must be
+    limited to the range *[0, N-1]*. Structs should include placeholder fields
+    to fill the ordinal positions of removed fields (for example "Unused_Field"
+    or "RemovedField", etc).
 
 You may reorder fields, but you must ensure that the ordinal values of existing
 fields remain unchanged. For example, the following struct remains
@@ -712,8 +780,8 @@  If you want an enum to be extensible in the future, you can apply the
 ``` cpp
 [Extensible]
 enum Department {
-  SALES,
-  DEV,
+  kSales,
+  kDev,
 };
 ```
 
@@ -722,9 +790,9 @@  And later you can extend this enum without breaking backwards compatibility:
 ``` cpp
 [Extensible]
 enum Department {
-  SALES,
-  DEV,
-  [MinVersion=1] RESEARCH,
+  kSales,
+  kDev,
+  [MinVersion=1] kResearch,
 };
 ```
 
diff --git a/utils/ipc/mojo/public/tools/bindings/checks/__init__.py b/utils/ipc/mojo/public/tools/bindings/checks/__init__.py
new file mode 100644
index 00000000..e69de29b
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
new file mode 100644
index 00000000..3a2d2a3b
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check.py
@@ -0,0 +1,168 @@ 
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Validate mojo attributes are allowed in Chrome before generation."""
+
+import mojom.generate.check as check
+import mojom.generate.module as module
+
+_COMMON_ATTRIBUTES = {
+    'EnableIf',
+    'EnableIfNot',
+}
+
+# For struct, union & parameter lists.
+_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+    'MinVersion',
+    'RenamedFrom',
+}
+
+# Note: `Default`` goes on the default _value_, not on the enum.
+# Note: [Stable] without [Extensible] is not allowed.
+_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+    'Extensible',
+    'Native',
+    'Stable',
+    'RenamedFrom',
+    'Uuid',
+}
+
+# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal.
+_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+    'Default',
+    'MinVersion',
+}
+
+_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+    'WebUIJsBridge',
+    'RenamedFrom',
+    'RequireContext',
+    'ServiceSandbox',
+    'Stable',
+    'Uuid',
+}
+
+_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+    'AllowedContext',
+    'MinVersion',
+    'NoInterrupt',
+    'Sync',
+    'UnlimitedSize',
+}
+
+_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+    'JavaConstantsClassName',
+    'JavaPackage',
+}
+
+_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
+
+_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+    'CustomSerializer',
+    'JavaClassName',
+    'Native',
+    'Stable',
+    'RenamedFrom',
+    'Uuid',
+}
+
+_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
+
+_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | {
+    'Extensible',
+    'Stable',
+    'RenamedFrom',
+    'Uuid',
+}
+
+_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | {
+    'Default',
+}
+
+# TODO(https://crbug.com/1193875) empty this set and remove the allowlist.
+_STABLE_ONLY_ALLOWLISTED_ENUMS = {
+    'crosapi.mojom.OptionalBool',
+    'crosapi.mojom.TriState',
+}
+
+
+class Check(check.Check):
+  def __init__(self, *args, **kwargs):
+    super(Check, self).__init__(*args, **kwargs)
+
+  def _Respell(self, allowed, attribute):
+    for a in allowed:
+      if a.lower() == attribute.lower():
+        return f" - Did you mean: {a}?"
+    return ""
+
+  def _CheckAttributes(self, context, allowed, attributes):
+    if not attributes:
+      return
+    for attribute in attributes:
+      if not attribute in allowed:
+        # Is there a close misspelling?
+        hint = self._Respell(allowed, attribute)
+        raise check.CheckException(
+            self.module,
+            f"attribute {attribute} not allowed on {context}{hint}")
+
+  def _CheckEnumAttributes(self, enum):
+    if enum.attributes:
+      self._CheckAttributes("enum", _ENUM_ATTRIBUTES, enum.attributes)
+      if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes:
+        full_name = f"{self.module.mojom_namespace}.{enum.mojom_name}"
+        if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS:
+          raise check.CheckException(
+              self.module,
+              f"[Extensible] required on [Stable] enum {full_name}")
+    for enumval in enum.fields:
+      self._CheckAttributes("enum value", _ENUMVAL_ATTRIBUTES,
+                            enumval.attributes)
+
+  def _CheckInterfaceAttributes(self, interface):
+    self._CheckAttributes("interface", _INTERFACE_ATTRIBUTES,
+                          interface.attributes)
+    for method in interface.methods:
+      self._CheckAttributes("method", _METHOD_ATTRIBUTES, method.attributes)
+      for param in method.parameters:
+        self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
+                              param.attributes)
+      if method.response_parameters:
+        for param in method.response_parameters:
+          self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
+                                param.attributes)
+    for enum in interface.enums:
+      self._CheckEnumAttributes(enum)
+
+  def _CheckModuleAttributes(self):
+    self._CheckAttributes("module", _MODULE_ATTRIBUTES, self.module.attributes)
+
+  def _CheckStructAttributes(self, struct):
+    self._CheckAttributes("struct", _STRUCT_ATTRIBUTES, struct.attributes)
+    for field in struct.fields:
+      self._CheckAttributes("struct field", _STRUCT_FIELD_ATTRIBUTES,
+                            field.attributes)
+    for enum in struct.enums:
+      self._CheckEnumAttributes(enum)
+
+  def _CheckUnionAttributes(self, union):
+    self._CheckAttributes("union", _UNION_ATTRIBUTES, union.attributes)
+    for field in union.fields:
+      self._CheckAttributes("union field", _UNION_FIELD_ATTRIBUTES,
+                            field.attributes)
+
+  def CheckModule(self):
+    """Note that duplicate attributes are forbidden at the parse phase.
+    We also do not need to look at the types of any parameters, as they will be
+    checked where they are defined. Consts do not have attributes so can be
+    skipped."""
+    self._CheckModuleAttributes()
+    for interface in self.module.interfaces:
+      self._CheckInterfaceAttributes(interface)
+    for enum in self.module.enums:
+      self._CheckEnumAttributes(enum)
+    for struct in self.module.structs:
+      self._CheckStructAttributes(struct)
+    for union in self.module.unions:
+      self._CheckUnionAttributes(union)
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
new file mode 100644
index 00000000..8c7f3c2c
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_attributes_check_unittest.py
@@ -0,0 +1,186 @@ 
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import mojom.generate.check as check
+from mojom_bindings_generator import LoadChecks, _Generate
+from mojom_parser_test_case import MojomParserTestCase
+
+
+class FakeArgs:
+  """Fakes args to _Generate - intention is to do just enough to run checks"""
+
+  def __init__(self, tester, files=None):
+    """ `tester` is MojomParserTestCase for paths.
+        `files` will have tester path added."""
+    self.checks_string = 'attributes'
+    self.depth = tester.GetPath('')
+    self.filelist = None
+    self.filename = [tester.GetPath(x) for x in files]
+    self.gen_directories = tester.GetPath('gen')
+    self.generators_string = ''
+    self.import_directories = []
+    self.output_dir = tester.GetPath('out')
+    self.scrambled_message_id_salt_paths = None
+    self.typemaps = []
+    self.variant = 'none'
+
+
+class MojoBindingsCheckTest(MojomParserTestCase):
+  def _ParseAndGenerate(self, mojoms):
+    self.ParseMojoms(mojoms)
+    args = FakeArgs(self, files=mojoms)
+    _Generate(args, {})
+
+  def _testValid(self, filename, content):
+    self.WriteFile(filename, content)
+    self._ParseAndGenerate([filename])
+
+  def _testThrows(self, filename, content, regexp):
+    mojoms = []
+    self.WriteFile(filename, content)
+    mojoms.append(filename)
+    with self.assertRaisesRegexp(check.CheckException, regexp):
+      self._ParseAndGenerate(mojoms)
+
+  def testLoads(self):
+    """Validate that the check is registered under the expected name."""
+    check_modules = LoadChecks('attributes')
+    self.assertTrue(check_modules['attributes'])
+
+  def testNoAnnotations(self):
+    # Undecorated mojom should be fine.
+    self._testValid(
+        "a.mojom", """
+      module a;
+      struct Bar { int32 a; };
+      enum Hello { kValue };
+      union Thingy { Bar b; Hello hi; };
+      interface Foo {
+        Foo(int32 a, Hello hi, Thingy t) => (Bar b);
+      };
+    """)
+
+  def testValidAnnotations(self):
+    # Obviously this is meaningless and won't generate, but it should pass
+    # the attribute check's validation.
+    self._testValid(
+        "a.mojom", """
+      [JavaConstantsClassName="FakeClass",JavaPackage="org.chromium.Fake"]
+      module a;
+      [Stable, Extensible]
+      enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 };
+      [Native]
+      enum NativeEnum {};
+      [Stable,Extensible]
+      union Thingy { Bar b; [Default]int32 c; Hello hi; };
+
+      [Stable,RenamedFrom="module.other.Foo",
+       Uuid="4C178401-4B07-4C2E-9255-5401A943D0C7"]
+      struct Structure { Hello hi; };
+
+      [ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable,
+       Uuid="2F17D7DD-865A-4B1C-9394-9C94E035E82F"]
+      interface Foo {
+        [AllowedContext=Hello.kValue]
+        Foo@0(int32 a) => (int32 b);
+        [MinVersion=2,Sync,UnlimitedSize,NoInterrupt]
+        Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c);
+      };
+    """)
+
+  def testWrongModuleStable(self):
+    contents = """
+      // err: module cannot be Stable
+      [Stable]
+      module a;
+      enum Hello { kValue, kValue2, kValue3 };
+      enum NativeEnum {};
+      struct Structure { Hello hi; };
+
+      interface Foo {
+        Foo(int32 a) => (int32 b);
+        Bar(int32 b, Structure? s) => (bool c);
+      };
+    """
+    self._testThrows('b.mojom', contents,
+                     'attribute Stable not allowed on module')
+
+  def testWrongEnumDefault(self):
+    contents = """
+      module a;
+      // err: default should go on EnumValue not Enum.
+      [Default=kValue]
+      enum Hello { kValue, kValue2, kValue3 };
+      enum NativeEnum {};
+      struct Structure { Hello hi; };
+
+      interface Foo {
+        Foo(int32 a) => (int32 b);
+        Bar(int32 b, Structure? s) => (bool c);
+      };
+    """
+    self._testThrows('b.mojom', contents,
+                     'attribute Default not allowed on enum')
+
+  def testWrongStructMinVersion(self):
+    contents = """
+      module a;
+      enum Hello { kValue, kValue2, kValue3 };
+      enum NativeEnum {};
+      // err: struct cannot have MinVersion.
+      [MinVersion=2]
+      struct Structure { Hello hi; };
+
+      interface Foo {
+        Foo(int32 a) => (int32 b);
+        Bar(int32 b, Structure? s) => (bool c);
+      };
+    """
+    self._testThrows('b.mojom', contents,
+                     'attribute MinVersion not allowed on struct')
+
+  def testWrongMethodRequireContext(self):
+    contents = """
+      module a;
+      enum Hello { kValue, kValue2, kValue3 };
+      enum NativeEnum {};
+      struct Structure { Hello hi; };
+
+      interface Foo {
+        // err: RequireContext is for interfaces.
+        [RequireContext=Hello.kValue]
+        Foo(int32 a) => (int32 b);
+        Bar(int32 b, Structure? s) => (bool c);
+      };
+    """
+    self._testThrows('b.mojom', contents,
+                     'RequireContext not allowed on method')
+
+  def testWrongMethodRequireContext(self):
+    # crbug.com/1230122
+    contents = """
+      module a;
+      interface Foo {
+        // err: sync not Sync.
+        [sync]
+        Foo(int32 a) => (int32 b);
+      };
+    """
+    self._testThrows('b.mojom', contents,
+                     'attribute sync not allowed.*Did you mean: Sync')
+
+  def testStableExtensibleEnum(self):
+    # crbug.com/1193875
+    contents = """
+      module a;
+      [Stable]
+      enum Foo {
+        kDefaultVal,
+        kOtherVal = 2,
+      };
+    """
+    self._testThrows('a.mojom', contents,
+                     'Extensible.*?required.*?Stable.*?enum')
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
new file mode 100644
index 00000000..702d41c3
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_definitions_check.py
@@ -0,0 +1,34 @@ 
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Ensure no duplicate type definitions before generation."""
+
+import mojom.generate.check as check
+import mojom.generate.module as module
+
+
+class Check(check.Check):
+  def __init__(self, *args, **kwargs):
+    super(Check, self).__init__(*args, **kwargs)
+
+  def CheckModule(self):
+    kinds = dict()
+    for module in self.module.imports:
+      for kind in module.enums + module.structs + module.unions:
+        kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
+        if kind_name in kinds:
+          previous_module = kinds[kind_name]
+          if previous_module.path != module.path:
+            raise check.CheckException(
+                self.module, f"multiple-definition for type {kind_name}" +
+                f"(defined in both {previous_module} and {module})")
+        kinds[kind_name] = kind.module
+
+    for kind in self.module.enums + self.module.structs + self.module.unions:
+      kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
+      if kind_name in kinds:
+        previous_module = kinds[kind_name]
+        raise check.CheckException(
+            self.module, f"multiple-definition for type {kind_name}" +
+            f"(previous definition in {previous_module})")
+    return True
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
new file mode 100644
index 00000000..d570e26c
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_check.py
@@ -0,0 +1,102 @@ 
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Validate RequireContext and AllowedContext annotations before generation."""
+
+import mojom.generate.check as check
+import mojom.generate.module as module
+
+
+class Check(check.Check):
+  def __init__(self, *args, **kwargs):
+    self.kind_to_interfaces = dict()
+    super(Check, self).__init__(*args, **kwargs)
+
+  def _IsPassedInterface(self, candidate):
+    if isinstance(
+        candidate.kind,
+        (module.PendingReceiver, module.PendingRemote,
+         module.PendingAssociatedReceiver, module.PendingAssociatedRemote)):
+      return True
+    return False
+
+  def _CheckInterface(self, method, param):
+    # |param| is a pending_x<Interface> so need .kind.kind to get Interface.
+    interface = param.kind.kind
+    if interface.require_context:
+      if method.allowed_context is None:
+        raise check.CheckException(
+            self.module, "method `{}` has parameter `{}` which passes interface"
+            " `{}` that requires an AllowedContext annotation but none exists.".
+            format(
+                method.mojom_name,
+                param.mojom_name,
+                interface.mojom_name,
+            ))
+      # If a string was provided, or if an enum was not imported, this will
+      # be a string and we cannot validate that it is in range.
+      if not isinstance(method.allowed_context, module.EnumValue):
+        raise check.CheckException(
+            self.module,
+            "method `{}` has AllowedContext={} which is not a valid enum value."
+            .format(method.mojom_name, method.allowed_context))
+      # EnumValue must be from the same enum to be compared.
+      if interface.require_context.enum != method.allowed_context.enum:
+        raise check.CheckException(
+            self.module, "method `{}` has parameter `{}` which passes interface"
+            " `{}` that requires AllowedContext={} but one of kind `{}` was "
+            "provided.".format(
+                method.mojom_name,
+                param.mojom_name,
+                interface.mojom_name,
+                interface.require_context.enum,
+                method.allowed_context.enum,
+            ))
+      # RestrictContext enums have most privileged field first (lowest value).
+      interface_value = interface.require_context.field.numeric_value
+      method_value = method.allowed_context.field.numeric_value
+      if interface_value < method_value:
+        raise check.CheckException(
+            self.module, "RequireContext={} > AllowedContext={} for method "
+            "`{}` which passes interface `{}`.".format(
+                interface.require_context.GetSpec(),
+                method.allowed_context.GetSpec(), method.mojom_name,
+                interface.mojom_name))
+      return True
+
+  def _GatherReferencedInterfaces(self, field):
+    key = field.kind.spec
+    # structs/unions can nest themselves so we need to bookkeep.
+    if not key in self.kind_to_interfaces:
+      # Might reference ourselves so have to create the list first.
+      self.kind_to_interfaces[key] = set()
+      for param in field.kind.fields:
+        if self._IsPassedInterface(param):
+          self.kind_to_interfaces[key].add(param)
+        elif isinstance(param.kind, (module.Struct, module.Union)):
+          for iface in self._GatherReferencedInterfaces(param):
+            self.kind_to_interfaces[key].add(iface)
+    return self.kind_to_interfaces[key]
+
+  def _CheckParams(self, method, params):
+    # Note: we have to repeat _CheckParams for each method as each might have
+    # different AllowedContext= attributes. We cannot memoize this function,
+    # but can do so for gathering referenced interfaces as their RequireContext
+    # attributes do not change.
+    for param in params:
+      if self._IsPassedInterface(param):
+        self._CheckInterface(method, param)
+      elif isinstance(param.kind, (module.Struct, module.Union)):
+        for interface in self._GatherReferencedInterfaces(param):
+          self._CheckInterface(method, interface)
+
+  def _CheckMethod(self, method):
+    if method.parameters:
+      self._CheckParams(method, method.parameters)
+    if method.response_parameters:
+      self._CheckParams(method, method.response_parameters)
+
+  def CheckModule(self):
+    for interface in self.module.interfaces:
+      for method in interface.methods:
+        self._CheckMethod(method)
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
new file mode 100644
index 00000000..a6cd71e2
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/checks/mojom_restrictions_checks_unittest.py
@@ -0,0 +1,254 @@ 
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import mojom.generate.check as check
+from mojom_bindings_generator import LoadChecks, _Generate
+from mojom_parser_test_case import MojomParserTestCase
+
+# Mojoms that we will use in multiple tests.
+basic_mojoms = {
+    'level.mojom':
+    """
+  module level;
+  enum Level {
+    kHighest,
+    kMiddle,
+    kLowest,
+  };
+  """,
+    'interfaces.mojom':
+    """
+  module interfaces;
+  import "level.mojom";
+  struct Foo {int32 bar;};
+  [RequireContext=level.Level.kHighest]
+  interface High {
+    DoFoo(Foo foo);
+  };
+  [RequireContext=level.Level.kMiddle]
+  interface Mid {
+    DoFoo(Foo foo);
+  };
+  [RequireContext=level.Level.kLowest]
+  interface Low {
+    DoFoo(Foo foo);
+  };
+  """
+}
+
+
+class FakeArgs:
+  """Fakes args to _Generate - intention is to do just enough to run checks"""
+
+  def __init__(self, tester, files=None):
+    """ `tester` is MojomParserTestCase for paths.
+        `files` will have tester path added."""
+    self.checks_string = 'restrictions'
+    self.depth = tester.GetPath('')
+    self.filelist = None
+    self.filename = [tester.GetPath(x) for x in files]
+    self.gen_directories = tester.GetPath('gen')
+    self.generators_string = ''
+    self.import_directories = []
+    self.output_dir = tester.GetPath('out')
+    self.scrambled_message_id_salt_paths = None
+    self.typemaps = []
+    self.variant = 'none'
+
+
+class MojoBindingsCheckTest(MojomParserTestCase):
+  def _WriteBasicMojoms(self):
+    for filename, contents in basic_mojoms.items():
+      self.WriteFile(filename, contents)
+    return list(basic_mojoms.keys())
+
+  def _ParseAndGenerate(self, mojoms):
+    self.ParseMojoms(mojoms)
+    args = FakeArgs(self, files=mojoms)
+    _Generate(args, {})
+
+  def testLoads(self):
+    """Validate that the check is registered under the expected name."""
+    check_modules = LoadChecks('restrictions')
+    self.assertTrue(check_modules['restrictions'])
+
+  def testValidAnnotations(self):
+    mojoms = self._WriteBasicMojoms()
+
+    a = 'a.mojom'
+    self.WriteFile(
+        a, """
+      module a;
+      import "level.mojom";
+      import "interfaces.mojom";
+
+      interface PassesHigh {
+        [AllowedContext=level.Level.kHighest]
+        DoHigh(pending_receiver<interfaces.High> hi);
+      };
+      interface PassesMedium {
+        [AllowedContext=level.Level.kMiddle]
+        DoMedium(pending_receiver<interfaces.Mid> hi);
+        [AllowedContext=level.Level.kMiddle]
+        DoMediumRem(pending_remote<interfaces.Mid> hi);
+        [AllowedContext=level.Level.kMiddle]
+        DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);
+        [AllowedContext=level.Level.kMiddle]
+        DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);
+      };
+      interface PassesLow {
+        [AllowedContext=level.Level.kLowest]
+        DoLow(pending_receiver<interfaces.Low> hi);
+      };
+
+      struct One { pending_receiver<interfaces.High> hi; };
+      struct Two { One one; };
+      interface PassesNestedHigh {
+        [AllowedContext=level.Level.kHighest]
+        DoNestedHigh(Two two);
+      };
+
+      // Allowed as PassesHigh is not itself restricted.
+      interface PassesPassesHigh {
+        DoPass(pending_receiver<PassesHigh> hiho);
+      };
+    """)
+    mojoms.append(a)
+    self._ParseAndGenerate(mojoms)
+
+  def _testThrows(self, filename, content, regexp):
+    mojoms = self._WriteBasicMojoms()
+    self.WriteFile(filename, content)
+    mojoms.append(filename)
+    with self.assertRaisesRegexp(check.CheckException, regexp):
+      self._ParseAndGenerate(mojoms)
+
+  def testMissingAnnotation(self):
+    contents = """
+      module b;
+      import "level.mojom";
+      import "interfaces.mojom";
+
+      interface PassesHigh {
+        // err: missing annotation.
+        DoHigh(pending_receiver<interfaces.High> hi);
+      };
+    """
+    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
+
+  def testAllowTooLow(self):
+    contents = """
+      module b;
+      import "level.mojom";
+      import "interfaces.mojom";
+
+      interface PassesHigh {
+        // err: level is worse than required.
+        [AllowedContext=level.Level.kMiddle]
+        DoHigh(pending_receiver<interfaces.High> hi);
+      };
+    """
+    self._testThrows('b.mojom', contents,
+                     'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
+
+  def testWrongEnumInAllow(self):
+    contents = """
+      module b;
+      import "level.mojom";
+      import "interfaces.mojom";
+      enum Blah {
+        kZero,
+      };
+      interface PassesHigh {
+        // err: different enums.
+        [AllowedContext=Blah.kZero]
+        DoHigh(pending_receiver<interfaces.High> hi);
+      };
+    """
+    self._testThrows('b.mojom', contents, 'but one of kind')
+
+  def testNotAnEnumInAllow(self):
+    contents = """
+      module b;
+      import "level.mojom";
+      import "interfaces.mojom";
+      interface PassesHigh {
+        // err: not an enum.
+        [AllowedContext=doopdedoo.mojom.kWhatever]
+        DoHigh(pending_receiver<interfaces.High> hi);
+      };
+    """
+    self._testThrows('b.mojom', contents, 'not a valid enum value')
+
+  def testMissingAllowedForNestedStructs(self):
+    contents = """
+      module b;
+      import "level.mojom";
+      import "interfaces.mojom";
+      struct One { pending_receiver<interfaces.High> hi; };
+      struct Two { One one; };
+      interface PassesNestedHigh {
+        // err: missing annotation.
+        DoNestedHigh(Two two);
+      };
+    """
+    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
+
+  def testMissingAllowedForNestedUnions(self):
+    contents = """
+      module b;
+      import "level.mojom";
+      import "interfaces.mojom";
+      struct One { pending_receiver<interfaces.High> hi; };
+      struct Two { One one; };
+      union Three {One one; Two two; };
+      interface PassesNestedHigh {
+        // err: missing annotation.
+        DoNestedHigh(Three three);
+      };
+    """
+    self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
+
+  def testMultipleInterfacesThrows(self):
+    contents = """
+      module b;
+      import "level.mojom";
+      import "interfaces.mojom";
+      struct One { pending_receiver<interfaces.High> hi; };
+      interface PassesMultipleInterfaces {
+        [AllowedContext=level.Level.kMiddle]
+        DoMultiple(
+          pending_remote<interfaces.Mid> mid,
+          pending_receiver<interfaces.High> hi,
+          One one
+        );
+      };
+    """
+    self._testThrows('b.mojom', contents,
+                     'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
+
+  def testMultipleInterfacesAllowed(self):
+    """Multiple interfaces can be passed, all satisfy the level."""
+    mojoms = self._WriteBasicMojoms()
+
+    b = "b.mojom"
+    self.WriteFile(
+        b, """
+      module b;
+      import "level.mojom";
+      import "interfaces.mojom";
+      struct One { pending_receiver<interfaces.High> hi; };
+      interface PassesMultipleInterfaces {
+        [AllowedContext=level.Level.kHighest]
+        DoMultiple(
+          pending_receiver<interfaces.High> hi,
+          pending_remote<interfaces.Mid> mid,
+          One one
+        );
+      };
+    """)
+    mojoms.append(b)
+    self._ParseAndGenerate(mojoms)
diff --git a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
index 48bc66fd..4dd26d4a 100755
--- a/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
+++ b/utils/ipc/mojo/public/tools/bindings/concatenate-files.py
@@ -1,5 +1,5 @@ 
 #!/usr/bin/env python
-# Copyright 2019 The Chromium Authors. All rights reserved.
+# Copyright 2019 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 #
@@ -15,6 +15,7 @@ 
 from __future__ import print_function
 
 import optparse
+import sys
 
 
 def Concatenate(filenames):
@@ -47,7 +48,7 @@  def main():
   parser.set_usage("""Concatenate several files into one.
       Equivalent to: cat file1 ... > target.""")
   (_options, args) = parser.parse_args()
-  exit(0 if Concatenate(args) else 1)
+  sys.exit(0 if Concatenate(args) else 1)
 
 
 if __name__ == "__main__":
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
index be8985ce..770081e1 100755
--- 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
@@ -1,5 +1,5 @@ 
 #!/usr/bin/env python
-# Copyright 2018 The Chromium Authors. All rights reserved.
+# Copyright 2018 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -20,6 +20,7 @@  from __future__ import print_function
 
 import optparse
 import re
+import sys
 
 
 _MOJO_INTERNAL_MODULE_NAME = "mojo.internal"
@@ -34,7 +35,7 @@  def FilterLine(filename, line, output):
     match = re.match("goog.provide\('([^']+)'\);", line)
     if not match:
       print("Invalid goog.provide line in %s:\n%s" % (filename, line))
-      exit(1)
+      sys.exit(1)
 
     module_name = match.group(1)
     if module_name == _MOJO_INTERNAL_MODULE_NAME:
@@ -67,7 +68,8 @@  def main():
     Concatenate several files into one, stripping Closure provide and
     require directives along the way.""")
   (_, args) = parser.parse_args()
-  exit(0 if ConcatenateAndReplaceExports(args) else 1)
+  sys.exit(0 if ConcatenateAndReplaceExports(args) else 1)
+
 
 if __name__ == "__main__":
   main()
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
index 8b78d092..c6daff03 100644
--- a/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
+++ b/utils/ipc/mojo/public/tools/bindings/gen_data_files_list.py
@@ -1,4 +1,4 @@ 
-# Copyright 2017 The Chromium Authors. All rights reserved.
+# Copyright 2017 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Generates a list of all files in a directory.
diff --git a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
index a0096649..d73c6ea4 100755
--- a/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
+++ b/utils/ipc/mojo/public/tools/bindings/generate_type_mappings.py
@@ -1,5 +1,5 @@ 
 #!/usr/bin/env python
-# Copyright 2016 The Chromium Authors. All rights reserved.
+# Copyright 2016 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Generates a JSON typemap from its command-line arguments and dependencies.
@@ -82,6 +82,7 @@  def LoadCppTypemapConfig(path):
       for entry in config['types']:
         configs[entry['mojom']] = {
             'typename': entry['cpp'],
+            'forward_declaration': entry.get('forward_declaration', None),
             'public_headers': config.get('traits_headers', []),
             'traits_headers': config.get('traits_private_headers', []),
             'copyable_pass_by_value': entry.get('copyable_pass_by_value',
diff --git a/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
new file mode 100755
index 00000000..cefee7a4
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/bindings/minify_with_terser.py
@@ -0,0 +1,47 @@ 
+#!/usr/bin/env python3
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# This utility minifies JS files with terser.
+#
+# Instance of 'node' has no 'RunNode' member (no-member)
+# pylint: disable=no-member
+
+import argparse
+import os
+import sys
+
+_HERE_PATH = os.path.dirname(__file__)
+_SRC_PATH = os.path.normpath(os.path.join(_HERE_PATH, '..', '..', '..', '..'))
+_CWD = os.getcwd()
+sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'node'))
+import node
+import node_modules
+
+
+def MinifyFile(input_file, output_file):
+  node.RunNode([
+      node_modules.PathToTerser(), input_file, '--mangle', '--compress',
+      '--comments', 'false', '--output', output_file
+  ])
+
+
+def main(argv):
+  parser = argparse.ArgumentParser()
+  parser.add_argument('--input', required=True)
+  parser.add_argument('--output', required=True)
+  args = parser.parse_args(argv)
+
+  # Delete the output file if it already exists. It may be a sym link to the
+  # input, because in non-optimized/pre-Terser builds the input file is copied
+  # to the output location with gn copy().
+  out_path = os.path.join(_CWD, args.output)
+  if (os.path.exists(out_path)):
+    os.remove(out_path)
+
+  MinifyFile(os.path.join(_CWD, args.input), out_path)
+
+
+if __name__ == '__main__':
+  main(sys.argv[1:])
diff --git a/utils/ipc/mojo/public/tools/bindings/mojom.gni b/utils/ipc/mojo/public/tools/bindings/mojom.gni
index fe2a1da3..ded53259 100644
--- a/utils/ipc/mojo/public/tools/bindings/mojom.gni
+++ b/utils/ipc/mojo/public/tools/bindings/mojom.gni
@@ -1,11 +1,11 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//build/config/python.gni")
 import("//third_party/closure_compiler/closure_args.gni")
 import("//third_party/closure_compiler/compile_js.gni")
 import("//third_party/protobuf/proto_library.gni")
+import("//ui/webui/resources/tools/generate_grd.gni")
 import("//ui/webui/webui_features.gni")
 
 # TODO(rockot): Maybe we can factor these dependencies out of //mojo. They're
@@ -16,10 +16,12 @@  import("//ui/webui/webui_features.gni")
 import("//build/config/chrome_build.gni")
 import("//build/config/chromecast_build.gni")
 import("//build/config/chromeos/ui_mode.gni")
+import("//build/config/features.gni")
 import("//build/config/nacl/config.gni")
 import("//build/toolchain/kythe.gni")
 import("//components/nacl/features.gni")
 import("//third_party/jinja2/jinja2.gni")
+import("//third_party/ply/ply.gni")
 import("//tools/ipc_fuzzer/ipc_fuzzer.gni")
 declare_args() {
   # Indicates whether typemapping should be supported in this build
@@ -38,17 +40,25 @@  declare_args() {
   # scrambling on all platforms.
   enable_mojom_message_id_scrambling = true
 
+  # Enables generating javascript fuzzing-related code and the bindings for the
+  # MojoLPM fuzzer targets. Off by default.
+  enable_mojom_fuzzer = false
+
   # Enables Closure compilation of generated JS lite bindings. In environments
   # where compilation is supported, any mojom target "foo" will also have a
   # corresponding "foo_js_library_for_compile" target generated.
-  enable_mojom_closure_compile = enable_js_type_check && optimize_webui
-
-  # Enables generating Typescript bindings and compiling them to JS bindings.
-  enable_typescript_bindings = false
+  if (is_chromeos_ash) {
+    enable_mojom_closure_compile = enable_js_type_check && optimize_webui
+  }
+}
 
-  # Enables generating javascript fuzzing-related code and the bindings for the
-  # MojoLPM fuzzer targets. Off by default.
-  enable_mojom_fuzzer = false
+# Closure libraries are needed for mojom_closure_compile, and when
+# js_type_check is enabled on Ash.
+if (is_chromeos_ash) {
+  generate_mojom_closure_libraries =
+      enable_mojom_closure_compile || enable_js_type_check
+} else {
+  generate_mojom_closure_libraries = false
 }
 
 # NOTE: We would like to avoid scrambling message IDs where it doesn't add
@@ -69,9 +79,8 @@  declare_args() {
 # lacros-chrome switches to target_os="chromeos"
 enable_scrambled_message_ids =
     enable_mojom_message_id_scrambling &&
-    (is_mac || is_win ||
-     (is_linux && !is_chromeos_ash && !is_chromecast && !is_chromeos_lacros) ||
-     ((enable_nacl || is_nacl || is_nacl_nonsfi) &&
+    (is_mac || is_win || (is_linux && !is_castos) ||
+     ((enable_nacl || is_nacl) &&
       (target_os != "chromeos" && !chromeos_is_browser_only)))
 
 _mojom_tools_root = "//mojo/public/tools"
@@ -80,7 +89,9 @@  mojom_parser_script = "$_mojom_tools_root/mojom/mojom_parser.py"
 mojom_parser_sources = [
   "$_mojom_library_root/__init__.py",
   "$_mojom_library_root/error.py",
+  "$_mojom_library_root/fileutil.py",
   "$_mojom_library_root/generate/__init__.py",
+  "$_mojom_library_root/generate/check.py",
   "$_mojom_library_root/generate/generator.py",
   "$_mojom_library_root/generate/module.py",
   "$_mojom_library_root/generate/pack.py",
@@ -88,20 +99,28 @@  mojom_parser_sources = [
   "$_mojom_library_root/generate/translate.py",
   "$_mojom_library_root/parse/__init__.py",
   "$_mojom_library_root/parse/ast.py",
+  "$_mojom_library_root/parse/conditional_features.py",
   "$_mojom_library_root/parse/lexer.py",
   "$_mojom_library_root/parse/parser.py",
+  "//tools/diagnosis/crbug_1001171.py",
 ]
 
 mojom_generator_root = "$_mojom_tools_root/bindings"
 mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py"
 mojom_generator_sources =
     mojom_parser_sources + [
+      "$mojom_generator_root/checks/__init__.py",
+      "$mojom_generator_root/checks/mojom_attributes_check.py",
+      "$mojom_generator_root/checks/mojom_definitions_check.py",
+      "$mojom_generator_root/checks/mojom_restrictions_check.py",
+      "$mojom_generator_root/generators/__init__.py",
       "$mojom_generator_root/generators/cpp_util.py",
       "$mojom_generator_root/generators/mojom_cpp_generator.py",
       "$mojom_generator_root/generators/mojom_java_generator.py",
-      "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
       "$mojom_generator_root/generators/mojom_js_generator.py",
+      "$mojom_generator_root/generators/mojom_mojolpm_generator.py",
       "$mojom_generator_root/generators/mojom_ts_generator.py",
+      "$mojom_generator_root/generators/mojom_webui_js_bridge_generator.py",
       "$mojom_generator_script",
     ]
 
@@ -243,12 +262,16 @@  if (enable_scrambled_message_ids) {
 #       |cpp_only| is set to true, it overrides this to prevent generation of
 #       Java bindings.
 #
-#   enable_fuzzing (optional)
+#   enable_js_fuzzing (optional)
+#       Enables generation of javascript fuzzing sources for the target if the
+#       global build arg |enable_mojom_fuzzer| is also set to |true|.
+#       Defaults to |true|. If JS fuzzing generation is enabled for a target,
+#       the target will always generate JS bindings even if |cpp_only| is set to
+#       |true|. See note above.
+#
+#   enable_mojolpm_fuzzing (optional)
 #       Enables generation of fuzzing sources for the target if the global build
-#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|. If
-#       fuzzing generation is enabled for a target, the target will always
-#       generate JS bindings even if |cpp_only| is set to |true|. See note
-#       above.
+#       arg |enable_mojom_fuzzer| is also set to |true|. Defaults to |true|.
 #
 #   support_lazy_serialization (optional)
 #       If set to |true|, generated C++ bindings will effectively prefer to
@@ -313,6 +336,16 @@  if (enable_scrambled_message_ids) {
 #   use_typescript_sources (optional)
 #       Uses the Typescript generator to generate JavaScript bindings.
 #
+#   generate_legacy_js_bindings (optional)
+#       Generate js_data_deps target containing legacy JavaScript bindings files
+#       for Blink tests and other non-WebUI users when generating TypeScript
+#       bindings for WebUI. Ignored if use_typescript_sources is not set to
+#       true.
+#
+#   webui_js_bridge_config (optional)
+#       Prefer to use the `mojom_with_webui_js_bridge` target below instead
+#       of using this argument directly.
+#
 #   js_generate_struct_deserializers (optional)
 #       Generates JS deerialize methods for structs.
 #
@@ -402,6 +435,12 @@  if (enable_scrambled_message_ids) {
 #             should be mapped in generated bindings. This is a string like
 #             "::base::Value" or "std::vector<::base::Value>".
 #
+#         forward_declaration (optional)
+#             A forward declaration of the C++ type, which bindings that don't
+#             need the full type definition can use to reduce the size of
+#             the generated code. This is a string like
+#             "namespace base { class Value; }".
+#
 #         move_only (optional)
 #             A boolean value (default false) which indicates whether the C++
 #             type is move-only. If true, generated bindings will pass the type
@@ -621,20 +660,26 @@  template("mojom") {
   build_metadata_filename = "$target_gen_dir/$target_name.build_metadata"
   build_metadata = {
   }
-  build_metadata.sources = rebase_path(sources_list)
+  build_metadata.sources = rebase_path(sources_list, target_gen_dir)
   build_metadata.deps = []
   foreach(dep, all_deps) {
     dep_target_gen_dir = get_label_info(dep, "target_gen_dir")
     dep_name = get_label_info(dep, "name")
     build_metadata.deps +=
-        [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata") ]
+        [ rebase_path("$dep_target_gen_dir/$dep_name.build_metadata",
+                      target_gen_dir) ]
   }
   write_file(build_metadata_filename, build_metadata, "json")
 
-  generate_fuzzing =
-      (!defined(invoker.enable_fuzzing) || invoker.enable_fuzzing) &&
+  generate_js_fuzzing =
+      (!defined(invoker.enable_js_fuzzing) || invoker.enable_js_fuzzing) &&
       enable_mojom_fuzzer && (!defined(invoker.testonly) || !invoker.testonly)
 
+  generate_mojolpm_fuzzing =
+      (!defined(invoker.enable_mojolpm_fuzzing) ||
+       invoker.enable_mojolpm_fuzzing) && enable_mojom_fuzzer &&
+      (!defined(invoker.testonly) || !invoker.testonly)
+
   parser_target_name = "${target_name}__parser"
   parser_deps = []
   foreach(dep, all_deps) {
@@ -683,12 +728,17 @@  template("mojom") {
       enabled_features += [ "is_win" ]
     }
 
-    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-    python2_action(parser_target_name) {
+    if (is_apple) {
+      enabled_features += [ "is_apple" ]
+    }
+
+    action(parser_target_name) {
+      allow_remote = true
+      custom_processor = "mojom_parser"
       script = mojom_parser_script
-      inputs = mojom_parser_sources + [ build_metadata_filename ]
+      inputs = mojom_parser_sources + ply_sources + [ build_metadata_filename ]
       sources = sources_list
-      deps = parser_deps
+      public_deps = parser_deps
       outputs = []
       foreach(base_path, output_file_base_paths) {
         filename = get_path_info(base_path, "file")
@@ -698,31 +748,35 @@  template("mojom") {
 
       filelist = []
       foreach(source, sources_list) {
-        filelist += [ rebase_path(source) ]
+        filelist += [ rebase_path(source, root_build_dir) ]
       }
-      response_file_contents = filelist
+
+      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+      rsp_file = "$target_gen_dir/${target_name}.rsp"
+      write_file(rsp_file, filelist)
+      inputs += [ rsp_file ]
 
       args = [
         # Resolve relative input mojom paths against both the root src dir and
         # the root gen dir.
         "--input-root",
-        rebase_path("//."),
+        rebase_path("//.", root_build_dir),
         "--input-root",
-        rebase_path(root_gen_dir),
+        rebase_path(root_gen_dir, root_build_dir),
 
         "--output-root",
-        rebase_path(root_gen_dir),
+        rebase_path(root_gen_dir, root_build_dir),
 
-        "--mojom-file-list={{response_file_name}}",
+        "--mojom-file-list=" + rebase_path(rsp_file, root_build_dir),
 
         "--check-imports",
-        rebase_path(build_metadata_filename),
+        rebase_path(build_metadata_filename, root_build_dir),
       ]
 
       if (defined(invoker.input_root_override)) {
         args += [
           "--input-root",
-          rebase_path(invoker.input_root_override),
+          rebase_path(invoker.input_root_override, root_build_dir),
         ]
       }
 
@@ -738,6 +792,9 @@  template("mojom") {
           "--add-module-metadata",
           "webui_module_path=${invoker.webui_module_path}",
         ]
+        if (defined(invoker.generate_legacy_js_bindings)) {
+          args += [ "legacy_js_only=${invoker.generate_legacy_js_bindings}" ]
+        }
       }
     }
   }
@@ -819,11 +876,12 @@  template("mojom") {
       }
     }
 
-    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-    python2_action(generator_cpp_message_ids_target_name) {
+    action(generator_cpp_message_ids_target_name) {
+      allow_remote = true
       script = mojom_generator_script
       inputs = mojom_generator_sources + jinja2_sources
-      sources = sources_list
+      sources = sources_list +
+                [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
       deps = [
         ":$parser_target_name",
         "//mojo/public/tools/bindings:precompile_templates",
@@ -835,16 +893,22 @@  template("mojom") {
       args = common_generator_args
       filelist = []
       foreach(source, sources_list) {
-        filelist += [ rebase_path("$source", root_build_dir) ]
+        filelist += [ rebase_path(source, root_build_dir) ]
       }
       foreach(base_path, output_file_base_paths) {
+        filename = get_path_info(base_path, "file")
+        dirname = get_path_info(base_path, "dir")
+        inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
         outputs += [ "$root_gen_dir/$base_path-shared-message-ids.h" ]
       }
 
-      response_file_contents = filelist
+      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+      rsp_file = "$target_gen_dir/${target_name}.rsp"
+      write_file(rsp_file, filelist)
+      inputs += [ rsp_file ]
 
       args += [
-        "--filelist={{response_file_name}}",
+        "--filelist=" + rebase_path(rsp_file, root_build_dir),
         "--generate_non_variant_code",
         "--generate_message_ids",
         "-g",
@@ -860,12 +924,13 @@  template("mojom") {
 
     generator_shared_target_name = "${target_name}_shared__generator"
 
-    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-    python2_action(generator_shared_target_name) {
+    action(generator_shared_target_name) {
+      allow_remote = true
       visibility = [ ":*" ]
       script = mojom_generator_script
       inputs = mojom_generator_sources + jinja2_sources
-      sources = sources_list
+      sources = sources_list +
+                [ "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip" ]
       deps = [
         ":$parser_target_name",
         "//mojo/public/tools/bindings:precompile_templates",
@@ -878,9 +943,14 @@  template("mojom") {
       args = common_generator_args
       filelist = []
       foreach(source, sources_list) {
-        filelist += [ rebase_path("$source", root_build_dir) ]
+        filelist += [ rebase_path(source, root_build_dir) ]
       }
       foreach(base_path, output_file_base_paths) {
+        # Need the mojom-module as an input to this action.
+        filename = get_path_info(base_path, "file")
+        dirname = get_path_info(base_path, "dir")
+        inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
+
         outputs += [
           "$root_gen_dir/$base_path-params-data.h",
           "$root_gen_dir/$base_path-shared-internal.h",
@@ -889,10 +959,13 @@  template("mojom") {
         ]
       }
 
-      response_file_contents = filelist
+      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+      rsp_file = "$target_gen_dir/${target_name}.rsp"
+      write_file(rsp_file, filelist)
+      inputs += [ rsp_file ]
 
       args += [
-        "--filelist={{response_file_name}}",
+        "--filelist=" + rebase_path(rsp_file, root_build_dir),
         "--generate_non_variant_code",
         "-g",
         "c++",
@@ -972,7 +1045,7 @@  template("mojom") {
     }
   }
 
-  if (generate_fuzzing) {
+  if (generate_mojolpm_fuzzing) {
     # This block generates the proto files used for the MojoLPM fuzzer,
     # and the corresponding proto targets that will be linked in the fuzzer
     # targets. These are independent of the typemappings, and can be done
@@ -981,11 +1054,15 @@  template("mojom") {
     generator_mojolpm_proto_target_name =
         "${target_name}_mojolpm_proto_generator"
 
-    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-    python2_action(generator_mojolpm_proto_target_name) {
+    action(generator_mojolpm_proto_target_name) {
+      allow_remote = true
       script = mojom_generator_script
       inputs = mojom_generator_sources + jinja2_sources
-      sources = invoker.sources
+      sources =
+          invoker.sources + [
+            "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
+            "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
+          ]
       deps = [
         ":$parser_target_name",
         "//mojo/public/tools/bindings:precompile_templates",
@@ -994,15 +1071,37 @@  template("mojom") {
       outputs = []
       args = common_generator_args
       filelist = []
-      foreach(source, invoker.sources) {
-        filelist += [ rebase_path("$source", root_build_dir) ]
+
+      # Split the input into generated and non-generated source files. They
+      # need to be processed separately.
+      gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
+      non_gen_sources =
+          filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
+      gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])
+
+      foreach(source, non_gen_sources) {
+        filelist += [ rebase_path(source, root_build_dir) ]
+        inputs += [ "$target_gen_dir/$source-module" ]
         outputs += [ "$target_gen_dir/$source.mojolpm.proto" ]
       }
 
-      response_file_contents = filelist
+      foreach(source, gen_sources) {
+        filelist += [ rebase_path(source, root_build_dir) ]
+
+        # For generated files, we assume they're in the target_gen_dir or a
+        # sub-folder of it. Rebase the path so we can get the relative location.
+        source_file = rebase_path(source, target_gen_dir)
+        inputs += [ "$target_gen_dir/$source_file-module" ]
+        outputs += [ "$target_gen_dir/$source_file.mojolpm.proto" ]
+      }
+
+      # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+      rsp_file = "$target_gen_dir/${target_name}.rsp"
+      write_file(rsp_file, filelist)
+      inputs += [ rsp_file ]
 
       args += [
-        "--filelist={{response_file_name}}",
+        "--filelist=" + rebase_path(rsp_file, root_build_dir),
         "--generate_non_variant_code",
         "-g",
         "mojolpm",
@@ -1014,9 +1113,20 @@  template("mojom") {
       proto_library(mojolpm_proto_target_name) {
         testonly = true
         generate_python = false
+
+        # Split the input into generated and non-generated source files. They
+        # need to be processed separately.
+        gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
+        non_gen_sources =
+            filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
+        gen_sources = filter_include(invoker.sources, [ gen_dir_path_wildcard ])
         sources = process_file_template(
-                invoker.sources,
+                non_gen_sources,
                 [ "{{source_gen_dir}}/{{source_file_part}}.mojolpm.proto" ])
+        sources += process_file_template(
+                gen_sources,
+                [ "{{source_dir}}/{{source_file_part}}.mojolpm.proto" ])
+
         import_dirs = [ "//" ]
         proto_in_dir = "${root_gen_dir}"
         proto_out_dir = "."
@@ -1055,7 +1165,7 @@  template("mojom") {
     component_macro_suffix = ""
   }
   if ((!defined(invoker.disable_variants) || !invoker.disable_variants) &&
-      !is_ios) {
+      use_blink) {
     blink_variant = {
       variant = "blink"
       component_macro_suffix = "_BLINK"
@@ -1149,39 +1259,6 @@  template("mojom") {
             "${bindings_configuration.component_macro_suffix}_IMPL" ]
     }
 
-    export_args = []
-    export_args_overridden = false
-    if (defined(bindings_configuration.for_blink) &&
-        bindings_configuration.for_blink) {
-      if (defined(invoker.export_class_attribute_blink)) {
-        export_args_overridden = true
-        export_args += [
-          "--export_attribute",
-          invoker.export_class_attribute_blink,
-          "--export_header",
-          invoker.export_header_blink,
-        ]
-      }
-    } else if (defined(invoker.export_class_attribute)) {
-      export_args_overridden = true
-      export_args += [
-        "--export_attribute",
-        invoker.export_class_attribute,
-        "--export_header",
-        invoker.export_header,
-      ]
-    }
-
-    if (!export_args_overridden && defined(invoker.component_macro_prefix)) {
-      export_args += [
-        "--export_attribute",
-        "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
-            "${bindings_configuration.component_macro_suffix})",
-        "--export_header",
-        "base/component_export.h",
-      ]
-    }
-
     generate_java = false
     if (!cpp_only && defined(invoker.generate_java)) {
       generate_java = invoker.generate_java
@@ -1190,6 +1267,38 @@  template("mojom") {
     type_mappings_path =
         "$target_gen_dir/${target_name}${variant_suffix}__type_mappings"
     if (sources_list != []) {
+      export_args = []
+      export_args_overridden = false
+      if (defined(bindings_configuration.for_blink) &&
+          bindings_configuration.for_blink) {
+        if (defined(invoker.export_class_attribute_blink)) {
+          export_args_overridden = true
+          export_args += [
+            "--export_attribute",
+            invoker.export_class_attribute_blink,
+            "--export_header",
+            invoker.export_header_blink,
+          ]
+        }
+      } else if (defined(invoker.export_class_attribute)) {
+        export_args_overridden = true
+        export_args += [
+          "--export_attribute",
+          invoker.export_class_attribute,
+          "--export_header",
+          invoker.export_header,
+        ]
+      }
+      if (!export_args_overridden && defined(invoker.component_macro_prefix)) {
+        export_args += [
+          "--export_attribute",
+          "COMPONENT_EXPORT(${invoker.component_macro_prefix}" +
+              "${bindings_configuration.component_macro_suffix})",
+          "--export_header",
+          "base/component_export.h",
+        ]
+      }
+
       generator_cpp_output_suffixes = []
       variant_dash_suffix = ""
       if (defined(variant)) {
@@ -1198,7 +1307,6 @@  template("mojom") {
       generator_cpp_output_suffixes += [
         "${variant_dash_suffix}-forward.h",
         "${variant_dash_suffix}-import-headers.h",
-        "${variant_dash_suffix}-test-utils.cc",
         "${variant_dash_suffix}-test-utils.h",
         "${variant_dash_suffix}.cc",
         "${variant_dash_suffix}.h",
@@ -1207,16 +1315,28 @@  template("mojom") {
       generator_target_name = "${target_name}${variant_suffix}__generator"
 
       # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-      python2_action(generator_target_name) {
+      action(generator_target_name) {
+        allow_remote = true
         visibility = [ ":*" ]
         script = mojom_generator_script
         inputs = mojom_generator_sources + jinja2_sources
-        sources = sources_list
+        sources =
+            sources_list + [
+              "$root_gen_dir/mojo/public/tools/bindings/cpp_templates.zip",
+              type_mappings_path,
+            ]
+        if (generate_mojolpm_fuzzing &&
+            !defined(bindings_configuration.variant)) {
+          sources += [
+            "$root_gen_dir/mojo/public/tools/bindings/mojolpm_templates.zip",
+          ]
+        }
         deps = [
           ":$parser_target_name",
           ":$type_mappings_target_name",
           "//mojo/public/tools/bindings:precompile_templates",
         ]
+
         if (defined(invoker.parser_deps)) {
           deps += invoker.parser_deps
         }
@@ -1224,18 +1344,22 @@  template("mojom") {
         args = common_generator_args + export_args
         filelist = []
         foreach(source, sources_list) {
-          filelist += [ rebase_path("$source", root_build_dir) ]
+          filelist += [ rebase_path(source, root_build_dir) ]
         }
         foreach(base_path, output_file_base_paths) {
+          filename = get_path_info(base_path, "file")
+          dirname = get_path_info(base_path, "dir")
+          inputs += [ "$root_gen_dir/$dirname/${filename}-module" ]
+
           outputs += [
             "$root_gen_dir/${base_path}${variant_dash_suffix}-forward.h",
             "$root_gen_dir/${base_path}${variant_dash_suffix}-import-headers.h",
-            "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.cc",
             "$root_gen_dir/${base_path}${variant_dash_suffix}-test-utils.h",
             "$root_gen_dir/${base_path}${variant_dash_suffix}.cc",
             "$root_gen_dir/${base_path}${variant_dash_suffix}.h",
           ]
-          if (generate_fuzzing && !defined(bindings_configuration.variant)) {
+          if (generate_mojolpm_fuzzing &&
+              !defined(bindings_configuration.variant)) {
             outputs += [
               "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.cc",
               "$root_gen_dir/${base_path}${variant_dash_suffix}-mojolpm.h",
@@ -1243,14 +1367,17 @@  template("mojom") {
           }
         }
 
-        response_file_contents = filelist
-
+        # Workaround for https://github.com/ninja-build/ninja/issues/1966.
+        rsp_file = "$target_gen_dir/${target_name}.rsp"
+        write_file(rsp_file, filelist)
+        inputs += [ rsp_file ]
         args += [
-          "--filelist={{response_file_name}}",
+          "--filelist=" + rebase_path("$rsp_file", root_build_dir),
           "-g",
         ]
 
-        if (generate_fuzzing && !defined(bindings_configuration.variant)) {
+        if (generate_mojolpm_fuzzing &&
+            !defined(bindings_configuration.variant)) {
           args += [ "c++,mojolpm" ]
         } else {
           args += [ "c++" ]
@@ -1294,6 +1421,8 @@  template("mojom") {
               "--extra_cpp_template_paths",
               rebase_path(extra_cpp_template, root_build_dir),
             ]
+            inputs += [ extra_cpp_template ]
+
             assert(
                 get_path_info(extra_cpp_template, "extension") == "tmpl",
                 "--extra_cpp_template_paths only accepts template files ending in extension .tmpl")
@@ -1306,62 +1435,6 @@  template("mojom") {
       }
     }
 
-    if (generate_fuzzing && !defined(variant)) {
-      # This block contains the C++ targets for the MojoLPM fuzzer, we need to
-      # do this here so that we can use the typemap configuration for the
-      # empty-variant Mojo target.
-
-      mojolpm_target_name = "${target_name}_mojolpm"
-      mojolpm_generator_target_name = "${target_name}__generator"
-      source_set(mojolpm_target_name) {
-        # There are still a few missing header dependencies between mojo targets
-        # with typemaps and the dependencies of their typemap headers. It would
-        # be good to enable include checking for these in the future though.
-        check_includes = false
-        testonly = true
-        if (defined(invoker.sources)) {
-          sources = process_file_template(
-                  invoker.sources,
-                  [
-                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
-                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
-                  ])
-          deps = []
-        } else {
-          sources = []
-          deps = []
-        }
-
-        public_deps = [
-          ":$generator_shared_target_name",
-
-          # NB: hardcoded dependency on the no-variant variant generator, since
-          # mojolpm only uses the no-variant type.
-          ":$mojolpm_generator_target_name",
-          ":$mojolpm_proto_target_name",
-          "//base",
-          "//mojo/public/tools/fuzzers:mojolpm",
-        ]
-
-        foreach(d, all_deps) {
-          # Resolve the name, so that a target //mojo/something becomes
-          # //mojo/something:something and we can append variant_suffix to
-          # get the cpp dependency name.
-          full_name = get_label_info("$d", "label_no_toolchain")
-          public_deps += [ "${full_name}_mojolpm" ]
-        }
-
-        foreach(config, cpp_typemap_configs) {
-          if (defined(config.traits_deps)) {
-            deps += config.traits_deps
-          }
-          if (defined(config.traits_public_deps)) {
-            public_deps += config.traits_public_deps
-          }
-        }
-      }
-    }
-
     # Write the typemapping configuration for this target out to a file to be
     # validated by a Python script. This helps catch mistakes that can't
     # be caught by logic in GN.
@@ -1389,20 +1462,20 @@  template("mojom") {
     write_file(_typemap_config_filename, _rebased_typemap_configs, "json")
     _mojom_target_name = target_name
 
-    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-    python2_action(_typemap_validator_target_name) {
+    action(_typemap_validator_target_name) {
+      allow_remote = true
       script = "$mojom_generator_root/validate_typemap_config.py"
       inputs = [ _typemap_config_filename ]
       outputs = [ _typemap_stamp_filename ]
       args = [
         get_label_info(_mojom_target_name, "label_no_toolchain"),
-        rebase_path(_typemap_config_filename),
-        rebase_path(_typemap_stamp_filename),
+        rebase_path(_typemap_config_filename, root_build_dir),
+        rebase_path(_typemap_stamp_filename, root_build_dir),
       ]
     }
 
-    # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-    python2_action(type_mappings_target_name) {
+    action(type_mappings_target_name) {
+      allow_remote = true
       inputs =
           mojom_generator_sources + jinja2_sources + [ _typemap_stamp_filename ]
       outputs = [ type_mappings_path ]
@@ -1413,6 +1486,7 @@  template("mojom") {
         rebase_path(type_mappings_path, root_build_dir),
       ]
 
+      sources = []
       foreach(d, all_deps) {
         name = get_label_info(d, "label_no_toolchain")
         toolchain = get_label_info(d, "toolchain")
@@ -1422,12 +1496,11 @@  template("mojom") {
         dependency_output_dir =
             get_label_info(dependency_output, "target_gen_dir")
         dependency_name = get_label_info(dependency_output, "name")
-        dependency_path =
-            rebase_path("$dependency_output_dir/${dependency_name}",
-                        root_build_dir)
+        dependency_path = "$dependency_output_dir/${dependency_name}"
+        sources += [ dependency_path ]
         args += [
           "--dependency",
-          dependency_path,
+          rebase_path(dependency_path, root_build_dir),
         ]
       }
 
@@ -1485,11 +1558,15 @@  template("mojom") {
       if (defined(output_name_override)) {
         output_name = output_name_override
       }
-      visibility = output_visibility + [ ":$output_target_name" ]
+      visibility = output_visibility + [
+                     ":$output_target_name",
+                     ":${target_name}_mojolpm",
+                   ]
       if (defined(invoker.testonly)) {
         testonly = invoker.testonly
       }
       defines = export_defines
+      configs += [ "//build/config/compiler:wexit_time_destructors" ]
       configs += extra_configs
       if (output_file_base_paths != []) {
         sources = []
@@ -1578,13 +1655,81 @@  template("mojom") {
       }
     }
 
+    if (generate_mojolpm_fuzzing && !defined(variant)) {
+      # This block contains the C++ targets for the MojoLPM fuzzer, we need to
+      # do this here so that we can use the typemap configuration for the
+      # empty-variant Mojo target.
+
+      mojolpm_target_name = "${target_name}_mojolpm"
+      mojolpm_generator_target_name = "${target_name}__generator"
+      source_set(mojolpm_target_name) {
+        # There are still a few missing header dependencies between mojo targets
+        # with typemaps and the dependencies of their typemap headers. It would
+        # be good to enable include checking for these in the future though.
+        check_includes = false
+        testonly = true
+        if (defined(invoker.sources)) {
+          # Split the input into generated and non-generated source files. They
+          # need to be processed separately.
+          gen_dir_path_wildcard = get_path_info("//", "gen_dir") + "/*"
+          non_gen_sources =
+              filter_exclude(invoker.sources, [ gen_dir_path_wildcard ])
+          gen_sources =
+              filter_include(invoker.sources, [ gen_dir_path_wildcard ])
+          sources = process_file_template(
+                  non_gen_sources,
+                  [
+                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.cc",
+                    "{{source_gen_dir}}/{{source_file_part}}-mojolpm.h",
+                  ])
+          sources += process_file_template(
+                  gen_sources,
+                  [
+                    "{{source_dir}}/{{source_file_part}}-mojolpm.cc",
+                    "{{source_dir}}/{{source_file_part}}-mojolpm.h",
+                  ])
+          deps = [ ":$output_target_name" ]
+        } else {
+          sources = []
+          deps = []
+        }
+
+        public_deps = [
+          ":$generator_shared_target_name",
+
+          # NB: hardcoded dependency on the no-variant variant generator, since
+          # mojolpm only uses the no-variant type.
+          ":$mojolpm_generator_target_name",
+          ":$mojolpm_proto_target_name",
+          "//base",
+          "//mojo/public/tools/fuzzers:mojolpm",
+        ]
+
+        foreach(d, all_deps) {
+          # Resolve the name, so that a target //mojo/something becomes
+          # //mojo/something:something and we can append variant_suffix to
+          # get the cpp dependency name.
+          full_name = get_label_info("$d", "label_no_toolchain")
+          public_deps += [ "${full_name}_mojolpm" ]
+        }
+
+        foreach(config, cpp_typemap_configs) {
+          if (defined(config.traits_deps)) {
+            deps += config.traits_deps
+          }
+          if (defined(config.traits_public_deps)) {
+            public_deps += config.traits_public_deps
+          }
+        }
+      }
+    }
+
     if (generate_java && is_android) {
       import("//build/config/android/rules.gni")
 
       java_generator_target_name = target_name + "_java__generator"
       if (sources_list != []) {
-        # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-        python2_action(java_generator_target_name) {
+        action(java_generator_target_name) {
           script = mojom_generator_script
           inputs = mojom_generator_sources + jinja2_sources
           sources = sources_list
@@ -1597,7 +1742,7 @@  template("mojom") {
           args = common_generator_args
           filelist = []
           foreach(source, sources_list) {
-            filelist += [ rebase_path("$source", root_build_dir) ]
+            filelist += [ rebase_path(source, root_build_dir) ]
           }
           foreach(base_path, output_file_base_paths) {
             outputs += [ "$root_gen_dir/$base_path.srcjar" ]
@@ -1624,8 +1769,7 @@  template("mojom") {
 
       java_srcjar_target_name = target_name + "_java_sources"
 
-      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-      python2_action(java_srcjar_target_name) {
+      action(java_srcjar_target_name) {
         script = "//build/android/gyp/zip.py"
         inputs = []
         if (output_file_base_paths != []) {
@@ -1651,7 +1795,6 @@  template("mojom") {
       android_library(java_target_name) {
         forward_variables_from(invoker, [ "enable_bytecode_checks" ])
         deps = [
-          "//base:base_java",
           "//mojo/public/java:bindings_java",
           "//mojo/public/java:system_java",
           "//third_party/androidx:androidx_annotation_annotation_java",
@@ -1673,21 +1816,103 @@  template("mojom") {
     }
   }
 
+  if (defined(invoker.webui_js_bridge_config)) {
+    if (sources_list != []) {
+      bridge_config = invoker.webui_js_bridge_config
+
+      generator_webui_js_bridge_impl_target_name =
+          "${target_name}_webui_js_bridge_impl__generator"
+      action(generator_webui_js_bridge_impl_target_name) {
+        visibility = [ ":*" ]
+        script = mojom_generator_script
+        inputs = mojom_generator_sources + jinja2_sources
+        sources = sources_list
+
+        deps = [
+          ":$parser_target_name",
+          "//mojo/public/tools/bindings:precompile_templates",
+        ]
+
+        filelist = []
+        foreach(source, sources_list) {
+          filelist += [ rebase_path(source, root_build_dir) ]
+        }
+
+        outputs = []
+        foreach(base_path, output_file_base_paths) {
+          outputs += [
+            "$root_gen_dir/${base_path}-webui-js-bridge-impl.cc",
+            "$root_gen_dir/${base_path}-webui-js-bridge-impl.h",
+          ]
+        }
+
+        response_file_contents = filelist
+
+        args = common_generator_args + export_args
+        args += [
+          "--filelist={{response_file_name}}",
+          "-g",
+          "webui_js_bridge",
+        ]
+
+        rebased_webui_controller_header =
+            rebase_path(bridge_config.webui_controller_header, "//")
+
+        # Use a single string for the argument to avoid it being parsed as a
+        # filename by the mojom_bindings_generator.py argument parser.
+        config_arg =
+            "--webui_js_bridge_config=" + bridge_config.webui_controller + "=" +
+            rebased_webui_controller_header
+
+        args += [ config_arg ]
+      }
+
+      cpp_target_name = target_name
+      source_set("${target_name}_webui_js_bridge_impl") {
+        if (defined(invoker.testonly)) {
+          testonly = invoker.testonly
+        }
+
+        sources = []
+        foreach(base_path, output_file_base_paths) {
+          sources += [
+            "$root_gen_dir/${base_path}-webui-js-bridge-impl.cc",
+            "$root_gen_dir/${base_path}-webui-js-bridge-impl.h",
+          ]
+        }
+        deps = [
+          ":$cpp_target_name",
+          ":$generator_webui_js_bridge_impl_target_name",
+        ]
+
+        deps += bridge_config.webui_controller_deps
+      }
+    }
+  }
+
   use_typescript_for_target =
-      enable_typescript_bindings && defined(invoker.use_typescript_sources) &&
-      invoker.use_typescript_sources
+      defined(invoker.use_typescript_sources) &&
+      invoker.use_typescript_sources && defined(invoker.webui_module_path)
 
   if (!use_typescript_for_target && defined(invoker.use_typescript_sources)) {
     not_needed(invoker, [ "use_typescript_sources" ])
   }
 
-  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&
-      !use_typescript_for_target) {
+  generate_legacy_js = !use_typescript_for_target ||
+                       (defined(invoker.generate_legacy_js_bindings) &&
+                        invoker.generate_legacy_js_bindings)
+
+  # Targets needed by both TS and JS bindings targets. These are needed
+  # unconditionally for JS bindings targets, and are needed for TS bindings
+  # targets when generate_legacy_js_bindings is true. This option is provided
+  # since the legacy bindings are needed by Blink tests and non-Chromium users,
+  # which are not expected to migrate to modules or TypeScript.
+  if (generate_legacy_js && (generate_js_fuzzing ||
+                             !defined(invoker.cpp_only) || !invoker.cpp_only)) {
     if (sources_list != []) {
       generator_js_target_name = "${target_name}_js__generator"
 
-      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-      python2_action(generator_js_target_name) {
+      action(generator_js_target_name) {
         script = mojom_generator_script
         inputs = mojom_generator_sources + jinja2_sources
         sources = sources_list
@@ -1702,19 +1927,18 @@  template("mojom") {
         args = common_generator_args
         filelist = []
         foreach(source, sources_list) {
-          filelist += [ rebase_path("$source", root_build_dir) ]
+          filelist += [ rebase_path(source, root_build_dir) ]
         }
         foreach(base_path, output_file_base_paths) {
           outputs += [
             "$root_gen_dir/$base_path.js",
-            "$root_gen_dir/$base_path.externs.js",
             "$root_gen_dir/$base_path.m.js",
             "$root_gen_dir/$base_path-lite.js",
-            "$root_gen_dir/$base_path.html",
             "$root_gen_dir/$base_path-lite-for-compile.js",
           ]
 
-          if (defined(invoker.webui_module_path)) {
+          if (defined(invoker.webui_module_path) &&
+              !use_typescript_for_target) {
             outputs += [ "$root_gen_dir/mojom-webui/$base_path-webui.js" ]
           }
         }
@@ -1725,7 +1949,6 @@  template("mojom") {
           "--filelist={{response_file_name}}",
           "-g",
           "javascript",
-          "--js_bindings_mode=new",
         ]
 
         if (defined(invoker.js_generate_struct_deserializers) &&
@@ -1739,7 +1962,7 @@  template("mojom") {
           args += message_scrambling_args
         }
 
-        if (generate_fuzzing) {
+        if (generate_js_fuzzing) {
           args += [ "--generate_fuzzing" ]
         }
       }
@@ -1783,31 +2006,13 @@  template("mojom") {
         data_deps += [ "${full_name}_js_data_deps" ]
       }
     }
+  }
 
-    js_library_target_name = "${target_name}_js_library"
-    if (sources_list != []) {
-      js_library(js_library_target_name) {
-        extra_public_deps = [ ":$generator_js_target_name" ]
-        sources = []
-        foreach(base_path, output_file_base_paths) {
-          sources += [ "$root_gen_dir/${base_path}-lite.js" ]
-        }
-        externs_list = [
-          "${externs_path}/mojo_core.js",
-          "${externs_path}/pending.js",
-        ]
-
-        deps = []
-        foreach(d, all_deps) {
-          full_name = get_label_info(d, "label_no_toolchain")
-          deps += [ "${full_name}_js_library" ]
-        }
-      }
-    } else {
-      group(js_library_target_name) {
-      }
-    }
-
+  # js_library() closure compiler targets, primarily used on ChromeOS. Only
+  # generate these targets if the mojom target is not C++ only and is not using
+  # TypeScript.
+  if (generate_mojom_closure_libraries &&
+      (!defined(invoker.cpp_only) || !invoker.cpp_only) && generate_legacy_js) {
     js_library_for_compile_target_name = "${target_name}_js_library_for_compile"
     if (sources_list != []) {
       js_library(js_library_for_compile_target_name) {
@@ -1834,35 +2039,9 @@  template("mojom") {
       }
     }
 
-    js_modules_target_name = "${target_name}_js_modules"
-    if (sources_list != []) {
-      js_library(js_modules_target_name) {
-        extra_public_deps = [ ":$generator_js_target_name" ]
-        sources = []
-        foreach(base_path, output_file_base_paths) {
-          sources += [ "$root_gen_dir/${base_path}.m.js" ]
-        }
-        externs_list = [
-          "${externs_path}/mojo_core.js",
-          "${externs_path}/pending.js",
-        ]
-        if (defined(invoker.disallow_native_types) &&
-            invoker.disallow_native_types) {
-          deps = []
-        } else {
-          deps = [ "//mojo/public/js:bindings_uncompiled" ]
-        }
-        foreach(d, all_deps) {
-          full_name = get_label_info(d, "label_no_toolchain")
-          deps += [ "${full_name}_js_modules" ]
-        }
-      }
-    } else {
-      group(js_modules_target_name) {
-      }
-    }
-
-    if (defined(invoker.webui_module_path)) {
+    # WebUI specific closure targets, not needed by targets that are generating
+    # TypeScript WebUI bindings or by legacy-only targets.
+    if (defined(invoker.webui_module_path) && !use_typescript_for_target) {
       webui_js_target_name = "${target_name}_webui_js"
       if (sources_list != []) {
         js_library(webui_js_target_name) {
@@ -1890,46 +2069,38 @@  template("mojom") {
         group(webui_js_target_name) {
         }
       }
-    }
-  }
-  if ((generate_fuzzing || !defined(invoker.cpp_only) || !invoker.cpp_only) &&
-      use_typescript_for_target) {
-    generator_js_target_names = []
-    source_filelist = []
-    foreach(source, sources_list) {
-      source_filelist += [ rebase_path("$source", root_build_dir) ]
-    }
 
-    dependency_types = [
-      {
-        name = "regular"
-        ts_extension = ".ts"
-        js_extension = ".js"
-      },
-      {
-        name = "es_modules"
-        ts_extension = ".m.ts"
-        js_extension = ".m.js"
-      },
-    ]
+      webui_grdp_target_name = "${target_name}_webui_grdp"
+      out_grd = "$target_gen_dir/${target_name}_webui_resources.grdp"
+      grd_prefix = "${target_name}_webui"
+      generate_grd(webui_grdp_target_name) {
+        grd_prefix = grd_prefix
+        out_grd = out_grd
 
-    foreach(dependency_type, dependency_types) {
-      ts_outputs = []
-      js_outputs = []
+        deps = [ ":$webui_js_target_name" ]
 
-      foreach(base_path, output_file_base_paths) {
-        ts_outputs +=
-            [ "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}" ]
-        js_outputs +=
-            [ "$root_gen_dir/$base_path-lite${dependency_type.js_extension}" ]
+        input_files = []
+        foreach(base_path, output_file_base_paths) {
+          input_files += [ "${base_path}-webui.js" ]
+        }
+
+        input_files_base_dir =
+            rebase_path("$root_gen_dir/mojom-webui", "$root_build_dir")
+      }
+    }
+  }
+  if ((generate_js_fuzzing || !defined(invoker.cpp_only) ||
+       !invoker.cpp_only) && use_typescript_for_target) {
+    if (sources_list != []) {
+      source_filelist = []
+      foreach(source, sources_list) {
+        source_filelist += [ rebase_path(source, root_build_dir) ]
       }
 
       # Generate Typescript bindings.
-      generator_ts_target_name =
-          "${target_name}_${dependency_type.name}__ts__generator"
+      generator_ts_target_name = "${target_name}_ts__generator"
 
-      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-      python2_action(generator_ts_target_name) {
+      action(generator_ts_target_name) {
         script = mojom_generator_script
         inputs = mojom_generator_sources + jinja2_sources
         sources = sources_list
@@ -1938,7 +2109,10 @@  template("mojom") {
           "//mojo/public/tools/bindings:precompile_templates",
         ]
 
-        outputs = ts_outputs
+        outputs = []
+        foreach(base_path, output_file_base_paths) {
+          outputs += [ "$root_gen_dir/$base_path-webui.ts" ]
+        }
         args = common_generator_args
         response_file_contents = source_filelist
 
@@ -1948,98 +2122,16 @@  template("mojom") {
           "typescript",
         ]
 
-        if (dependency_type.name == "es_modules") {
-          args += [ "--ts_use_es_modules" ]
+        if (!defined(invoker.scramble_message_ids) ||
+            invoker.scramble_message_ids) {
+          inputs += message_scrambling_inputs
+          args += message_scrambling_args
         }
 
-        # TODO(crbug.com/1007587): Support scramble_message_ids.
+        # TODO(crbug.com/1007587): Support scramble_message_ids if above is
+        # insufficient.
         # TODO(crbug.com/1007591): Support generate_fuzzing.
       }
-
-      # Create tsconfig.json for the generated Typescript.
-      tsconfig_filename =
-          "$target_gen_dir/$target_name-${dependency_type.name}-tsconfig.json"
-      tsconfig = {
-      }
-      tsconfig.compilerOptions = {
-        composite = true
-        target = "es6"
-        module = "es6"
-        lib = [
-          "es6",
-          "esnext.bigint",
-        ]
-        strict = true
-      }
-      tsconfig.files = []
-      foreach(base_path, output_file_base_paths) {
-        tsconfig.files += [ rebase_path(
-                "$root_gen_dir/$base_path-lite${dependency_type.ts_extension}",
-                target_gen_dir,
-                root_gen_dir) ]
-      }
-      tsconfig.references = []
-
-      # Get tsconfigs for deps.
-      foreach(d, all_deps) {
-        dep_target_gen_dir = rebase_path(get_label_info(d, "target_gen_dir"))
-        dep_name = get_label_info(d, "name")
-        reference = {
-        }
-        reference.path = "$dep_target_gen_dir/$dep_name-${dependency_type.name}-tsconfig.json"
-        tsconfig.references += [ reference ]
-      }
-      write_file(tsconfig_filename, tsconfig, "json")
-
-      # Compile previously generated Typescript to Javascript.
-      generator_js_target_name =
-          "${target_name}_${dependency_type.name}__js__generator"
-      generator_js_target_names += [ generator_js_target_name ]
-
-      # TODO(crbug.com/1194274): Investigate nondeterminism in Py3 builds.
-      python2_action(generator_js_target_name) {
-        script = "$mojom_generator_root/compile_typescript.py"
-        sources = ts_outputs
-        outputs = js_outputs
-        public_deps = [ ":$generator_ts_target_name" ]
-        foreach(d, all_deps) {
-          full_name = get_label_info(d, "label_no_toolchain")
-          public_deps +=
-              [ "${full_name}_${dependency_type.name}__js__generator" ]
-        }
-
-        absolute_tsconfig_path =
-            rebase_path(tsconfig_filename, "", target_gen_dir)
-        args = [ "--tsconfig_path=$absolute_tsconfig_path" ]
-      }
-    }
-
-    js_target_name = target_name + "_js"
-    group(js_target_name) {
-      public_deps = []
-      if (sources_list != []) {
-        foreach(generator_js_target_name, generator_js_target_names) {
-          public_deps += [ ":$generator_js_target_name" ]
-        }
-      }
-
-      foreach(d, all_deps) {
-        full_name = get_label_info(d, "label_no_toolchain")
-        public_deps += [ "${full_name}_js" ]
-      }
-    }
-
-    group(js_data_deps_target_name) {
-      data = js_outputs
-      deps = []
-      foreach(generator_js_target_name, generator_js_target_names) {
-        deps += [ ":$generator_js_target_name" ]
-      }
-      data_deps = []
-      foreach(d, all_deps) {
-        full_name = get_label_info(d, "label_no_toolchain")
-        data_deps += [ "${full_name}_js_data_deps" ]
-      }
     }
   }
 }
@@ -2063,3 +2155,53 @@  template("mojom_component") {
     component_macro_prefix = invoker.macro_prefix
   }
 }
+
+# A helper for the mojom() template above for defining a WebUIJsBridge.
+# Supports all the same arguments as mojom() except for `sources` and adds
+# `source`, `webui_controller`, `webui_controller_header`, and
+# `webui_controller_deps` which are *required*. See below for more details.
+#
+# A WebUIJsBridge is a Mojo interface that contains binders for all interfaces
+# that can be bound from a WebUI's JavaScript. This taret
+# will generate a C++ implementation of the WebUIJsBridge interface
+# and a {target_name}_webui_js_bridge_impl target that can be depended
+# on.
+#
+# The following arguments are required:
+#  - `source`: The mojom file that defines the WebUIJsBridge interface. It's
+#    allowed to define other interfaces.
+#  - `webui_controller`: The name of the WebUIController associated with the
+#    WebUIJsBridge.
+#  - `webui_controller_header`: The header where the WebUIController
+#    is declared.
+#  - `webui_controller_deps`: The target containing the WebUIController.
+template("mojom_with_webui_js_bridge") {
+  assert(defined(invoker.source))
+  assert(defined(invoker.webui_controller),
+         "mojom_with_webui_js_bridge should have a " +
+             "`webui_controller` parameter e.g. `foo::FooUI`.")
+  assert(defined(invoker.webui_controller_header),
+         "mojom_with_webui_js_bridge should have a " +
+             "`webui_controller_header` parameter.")
+  assert(defined(invoker.webui_controller_deps),
+         "mojom_with_webui_js_bridge should have a " +
+             "`webui_controller_deps` parameter.")
+
+  mojom(target_name) {
+    forward_variables_from(invoker,
+                           "*",
+                           [
+                             "source",
+                             "webui_controller",
+                             "webui_controller_header",
+                             "webui_controller_deps",
+                           ])
+    sources = [ invoker.source ]
+
+    webui_js_bridge_config = {
+      webui_controller = invoker.webui_controller
+      webui_controller_header = invoker.webui_controller_header
+      webui_controller_deps = invoker.webui_controller_deps
+    }
+  }
+}
diff --git a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
index da9efc71..98cac149 100755
--- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
+++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator.py
@@ -1,5 +1,5 @@ 
 #!/usr/bin/env python
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -51,16 +51,23 @@  import crbug_1001171
 
 _BUILTIN_GENERATORS = {
     "c++": "mojom_cpp_generator",
+    "webui_js_bridge": "mojom_webui_js_bridge_generator",
     "javascript": "mojom_js_generator",
     "java": "mojom_java_generator",
     "mojolpm": "mojom_mojolpm_generator",
     "typescript": "mojom_ts_generator",
 }
 
+_BUILTIN_CHECKS = {
+    "attributes": "mojom_attributes_check",
+    "definitions": "mojom_definitions_check",
+    "restrictions": "mojom_restrictions_check",
+}
+
 
 def LoadGenerators(generators_string):
   if not generators_string:
-    return []  # No generators.
+    return {}  # No generators.
 
   generators = {}
   for generator_name in [s.strip() for s in generators_string.split(",")]:
@@ -74,6 +81,21 @@  def LoadGenerators(generators_string):
   return generators
 
 
+def LoadChecks(checks_string):
+  if not checks_string:
+    return {}  # No checks.
+
+  checks = {}
+  for check_name in [s.strip() for s in checks_string.split(",")]:
+    check = check_name.lower()
+    if check not in _BUILTIN_CHECKS:
+      print("Unknown check name %s" % check_name)
+      sys.exit(1)
+    check_module = importlib.import_module("checks.%s" % _BUILTIN_CHECKS[check])
+    checks[check] = check_module
+  return checks
+
+
 def MakeImportStackMessage(imported_filename_stack):
   """Make a (human-readable) message listing a chain of imports. (Returned
   string begins with a newline (if nonempty) and does not end with one.)"""
@@ -82,7 +104,7 @@  def MakeImportStackMessage(imported_filename_stack):
                     zip(imported_filename_stack[1:], imported_filename_stack)]))
 
 
-class RelativePath(object):
+class RelativePath:
   """Represents a path relative to the source tree or generated output dir."""
 
   def __init__(self, path, source_root, output_dir):
@@ -142,7 +164,7 @@  def ReadFileContents(filename):
     return f.read()
 
 
-class MojomProcessor(object):
+class MojomProcessor:
   """Takes parsed mojom modules and generates language bindings from them.
 
   Attributes:
@@ -169,8 +191,8 @@  class MojomProcessor(object):
     if 'c++' in self._typemap:
       self._typemap['mojolpm'] = self._typemap['c++']
 
-  def _GenerateModule(self, args, remaining_args, generator_modules,
-                      rel_filename, imported_filename_stack):
+  def _GenerateModule(self, args, remaining_args, check_modules,
+                      generator_modules, rel_filename, imported_filename_stack):
     # Return the already-generated module.
     if rel_filename.path in self._processed_files:
       return self._processed_files[rel_filename.path]
@@ -190,12 +212,16 @@  class MojomProcessor(object):
       ScrambleMethodOrdinals(module.interfaces, salt)
 
     if self._should_generate(rel_filename.path):
+      # Run checks on module first.
+      for check_module in check_modules.values():
+        checker = check_module.Check(module)
+        checker.CheckModule()
+      # Then run generation.
       for language, generator_module in generator_modules.items():
         generator = generator_module.Generator(
             module, args.output_dir, typemap=self._typemap.get(language, {}),
             variant=args.variant, bytecode_path=args.bytecode_path,
             for_blink=args.for_blink,
-            js_bindings_mode=args.js_bindings_mode,
             js_generate_struct_deserializers=\
                     args.js_generate_struct_deserializers,
             export_attribute=args.export_attribute,
@@ -234,6 +260,7 @@  def _Generate(args, remaining_args):
       args.import_directories[idx] = RelativePath(tokens[0], args.depth,
                                                   args.output_dir)
   generator_modules = LoadGenerators(args.generators_string)
+  check_modules = LoadChecks(args.checks_string)
 
   fileutil.EnsureDirectoryExists(args.output_dir)
 
@@ -246,7 +273,7 @@  def _Generate(args, remaining_args):
 
   for filename in args.filename:
     processor._GenerateModule(
-        args, remaining_args, generator_modules,
+        args, remaining_args, check_modules, generator_modules,
         RelativePath(filename, args.depth, args.output_dir), [])
 
   return 0
@@ -286,6 +313,12 @@  def main():
                                metavar="GENERATORS",
                                default="c++,javascript,java,mojolpm",
                                help="comma-separated list of generators")
+  generate_parser.add_argument("-c",
+                               "--checks",
+                               dest="checks_string",
+                               metavar="CHECKS",
+                               default="attributes,definitions,restrictions",
+                               help="comma-separated list of checks")
   generate_parser.add_argument(
       "--gen_dir", dest="gen_directories", action="append", metavar="directory",
       default=[], help="add a directory to be searched for the syntax trees.")
@@ -308,11 +341,6 @@  def main():
   generate_parser.add_argument("--for_blink", action="store_true",
                                help="Use WTF types as generated types for mojo "
                                "string/array/map.")
-  generate_parser.add_argument(
-      "--js_bindings_mode", choices=["new", "old"], default="old",
-      help="This option only affects the JavaScript bindings. The value could "
-      "be \"new\" to generate new-style lite JS bindings in addition to the "
-      "old, or \"old\" to only generate old bindings.")
   generate_parser.add_argument(
       "--js_generate_struct_deserializers", action="store_true",
       help="Generate javascript deserialize methods for structs in "
@@ -387,4 +415,10 @@  def main():
 
 if __name__ == "__main__":
   with crbug_1001171.DumpStateOnLookupError():
-    sys.exit(main())
+    ret = main()
+    # Exit without running GC, which can save multiple seconds due to the large
+    # number of object created. But flush is necessary as os._exit doesn't do
+    # that.
+    sys.stdout.flush()
+    sys.stderr.flush()
+    os._exit(ret)
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
index bddbe3f4..761922b6 100644
--- a/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
+++ b/utils/ipc/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -8,13 +8,13 @@  from mojom_bindings_generator import MakeImportStackMessage
 from mojom_bindings_generator import ScrambleMethodOrdinals
 
 
-class FakeIface(object):
+class FakeIface:
   def __init__(self):
     self.mojom_name = None
     self.methods = None
 
 
-class FakeMethod(object):
+class FakeMethod:
   def __init__(self, explicit_ordinal=None):
     self.explicit_ordinal = explicit_ordinal
     self.ordinal = explicit_ordinal
diff --git a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
index f1783d59..1d940a21 100755
--- a/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
+++ b/utils/ipc/mojo/public/tools/bindings/validate_typemap_config.py
@@ -1,5 +1,5 @@ 
 #!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -17,7 +17,7 @@  def CheckCppTypemapConfigs(target_name, config_filename, out_filename):
   ])
   _SUPPORTED_TYPE_KEYS = set([
       'mojom', 'cpp', 'copyable_pass_by_value', 'force_serialize', 'hashable',
-      'move_only', 'nullable_is_same_type'
+      'move_only', 'nullable_is_same_type', 'forward_declaration'
   ])
   with open(config_filename, 'r') as f:
     for config in json.load(f):
diff --git a/utils/ipc/mojo/public/tools/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
new file mode 100644
index 00000000..4e456c0e
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/mojom/BUILD.gn
@@ -0,0 +1,17 @@ 
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+group("tests") {
+  data = [
+    "check_stable_mojom_compatibility_unittest.py",
+    "check_stable_mojom_compatibility.py",
+    "const_unittest.py",
+    "enum_unittest.py",
+    "mojom_parser_test_case.py",
+    "mojom_parser_unittest.py",
+    "mojom_parser.py",
+    "stable_attribute_unittest.py",
+    "version_compatibility_unittest.py",
+  ]
+}
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
index 08bd672f..3c1ac42c 100755
--- a/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
+++ b/utils/ipc/mojo/public/tools/mojom/check_stable_mojom_compatibility.py
@@ -1,5 +1,5 @@ 
-#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Verifies backward-compatibility of mojom type changes.
@@ -12,20 +12,18 @@  This can be used e.g. by a presubmit check to prevent developers from making
 breaking changes to stable mojoms."""
 
 import argparse
-import errno
 import io
 import json
 import os
 import os.path
-import shutil
-import six
 import sys
-import tempfile
 
 from mojom.generate import module
 from mojom.generate import translate
 from mojom.parse import parser
 
+# pylint: disable=raise-missing-from
+
 
 class ParseError(Exception):
   pass
@@ -41,6 +39,8 @@  def _ValidateDelta(root, delta):
   transitive closure of a mojom's input dependencies all at once.
   """
 
+  translate.is_running_backwards_compatibility_check_hack = True
+
   # First build a map of all files covered by the delta
   affected_files = set()
   old_files = {}
@@ -73,11 +73,33 @@  def _ValidateDelta(root, delta):
     try:
       ast = parser.Parse(contents, mojom)
     except Exception as e:
-      six.reraise(
-          ParseError,
-          'encountered exception {0} while parsing {1}'.format(e, mojom),
-          sys.exc_info()[2])
+      raise ParseError('encountered exception {0} while parsing {1}'.format(
+          e, mojom))
+
+    # Files which are generated at compile time can't be checked by this script
+    # (at the moment) since they may not exist in the output directory.
+    generated_files_to_skip = {
+        ('third_party/blink/public/mojom/runtime_feature_state/'
+         'runtime_feature_state.mojom'),
+    }
+
+    ast.import_list.items = [
+        x for x in ast.import_list.items
+        if x.import_filename not in generated_files_to_skip
+    ]
+
     for imp in ast.import_list:
+      if (not file_overrides.get(imp.import_filename)
+          and not os.path.exists(os.path.join(root, imp.import_filename))):
+        # Speculatively construct a path prefix to locate the import_filename
+        mojom_path = os.path.dirname(os.path.normpath(mojom)).split(os.sep)
+        test_prefix = ''
+        for path_component in mojom_path:
+          test_prefix = os.path.join(test_prefix, path_component)
+          test_import_filename = os.path.join(test_prefix, imp.import_filename)
+          if os.path.exists(os.path.join(root, test_import_filename)):
+            imp.import_filename = test_import_filename
+            break
       parseMojom(imp.import_filename, file_overrides, override_modules)
 
     # Now that the transitive set of dependencies has been imported and parsed
@@ -89,10 +111,10 @@  def _ValidateDelta(root, delta):
     modules[mojom] = translate.OrderedModule(ast, mojom, all_modules)
 
   old_modules = {}
-  for mojom in old_files.keys():
+  for mojom in old_files:
     parseMojom(mojom, old_files, old_modules)
   new_modules = {}
-  for mojom in new_files.keys():
+  for mojom in new_files:
     parseMojom(mojom, new_files, new_modules)
 
   # At this point we have a complete set of translated Modules from both the
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
index 9f51ea77..06769c95 100755
--- 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
@@ -1,5 +1,5 @@ 
-#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -15,7 +15,7 @@  import check_stable_mojom_compatibility
 from mojom.generate import module
 
 
-class Change(object):
+class Change:
   """Helper to clearly define a mojom file delta to be analyzed."""
 
   def __init__(self, filename, old=None, new=None):
@@ -28,7 +28,7 @@  class Change(object):
 
 class UnchangedFile(Change):
   def __init__(self, filename, contents):
-    super(UnchangedFile, self).__init__(filename, old=contents, new=contents)
+    super().__init__(filename, old=contents, new=contents)
 
 
 class CheckStableMojomCompatibilityTest(unittest.TestCase):
@@ -258,3 +258,82 @@  class CheckStableMojomCompatibilityTest(unittest.TestCase):
                [Stable] struct T { foo.S s; int32 x; };
                """)
     ])
+
+  def testWithPartialImport(self):
+    """The compatibility checking tool correctly parses imports with partial
+    paths."""
+    self.assertBackwardCompatible([
+        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
+        Change('foo/bar.mojom',
+               old="""\
+               module bar;
+               import "foo/foo.mojom";
+               [Stable] struct T { foo.S s; };
+               """,
+               new="""\
+               module bar;
+               import "foo.mojom";
+               [Stable] struct T { foo.S s; };
+               """)
+    ])
+
+    self.assertBackwardCompatible([
+        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
+        Change('foo/bar.mojom',
+               old="""\
+               module bar;
+               import "foo.mojom";
+               [Stable] struct T { foo.S s; };
+               """,
+               new="""\
+               module bar;
+               import "foo/foo.mojom";
+               [Stable] struct T { foo.S s; };
+               """)
+    ])
+
+    self.assertNotBackwardCompatible([
+        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
+        Change('bar/bar.mojom',
+               old="""\
+               module bar;
+               import "foo/foo.mojom";
+               [Stable] struct T { foo.S s; };
+               """,
+               new="""\
+               module bar;
+               import "foo.mojom";
+               [Stable] struct T { foo.S s; };
+               """)
+    ])
+
+    self.assertNotBackwardCompatible([
+        UnchangedFile('foo/foo.mojom', 'module foo; [Stable] struct S {};'),
+        Change('bar/bar.mojom',
+               old="""\
+               module bar;
+               import "foo.mojom";
+               [Stable] struct T { foo.S s; };
+               """,
+               new="""\
+               module bar;
+               import "foo/foo.mojom";
+               [Stable] struct T { foo.S s; };
+               """)
+    ])
+
+  def testNewEnumDefault(self):
+    # Should be backwards compatible since it does not affect the wire format.
+    # This specific case also checks that the backwards compatibility checker
+    # does not throw an error due to the older version of the enum not
+    # specifying [Default].
+    self.assertBackwardCompatible([
+        Change('foo/foo.mojom',
+               old='[Extensible] enum E { One };',
+               new='[Extensible] enum E { [Default] One };')
+    ])
+    self.assertBackwardCompatible([
+        Change('foo/foo.mojom',
+               old='[Extensible] enum E { [Default] One, Two, };',
+               new='[Extensible] enum E { One, [Default] Two, };')
+    ])
diff --git a/utils/ipc/mojo/public/tools/mojom/const_unittest.py b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
index cb42dfac..e8ed36a7 100644
--- a/utils/ipc/mojo/public/tools/mojom/const_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/const_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
diff --git a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
index d9005078..9269cde5 100644
--- a/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/enum_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -90,3 +90,31 @@  class EnumTest(MojomParserTestCase):
     self.assertEqual('F', b.enums[0].mojom_name)
     self.assertEqual('kFoo', b.enums[0].fields[0].mojom_name)
     self.assertEqual(37, b.enums[0].fields[0].numeric_value)
+
+  def testEnumAttributesAreEnums(self):
+    """Verifies that enum values in attributes are really enum types."""
+    a_mojom = 'a.mojom'
+    self.WriteFile(a_mojom, 'module a; enum E { kFoo, kBar };')
+    b_mojom = 'b.mojom'
+    self.WriteFile(
+        b_mojom, 'module b;'
+        'import "a.mojom";'
+        '[MooCow=a.E.kFoo]'
+        'interface Foo { Foo(); };')
+    self.ParseMojoms([a_mojom, b_mojom])
+    b = self.LoadModule(b_mojom)
+    self.assertEqual(b.interfaces[0].attributes['MooCow'].mojom_name, 'kFoo')
+
+  def testConstantAttributes(self):
+    """Verifies that constants as attributes are translated to the constant."""
+    a_mojom = 'a.mojom'
+    self.WriteFile(
+        a_mojom, 'module a;'
+        'enum E { kFoo, kBar };'
+        'const E kB = E.kFoo;'
+        '[Attr=kB] interface Hello { Foo(); };')
+    self.ParseMojoms([a_mojom])
+    a = self.LoadModule(a_mojom)
+    self.assertEqual(a.interfaces[0].attributes['Attr'].mojom_name, 'kB')
+    self.assertEquals(a.interfaces[0].attributes['Attr'].value.mojom_name,
+                      'kFoo')
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
index 51facc0c..a0edf0eb 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/BUILD.gn
@@ -1,4 +1,4 @@ 
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -8,6 +8,7 @@  group("mojom") {
     "error.py",
     "fileutil.py",
     "generate/__init__.py",
+    "generate/check.py",
     "generate/generator.py",
     "generate/module.py",
     "generate/pack.py",
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/error.py b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
index 8a1e03da..dd53b835 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/error.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/error.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
index bf626f54..29daec36 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil.py
@@ -1,4 +1,4 @@ 
-# Copyright 2015 The Chromium Authors. All rights reserved.
+# Copyright 2015 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
index ff5753a2..48eaf4ec 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/fileutil_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2015 The Chromium Authors. All rights reserved.
+# Copyright 2015 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
new file mode 100644
index 00000000..1efe2022
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/check.py
@@ -0,0 +1,26 @@ 
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Code shared by the various pre-generation mojom checkers."""
+
+
+class CheckException(Exception):
+  def __init__(self, module, message):
+    self.module = module
+    self.message = message
+    super().__init__(self.message)
+
+  def __str__(self):
+    return "Failed mojo pre-generation check for {}:\n{}".format(
+        self.module.path, self.message)
+
+
+class Check:
+  def __init__(self, module):
+    self.module = module
+
+  def CheckModule(self):
+    """ Subclass should return True if its Checks pass, and throw an
+    exception otherwise. CheckModule will be called immediately before
+    mojom.generate.Generator.GenerateFiles()"""
+    raise NotImplementedError("Subclasses must override/implement this method")
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
index 4a1c73fc..31cacd0e 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator.py
@@ -1,4 +1,4 @@ 
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Code shared by the various language-specific code generators."""
@@ -97,7 +97,7 @@  def ToLowerSnakeCase(identifier):
   return _ToSnakeCase(identifier, upper=False)
 
 
-class Stylizer(object):
+class Stylizer:
   """Stylizers specify naming rules to map mojom names to names in generated
   code. For example, if you would like method_name in mojom to be mapped to
   MethodName in the generated code, you need to define a subclass of Stylizer
@@ -233,7 +233,7 @@  def AddComputedData(module):
     _AddInterfaceComputedData(interface)
 
 
-class Generator(object):
+class Generator:
   # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all
   # files to stdout.
   def __init__(self,
@@ -243,7 +243,6 @@  class Generator(object):
                variant=None,
                bytecode_path=None,
                for_blink=False,
-               js_bindings_mode="new",
                js_generate_struct_deserializers=False,
                export_attribute=None,
                export_header=None,
@@ -262,7 +261,6 @@  class Generator(object):
     self.variant = variant
     self.bytecode_path = bytecode_path
     self.for_blink = for_blink
-    self.js_bindings_mode = js_bindings_mode
     self.js_generate_struct_deserializers = js_generate_struct_deserializers
     self.export_attribute = export_attribute
     self.export_header = export_header
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
index 32c884a8..76cda398 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/generator_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
index 9bdb28e0..f0664b31 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module.py
@@ -1,4 +1,4 @@ 
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -12,15 +12,14 @@ 
 # method = interface.AddMethod('Tat', 0)
 # method.AddParameter('baz', 0, mojom.INT32)
 
-import sys
-if sys.version_info.major == 2:
-  import cPickle as pickle
-else:
-  import pickle
+import pickle
+from collections import OrderedDict
 from uuid import UUID
 
+# pylint: disable=raise-missing-from
 
-class BackwardCompatibilityChecker(object):
+
+class BackwardCompatibilityChecker:
   """Used for memoization while recursively checking two type definitions for
   backward-compatibility."""
 
@@ -64,23 +63,20 @@  def Repr(obj, as_ref=True):
     return obj.Repr(as_ref=as_ref)
   # Since we cannot implement Repr for existing container types, we
   # handle them here.
-  elif isinstance(obj, list):
+  if isinstance(obj, list):
     if not obj:
       return '[]'
-    else:
-      return ('[\n%s\n]' % (',\n'.join(
-          '    %s' % Repr(elem, as_ref).replace('\n', '\n    ')
-          for elem in obj)))
-  elif isinstance(obj, dict):
+    return ('[\n%s\n]' %
+            (',\n'.join('    %s' % Repr(elem, as_ref).replace('\n', '\n    ')
+                        for elem in obj)))
+  if isinstance(obj, dict):
     if not obj:
       return '{}'
-    else:
-      return ('{\n%s\n}' % (',\n'.join(
-          '    %s: %s' % (Repr(key, as_ref).replace('\n', '\n    '),
-                          Repr(val, as_ref).replace('\n', '\n    '))
-          for key, val in obj.items())))
-  else:
-    return repr(obj)
+    return ('{\n%s\n}' % (',\n'.join('    %s: %s' %
+                                     (Repr(key, as_ref).replace('\n', '\n    '),
+                                      Repr(val, as_ref).replace('\n', '\n    '))
+                                     for key, val in obj.items())))
+  return repr(obj)
 
 
 def GenericRepr(obj, names):
@@ -104,7 +100,7 @@  def GenericRepr(obj, names):
       ReprIndent(name, as_ref) for (name, as_ref) in names.items()))
 
 
-class Kind(object):
+class Kind:
   """Kind represents a type (e.g. int8, string).
 
   Attributes:
@@ -112,16 +108,43 @@  class Kind(object):
     module: {Module} The defining module. Set to None for built-in types.
     parent_kind: The enclosing type. For example, an enum defined
         inside an interface has that interface as its parent. May be None.
+    is_nullable: True if the type is nullable.
   """
 
-  def __init__(self, spec=None, module=None):
+  def __init__(self, spec=None, is_nullable=False, module=None):
     self.spec = spec
     self.module = module
     self.parent_kind = None
+    self.is_nullable = is_nullable
+    self.shared_definition = {}
+
+  @classmethod
+  def AddSharedProperty(cls, name):
+    """Adds a property |name| to |cls|, which accesses the corresponding item in
+       |shared_definition|.
+
+       The reason of adding such indirection is to enable sharing definition
+       between a reference kind and its nullable variation. For example:
+         a = Struct('test_struct_1')
+         b = a.MakeNullableKind()
+         a.name = 'test_struct_2'
+         print(b.name)  # Outputs 'test_struct_2'.
+    """
+    def Get(self):
+      try:
+        return self.shared_definition[name]
+      except KeyError:  # Must raise AttributeError if property doesn't exist.
+        raise AttributeError
+
+    def Set(self, value):
+      self.shared_definition[name] = value
+
+    setattr(cls, name, property(Get, Set))
 
   def Repr(self, as_ref=True):
     # pylint: disable=unused-argument
-    return '<%s spec=%r>' % (self.__class__.__name__, self.spec)
+    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,
+                                            self.is_nullable)
 
   def __repr__(self):
     # Gives us a decent __repr__ for all kinds.
@@ -130,7 +153,8 @@  class Kind(object):
   def __eq__(self, rhs):
     # pylint: disable=unidiomatic-typecheck
     return (type(self) == type(rhs)
-            and (self.spec, self.parent_kind) == (rhs.spec, rhs.parent_kind))
+            and (self.spec, self.parent_kind, self.is_nullable)
+            == (rhs.spec, rhs.parent_kind, rhs.is_nullable))
 
   def __hash__(self):
     # TODO(crbug.com/1060471): Remove this and other __hash__ methods on Kind
@@ -138,32 +162,113 @@  class Kind(object):
     # some primitive Kinds as dict keys. The default hash (object identity)
     # breaks these dicts when a pickled Module instance is unpickled and used
     # during a subsequent run of the parser.
-    return hash((self.spec, self.parent_kind))
+    return hash((self.spec, self.parent_kind, self.is_nullable))
 
   # pylint: disable=unused-argument
   def IsBackwardCompatible(self, rhs, checker):
     return self == rhs
 
 
+class ValueKind(Kind):
+  """ValueKind represents values that aren't reference kinds.
+
+  The primary difference is the wire representation for nullable value kinds
+  still reserves space for the value type itself, even if that value itself
+  is logically null.
+  """
+  def __init__(self, spec=None, is_nullable=False, module=None):
+    assert spec is None or is_nullable == spec.startswith('?')
+    Kind.__init__(self, spec, is_nullable, module)
+
+  def MakeNullableKind(self):
+    assert not self.is_nullable
+
+    if self == BOOL:
+      return NULLABLE_BOOL
+    if self == INT8:
+      return NULLABLE_INT8
+    if self == INT16:
+      return NULLABLE_INT16
+    if self == INT32:
+      return NULLABLE_INT32
+    if self == INT64:
+      return NULLABLE_INT64
+    if self == UINT8:
+      return NULLABLE_UINT8
+    if self == UINT16:
+      return NULLABLE_UINT16
+    if self == UINT32:
+      return NULLABLE_UINT32
+    if self == UINT64:
+      return NULLABLE_UINT64
+    if self == FLOAT:
+      return NULLABLE_FLOAT
+    if self == DOUBLE:
+      return NULLABLE_DOUBLE
+
+    nullable_kind = type(self)()
+    nullable_kind.shared_definition = self.shared_definition
+    if self.spec is not None:
+      nullable_kind.spec = '?' + self.spec
+    nullable_kind.is_nullable = True
+    nullable_kind.parent_kind = self.parent_kind
+    nullable_kind.module = self.module
+
+    return nullable_kind
+
+  def MakeUnnullableKind(self):
+    assert self.is_nullable
+
+    if self == NULLABLE_BOOL:
+      return BOOL
+    if self == NULLABLE_INT8:
+      return INT8
+    if self == NULLABLE_INT16:
+      return INT16
+    if self == NULLABLE_INT32:
+      return INT32
+    if self == NULLABLE_INT64:
+      return INT64
+    if self == NULLABLE_UINT8:
+      return UINT8
+    if self == NULLABLE_UINT16:
+      return UINT16
+    if self == NULLABLE_UINT32:
+      return UINT32
+    if self == NULLABLE_UINT64:
+      return UINT64
+    if self == NULLABLE_FLOAT:
+      return FLOAT
+    if self == NULLABLE_DOUBLE:
+      return DOUBLE
+
+    nullable_kind = type(self)()
+    nullable_kind.shared_definition = self.shared_definition
+    if self.spec is not None:
+      nullable_kind.spec = self.spec[1:]
+    nullable_kind.is_nullable = False
+    nullable_kind.parent_kind = self.parent_kind
+    nullable_kind.module = self.module
+
+    return nullable_kind
+
+  def __eq__(self, rhs):
+    return (isinstance(rhs, ValueKind) and super().__eq__(rhs))
+
+  def __hash__(self):  # pylint: disable=useless-super-delegation
+    return super().__hash__()
+
+
 class ReferenceKind(Kind):
   """ReferenceKind represents pointer and handle types.
 
   A type is nullable if null (for pointer types) or invalid handle (for handle
   types) is a legal value for the type.
-
-  Attributes:
-    is_nullable: True if the type is nullable.
   """
 
   def __init__(self, spec=None, is_nullable=False, module=None):
     assert spec is None or is_nullable == spec.startswith('?')
-    Kind.__init__(self, spec, module)
-    self.is_nullable = is_nullable
-    self.shared_definition = {}
-
-  def Repr(self, as_ref=True):
-    return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec,
-                                            self.is_nullable)
+    Kind.__init__(self, spec, is_nullable, module)
 
   def MakeNullableKind(self):
     assert not self.is_nullable
@@ -193,55 +298,36 @@  class ReferenceKind(Kind):
 
     return nullable_kind
 
-  @classmethod
-  def AddSharedProperty(cls, name):
-    """Adds a property |name| to |cls|, which accesses the corresponding item in
-       |shared_definition|.
-
-       The reason of adding such indirection is to enable sharing definition
-       between a reference kind and its nullable variation. For example:
-         a = Struct('test_struct_1')
-         b = a.MakeNullableKind()
-         a.name = 'test_struct_2'
-         print(b.name)  # Outputs 'test_struct_2'.
-    """
-
-    def Get(self):
-      try:
-        return self.shared_definition[name]
-      except KeyError:  # Must raise AttributeError if property doesn't exist.
-        raise AttributeError
-
-    def Set(self, value):
-      self.shared_definition[name] = value
-
-    setattr(cls, name, property(Get, Set))
-
   def __eq__(self, rhs):
-    return (isinstance(rhs, ReferenceKind)
-            and super(ReferenceKind, self).__eq__(rhs)
-            and self.is_nullable == rhs.is_nullable)
-
-  def __hash__(self):
-    return hash((super(ReferenceKind, self).__hash__(), self.is_nullable))
+    return (isinstance(rhs, ReferenceKind) and super().__eq__(rhs))
 
-  def IsBackwardCompatible(self, rhs, checker):
-    return (super(ReferenceKind, self).IsBackwardCompatible(rhs, checker)
-            and self.is_nullable == rhs.is_nullable)
+  def __hash__(self):  # pylint: disable=useless-super-delegation
+    return super().__hash__()
 
 
 # Initialize the set of primitive types. These can be accessed by clients.
-BOOL = Kind('b')
-INT8 = Kind('i8')
-INT16 = Kind('i16')
-INT32 = Kind('i32')
-INT64 = Kind('i64')
-UINT8 = Kind('u8')
-UINT16 = Kind('u16')
-UINT32 = Kind('u32')
-UINT64 = Kind('u64')
-FLOAT = Kind('f')
-DOUBLE = Kind('d')
+BOOL = ValueKind('b')
+INT8 = ValueKind('i8')
+INT16 = ValueKind('i16')
+INT32 = ValueKind('i32')
+INT64 = ValueKind('i64')
+UINT8 = ValueKind('u8')
+UINT16 = ValueKind('u16')
+UINT32 = ValueKind('u32')
+UINT64 = ValueKind('u64')
+FLOAT = ValueKind('f')
+DOUBLE = ValueKind('d')
+NULLABLE_BOOL = ValueKind('?b', True)
+NULLABLE_INT8 = ValueKind('?i8', True)
+NULLABLE_INT16 = ValueKind('?i16', True)
+NULLABLE_INT32 = ValueKind('?i32', True)
+NULLABLE_INT64 = ValueKind('?i64', True)
+NULLABLE_UINT8 = ValueKind('?u8', True)
+NULLABLE_UINT16 = ValueKind('?u16', True)
+NULLABLE_UINT32 = ValueKind('?u32', True)
+NULLABLE_UINT64 = ValueKind('?u64', True)
+NULLABLE_FLOAT = ValueKind('?f', True)
+NULLABLE_DOUBLE = ValueKind('?d', True)
 STRING = ReferenceKind('s')
 HANDLE = ReferenceKind('h')
 DCPIPE = ReferenceKind('h:d:c')
@@ -270,6 +356,17 @@  PRIMITIVES = (
     UINT64,
     FLOAT,
     DOUBLE,
+    NULLABLE_BOOL,
+    NULLABLE_INT8,
+    NULLABLE_INT16,
+    NULLABLE_INT32,
+    NULLABLE_INT64,
+    NULLABLE_UINT8,
+    NULLABLE_UINT16,
+    NULLABLE_UINT32,
+    NULLABLE_UINT64,
+    NULLABLE_FLOAT,
+    NULLABLE_DOUBLE,
     STRING,
     HANDLE,
     DCPIPE,
@@ -294,9 +391,12 @@  ATTRIBUTE_STABLE = 'Stable'
 ATTRIBUTE_SYNC = 'Sync'
 ATTRIBUTE_UNLIMITED_SIZE = 'UnlimitedSize'
 ATTRIBUTE_UUID = 'Uuid'
+ATTRIBUTE_SERVICE_SANDBOX = 'ServiceSandbox'
+ATTRIBUTE_REQUIRE_CONTEXT = 'RequireContext'
+ATTRIBUTE_ALLOWED_CONTEXT = 'AllowedContext'
 
 
-class NamedValue(object):
+class NamedValue:
   def __init__(self, module, parent_kind, mojom_name):
     self.module = module
     self.parent_kind = parent_kind
@@ -316,7 +416,7 @@  class NamedValue(object):
     return hash((self.parent_kind, self.mojom_name))
 
 
-class BuiltinValue(object):
+class BuiltinValue:
   def __init__(self, value):
     self.value = value
 
@@ -350,7 +450,7 @@  class EnumValue(NamedValue):
     return self.field.name
 
 
-class Constant(object):
+class Constant:
   def __init__(self, mojom_name=None, kind=None, value=None, parent_kind=None):
     self.mojom_name = mojom_name
     self.name = None
@@ -368,7 +468,7 @@  class Constant(object):
                                        rhs.parent_kind))
 
 
-class Field(object):
+class Field:
   def __init__(self,
                mojom_name=None,
                kind=None,
@@ -414,7 +514,18 @@  class StructField(Field):
 
 
 class UnionField(Field):
-  pass
+  def __init__(self,
+               mojom_name=None,
+               kind=None,
+               ordinal=None,
+               default=None,
+               attributes=None):
+    Field.__init__(self, mojom_name, kind, ordinal, default, attributes)
+
+  @property
+  def is_default(self):
+    return self.attributes.get(ATTRIBUTE_DEFAULT, False) \
+        if self.attributes else False
 
 
 def _IsFieldBackwardCompatible(new_field, old_field, checker):
@@ -441,14 +552,14 @@  class Struct(ReferenceKind):
         if it's a native struct.
   """
 
-  ReferenceKind.AddSharedProperty('mojom_name')
-  ReferenceKind.AddSharedProperty('name')
-  ReferenceKind.AddSharedProperty('native_only')
-  ReferenceKind.AddSharedProperty('custom_serializer')
-  ReferenceKind.AddSharedProperty('fields')
-  ReferenceKind.AddSharedProperty('enums')
-  ReferenceKind.AddSharedProperty('constants')
-  ReferenceKind.AddSharedProperty('attributes')
+  Kind.AddSharedProperty('mojom_name')
+  Kind.AddSharedProperty('name')
+  Kind.AddSharedProperty('native_only')
+  Kind.AddSharedProperty('custom_serializer')
+  Kind.AddSharedProperty('fields')
+  Kind.AddSharedProperty('enums')
+  Kind.AddSharedProperty('constants')
+  Kind.AddSharedProperty('attributes')
 
   def __init__(self, mojom_name=None, module=None, attributes=None):
     if mojom_name is not None:
@@ -470,12 +581,11 @@  class Struct(ReferenceKind):
       return '<%s mojom_name=%r module=%s>' % (self.__class__.__name__,
                                                self.mojom_name,
                                                Repr(self.module, as_ref=True))
-    else:
-      return GenericRepr(self, {
-          'mojom_name': False,
-          'fields': False,
-          'module': True
-      })
+    return GenericRepr(self, {
+        'mojom_name': False,
+        'fields': False,
+        'module': True
+    })
 
   def AddField(self,
                mojom_name,
@@ -496,13 +606,13 @@  class Struct(ReferenceKind):
     for constant in self.constants:
       constant.Stylize(stylizer)
 
-  def IsBackwardCompatible(self, older_struct, checker):
-    """This struct is backward-compatible with older_struct if and only if all
-    of the following conditions hold:
+  def IsBackwardCompatible(self, rhs, checker):
+    """This struct is backward-compatible with rhs (older_struct) if and only if
+    all of the following conditions hold:
       - Any newly added field is tagged with a [MinVersion] attribute specifying
         a version number greater than all previously used [MinVersion]
         attributes within the struct.
-      - All fields present in older_struct remain present in the new struct,
+      - All fields present in rhs remain present in the new struct,
         with the same ordinal position, same optional or non-optional status,
         same (or backward-compatible) type and where applicable, the same
         [MinVersion] attribute value.
@@ -521,7 +631,7 @@  class Struct(ReferenceKind):
       return fields_by_ordinal
 
     new_fields = buildOrdinalFieldMap(self)
-    old_fields = buildOrdinalFieldMap(older_struct)
+    old_fields = buildOrdinalFieldMap(rhs)
     if len(new_fields) < len(old_fields):
       # At least one field was removed, which is not OK.
       return False
@@ -574,11 +684,18 @@  class Struct(ReferenceKind):
       prefix = self.module.GetNamespacePrefix()
     return '%s%s' % (prefix, self.mojom_name)
 
+  def _tuple(self):
+    return (self.mojom_name, self.native_only, self.fields, self.constants,
+            self.attributes)
+
   def __eq__(self, rhs):
-    return (isinstance(rhs, Struct) and
-            (self.mojom_name, self.native_only, self.fields, self.constants,
-             self.attributes) == (rhs.mojom_name, rhs.native_only, rhs.fields,
-                                  rhs.constants, rhs.attributes))
+    return isinstance(rhs, Struct) and self._tuple() == rhs._tuple()
+
+  def __lt__(self, rhs):
+    if not isinstance(self, type(rhs)):
+      return str(type(self)) < str(type(rhs))
+
+    return self._tuple() < rhs._tuple()
 
   def __hash__(self):
     return id(self)
@@ -595,10 +712,11 @@  class Union(ReferenceKind):
         which Java class name to use to represent it in the generated
         bindings.
   """
-  ReferenceKind.AddSharedProperty('mojom_name')
-  ReferenceKind.AddSharedProperty('name')
-  ReferenceKind.AddSharedProperty('fields')
-  ReferenceKind.AddSharedProperty('attributes')
+  Kind.AddSharedProperty('mojom_name')
+  Kind.AddSharedProperty('name')
+  Kind.AddSharedProperty('fields')
+  Kind.AddSharedProperty('attributes')
+  Kind.AddSharedProperty('default_field')
 
   def __init__(self, mojom_name=None, module=None, attributes=None):
     if mojom_name is not None:
@@ -610,14 +728,14 @@  class Union(ReferenceKind):
     self.name = None
     self.fields = []
     self.attributes = attributes
+    self.default_field = None
 
   def Repr(self, as_ref=True):
     if as_ref:
       return '<%s spec=%r is_nullable=%r fields=%s>' % (
           self.__class__.__name__, self.spec, self.is_nullable, Repr(
               self.fields))
-    else:
-      return GenericRepr(self, {'fields': True, 'is_nullable': False})
+    return GenericRepr(self, {'fields': True, 'is_nullable': False})
 
   def AddField(self, mojom_name, kind, ordinal=None, attributes=None):
     field = UnionField(mojom_name, kind, ordinal, None, attributes)
@@ -629,13 +747,13 @@  class Union(ReferenceKind):
     for field in self.fields:
       field.Stylize(stylizer)
 
-  def IsBackwardCompatible(self, older_union, checker):
-    """This union is backward-compatible with older_union if and only if all
-    of the following conditions hold:
+  def IsBackwardCompatible(self, rhs, checker):
+    """This union is backward-compatible with rhs (older_union) if and only if
+    all of the following conditions hold:
       - Any newly added field is tagged with a [MinVersion] attribute specifying
         a version number greater than all previously used [MinVersion]
         attributes within the union.
-      - All fields present in older_union remain present in the new union,
+      - All fields present in rhs remain present in the new union,
         with the same ordinal value, same optional or non-optional status,
         same (or backward-compatible) type, and where applicable, the same
         [MinVersion] attribute value.
@@ -651,7 +769,7 @@  class Union(ReferenceKind):
       return fields_by_ordinal
 
     new_fields = buildOrdinalFieldMap(self)
-    old_fields = buildOrdinalFieldMap(older_union)
+    old_fields = buildOrdinalFieldMap(rhs)
     if len(new_fields) < len(old_fields):
       # At least one field was removed, which is not OK.
       return False
@@ -677,6 +795,11 @@  class Union(ReferenceKind):
 
     return True
 
+  @property
+  def extensible(self):
+    return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \
+        if self.attributes else False
+
   @property
   def stable(self):
     return self.attributes.get(ATTRIBUTE_STABLE, False) \
@@ -690,10 +813,17 @@  class Union(ReferenceKind):
       prefix = self.module.GetNamespacePrefix()
     return '%s%s' % (prefix, self.mojom_name)
 
+  def _tuple(self):
+    return (self.mojom_name, self.fields, self.attributes)
+
   def __eq__(self, rhs):
-    return (isinstance(rhs, Union) and
-            (self.mojom_name, self.fields,
-             self.attributes) == (rhs.mojom_name, rhs.fields, rhs.attributes))
+    return isinstance(rhs, Union) and self._tuple() == rhs._tuple()
+
+  def __lt__(self, rhs):
+    if not isinstance(self, type(rhs)):
+      return str(type(self)) < str(type(rhs))
+
+    return self._tuple() < rhs._tuple()
 
   def __hash__(self):
     return id(self)
@@ -707,8 +837,8 @@  class Array(ReferenceKind):
     length: The number of elements. None if unknown.
   """
 
-  ReferenceKind.AddSharedProperty('kind')
-  ReferenceKind.AddSharedProperty('length')
+  Kind.AddSharedProperty('kind')
+  Kind.AddSharedProperty('length')
 
   def __init__(self, kind=None, length=None):
     if kind is not None:
@@ -728,12 +858,11 @@  class Array(ReferenceKind):
       return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % (
           self.__class__.__name__, self.spec, self.is_nullable, Repr(
               self.kind), self.length)
-    else:
-      return GenericRepr(self, {
-          'kind': True,
-          'length': False,
-          'is_nullable': False
-      })
+    return GenericRepr(self, {
+        'kind': True,
+        'length': False,
+        'is_nullable': False
+    })
 
   def __eq__(self, rhs):
     return (isinstance(rhs, Array)
@@ -754,8 +883,8 @@  class Map(ReferenceKind):
     key_kind: {Kind} The type of the keys. May be None.
     value_kind: {Kind} The type of the elements. May be None.
   """
-  ReferenceKind.AddSharedProperty('key_kind')
-  ReferenceKind.AddSharedProperty('value_kind')
+  Kind.AddSharedProperty('key_kind')
+  Kind.AddSharedProperty('value_kind')
 
   def __init__(self, key_kind=None, value_kind=None):
     if (key_kind is not None and value_kind is not None):
@@ -780,8 +909,7 @@  class Map(ReferenceKind):
       return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % (
           self.__class__.__name__, self.spec, self.is_nullable,
           Repr(self.key_kind), Repr(self.value_kind))
-    else:
-      return GenericRepr(self, {'key_kind': True, 'value_kind': True})
+    return GenericRepr(self, {'key_kind': True, 'value_kind': True})
 
   def __eq__(self, rhs):
     return (isinstance(rhs, Map) and
@@ -797,7 +925,7 @@  class Map(ReferenceKind):
 
 
 class PendingRemote(ReferenceKind):
-  ReferenceKind.AddSharedProperty('kind')
+  Kind.AddSharedProperty('kind')
 
   def __init__(self, kind=None):
     if kind is not None:
@@ -822,7 +950,7 @@  class PendingRemote(ReferenceKind):
 
 
 class PendingReceiver(ReferenceKind):
-  ReferenceKind.AddSharedProperty('kind')
+  Kind.AddSharedProperty('kind')
 
   def __init__(self, kind=None):
     if kind is not None:
@@ -847,7 +975,7 @@  class PendingReceiver(ReferenceKind):
 
 
 class PendingAssociatedRemote(ReferenceKind):
-  ReferenceKind.AddSharedProperty('kind')
+  Kind.AddSharedProperty('kind')
 
   def __init__(self, kind=None):
     if kind is not None:
@@ -873,7 +1001,7 @@  class PendingAssociatedRemote(ReferenceKind):
 
 
 class PendingAssociatedReceiver(ReferenceKind):
-  ReferenceKind.AddSharedProperty('kind')
+  Kind.AddSharedProperty('kind')
 
   def __init__(self, kind=None):
     if kind is not None:
@@ -899,7 +1027,7 @@  class PendingAssociatedReceiver(ReferenceKind):
 
 
 class InterfaceRequest(ReferenceKind):
-  ReferenceKind.AddSharedProperty('kind')
+  Kind.AddSharedProperty('kind')
 
   def __init__(self, kind=None):
     if kind is not None:
@@ -923,7 +1051,7 @@  class InterfaceRequest(ReferenceKind):
 
 
 class AssociatedInterfaceRequest(ReferenceKind):
-  ReferenceKind.AddSharedProperty('kind')
+  Kind.AddSharedProperty('kind')
 
   def __init__(self, kind=None):
     if kind is not None:
@@ -949,7 +1077,7 @@  class AssociatedInterfaceRequest(ReferenceKind):
             self.kind, rhs.kind)
 
 
-class Parameter(object):
+class Parameter:
   def __init__(self,
                mojom_name=None,
                kind=None,
@@ -983,7 +1111,7 @@  class Parameter(object):
                                       rhs.default, rhs.attributes))
 
 
-class Method(object):
+class Method:
   def __init__(self, interface, mojom_name, ordinal=None, attributes=None):
     self.interface = interface
     self.mojom_name = mojom_name
@@ -999,12 +1127,11 @@  class Method(object):
   def Repr(self, as_ref=True):
     if as_ref:
       return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
-    else:
-      return GenericRepr(self, {
-          'mojom_name': False,
-          'parameters': True,
-          'response_parameters': True
-      })
+    return GenericRepr(self, {
+        'mojom_name': False,
+        'parameters': True,
+        'response_parameters': True
+    })
 
   def AddParameter(self,
                    mojom_name,
@@ -1061,21 +1188,32 @@  class Method(object):
     return self.attributes.get(ATTRIBUTE_UNLIMITED_SIZE) \
         if self.attributes else False
 
+  @property
+  def allowed_context(self):
+    return self.attributes.get(ATTRIBUTE_ALLOWED_CONTEXT) \
+        if self.attributes else None
+
+  def _tuple(self):
+    return (self.mojom_name, self.ordinal, self.parameters,
+            self.response_parameters, self.attributes)
+
   def __eq__(self, rhs):
-    return (isinstance(rhs, Method) and
-            (self.mojom_name, self.ordinal, self.parameters,
-             self.response_parameters,
-             self.attributes) == (rhs.mojom_name, rhs.ordinal, rhs.parameters,
-                                  rhs.response_parameters, rhs.attributes))
+    return isinstance(rhs, Method) and self._tuple() == rhs._tuple()
+
+  def __lt__(self, rhs):
+    if not isinstance(self, type(rhs)):
+      return str(type(self)) < str(type(rhs))
+
+    return self._tuple() < rhs._tuple()
 
 
 class Interface(ReferenceKind):
-  ReferenceKind.AddSharedProperty('mojom_name')
-  ReferenceKind.AddSharedProperty('name')
-  ReferenceKind.AddSharedProperty('methods')
-  ReferenceKind.AddSharedProperty('enums')
-  ReferenceKind.AddSharedProperty('constants')
-  ReferenceKind.AddSharedProperty('attributes')
+  Kind.AddSharedProperty('mojom_name')
+  Kind.AddSharedProperty('name')
+  Kind.AddSharedProperty('methods')
+  Kind.AddSharedProperty('enums')
+  Kind.AddSharedProperty('constants')
+  Kind.AddSharedProperty('attributes')
 
   def __init__(self, mojom_name=None, module=None, attributes=None):
     if mojom_name is not None:
@@ -1093,12 +1231,11 @@  class Interface(ReferenceKind):
   def Repr(self, as_ref=True):
     if as_ref:
       return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
-    else:
-      return GenericRepr(self, {
-          'mojom_name': False,
-          'attributes': False,
-          'methods': False
-      })
+    return GenericRepr(self, {
+        'mojom_name': False,
+        'attributes': False,
+        'methods': False
+    })
 
   def AddMethod(self, mojom_name, ordinal=None, attributes=None):
     method = Method(self, mojom_name, ordinal, attributes)
@@ -1114,10 +1251,10 @@  class Interface(ReferenceKind):
     for constant in self.constants:
       constant.Stylize(stylizer)
 
-  def IsBackwardCompatible(self, older_interface, checker):
-    """This interface is backward-compatible with older_interface if and only
-    if all of the following conditions hold:
-      - All defined methods in older_interface (when identified by ordinal) have
+  def IsBackwardCompatible(self, rhs, checker):
+    """This interface is backward-compatible with rhs (older_interface) if and
+    only if all of the following conditions hold:
+      - All defined methods in rhs (when identified by ordinal) have
         backward-compatible definitions in this interface. For each method this
         means:
           - The parameter list is backward-compatible, according to backward-
@@ -1131,7 +1268,7 @@  class Interface(ReferenceKind):
             rules for structs.
       - All newly introduced methods in this interface have a [MinVersion]
         attribute specifying a version greater than any method in
-        older_interface.
+        rhs.
     """
 
     def buildOrdinalMethodMap(interface):
@@ -1144,7 +1281,7 @@  class Interface(ReferenceKind):
       return methods_by_ordinal
 
     new_methods = buildOrdinalMethodMap(self)
-    old_methods = buildOrdinalMethodMap(older_interface)
+    old_methods = buildOrdinalMethodMap(rhs)
     max_old_min_version = 0
     for ordinal, old_method in old_methods.items():
       new_method = new_methods.get(ordinal)
@@ -1186,6 +1323,27 @@  class Interface(ReferenceKind):
 
     return True
 
+  @property
+  def service_sandbox(self):
+    if not self.attributes:
+      return None
+    service_sandbox = self.attributes.get(ATTRIBUTE_SERVICE_SANDBOX, None)
+    if service_sandbox is None:
+      return None
+    # Constants are only allowed to refer to an enum here, so replace.
+    if isinstance(service_sandbox, Constant):
+      service_sandbox = service_sandbox.value
+    if not isinstance(service_sandbox, EnumValue):
+      raise Exception("ServiceSandbox attribute on %s must be an enum value." %
+                      self.module.name)
+    return service_sandbox
+
+  @property
+  def require_context(self):
+    if not self.attributes:
+      return None
+    return self.attributes.get(ATTRIBUTE_REQUIRE_CONTEXT, None)
+
   @property
   def stable(self):
     return self.attributes.get(ATTRIBUTE_STABLE, False) \
@@ -1199,11 +1357,18 @@  class Interface(ReferenceKind):
       prefix = self.module.GetNamespacePrefix()
     return '%s%s' % (prefix, self.mojom_name)
 
+  def _tuple(self):
+    return (self.mojom_name, self.methods, self.enums, self.constants,
+            self.attributes)
+
   def __eq__(self, rhs):
-    return (isinstance(rhs, Interface)
-            and (self.mojom_name, self.methods, self.enums, self.constants,
-                 self.attributes) == (rhs.mojom_name, rhs.methods, rhs.enums,
-                                      rhs.constants, rhs.attributes))
+    return isinstance(rhs, Interface) and self._tuple() == rhs._tuple()
+
+  def __lt__(self, rhs):
+    if not isinstance(self, type(rhs)):
+      return str(type(self)) < str(type(rhs))
+
+    return self._tuple() < rhs._tuple()
 
   @property
   def uuid(self):
@@ -1224,7 +1389,7 @@  class Interface(ReferenceKind):
 
 
 class AssociatedInterface(ReferenceKind):
-  ReferenceKind.AddSharedProperty('kind')
+  Kind.AddSharedProperty('kind')
 
   def __init__(self, kind=None):
     if kind is not None:
@@ -1249,7 +1414,7 @@  class AssociatedInterface(ReferenceKind):
                           self.kind, rhs.kind)
 
 
-class EnumField(object):
+class EnumField:
   def __init__(self,
                mojom_name=None,
                value=None,
@@ -1281,16 +1446,25 @@  class EnumField(object):
                                          rhs.attributes, rhs.numeric_value))
 
 
-class Enum(Kind):
+class Enum(ValueKind):
+  Kind.AddSharedProperty('mojom_name')
+  Kind.AddSharedProperty('name')
+  Kind.AddSharedProperty('native_only')
+  Kind.AddSharedProperty('fields')
+  Kind.AddSharedProperty('attributes')
+  Kind.AddSharedProperty('min_value')
+  Kind.AddSharedProperty('max_value')
+  Kind.AddSharedProperty('default_field')
+
   def __init__(self, mojom_name=None, module=None, attributes=None):
-    self.mojom_name = mojom_name
-    self.name = None
-    self.native_only = False
     if mojom_name is not None:
       spec = 'x:' + mojom_name
     else:
       spec = None
-    Kind.__init__(self, spec, module)
+    ValueKind.__init__(self, spec, False, module)
+    self.mojom_name = mojom_name
+    self.name = None
+    self.native_only = False
     self.fields = []
     self.attributes = attributes
     self.min_value = None
@@ -1300,8 +1474,7 @@  class Enum(Kind):
   def Repr(self, as_ref=True):
     if as_ref:
       return '<%s mojom_name=%r>' % (self.__class__.__name__, self.mojom_name)
-    else:
-      return GenericRepr(self, {'mojom_name': False, 'fields': False})
+    return GenericRepr(self, {'mojom_name': False, 'fields': False})
 
   def Stylize(self, stylizer):
     self.name = stylizer.StylizeEnum(self.mojom_name)
@@ -1327,14 +1500,14 @@  class Enum(Kind):
     return '%s%s' % (prefix, self.mojom_name)
 
   # pylint: disable=unused-argument
-  def IsBackwardCompatible(self, older_enum, checker):
-    """This enum is backward-compatible with older_enum if and only if one of
-    the following conditions holds:
+  def IsBackwardCompatible(self, rhs, checker):
+    """This enum is backward-compatible with rhs (older_enum) if and only if one
+    of the following conditions holds:
         - Neither enum is [Extensible] and both have the exact same set of valid
           numeric values. Field names and aliases for the same numeric value do
           not affect compatibility.
-        - older_enum is [Extensible], and for every version defined by
-          older_enum, this enum has the exact same set of valid numeric values.
+        - rhs is [Extensible], and for every version defined by
+          rhs, this enum has the exact same set of valid numeric values.
     """
 
     def buildVersionFieldMap(enum):
@@ -1345,10 +1518,10 @@  class Enum(Kind):
         fields_by_min_version[field.min_version].add(field.numeric_value)
       return fields_by_min_version
 
-    old_fields = buildVersionFieldMap(older_enum)
+    old_fields = buildVersionFieldMap(rhs)
     new_fields = buildVersionFieldMap(self)
 
-    if new_fields.keys() != old_fields.keys() and not older_enum.extensible:
+    if new_fields.keys() != old_fields.keys() and not rhs.extensible:
       return False
 
     for min_version, valid_values in old_fields.items():
@@ -1358,19 +1531,24 @@  class Enum(Kind):
 
     return True
 
+  def _tuple(self):
+    return (self.mojom_name, self.native_only, self.fields, self.attributes,
+            self.min_value, self.max_value, self.default_field)
+
   def __eq__(self, rhs):
-    return (isinstance(rhs, Enum) and
-            (self.mojom_name, self.native_only, self.fields, self.attributes,
-             self.min_value, self.max_value,
-             self.default_field) == (rhs.mojom_name, rhs.native_only,
-                                     rhs.fields, rhs.attributes, rhs.min_value,
-                                     rhs.max_value, rhs.default_field))
+    return isinstance(rhs, Enum) and self._tuple() == rhs._tuple()
+
+  def __lt__(self, rhs):
+    if not isinstance(self, type(rhs)):
+      return str(type(self)) < str(type(rhs))
+
+    return self._tuple() < rhs._tuple()
 
   def __hash__(self):
     return id(self)
 
 
-class Module(object):
+class Module:
   def __init__(self, path=None, mojom_namespace=None, attributes=None):
     self.path = path
     self.mojom_namespace = mojom_namespace
@@ -1380,11 +1558,11 @@  class Module(object):
     self.interfaces = []
     self.enums = []
     self.constants = []
-    self.kinds = {}
+    self.kinds = OrderedDict()
     self.attributes = attributes
     self.imports = []
-    self.imported_kinds = {}
-    self.metadata = {}
+    self.imported_kinds = OrderedDict()
+    self.metadata = OrderedDict()
 
   def __repr__(self):
     # Gives us a decent __repr__ for modules.
@@ -1405,16 +1583,15 @@  class Module(object):
     if as_ref:
       return '<%s path=%r mojom_namespace=%r>' % (
           self.__class__.__name__, self.path, self.mojom_namespace)
-    else:
-      return GenericRepr(
-          self, {
-              'path': False,
-              'mojom_namespace': False,
-              'attributes': False,
-              'structs': False,
-              'interfaces': False,
-              'unions': False
-          })
+    return GenericRepr(
+        self, {
+            'path': False,
+            'mojom_namespace': False,
+            'attributes': False,
+            'structs': False,
+            'interfaces': False,
+            'unions': False
+        })
 
   def GetNamespacePrefix(self):
     return '%s.' % self.mojom_namespace if self.mojom_namespace else ''
@@ -1451,7 +1628,7 @@  class Module(object):
       imported_module.Stylize(stylizer)
 
   def Dump(self, f):
-    pickle.dump(self, f, 2)
+    pickle.dump(self, f)
 
   @classmethod
   def Load(cls, f):
@@ -1461,15 +1638,15 @@  class Module(object):
 
 
 def IsBoolKind(kind):
-  return kind.spec == BOOL.spec
+  return kind.spec == BOOL.spec or kind.spec == NULLABLE_BOOL.spec
 
 
 def IsFloatKind(kind):
-  return kind.spec == FLOAT.spec
+  return kind.spec == FLOAT.spec or kind.spec == NULLABLE_FLOAT.spec
 
 
 def IsDoubleKind(kind):
-  return kind.spec == DOUBLE.spec
+  return kind.spec == DOUBLE.spec or kind.spec == NULLABLE_DOUBLE.spec
 
 
 def IsIntegralKind(kind):
@@ -1477,7 +1654,14 @@  def IsIntegralKind(kind):
           or kind.spec == INT16.spec or kind.spec == INT32.spec
           or kind.spec == INT64.spec or kind.spec == UINT8.spec
           or kind.spec == UINT16.spec or kind.spec == UINT32.spec
-          or kind.spec == UINT64.spec)
+          or kind.spec == UINT64.spec or kind.spec == NULLABLE_BOOL.spec
+          or kind.spec == NULLABLE_INT8.spec or kind.spec == NULLABLE_INT16.spec
+          or kind.spec == NULLABLE_INT32.spec
+          or kind.spec == NULLABLE_INT64.spec
+          or kind.spec == NULLABLE_UINT8.spec
+          or kind.spec == NULLABLE_UINT16.spec
+          or kind.spec == NULLABLE_UINT32.spec
+          or kind.spec == NULLABLE_UINT64.spec)
 
 
 def IsStringKind(kind):
@@ -1563,7 +1747,7 @@  def IsReferenceKind(kind):
 
 
 def IsNullableKind(kind):
-  return IsReferenceKind(kind) and kind.is_nullable
+  return kind.is_nullable
 
 
 def IsMapKind(kind):
@@ -1664,11 +1848,8 @@  def MethodPassesInterfaces(method):
   return _AnyMethodParameterRecursive(method, IsInterfaceKind)
 
 
-def HasSyncMethods(interface):
-  for method in interface.methods:
-    if method.sync:
-      return True
-  return False
+def GetSyncMethodOrdinals(interface):
+  return [method.ordinal for method in interface.methods if method.sync]
 
 
 def HasUninterruptableMethods(interface):
@@ -1700,18 +1881,17 @@  def ContainsHandlesOrInterfaces(kind):
     checked.add(kind.spec)
     if IsStructKind(kind):
       return any(Check(field.kind) for field in kind.fields)
-    elif IsUnionKind(kind):
+    if IsUnionKind(kind):
       return any(Check(field.kind) for field in kind.fields)
-    elif IsAnyHandleKind(kind):
+    if IsAnyHandleKind(kind):
       return True
-    elif IsAnyInterfaceKind(kind):
+    if IsAnyInterfaceKind(kind):
       return True
-    elif IsArrayKind(kind):
+    if IsArrayKind(kind):
       return Check(kind.kind)
-    elif IsMapKind(kind):
+    if IsMapKind(kind):
       return Check(kind.key_kind) or Check(kind.value_kind)
-    else:
-      return False
+    return False
 
   return Check(kind)
 
@@ -1738,21 +1918,20 @@  def ContainsNativeTypes(kind):
     checked.add(kind.spec)
     if IsEnumKind(kind):
       return kind.native_only
-    elif IsStructKind(kind):
+    if IsStructKind(kind):
       if kind.native_only:
         return True
       if any(enum.native_only for enum in kind.enums):
         return True
       return any(Check(field.kind) for field in kind.fields)
-    elif IsUnionKind(kind):
+    if IsUnionKind(kind):
       return any(Check(field.kind) for field in kind.fields)
-    elif IsInterfaceKind(kind):
+    if IsInterfaceKind(kind):
       return any(enum.native_only for enum in kind.enums)
-    elif IsArrayKind(kind):
+    if IsArrayKind(kind):
       return Check(kind.kind)
-    elif IsMapKind(kind):
+    if IsMapKind(kind):
       return Check(kind.key_kind) or Check(kind.value_kind)
-    else:
-      return False
+    return False
 
   return Check(kind)
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
index e8fd4936..2a4e852c 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/module_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
index 88b77c98..71011109 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack.py
@@ -1,7 +1,8 @@ 
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import copy
 from mojom.generate import module as mojom
 
 # This module provides a mechanism for determining the packed order and offsets
@@ -15,7 +16,7 @@  from mojom.generate import module as mojom
 HEADER_SIZE = 8
 
 
-class PackedField(object):
+class PackedField:
   kind_to_size = {
       mojom.BOOL: 1,
       mojom.INT8: 1,
@@ -75,18 +76,55 @@  class PackedField(object):
       return 8
     return cls.GetSizeForKind(kind)
 
-  def __init__(self, field, index, ordinal):
+  def __init__(self,
+               field,
+               index,
+               ordinal,
+               original_field=None,
+               sub_ordinal=None,
+               linked_value_packed_field=None):
     """
     Args:
       field: the original field.
       index: the position of the original field in the struct.
       ordinal: the ordinal of the field for serialization.
+      original_field: See below.
+      sub_ordinal: See below.
+      linked_value_packed_field: See below.
+
+    original_field, sub_ordinal, and linked_value_packed_field are used to
+    support nullable ValueKind fields. For legacy reasons, nullable ValueKind
+    fields actually generate two PackedFields. This allows:
+
+    - backwards compatibility prior to Mojo support for nullable ValueKinds.
+    - correct packing of fields for the aforementioned backwards compatibility.
+
+    When translating Fields to PackedFields, the original field is turned into
+    two PackedFields: the first PackedField always has type mojom.BOOL, while
+    the second PackedField has the non-nullable version of the field's kind.
+
+    When constructing these PackedFields, original_field references the field
+    as defined in the mojom; the name as defined in the mojom will be used for
+    all layers above the wire/data layer.
+
+    sub_ordinal is used to sort the two PackedFields correctly with respect to
+    each other: the first mojom.BOOL field always has sub_ordinal 0, while the
+    second field always has sub_ordinal 1.
+
+    Finally, linked_value_packed_field is used by the serialization and
+    deserialization helpers, which generally just iterate over a PackedStruct's
+    PackedField's in ordinal order. This allows the helpers to easily reference
+    any related PackedFields rather than having to lookup related PackedFields
+    by index while iterating.
     """
     self.field = field
     self.index = index
     self.ordinal = ordinal
-    self.size = self.GetSizeForKind(field.kind)
-    self.alignment = self.GetAlignmentForKind(field.kind)
+    self.original_field = original_field
+    self.sub_ordinal = sub_ordinal
+    self.linked_value_packed_field = linked_value_packed_field
+    self.size = self.GetSizeForKind(self.field.kind)
+    self.alignment = self.GetAlignmentForKind(self.field.kind)
     self.offset = None
     self.bit = None
     self.min_version = None
@@ -120,7 +158,33 @@  def GetPayloadSizeUpToField(field):
   return offset + pad
 
 
-class PackedStruct(object):
+def IsNullableValueKindPackedField(field):
+  """Returns true if `field` is derived from a nullable ValueKind field.
+
+  Nullable ValueKind fields often require special handling in the bindings due
+  to the way the implementation is constrained for wire compatibility.
+  """
+  assert isinstance(field, PackedField)
+  return field.sub_ordinal is not None
+
+
+def IsPrimaryNullableValueKindPackedField(field):
+  """Returns true if `field` is derived from a nullable ValueKind mojom field
+  and is the "primary" field.
+
+  The primary field is a bool PackedField that controls if the field should be
+  considered as present or not; it will have a reference to the PackedField that
+  holds the actual value representation if considered present.
+
+  Bindings code that translates between the wire protocol and the higher layers
+  can use this to simplify mapping multiple PackedFields to the single field
+  that is logically exposed to bindings consumers.
+  """
+  assert isinstance(field, PackedField)
+  return field.linked_value_packed_field is not None
+
+
+class PackedStruct:
   def __init__(self, struct):
     self.struct = struct
     # |packed_fields| contains all the fields, in increasing offset order.
@@ -139,9 +203,41 @@  class PackedStruct(object):
     for index, field in enumerate(struct.fields):
       if field.ordinal is not None:
         ordinal = field.ordinal
-      src_fields.append(PackedField(field, index, ordinal))
+      # Nullable value types are a bit weird: they generate two PackedFields
+      # despite being a single ValueKind. This is for wire compatibility to
+      # ease the transition from legacy mojom syntax where nullable value types
+      # were not supported.
+      if isinstance(field.kind, mojom.ValueKind) and field.kind.is_nullable:
+        # The suffixes intentionally use Unicode codepoints which are considered
+        # valid C++/Java/JavaScript identifiers, yet are unlikely to be used in
+        # actual user code.
+        has_value_field = copy.copy(field)
+        has_value_field.name = f'{field.mojom_name}_$flag'
+        has_value_field.kind = mojom.BOOL
+
+        value_field = copy.copy(field)
+        value_field.name = f'{field.mojom_name}_$value'
+        value_field.kind = field.kind.MakeUnnullableKind()
+
+        value_packed_field = PackedField(value_field,
+                                         index,
+                                         ordinal,
+                                         original_field=field,
+                                         sub_ordinal=1,
+                                         linked_value_packed_field=None)
+        has_value_packed_field = PackedField(
+            has_value_field,
+            index,
+            ordinal,
+            original_field=field,
+            sub_ordinal=0,
+            linked_value_packed_field=value_packed_field)
+        src_fields.append(has_value_packed_field)
+        src_fields.append(value_packed_field)
+      else:
+        src_fields.append(PackedField(field, index, ordinal))
       ordinal += 1
-    src_fields.sort(key=lambda field: field.ordinal)
+    src_fields.sort(key=lambda field: (field.ordinal, field.sub_ordinal))
 
     # Set |min_version| for each field.
     next_min_version = 0
@@ -156,10 +252,11 @@  class PackedStruct(object):
       if (packed_field.min_version != 0
           and mojom.IsReferenceKind(packed_field.field.kind)
           and not packed_field.field.kind.is_nullable):
-        raise Exception("Non-nullable fields are only allowed in version 0 of "
-                        "a struct. %s.%s is defined with [MinVersion=%d]." %
-                        (self.struct.name, packed_field.field.name,
-                         packed_field.min_version))
+        raise Exception(
+            "Non-nullable reference fields are only allowed in version 0 of a "
+            "struct. %s.%s is defined with [MinVersion=%d]." %
+            (self.struct.name, packed_field.field.name,
+             packed_field.min_version))
 
     src_field = src_fields[0]
     src_field.offset = 0
@@ -186,7 +283,7 @@  class PackedStruct(object):
         dst_fields.append(src_field)
 
 
-class ByteInfo(object):
+class ByteInfo:
   def __init__(self):
     self.is_padding = False
     self.packed_fields = []
@@ -214,7 +311,7 @@  def GetByteLayout(packed_struct):
   return byte_info
 
 
-class VersionInfo(object):
+class VersionInfo:
   def __init__(self, version, num_fields, num_bytes):
     self.version = version
     self.num_fields = num_fields
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
index 98c705ad..5c6c36d5 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/pack_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
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
index 0da90058..807e2a4f 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/template_expander.py
@@ -1,4 +1,4 @@ 
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
index 7580b780..71ce3c03 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate.py
@@ -1,4 +1,4 @@ 
-# Copyright 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Convert parse tree to AST.
@@ -12,17 +12,296 @@  already been parsed and converted to ASTs before.
 import itertools
 import os
 import re
-import sys
 
+from collections import OrderedDict
 from mojom.generate import generator
 from mojom.generate import module as mojom
 from mojom.parse import ast
 
 
-def _IsStrOrUnicode(x):
-  if sys.version_info[0] < 3:
-    return isinstance(x, (unicode, str))
-  return isinstance(x, str)
+is_running_backwards_compatibility_check_hack = False
+
+### DO NOT ADD ENTRIES TO THIS LIST. ###
+_EXTENSIBLE_ENUMS_MISSING_DEFAULT = (
+    'x:arc.keymaster.mojom.Algorithm',
+    'x:arc.keymaster.mojom.Digest',
+    'x:arc.keymaster.mojom.SignatureResult',
+    'x:arc.mojom.AccessibilityActionType',
+    'x:arc.mojom.AccessibilityBooleanProperty',
+    'x:arc.mojom.AccessibilityEventIntListProperty',
+    'x:arc.mojom.AccessibilityEventIntProperty',
+    'x:arc.mojom.AccessibilityEventStringProperty',
+    'x:arc.mojom.AccessibilityEventType',
+    'x:arc.mojom.AccessibilityFilterType',
+    'x:arc.mojom.AccessibilityIntListProperty',
+    'x:arc.mojom.AccessibilityIntProperty',
+    'x:arc.mojom.AccessibilityLiveRegionType',
+    'x:arc.mojom.AccessibilityNotificationStateType',
+    'x:arc.mojom.AccessibilityRangeType',
+    'x:arc.mojom.AccessibilitySelectionMode',
+    'x:arc.mojom.AccessibilityStringListProperty',
+    'x:arc.mojom.AccessibilityStringProperty',
+    'x:arc.mojom.AccessibilityWindowBooleanProperty',
+    'x:arc.mojom.AccessibilityWindowIntListProperty',
+    'x:arc.mojom.AccessibilityWindowIntProperty',
+    'x:arc.mojom.AccessibilityWindowStringProperty',
+    'x:arc.mojom.AccessibilityWindowType',
+    'x:arc.mojom.AccountCheckStatus',
+    'x:arc.mojom.AccountUpdateType',
+    'x:arc.mojom.ActionType',
+    'x:arc.mojom.Algorithm',
+    'x:arc.mojom.AndroidIdSource',
+    'x:arc.mojom.AnrSource',
+    'x:arc.mojom.AnrType',
+    'x:arc.mojom.AppDiscoveryRequestState',
+    'x:arc.mojom.AppKillType',
+    'x:arc.mojom.AppPermission',
+    'x:arc.mojom.AppPermissionGroup',
+    'x:arc.mojom.AppReinstallState',
+    'x:arc.mojom.AppShortcutItemType',
+    'x:arc.mojom.ArcAuthCodeStatus',
+    'x:arc.mojom.ArcClipboardDragDropEvent',
+    'x:arc.mojom.ArcCorePriAbiMigEvent',
+    'x:arc.mojom.ArcDnsQuery',
+    'x:arc.mojom.ArcImageCopyPasteCompatAction',
+    'x:arc.mojom.ArcNetworkError',
+    'x:arc.mojom.ArcNetworkEvent',
+    'x:arc.mojom.ArcNotificationEvent',
+    'x:arc.mojom.ArcNotificationExpandState',
+    'x:arc.mojom.ArcNotificationPriority',
+    'x:arc.mojom.ArcNotificationRemoteInputState',
+    'x:arc.mojom.ArcNotificationShownContents',
+    'x:arc.mojom.ArcNotificationStyle',
+    'x:arc.mojom.ArcNotificationType',
+    'x:arc.mojom.ArcPipEvent',
+    'x:arc.mojom.ArcResizeLockState',
+    'x:arc.mojom.ArcSignInSuccess',
+    'x:arc.mojom.ArcTimerResult',
+    'x:arc.mojom.AudioSwitch',
+    'x:arc.mojom.BluetoothAclState',
+    'x:arc.mojom.BluetoothAdapterState',
+    'x:arc.mojom.BluetoothAdvertisingDataType',
+    'x:arc.mojom.BluetoothBondState',
+    'x:arc.mojom.BluetoothDeviceType',
+    'x:arc.mojom.BluetoothDiscoveryState',
+    'x:arc.mojom.BluetoothGattDBAttributeType',
+    'x:arc.mojom.BluetoothGattStatus',
+    'x:arc.mojom.BluetoothPropertyType',
+    'x:arc.mojom.BluetoothScanMode',
+    'x:arc.mojom.BluetoothSdpAttributeType',
+    'x:arc.mojom.BluetoothSocketType',
+    'x:arc.mojom.BluetoothStatus',
+    'x:arc.mojom.BootType',
+    'x:arc.mojom.CaptionTextShadowType',
+    'x:arc.mojom.ChangeType',
+    'x:arc.mojom.ChromeAccountType',
+    'x:arc.mojom.ChromeApp',
+    'x:arc.mojom.ChromePage',
+    'x:arc.mojom.ClockId',
+    'x:arc.mojom.CloudProvisionFlowError',
+    'x:arc.mojom.CommandResultType',
+    'x:arc.mojom.CompanionLibApiId',
+    'x:arc.mojom.ConnectionStateType',
+    'x:arc.mojom.ContentChangeType',
+    'x:arc.mojom.CpuRestrictionState',
+    'x:arc.mojom.CursorCoordinateSpace',
+    'x:arc.mojom.DataRestoreStatus',
+    'x:arc.mojom.DecoderStatus',
+    'x:arc.mojom.DeviceType',
+    'x:arc.mojom.Digest',
+    'x:arc.mojom.DisplayWakeLockType',
+    'x:arc.mojom.EapMethod',
+    'x:arc.mojom.EapPhase2Method',
+    'x:arc.mojom.FileSelectorEventType',
+    'x:arc.mojom.GMSCheckInError',
+    'x:arc.mojom.GMSSignInError',
+    'x:arc.mojom.GeneralSignInError',
+    'x:arc.mojom.GetNetworksRequestType',
+    'x:arc.mojom.HalPixelFormat',
+    'x:arc.mojom.IPAddressType',
+    'x:arc.mojom.InstallErrorReason',
+    'x:arc.mojom.KeyFormat',
+    'x:arc.mojom.KeyManagement',
+    'x:arc.mojom.KeyPurpose',
+    'x:arc.mojom.KeymasterError',
+    'x:arc.mojom.MainAccountHashMigrationStatus',
+    'x:arc.mojom.MainAccountResolutionStatus',
+    'x:arc.mojom.ManagementChangeStatus',
+    'x:arc.mojom.ManagementState',
+    'x:arc.mojom.MessageCenterVisibility',
+    'x:arc.mojom.MetricsType',
+    'x:arc.mojom.MountEvent',
+    'x:arc.mojom.NativeBridgeType',
+    'x:arc.mojom.NetworkResult',
+    'x:arc.mojom.NetworkType',
+    'x:arc.mojom.OemCryptoAlgorithm',
+    'x:arc.mojom.OemCryptoCipherMode',
+    'x:arc.mojom.OemCryptoHdcpCapability',
+    'x:arc.mojom.OemCryptoLicenseType',
+    'x:arc.mojom.OemCryptoPrivateKey',
+    'x:arc.mojom.OemCryptoProvisioningMethod',
+    'x:arc.mojom.OemCryptoResult',
+    'x:arc.mojom.OemCryptoRsaPaddingScheme',
+    'x:arc.mojom.OemCryptoUsageEntryStatus',
+    'x:arc.mojom.Padding',
+    'x:arc.mojom.PaiFlowState',
+    'x:arc.mojom.PatternType',
+    'x:arc.mojom.PressureLevel',
+    'x:arc.mojom.PrintColorMode',
+    'x:arc.mojom.PrintContentType',
+    'x:arc.mojom.PrintDuplexMode',
+    'x:arc.mojom.PrinterStatus',
+    'x:arc.mojom.ProcessState',
+    'x:arc.mojom.PurchaseState',
+    'x:arc.mojom.ReauthReason',
+    'x:arc.mojom.ScaleFactor',
+    'x:arc.mojom.SecurityType',
+    'x:arc.mojom.SegmentStyle',
+    'x:arc.mojom.SelectFilesActionType',
+    'x:arc.mojom.SetNativeChromeVoxResponse',
+    'x:arc.mojom.ShareFiles',
+    'x:arc.mojom.ShowPackageInfoPage',
+    'x:arc.mojom.SpanType',
+    'x:arc.mojom.SupportedLinkChangeSource',
+    'x:arc.mojom.TetheringClientState',
+    'x:arc.mojom.TextInputType',
+    'x:arc.mojom.TtsEventType',
+    'x:arc.mojom.VideoCodecProfile',
+    'x:arc.mojom.VideoDecodeAccelerator.Result',
+    'x:arc.mojom.VideoEncodeAccelerator.Error',
+    'x:arc.mojom.VideoFrameStorageType',
+    'x:arc.mojom.VideoPixelFormat',
+    'x:arc.mojom.WakefulnessMode',
+    'x:arc.mojom.WebApkInstallResult',
+    'x:ash.ime.mojom.InputFieldType',
+    'x:ash.ime.mojom.PersonalizationMode',
+    'x:ash.language.mojom.FeatureId',
+    'x:blink.mojom.ScrollRestorationType',
+    'x:chrome_cleaner.mojom.PromptAcceptance',
+    'x:chromeos.cdm.mojom.CdmKeyStatus',
+    'x:chromeos.cdm.mojom.CdmMessageType',
+    'x:chromeos.cdm.mojom.CdmSessionType',
+    'x:chromeos.cdm.mojom.DecryptStatus',
+    'x:chromeos.cdm.mojom.EmeInitDataType',
+    'x:chromeos.cdm.mojom.EncryptionScheme',
+    'x:chromeos.cdm.mojom.HdcpVersion',
+    'x:chromeos.cdm.mojom.OutputProtection.LinkType',
+    'x:chromeos.cdm.mojom.OutputProtection.ProtectionType',
+    'x:chromeos.cdm.mojom.PromiseException',
+    'x:chromeos.cfm.mojom.EnqueuePriority',
+    'x:chromeos.cfm.mojom.LoggerErrorCode',
+    'x:chromeos.cfm.mojom.LoggerState',
+    'x:chromeos.cros_healthd.mojom.CryptoAlgorithm',
+    'x:chromeos.cros_healthd.mojom.EncryptionState',
+    'x:chromeos.machine_learning.mojom.AnnotationUsecase',
+    'x:chromeos.machine_learning.mojom.BuiltinModelId',
+    'x:chromeos.machine_learning.mojom.CreateGraphExecutorResult',
+    'x:chromeos.machine_learning.mojom.DocumentScannerResultStatus',
+    'x:chromeos.machine_learning.mojom.EndpointReason',
+    'x:chromeos.machine_learning.mojom.EndpointerType',
+    'x:chromeos.machine_learning.mojom.ExecuteResult',
+    'x:chromeos.machine_learning.mojom.GrammarCheckerResult.Status',
+    'x:chromeos.machine_learning.mojom.HandwritingRecognizerResult.Status',
+    'x:chromeos.machine_learning.mojom.LoadHandwritingModelResult',
+    'x:chromeos.machine_learning.mojom.LoadModelResult',
+    'x:chromeos.machine_learning.mojom.Rotation',
+    'x:chromeos.network_config.mojom.ConnectionStateType',
+    'x:chromeos.network_config.mojom.DeviceStateType',
+    'x:chromeos.network_config.mojom.IPConfigType',
+    'x:chromeos.network_config.mojom.NetworkType',
+    'x:chromeos.network_config.mojom.OncSource',
+    'x:chromeos.network_config.mojom.PolicySource',
+    'x:chromeos.network_config.mojom.PortalState',
+    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdEvent',
+    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestHttpMethod',
+    'x:chromeos.wilco_dtc_supportd.mojom.WilcoDtcSupportdWebRequestStatus',
+    'x:cros.mojom.CameraClientType',
+    'x:cros.mojom.CameraMetadataSectionStart',
+    'x:cros.mojom.CameraMetadataTag',
+    'x:cros.mojom.HalPixelFormat',
+    'x:crosapi.mojom.AllowedPaths',
+    'x:crosapi.mojom.BrowserAppInstanceType',
+    'x:crosapi.mojom.CreationResult',
+    'x:crosapi.mojom.DeviceAccessResultCode',
+    'x:crosapi.mojom.DeviceMode',
+    'x:crosapi.mojom.DlpRestrictionLevel',
+    'x:crosapi.mojom.ExoImeSupport',
+    'x:crosapi.mojom.FullscreenVisibility',
+    'x:crosapi.mojom.GoogleServiceAuthError.State',
+    'x:crosapi.mojom.IsInstallableResult',
+    'x:crosapi.mojom.KeyTag',
+    'x:crosapi.mojom.KeystoreSigningAlgorithmName',
+    'x:crosapi.mojom.KeystoreType',
+    'x:crosapi.mojom.LacrosFeedbackSource',
+    'x:crosapi.mojom.MemoryPressureLevel',
+    'x:crosapi.mojom.MetricsReportingManaged',
+    'x:crosapi.mojom.NotificationType',
+    'x:crosapi.mojom.OndeviceHandwritingSupport',
+    'x:crosapi.mojom.OpenResult',
+    'x:crosapi.mojom.PolicyDomain',
+    'x:crosapi.mojom.RegistrationCodeType',
+    'x:crosapi.mojom.ScaleFactor',
+    'x:crosapi.mojom.SearchResult.OptionalBool',
+    'x:crosapi.mojom.SelectFileDialogType',
+    'x:crosapi.mojom.SelectFileResult',
+    'x:crosapi.mojom.SharesheetResult',
+    'x:crosapi.mojom.TouchEventType',
+    'x:crosapi.mojom.VideoRotation',
+    'x:crosapi.mojom.WallpaperLayout',
+    'x:crosapi.mojom.WebAppInstallResultCode',
+    'x:crosapi.mojom.WebAppUninstallResultCode',
+    'x:device.mojom.HidBusType',
+    'x:device.mojom.WakeLockReason',
+    'x:device.mojom.WakeLockType',
+    'x:drivefs.mojom.DialogReason.Type',
+    'x:drivefs.mojom.DriveError.Type',
+    'x:drivefs.mojom.DriveFsDelegate.ExtensionConnectionStatus',
+    'x:drivefs.mojom.FileMetadata.CanPinStatus',
+    'x:drivefs.mojom.FileMetadata.Type',
+    'x:drivefs.mojom.ItemEventReason',
+    'x:drivefs.mojom.MirrorPathStatus',
+    'x:drivefs.mojom.MirrorSyncStatus',
+    'x:drivefs.mojom.QueryParameters.SortField',
+    'x:fuzz.mojom.FuzzEnum',
+    'x:media.mojom.FillLightMode',
+    'x:media.mojom.MeteringMode',
+    'x:media.mojom.PowerLineFrequency',
+    'x:media.mojom.RedEyeReduction',
+    'x:media.mojom.ResolutionChangePolicy',
+    'x:media.mojom.VideoCaptureApi',
+    'x:media.mojom.VideoCaptureBufferType',
+    'x:media.mojom.VideoCaptureError',
+    'x:media.mojom.VideoCaptureFrameDropReason',
+    'x:media.mojom.VideoCapturePixelFormat',
+    'x:media.mojom.VideoCaptureTransportType',
+    'x:media.mojom.VideoFacingMode',
+    'x:media_session.mojom.AudioFocusType',
+    'x:media_session.mojom.CameraState',
+    'x:media_session.mojom.EnforcementMode',
+    'x:media_session.mojom.MediaAudioVideoState',
+    'x:media_session.mojom.MediaImageBitmapColorType',
+    'x:media_session.mojom.MediaPictureInPictureState',
+    'x:media_session.mojom.MediaPlaybackState',
+    'x:media_session.mojom.MediaSession.SuspendType',
+    'x:media_session.mojom.MediaSessionAction',
+    'x:media_session.mojom.MediaSessionImageType',
+    'x:media_session.mojom.MediaSessionInfo.SessionState',
+    'x:media_session.mojom.MicrophoneState',
+    'x:ml.model_loader.mojom.ComputeResult',
+    'x:ml.model_loader.mojom.CreateModelLoaderResult',
+    'x:ml.model_loader.mojom.LoadModelResult',
+    'x:mojo.test.AnExtensibleEnum',
+    'x:mojo.test.EnumB',
+    'x:mojo.test.ExtensibleEmptyEnum',
+    'x:mojo.test.enum_default_unittest.mojom.ExtensibleEnumWithoutDefault',
+    'x:network.mojom.WebSandboxFlags',
+    'x:payments.mojom.BillingResponseCode',
+    'x:payments.mojom.CreateDigitalGoodsResponseCode',
+    'x:payments.mojom.ItemType',
+    'x:printing.mojom.PrinterType',
+    'x:ui.mojom.KeyboardCode',
+)
+### DO NOT ADD ENTRIES TO THIS LIST. ###
 
 
 def _DuplicateName(values):
@@ -98,12 +377,6 @@  def _MapKind(kind):
   }
   if kind.endswith('?'):
     base_kind = _MapKind(kind[0:-1])
-    # NOTE: This doesn't rule out enum types. Those will be detected later, when
-    # cross-reference is established.
-    reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso', 'rmt', 'rcv',
-                       'rma', 'rca')
-    if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds:
-      raise Exception('A type (spec "%s") cannot be made nullable' % base_kind)
     return '?' + base_kind
   if kind.endswith('}'):
     lbracket = kind.rfind('{')
@@ -113,8 +386,6 @@  def _MapKind(kind):
     lbracket = kind.rfind('[')
     typename = kind[0:lbracket]
     return 'a' + kind[lbracket + 1:-1] + ':' + _MapKind(typename)
-  if kind.endswith('&'):
-    return 'r:' + _MapKind(kind[0:-1])
   if kind.startswith('asso<'):
     assert kind.endswith('>')
     return 'asso:' + _MapKind(kind[5:-1])
@@ -135,13 +406,35 @@  def _MapKind(kind):
   return 'x:' + kind
 
 
-def _AttributeListToDict(attribute_list):
+def _MapValueToEnum(module, value):
+  # True/False/None
+  if value is None:
+    return value
+  if not isinstance(value, str):
+    return value
+  # Otherwise try to find it.
+  try:
+    trial = _LookupValue(module, None, None, ('IDENTIFIER', value))
+    if isinstance(trial, mojom.ConstantValue):
+      return trial.constant
+    if isinstance(trial, mojom.EnumValue):
+      return trial
+  except ValueError:
+    pass
+  # Return the string if it did not resolve to a constant or enum.
+  return value
+
+
+def _AttributeListToDict(module, attribute_list):
   if attribute_list is None:
     return None
   assert isinstance(attribute_list, ast.AttributeList)
-  # TODO(vtl): Check for duplicate keys here.
-  return dict(
-      [(attribute.key, attribute.value) for attribute in attribute_list])
+  attributes = dict()
+  for attribute in attribute_list:
+    if attribute.key in attributes:
+      raise Exception("Duplicate key (%s) in attribute list" % attribute.key)
+    attributes[attribute.key] = _MapValueToEnum(module, attribute.value)
+  return attributes
 
 
 builtin_values = frozenset([
@@ -257,7 +550,8 @@  def _Kind(kinds, spec, scope):
     return kind
 
   if spec.startswith('?'):
-    kind = _Kind(kinds, spec[1:], scope).MakeNullableKind()
+    kind = _Kind(kinds, spec[1:], scope)
+    kind = kind.MakeNullableKind()
   elif spec.startswith('a:'):
     kind = mojom.Array(_Kind(kinds, spec[2:], scope))
   elif spec.startswith('asso:'):
@@ -345,7 +639,7 @@  def _Struct(module, parsed_struct):
             struct.fields_data.append,
         })
 
-  struct.attributes = _AttributeListToDict(parsed_struct.attribute_list)
+  struct.attributes = _AttributeListToDict(module, parsed_struct.attribute_list)
 
   # Enforce that a [Native] attribute is set to make native-only struct
   # declarations more explicit.
@@ -377,7 +671,7 @@  def _Union(module, parsed_union):
   union.fields_data = []
   _ProcessElements(parsed_union.mojom_name, parsed_union.body,
                    {ast.UnionField: union.fields_data.append})
-  union.attributes = _AttributeListToDict(parsed_union.attribute_list)
+  union.attributes = _AttributeListToDict(module, parsed_union.attribute_list)
   return union
 
 
@@ -398,7 +692,7 @@  def _StructField(module, parsed_field, struct):
   field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
   field.default = _LookupValue(module, struct, field.kind,
                                parsed_field.default_value)
-  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
+  field.attributes = _AttributeListToDict(module, parsed_field.attribute_list)
   return field
 
 
@@ -414,11 +708,21 @@  def _UnionField(module, parsed_field, union):
   """
   field = mojom.UnionField()
   field.mojom_name = parsed_field.mojom_name
+  # Disallow unions from being self-recursive.
+  parsed_typename = parsed_field.typename
+  if parsed_typename.endswith('?'):
+    parsed_typename = parsed_typename[:-1]
+  assert parsed_typename != union.mojom_name
   field.kind = _Kind(module.kinds, _MapKind(parsed_field.typename),
                      (module.mojom_namespace, union.mojom_name))
   field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None
   field.default = None
-  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
+  field.attributes = _AttributeListToDict(module, parsed_field.attribute_list)
+  if field.is_default and not mojom.IsNullableKind(field.kind) and \
+     not mojom.IsIntegralKind(field.kind):
+    raise Exception(
+        '[Default] field for union %s must be nullable or integral type.' %
+        union.mojom_name)
   return field
 
 
@@ -439,7 +743,8 @@  def _Parameter(module, parsed_param, interface):
   parameter.ordinal = (parsed_param.ordinal.value
                        if parsed_param.ordinal else None)
   parameter.default = None  # TODO(tibell): We never have these. Remove field?
-  parameter.attributes = _AttributeListToDict(parsed_param.attribute_list)
+  parameter.attributes = _AttributeListToDict(module,
+                                              parsed_param.attribute_list)
   return parameter
 
 
@@ -464,7 +769,7 @@  def _Method(module, parsed_method, interface):
     method.response_parameters = list(
         map(lambda parameter: _Parameter(module, parameter, interface),
             parsed_method.response_parameter_list))
-  method.attributes = _AttributeListToDict(parsed_method.attribute_list)
+  method.attributes = _AttributeListToDict(module, parsed_method.attribute_list)
 
   # Enforce that only methods with response can have a [Sync] attribute.
   if method.sync and method.response_parameters is None:
@@ -492,7 +797,8 @@  def _Interface(module, parsed_iface):
   interface.mojom_name = parsed_iface.mojom_name
   interface.spec = 'x:' + module.GetNamespacePrefix() + interface.mojom_name
   module.kinds[interface.spec] = interface
-  interface.attributes = _AttributeListToDict(parsed_iface.attribute_list)
+  interface.attributes = _AttributeListToDict(module,
+                                              parsed_iface.attribute_list)
   interface.enums = []
   interface.constants = []
   interface.methods_data = []
@@ -522,7 +828,7 @@  def _EnumField(module, enum, parsed_field):
   field = mojom.EnumField()
   field.mojom_name = parsed_field.mojom_name
   field.value = _LookupValue(module, enum, None, parsed_field.value)
-  field.attributes = _AttributeListToDict(parsed_field.attribute_list)
+  field.attributes = _AttributeListToDict(module, parsed_field.attribute_list)
   value = mojom.EnumValue(module, enum, field)
   module.values[value.GetSpec()] = value
   return field
@@ -544,7 +850,7 @@  def _ResolveNumericEnumValues(enum):
       prev_value += 1
 
     # Integral value (e.g: BEGIN = -0x1).
-    elif _IsStrOrUnicode(field.value):
+    elif isinstance(field.value, str):
       prev_value = int(field.value, 0)
 
     # Reference to a previous enum value (e.g: INIT = BEGIN).
@@ -588,7 +894,7 @@  def _Enum(module, parsed_enum, parent_kind):
     mojom_name = parent_kind.mojom_name + '.' + mojom_name
   enum.spec = 'x:%s.%s' % (module.mojom_namespace, mojom_name)
   enum.parent_kind = parent_kind
-  enum.attributes = _AttributeListToDict(parsed_enum.attribute_list)
+  enum.attributes = _AttributeListToDict(module, parsed_enum.attribute_list)
 
   if not enum.native_only:
     enum.fields = list(
@@ -600,11 +906,18 @@  def _Enum(module, parsed_enum, parent_kind):
     for field in enum.fields:
       if field.default:
         if not enum.extensible:
-          raise Exception('Non-extensible enums may not specify a default')
-        if enum.default_field is not None:
           raise Exception(
-              'Only one enumerator value may be specified as the default')
+              f'Non-extensible enum {enum.spec} may not specify a default')
+        if enum.default_field is not None:
+          raise Exception(f'Multiple [Default] enumerators in enum {enum.spec}')
         enum.default_field = field
+    # While running the backwards compatibility check, ignore errors because the
+    # old version of the enum might not specify [Default].
+    if (enum.extensible and enum.default_field is None
+        and enum.spec not in _EXTENSIBLE_ENUMS_MISSING_DEFAULT
+        and not is_running_backwards_compatibility_check_hack):
+      raise Exception(
+          f'Extensible enum {enum.spec} must specify a [Default] enumerator')
 
   module.kinds[enum.spec] = enum
 
@@ -696,6 +1009,11 @@  def _CollectReferencedKinds(module, all_defined_kinds):
         for referenced_kind in extract_referenced_user_kinds(param.kind):
           sanitized_kind = sanitize_kind(referenced_kind)
           referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
+  # Consts can reference imported enums.
+  for const in module.constants:
+    if not const.kind in mojom.PRIMITIVES:
+      sanitized_kind = sanitize_kind(const.kind)
+      referenced_user_kinds[sanitized_kind.spec] = sanitized_kind
 
   return referenced_user_kinds
 
@@ -741,6 +1059,16 @@  def _AssertTypeIsStable(kind):
           assertDependencyIsStable(response_param.kind)
 
 
+def _AssertStructIsValid(kind):
+  expected_ordinals = set(range(0, len(kind.fields)))
+  ordinals = set(map(lambda field: field.ordinal, kind.fields))
+  if ordinals != expected_ordinals:
+    raise Exception(
+        'Structs must use contiguous ordinals starting from 0. ' +
+        '{} is missing the following ordinals: {}.'.format(
+            kind.mojom_name, ', '.join(map(str, expected_ordinals - ordinals))))
+
+
 def _Module(tree, path, imports):
   """
   Args:
@@ -810,8 +1138,17 @@  def _Module(tree, path, imports):
     union.fields = list(
         map(lambda field: _UnionField(module, field, union), union.fields_data))
     _AssignDefaultOrdinals(union.fields)
+    for field in union.fields:
+      if field.is_default:
+        if union.default_field is not None:
+          raise Exception('Multiple [Default] fields in union %s.' %
+                          union.mojom_name)
+        union.default_field = field
     del union.fields_data
     all_defined_kinds[union.spec] = union
+    if union.extensible and union.default_field is None:
+      raise Exception('Extensible union %s must specify a [Default] field' %
+                      union.mojom_name)
 
   for interface in module.interfaces:
     interface.methods = list(
@@ -829,8 +1166,8 @@  def _Module(tree, path, imports):
                                                  all_defined_kinds.values())
   imported_kind_specs = set(all_referenced_kinds.keys()).difference(
       set(all_defined_kinds.keys()))
-  module.imported_kinds = dict(
-      (spec, all_referenced_kinds[spec]) for spec in imported_kind_specs)
+  module.imported_kinds = OrderedDict((spec, all_referenced_kinds[spec])
+                                      for spec in sorted(imported_kind_specs))
 
   generator.AddComputedData(module)
   for iface in module.interfaces:
@@ -847,6 +1184,9 @@  def _Module(tree, path, imports):
       if kind.stable:
         _AssertTypeIsStable(kind)
 
+  for kind in module.structs:
+    _AssertStructIsValid(kind)
+
   return module
 
 
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
index 19905c8a..42593745 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/generate/translate_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -69,5 +69,38 @@  class TranslateTest(unittest.TestCase):
     # pylint: disable=W0212
     self.assertEquals(
         translate._MapKind("asso<SomeInterface>?"), "?asso:x:SomeInterface")
-    self.assertEquals(
-        translate._MapKind("asso<SomeInterface&>?"), "?asso:r:x:SomeInterface")
+    self.assertEquals(translate._MapKind("rca<SomeInterface>?"),
+                      "?rca:x:SomeInterface")
+
+  def testSelfRecursiveUnions(self):
+    """Verifies _UnionField() raises when a union is self-recursive."""
+    tree = ast.Mojom(None, ast.ImportList(), [
+        ast.Union("SomeUnion", None,
+                  ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion")]))
+    ])
+    with self.assertRaises(Exception):
+      translate.OrderedModule(tree, "mojom_tree", [])
+
+    tree = ast.Mojom(None, ast.ImportList(), [
+        ast.Union(
+            "SomeUnion", None,
+            ast.UnionBody([ast.UnionField("a", None, None, "SomeUnion?")]))
+    ])
+    with self.assertRaises(Exception):
+      translate.OrderedModule(tree, "mojom_tree", [])
+
+  def testDuplicateAttributesException(self):
+    tree = ast.Mojom(None, ast.ImportList(), [
+        ast.Union(
+            "FakeUnion",
+            ast.AttributeList([
+                ast.Attribute("key1", "value"),
+                ast.Attribute("key1", "value")
+            ]),
+            ast.UnionBody([
+                ast.UnionField("a", None, None, "int32"),
+                ast.UnionField("b", None, None, "string")
+            ]))
+    ])
+    with self.assertRaises(Exception):
+      translate.OrderedModule(tree, "mojom_tree", [])
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
index 1f0db200..80e8c657 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Node classes for the AST for a Mojo IDL file."""
@@ -8,17 +8,14 @@ 
 # and lineno). You may also define __repr__() to help with analyzing test
 # failures, especially for more complex types.
 
+import os.path
 
-import sys
 
+# Instance of 'NodeListBase' has no '_list_item_type' member (no-member)
+# pylint: disable=no-member
 
-def _IsStrOrUnicode(x):
-  if sys.version_info[0] < 3:
-    return isinstance(x, (unicode, str))
-  return isinstance(x, str)
 
-
-class NodeBase(object):
+class NodeBase:
   """Base class for nodes in the AST."""
 
   def __init__(self, filename=None, lineno=None):
@@ -43,7 +40,7 @@  class NodeListBase(NodeBase):
   classes, in a tuple) of the members of the list.)"""
 
   def __init__(self, item_or_items=None, **kwargs):
-    super(NodeListBase, self).__init__(**kwargs)
+    super().__init__(**kwargs)
     self.items = []
     if item_or_items is None:
       pass
@@ -62,7 +59,7 @@  class NodeListBase(NodeBase):
     return self.items.__iter__()
 
   def __eq__(self, other):
-    return super(NodeListBase, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.items == other.items
 
   # Implement this so that on failure, we get slightly more sensible output.
@@ -96,7 +93,7 @@  class Definition(NodeBase):
   include parameter definitions.) This class is meant to be subclassed."""
 
   def __init__(self, mojom_name, **kwargs):
-    assert _IsStrOrUnicode(mojom_name)
+    assert isinstance(mojom_name, str)
     NodeBase.__init__(self, **kwargs)
     self.mojom_name = mojom_name
 
@@ -108,13 +105,13 @@  class Attribute(NodeBase):
   """Represents an attribute."""
 
   def __init__(self, key, value, **kwargs):
-    assert _IsStrOrUnicode(key)
-    super(Attribute, self).__init__(**kwargs)
+    assert isinstance(key, str)
+    super().__init__(**kwargs)
     self.key = key
     self.value = value
 
   def __eq__(self, other):
-    return super(Attribute, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.key == other.key and \
            self.value == other.value
 
@@ -131,17 +128,17 @@  class Const(Definition):
   def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
     # The typename is currently passed through as a string.
-    assert _IsStrOrUnicode(typename)
+    assert isinstance(typename, str)
     # The value is either a literal (currently passed through as a string) or a
     # "wrapped identifier".
-    assert _IsStrOrUnicode or isinstance(value, tuple)
-    super(Const, self).__init__(mojom_name, **kwargs)
+    assert isinstance(value, (tuple, str))
+    super().__init__(mojom_name, **kwargs)
     self.attribute_list = attribute_list
     self.typename = typename
     self.value = value
 
   def __eq__(self, other):
-    return super(Const, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.typename == other.typename and \
            self.value == other.value
@@ -153,12 +150,12 @@  class Enum(Definition):
   def __init__(self, mojom_name, attribute_list, enum_value_list, **kwargs):
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
     assert enum_value_list is None or isinstance(enum_value_list, EnumValueList)
-    super(Enum, self).__init__(mojom_name, **kwargs)
+    super().__init__(mojom_name, **kwargs)
     self.attribute_list = attribute_list
     self.enum_value_list = enum_value_list
 
   def __eq__(self, other):
-    return super(Enum, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.enum_value_list == other.enum_value_list
 
@@ -170,13 +167,13 @@  class EnumValue(Definition):
     # The optional value is either an int (which is current a string) or a
     # "wrapped identifier".
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
-    assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)
-    super(EnumValue, self).__init__(mojom_name, **kwargs)
+    assert value is None or isinstance(value, (tuple, str))
+    super().__init__(mojom_name, **kwargs)
     self.attribute_list = attribute_list
     self.value = value
 
   def __eq__(self, other):
-    return super(EnumValue, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.value == other.value
 
@@ -193,13 +190,14 @@  class Import(NodeBase):
 
   def __init__(self, attribute_list, import_filename, **kwargs):
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
-    assert _IsStrOrUnicode(import_filename)
-    super(Import, self).__init__(**kwargs)
+    assert isinstance(import_filename, str)
+    super().__init__(**kwargs)
     self.attribute_list = attribute_list
-    self.import_filename = import_filename
+    # TODO(crbug.com/953884): Use pathlib once we're migrated fully to Python 3.
+    self.import_filename = os.path.normpath(import_filename).replace('\\', '/')
 
   def __eq__(self, other):
-    return super(Import, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.import_filename == other.import_filename
 
@@ -216,12 +214,12 @@  class Interface(Definition):
   def __init__(self, mojom_name, attribute_list, body, **kwargs):
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
     assert isinstance(body, InterfaceBody)
-    super(Interface, self).__init__(mojom_name, **kwargs)
+    super().__init__(mojom_name, **kwargs)
     self.attribute_list = attribute_list
     self.body = body
 
   def __eq__(self, other):
-    return super(Interface, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.body == other.body
 
@@ -236,14 +234,14 @@  class Method(Definition):
     assert isinstance(parameter_list, ParameterList)
     assert response_parameter_list is None or \
            isinstance(response_parameter_list, ParameterList)
-    super(Method, self).__init__(mojom_name, **kwargs)
+    super().__init__(mojom_name, **kwargs)
     self.attribute_list = attribute_list
     self.ordinal = ordinal
     self.parameter_list = parameter_list
     self.response_parameter_list = response_parameter_list
 
   def __eq__(self, other):
-    return super(Method, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.ordinal == other.ordinal and \
            self.parameter_list == other.parameter_list and \
@@ -264,12 +262,12 @@  class Module(NodeBase):
     # |mojom_namespace| is either none or a "wrapped identifier".
     assert mojom_namespace is None or isinstance(mojom_namespace, tuple)
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
-    super(Module, self).__init__(**kwargs)
+    super().__init__(**kwargs)
     self.mojom_namespace = mojom_namespace
     self.attribute_list = attribute_list
 
   def __eq__(self, other):
-    return super(Module, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.mojom_namespace == other.mojom_namespace and \
            self.attribute_list == other.attribute_list
 
@@ -281,13 +279,13 @@  class Mojom(NodeBase):
     assert module is None or isinstance(module, Module)
     assert isinstance(import_list, ImportList)
     assert isinstance(definition_list, list)
-    super(Mojom, self).__init__(**kwargs)
+    super().__init__(**kwargs)
     self.module = module
     self.import_list = import_list
     self.definition_list = definition_list
 
   def __eq__(self, other):
-    return super(Mojom, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.module == other.module and \
            self.import_list == other.import_list and \
            self.definition_list == other.definition_list
@@ -302,11 +300,11 @@  class Ordinal(NodeBase):
 
   def __init__(self, value, **kwargs):
     assert isinstance(value, int)
-    super(Ordinal, self).__init__(**kwargs)
+    super().__init__(**kwargs)
     self.value = value
 
   def __eq__(self, other):
-    return super(Ordinal, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.value == other.value
 
 
@@ -314,18 +312,18 @@  class Parameter(NodeBase):
   """Represents a method request or response parameter."""
 
   def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
-    assert _IsStrOrUnicode(mojom_name)
+    assert isinstance(mojom_name, str)
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
     assert ordinal is None or isinstance(ordinal, Ordinal)
-    assert _IsStrOrUnicode(typename)
-    super(Parameter, self).__init__(**kwargs)
+    assert isinstance(typename, str)
+    super().__init__(**kwargs)
     self.mojom_name = mojom_name
     self.attribute_list = attribute_list
     self.ordinal = ordinal
     self.typename = typename
 
   def __eq__(self, other):
-    return super(Parameter, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.mojom_name == other.mojom_name and \
            self.attribute_list == other.attribute_list and \
            self.ordinal == other.ordinal and \
@@ -344,42 +342,51 @@  class Struct(Definition):
   def __init__(self, mojom_name, attribute_list, body, **kwargs):
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
     assert isinstance(body, StructBody) or body is None
-    super(Struct, self).__init__(mojom_name, **kwargs)
+    super().__init__(mojom_name, **kwargs)
     self.attribute_list = attribute_list
     self.body = body
 
   def __eq__(self, other):
-    return super(Struct, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.body == other.body
 
+  def __repr__(self):
+    return "Struct(mojom_name = %s, attribute_list = %s, body = %s)" % (
+        self.mojom_name, self.attribute_list, self.body)
+
 
 class StructField(Definition):
   """Represents a struct field definition."""
 
   def __init__(self, mojom_name, attribute_list, ordinal, typename,
                default_value, **kwargs):
-    assert _IsStrOrUnicode(mojom_name)
+    assert isinstance(mojom_name, str)
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
     assert ordinal is None or isinstance(ordinal, Ordinal)
-    assert _IsStrOrUnicode(typename)
+    assert isinstance(typename, str)
     # The optional default value is currently either a value as a string or a
     # "wrapped identifier".
-    assert default_value is None or _IsStrOrUnicode(default_value) or \
-        isinstance(default_value, tuple)
-    super(StructField, self).__init__(mojom_name, **kwargs)
+    assert default_value is None or isinstance(default_value, (str, tuple))
+    super().__init__(mojom_name, **kwargs)
     self.attribute_list = attribute_list
     self.ordinal = ordinal
     self.typename = typename
     self.default_value = default_value
 
   def __eq__(self, other):
-    return super(StructField, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.ordinal == other.ordinal and \
            self.typename == other.typename and \
            self.default_value == other.default_value
 
+  def __repr__(self):
+    return ("StructField(mojom_name = %s, attribute_list = %s, ordinal = %s, "
+            "typename = %s, default_value = %s") % (
+                self.mojom_name, self.attribute_list, self.ordinal,
+                self.typename, self.default_value)
+
 
 # This needs to be declared after |StructField|.
 class StructBody(NodeListBase):
@@ -394,29 +401,29 @@  class Union(Definition):
   def __init__(self, mojom_name, attribute_list, body, **kwargs):
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
     assert isinstance(body, UnionBody)
-    super(Union, self).__init__(mojom_name, **kwargs)
+    super().__init__(mojom_name, **kwargs)
     self.attribute_list = attribute_list
     self.body = body
 
   def __eq__(self, other):
-    return super(Union, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.body == other.body
 
 
 class UnionField(Definition):
   def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
-    assert _IsStrOrUnicode(mojom_name)
+    assert isinstance(mojom_name, str)
     assert attribute_list is None or isinstance(attribute_list, AttributeList)
     assert ordinal is None or isinstance(ordinal, Ordinal)
-    assert _IsStrOrUnicode(typename)
-    super(UnionField, self).__init__(mojom_name, **kwargs)
+    assert isinstance(typename, str)
+    super().__init__(mojom_name, **kwargs)
     self.attribute_list = attribute_list
     self.ordinal = ordinal
     self.typename = typename
 
   def __eq__(self, other):
-    return super(UnionField, self).__eq__(other) and \
+    return super().__eq__(other) and \
            self.attribute_list == other.attribute_list and \
            self.ordinal == other.ordinal and \
            self.typename == other.typename
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
index 62798631..c3637671 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/ast_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -14,11 +14,11 @@  class _TestNode(ast.NodeBase):
   """Node type for tests."""
 
   def __init__(self, value, **kwargs):
-    super(_TestNode, self).__init__(**kwargs)
+    super().__init__(**kwargs)
     self.value = value
 
   def __eq__(self, other):
-    return super(_TestNode, self).__eq__(other) and self.value == other.value
+    return super().__eq__(other) and self.value == other.value
 
 
 class _TestNodeList(ast.NodeListBase):
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
index 3cb73c5d..b7b06bfb 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/conditional_features.py
@@ -1,4 +1,4 @@ 
-# Copyright 2018 The Chromium Authors. All rights reserved.
+# Copyright 2018 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Helpers for processing conditionally enabled features in a mojom."""
@@ -17,8 +17,10 @@  class EnableIfError(Error):
 def _IsEnabled(definition, enabled_features):
   """Returns true if a definition is enabled.
 
-  A definition is enabled if it has no EnableIf attribute, or if the value of
-  the EnableIf attribute is in enabled_features.
+  A definition is enabled if it has no EnableIf/EnableIfNot attribute.
+  It is retained if it has an EnableIf attribute and the attribute is in
+  enabled_features. It is retained if it has an EnableIfNot attribute and the
+  attribute is not in enabled features.
   """
   if not hasattr(definition, "attribute_list"):
     return True
@@ -27,17 +29,19 @@  def _IsEnabled(definition, enabled_features):
 
   already_defined = False
   for a in definition.attribute_list:
-    if a.key == 'EnableIf':
+    if a.key == 'EnableIf' or a.key == 'EnableIfNot':
       if already_defined:
         raise EnableIfError(
             definition.filename,
-            "EnableIf attribute may only be defined once per field.",
+            "EnableIf/EnableIfNot attribute may only be set once per field.",
             definition.lineno)
       already_defined = True
 
   for attribute in definition.attribute_list:
     if attribute.key == 'EnableIf' and attribute.value not in enabled_features:
       return False
+    if attribute.key == 'EnableIfNot' and attribute.value in enabled_features:
+      return False
   return True
 
 
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
index aa609be7..5fc58202 100644
--- 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
@@ -1,4 +1,4 @@ 
-# Copyright 2018 The Chromium Authors. All rights reserved.
+# Copyright 2018 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -55,6 +55,48 @@  class ConditionalFeaturesTest(unittest.TestCase):
     """
     self.parseAndAssertEqual(const_source, expected_source)
 
+  def testFilterIfNotConst(self):
+    """Test that Consts are correctly filtered."""
+    const_source = """
+      [EnableIfNot=blue]
+      const int kMyConst1 = 1;
+      [EnableIfNot=orange]
+      const double kMyConst2 = 2;
+      [EnableIf=blue]
+      const int kMyConst3 = 3;
+      [EnableIfNot=blue]
+      const int kMyConst4 = 4;
+      [EnableIfNot=purple]
+      const int kMyConst5 = 5;
+    """
+    expected_source = """
+      [EnableIfNot=orange]
+      const double kMyConst2 = 2;
+      [EnableIf=blue]
+      const int kMyConst3 = 3;
+      [EnableIfNot=purple]
+      const int kMyConst5 = 5;
+    """
+    self.parseAndAssertEqual(const_source, expected_source)
+
+  def testFilterIfNotMultipleConst(self):
+    """Test that Consts are correctly filtered."""
+    const_source = """
+      [EnableIfNot=blue]
+      const int kMyConst1 = 1;
+      [EnableIfNot=orange]
+      const double kMyConst2 = 2;
+      [EnableIfNot=orange]
+      const int kMyConst3 = 3;
+    """
+    expected_source = """
+      [EnableIfNot=orange]
+      const double kMyConst2 = 2;
+      [EnableIfNot=orange]
+      const int kMyConst3 = 3;
+    """
+    self.parseAndAssertEqual(const_source, expected_source)
+
   def testFilterEnum(self):
     """Test that EnumValues are correctly filtered from an Enum."""
     enum_source = """
@@ -91,6 +133,24 @@  class ConditionalFeaturesTest(unittest.TestCase):
     """
     self.parseAndAssertEqual(import_source, expected_source)
 
+  def testFilterIfNotImport(self):
+    """Test that imports are correctly filtered from a Mojom."""
+    import_source = """
+      [EnableIf=blue]
+      import "foo.mojom";
+      [EnableIfNot=purple]
+      import "bar.mojom";
+      [EnableIfNot=green]
+      import "baz.mojom";
+    """
+    expected_source = """
+      [EnableIf=blue]
+      import "foo.mojom";
+      [EnableIfNot=purple]
+      import "bar.mojom";
+    """
+    self.parseAndAssertEqual(import_source, expected_source)
+
   def testFilterInterface(self):
     """Test that definitions are correctly filtered from an Interface."""
     interface_source = """
@@ -175,6 +235,50 @@  class ConditionalFeaturesTest(unittest.TestCase):
     """
     self.parseAndAssertEqual(struct_source, expected_source)
 
+  def testFilterIfNotStruct(self):
+    """Test that definitions are correctly filtered from a Struct."""
+    struct_source = """
+      struct MyStruct {
+        [EnableIf=blue]
+        enum MyEnum {
+          VALUE1,
+          [EnableIfNot=red]
+          VALUE2,
+        };
+        [EnableIfNot=yellow]
+        const double kMyConst = 1.23;
+        [EnableIf=green]
+        int32 a;
+        double b;
+        [EnableIfNot=purple]
+        int32 c;
+        [EnableIf=blue]
+        double d;
+        int32 e;
+        [EnableIfNot=red]
+        double f;
+      };
+    """
+    expected_source = """
+      struct MyStruct {
+        [EnableIf=blue]
+        enum MyEnum {
+          VALUE1,
+        };
+        [EnableIfNot=yellow]
+        const double kMyConst = 1.23;
+        [EnableIf=green]
+        int32 a;
+        double b;
+        [EnableIfNot=purple]
+        int32 c;
+        [EnableIf=blue]
+        double d;
+        int32 e;
+      };
+    """
+    self.parseAndAssertEqual(struct_source, expected_source)
+
   def testFilterUnion(self):
     """Test that UnionFields are correctly filtered from a Union."""
     union_source = """
@@ -228,6 +332,30 @@  class ConditionalFeaturesTest(unittest.TestCase):
                       conditional_features.RemoveDisabledDefinitions,
                       definition, ENABLED_FEATURES)
 
+  def testMultipleEnableIfs(self):
+    source = """
+      enum Foo {
+        [EnableIf=red,EnableIfNot=yellow]
+        kBarValue = 5,
+      };
+    """
+    definition = parser.Parse(source, "my_file.mojom")
+    self.assertRaises(conditional_features.EnableIfError,
+                      conditional_features.RemoveDisabledDefinitions,
+                      definition, ENABLED_FEATURES)
+
+  def testMultipleEnableIfs(self):
+    source = """
+      enum Foo {
+        [EnableIfNot=red,EnableIfNot=yellow]
+        kBarValue = 5,
+      };
+    """
+    definition = parser.Parse(source, "my_file.mojom")
+    self.assertRaises(conditional_features.EnableIfError,
+                      conditional_features.RemoveDisabledDefinitions,
+                      definition, ENABLED_FEATURES)
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
index 3e084bbf..73ca15df 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -22,7 +22,7 @@  class LexError(Error):
 
 # We have methods which look like they could be functions:
 # pylint: disable=R0201
-class Lexer(object):
+class Lexer:
   def __init__(self, filename):
     self.filename = filename
 
@@ -81,7 +81,6 @@  class Lexer(object):
       # Operators
       'MINUS',
       'PLUS',
-      'AMP',
       'QSTN',
 
       # Assignment
@@ -168,7 +167,6 @@  class Lexer(object):
   # Operators
   t_MINUS = r'-'
   t_PLUS = r'\+'
-  t_AMP = r'&'
   t_QSTN = r'\?'
 
   # =
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
index eadc6587..ce376da6 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/lexer_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -146,7 +146,6 @@  class LexerTest(unittest.TestCase):
         self._SingleTokenForInput("+"), _MakeLexToken("PLUS", "+"))
     self.assertEquals(
         self._SingleTokenForInput("-"), _MakeLexToken("MINUS", "-"))
-    self.assertEquals(self._SingleTokenForInput("&"), _MakeLexToken("AMP", "&"))
     self.assertEquals(
         self._SingleTokenForInput("?"), _MakeLexToken("QSTN", "?"))
     self.assertEquals(
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
index b3b803d6..683ae757 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Generates a syntax tree from a Mojo IDL file."""
@@ -33,7 +33,7 @@  class ParseError(Error):
 
 # We have methods which look like they could be functions:
 # pylint: disable=R0201
-class Parser(object):
+class Parser:
   def __init__(self, lexer, source, filename):
     self.tokens = lexer.tokens
     self.source = source
@@ -140,11 +140,18 @@  class Parser(object):
     p[0].Append(p[3])
 
   def p_attribute_1(self, p):
+    """attribute : NAME EQUALS identifier_wrapped"""
+    p[0] = ast.Attribute(p[1],
+                         p[3][1],
+                         filename=self.filename,
+                         lineno=p.lineno(1))
+
+  def p_attribute_2(self, p):
     """attribute : NAME EQUALS evaled_literal
                  | NAME EQUALS NAME"""
     p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1))
 
-  def p_attribute_2(self, p):
+  def p_attribute_3(self, p):
     """attribute : NAME"""
     p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1))
 
@@ -271,8 +278,7 @@  class Parser(object):
     """nonnullable_typename : basictypename
                             | array
                             | fixed_array
-                            | associative_array
-                            | interfacerequest"""
+                            | associative_array"""
     p[0] = p[1]
 
   def p_basictypename(self, p):
@@ -342,14 +348,6 @@  class Parser(object):
     """associative_array : MAP LANGLE identifier COMMA typename RANGLE"""
     p[0] = p[5] + "{" + p[3] + "}"
 
-  def p_interfacerequest(self, p):
-    """interfacerequest : identifier AMP
-                        | ASSOCIATED identifier AMP"""
-    if len(p) == 3:
-      p[0] = p[1] + "&"
-    else:
-      p[0] = "asso<" + p[2] + "&>"
-
   def p_ordinal_1(self, p):
     """ordinal : """
     p[0] = None
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
index 6d6b7153..0513343e 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom/parse/parser_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2014 The Chromium Authors. All rights reserved.
+# Copyright 2014 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -1086,7 +1086,7 @@  class ParserTest(unittest.TestCase):
           handle<data_pipe_producer>? k;
           handle<message_pipe>? l;
           handle<shared_buffer>? m;
-          some_interface&? n;
+          pending_receiver<some_interface>? n;
           handle<platform>? o;
         };
         """
@@ -1110,7 +1110,7 @@  class ParserTest(unittest.TestCase):
                 ast.StructField('l', None, None, 'handle<message_pipe>?', None),
                 ast.StructField('m', None, None, 'handle<shared_buffer>?',
                                 None),
-                ast.StructField('n', None, None, 'some_interface&?', None),
+                ast.StructField('n', None, None, 'rcv<some_interface>?', None),
                 ast.StructField('o', None, None, 'handle<platform>?', None)
             ]))
     ])
@@ -1138,16 +1138,6 @@  class ParserTest(unittest.TestCase):
         r" *handle\?<data_pipe_consumer> a;$"):
       parser.Parse(source2, "my_file.mojom")
 
-    source3 = """\
-        struct MyStruct {
-          some_interface?& a;
-        };
-        """
-    with self.assertRaisesRegexp(
-        parser.ParseError, r"^my_file\.mojom:2: Error: Unexpected '&':\n"
-        r" *some_interface\?& a;$"):
-      parser.Parse(source3, "my_file.mojom")
-
   def testSimpleUnion(self):
     """Tests a simple .mojom source that just defines a union."""
     source = """\
@@ -1317,9 +1307,9 @@  class ParserTest(unittest.TestCase):
     source1 = """\
         struct MyStruct {
           associated MyInterface a;
-          associated MyInterface& b;
+          pending_associated_receiver<MyInterface> b;
           associated MyInterface? c;
-          associated MyInterface&? d;
+          pending_associated_receiver<MyInterface>? d;
         };
         """
     expected1 = ast.Mojom(None, ast.ImportList(), [
@@ -1327,16 +1317,16 @@  class ParserTest(unittest.TestCase):
             'MyStruct', None,
             ast.StructBody([
                 ast.StructField('a', None, None, 'asso<MyInterface>', None),
-                ast.StructField('b', None, None, 'asso<MyInterface&>', None),
+                ast.StructField('b', None, None, 'rca<MyInterface>', None),
                 ast.StructField('c', None, None, 'asso<MyInterface>?', None),
-                ast.StructField('d', None, None, 'asso<MyInterface&>?', None)
+                ast.StructField('d', None, None, 'rca<MyInterface>?', None)
             ]))
     ])
     self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1)
 
     source2 = """\
         interface MyInterface {
-          MyMethod(associated A a) =>(associated B& b);
+          MyMethod(associated A a) =>(pending_associated_receiver<B> b);
         };"""
     expected2 = ast.Mojom(None, ast.ImportList(), [
         ast.Interface(
@@ -1344,10 +1334,10 @@  class ParserTest(unittest.TestCase):
             ast.InterfaceBody(
                 ast.Method(
                     'MyMethod', None, None,
-                    ast.ParameterList(
-                        ast.Parameter('a', None, None, 'asso<A>')),
-                    ast.ParameterList(
-                        ast.Parameter('b', None, None, 'asso<B&>')))))
+                    ast.ParameterList(ast.Parameter('a', None, None,
+                                                    'asso<A>')),
+                    ast.ParameterList(ast.Parameter('b', None, None,
+                                                    'rca<B>')))))
     ])
     self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2)
 
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
index eb90c825..9693090e 100755
--- a/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser.py
@@ -1,5 +1,5 @@ 
-#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 """Parses mojom IDL files.
@@ -11,6 +11,7 @@  generate usable language bindings.
 """
 
 import argparse
+import builtins
 import codecs
 import errno
 import json
@@ -19,6 +20,7 @@  import multiprocessing
 import os
 import os.path
 import sys
+import traceback
 from collections import defaultdict
 
 from mojom.generate import module
@@ -28,16 +30,12 @@  from mojom.parse import conditional_features
 
 
 # Disable this for easier debugging.
-# In Python 2, subprocesses just hang when exceptions are thrown :(.
-_ENABLE_MULTIPROCESSING = sys.version_info[0] > 2
+_ENABLE_MULTIPROCESSING = True
 
-if sys.version_info < (3, 4):
-  _MULTIPROCESSING_USES_FORK = sys.platform.startswith('linux')
-else:
-  # https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
-  if __name__ == '__main__' and sys.platform == 'darwin':
-    multiprocessing.set_start_method('fork')
-  _MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
+# https://docs.python.org/3/library/multiprocessing.html#:~:text=bpo-33725
+if __name__ == '__main__' and sys.platform == 'darwin':
+  multiprocessing.set_start_method('fork')
+_MULTIPROCESSING_USES_FORK = multiprocessing.get_start_method() == 'fork'
 
 
 def _ResolveRelativeImportPath(path, roots):
@@ -63,7 +61,7 @@  def _ResolveRelativeImportPath(path, roots):
   raise ValueError('"%s" does not exist in any of %s' % (path, roots))
 
 
-def _RebaseAbsolutePath(path, roots):
+def RebaseAbsolutePath(path, roots):
   """Rewrites an absolute file path as relative to an absolute directory path in
   roots.
 
@@ -139,7 +137,7 @@  def _EnsureInputLoaded(mojom_abspath, module_path, abs_paths, asts,
     # Already done.
     return
 
-  for dep_abspath, dep_path in dependencies[mojom_abspath]:
+  for dep_abspath, dep_path in sorted(dependencies[mojom_abspath]):
     if dep_abspath not in loaded_modules:
       _EnsureInputLoaded(dep_abspath, dep_path, abs_paths, asts, dependencies,
                          loaded_modules, module_metadata)
@@ -159,11 +157,19 @@  def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
 
   def collect(metadata_filename):
     processed_deps.add(metadata_filename)
+
+    # Paths in the metadata file are relative to the metadata file's dir.
+    metadata_dir = os.path.abspath(os.path.dirname(metadata_filename))
+
+    def to_abs(s):
+      return os.path.normpath(os.path.join(metadata_dir, s))
+
     with open(metadata_filename) as f:
       metadata = json.load(f)
       allowed_imports.update(
-          map(os.path.normcase, map(os.path.normpath, metadata['sources'])))
+          [os.path.normcase(to_abs(s)) for s in metadata['sources']])
       for dep_metadata in metadata['deps']:
+        dep_metadata = to_abs(dep_metadata)
         if dep_metadata not in processed_deps:
           collect(dep_metadata)
 
@@ -172,8 +178,7 @@  def _CollectAllowedImportsFromBuildMetadata(build_metadata_filename):
 
 
 # multiprocessing helper.
-def _ParseAstHelper(args):
-  mojom_abspath, enabled_features = args
+def _ParseAstHelper(mojom_abspath, enabled_features):
   with codecs.open(mojom_abspath, encoding='utf-8') as f:
     ast = parser.Parse(f.read(), mojom_abspath)
     conditional_features.RemoveDisabledDefinitions(ast, enabled_features)
@@ -181,8 +186,7 @@  def _ParseAstHelper(args):
 
 
 # multiprocessing helper.
-def _SerializeHelper(args):
-  mojom_abspath, mojom_path = args
+def _SerializeHelper(mojom_abspath, mojom_path):
   module_path = os.path.join(_SerializeHelper.output_root_path,
                              _GetModuleFilename(mojom_path))
   module_dir = os.path.dirname(module_path)
@@ -199,12 +203,33 @@  def _SerializeHelper(args):
     _SerializeHelper.loaded_modules[mojom_abspath].Dump(f)
 
 
-def _Shard(target_func, args, processes=None):
-  args = list(args)
+class _ExceptionWrapper:
+  def __init__(self):
+    # Do not capture exception object to ensure pickling works.
+    self.formatted_trace = traceback.format_exc()
+
+
+class _FuncWrapper:
+  """Marshals exceptions and spreads args."""
+
+  def __init__(self, func):
+    self._func = func
+
+  def __call__(self, args):
+    # multiprocessing does not gracefully handle excptions.
+    # https://crbug.com/1219044
+    try:
+      return self._func(*args)
+    except:  # pylint: disable=bare-except
+      return _ExceptionWrapper()
+
+
+def _Shard(target_func, arg_list, processes=None):
+  arg_list = list(arg_list)
   if processes is None:
     processes = multiprocessing.cpu_count()
   # Seems optimal to have each process perform at least 2 tasks.
-  processes = min(processes, len(args) // 2)
+  processes = min(processes, len(arg_list) // 2)
 
   if sys.platform == 'win32':
     # TODO(crbug.com/1190269) - we can't use more than 56
@@ -213,13 +238,17 @@  def _Shard(target_func, args, processes=None):
 
   # Don't spin up processes unless there is enough work to merit doing so.
   if not _ENABLE_MULTIPROCESSING or processes < 2:
-    for result in map(target_func, args):
-      yield result
+    for arg_tuple in arg_list:
+      yield target_func(*arg_tuple)
     return
 
   pool = multiprocessing.Pool(processes=processes)
   try:
-    for result in pool.imap_unordered(target_func, args):
+    wrapped_func = _FuncWrapper(target_func)
+    for result in pool.imap_unordered(wrapped_func, arg_list):
+      if isinstance(result, _ExceptionWrapper):
+        sys.stderr.write(result.formatted_trace)
+        sys.exit(1)
       yield result
   finally:
     pool.close()
@@ -230,6 +259,7 @@  def _Shard(target_func, args, processes=None):
 def _ParseMojoms(mojom_files,
                  input_root_paths,
                  output_root_path,
+                 module_root_paths,
                  enabled_features,
                  module_metadata,
                  allowed_imports=None):
@@ -245,8 +275,10 @@  def _ParseMojoms(mojom_files,
         are based on the mojom's relative path, rebased onto this path.
         Additionally, the script expects this root to contain already-generated
         modules for any transitive dependencies not listed in mojom_files.
+    module_root_paths: A list of absolute filesystem paths which contain
+        already-generated modules for any non-transitive dependencies.
     enabled_features: A list of enabled feature names, controlling which AST
-        nodes are filtered by [EnableIf] attributes.
+        nodes are filtered by [EnableIf] or [EnableIfNot] attributes.
     module_metadata: A list of 2-tuples representing metadata key-value pairs to
         attach to each compiled module output.
 
@@ -262,7 +294,7 @@  def _ParseMojoms(mojom_files,
   loaded_modules = {}
   input_dependencies = defaultdict(set)
   mojom_files_to_parse = dict((os.path.normcase(abs_path),
-                               _RebaseAbsolutePath(abs_path, input_root_paths))
+                               RebaseAbsolutePath(abs_path, input_root_paths))
                               for abs_path in mojom_files)
   abs_paths = dict(
       (path, abs_path) for abs_path, path in mojom_files_to_parse.items())
@@ -274,7 +306,7 @@  def _ParseMojoms(mojom_files,
     loaded_mojom_asts[mojom_abspath] = ast
 
   logging.info('Processing dependencies')
-  for mojom_abspath, ast in loaded_mojom_asts.items():
+  for mojom_abspath, ast in sorted(loaded_mojom_asts.items()):
     invalid_imports = []
     for imp in ast.import_list:
       import_abspath = _ResolveRelativeImportPath(imp.import_filename,
@@ -295,8 +327,8 @@  def _ParseMojoms(mojom_files,
         # be parsed and have a module file sitting in a corresponding output
         # location.
         module_path = _GetModuleFilename(imp.import_filename)
-        module_abspath = _ResolveRelativeImportPath(module_path,
-                                                    [output_root_path])
+        module_abspath = _ResolveRelativeImportPath(
+            module_path, module_root_paths + [output_root_path])
         with open(module_abspath, 'rb') as module_file:
           loaded_modules[import_abspath] = module.Module.Load(module_file)
 
@@ -370,6 +402,15 @@  already present in the provided output root.""")
       'based on the relative input path, rebased onto this root. Note that '
       'ROOT is also searched for existing modules of any transitive imports '
       'which were not included in the set of inputs.')
+  arg_parser.add_argument(
+      '--module-root',
+      default=[],
+      action='append',
+      metavar='ROOT',
+      dest='module_root_paths',
+      help='Adds ROOT to the set of root paths to search for existing modules '
+      'of non-transitive imports. Provided root paths are always searched in '
+      'order from longest absolute path to shortest.')
   arg_parser.add_argument(
       '--mojoms',
       nargs='+',
@@ -396,9 +437,9 @@  already present in the provided output root.""")
       help='Enables a named feature when parsing the given mojoms. Features '
       'are identified by arbitrary string values. Specifying this flag with a '
       'given FEATURE name will cause the parser to process any syntax elements '
-      'tagged with an [EnableIf=FEATURE] attribute. If this flag is not '
-      'provided for a given FEATURE, such tagged elements are discarded by the '
-      'parser and will not be present in the compiled output.')
+      'tagged with an [EnableIf=FEATURE] or [EnableIfNot] attribute. If this '
+      'flag is not provided for a given FEATURE, such tagged elements are '
+      'discarded by the parser and will not be present in the compiled output.')
   arg_parser.add_argument(
       '--check-imports',
       dest='build_metadata_filename',
@@ -436,6 +477,7 @@  already present in the provided output root.""")
   mojom_files = list(map(os.path.abspath, args.mojom_files))
   input_roots = list(map(os.path.abspath, args.input_root_paths))
   output_root = os.path.abspath(args.output_root_path)
+  module_roots = list(map(os.path.abspath, args.module_root_paths))
 
   if args.build_metadata_filename:
     allowed_imports = _CollectAllowedImportsFromBuildMetadata(
@@ -445,13 +487,16 @@  already present in the provided output root.""")
 
   module_metadata = list(
       map(lambda kvp: tuple(kvp.split('=')), args.module_metadata))
-  _ParseMojoms(mojom_files, input_roots, output_root, args.enabled_features,
-               module_metadata, allowed_imports)
+  _ParseMojoms(mojom_files, input_roots, output_root, module_roots,
+               args.enabled_features, module_metadata, allowed_imports)
   logging.info('Finished')
-  # Exit without running GC, which can save multiple seconds due the large
-  # number of object created.
-  os._exit(0)
 
 
 if __name__ == '__main__':
   Run(sys.argv[1:])
+  # Exit without running GC, which can save multiple seconds due to the large
+  # number of object created. But flush is necessary as os._exit doesn't do
+  # that.
+  sys.stdout.flush()
+  sys.stderr.flush()
+  os._exit(0)
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
index e213fbfa..45803ebe 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_test_case.py
@@ -1,4 +1,4 @@ 
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -20,7 +20,7 @@  class MojomParserTestCase(unittest.TestCase):
   resolution, and module serialization and deserialization."""
 
   def __init__(self, method_name):
-    super(MojomParserTestCase, self).__init__(method_name)
+    super().__init__(method_name)
     self._temp_dir = None
 
   def setUp(self):
diff --git a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
index a93f34ba..353a2b6e 100644
--- a/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/mojom_parser_unittest.py
@@ -1,7 +1,9 @@ 
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import json
+
 from mojom_parser_test_case import MojomParserTestCase
 
 
@@ -119,15 +121,22 @@  class MojomParserTest(MojomParserTestCase):
     c = 'c.mojom'
     c_metadata = 'out/c.build_metadata'
     self.WriteFile(a_metadata,
-                   '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
+                   json.dumps({
+                       "sources": [self.GetPath(a)],
+                       "deps": []
+                   }))
     self.WriteFile(
         b_metadata,
-        '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(b),
-                                                   self.GetPath(a_metadata)))
+        json.dumps({
+            "sources": [self.GetPath(b)],
+            "deps": [self.GetPath(a_metadata)]
+        }))
     self.WriteFile(
         c_metadata,
-        '{"sources": ["%s"], "deps": ["%s"]}\n' % (self.GetPath(c),
-                                                   self.GetPath(b_metadata)))
+        json.dumps({
+            "sources": [self.GetPath(c)],
+            "deps": [self.GetPath(b_metadata)]
+        }))
     self.WriteFile(a, """\
         module a;
         struct Bar {};""")
@@ -154,9 +163,15 @@  class MojomParserTest(MojomParserTestCase):
     b = 'b.mojom'
     b_metadata = 'out/b.build_metadata'
     self.WriteFile(a_metadata,
-                   '{"sources": ["%s"], "deps": []}\n' % self.GetPath(a))
+                   json.dumps({
+                       "sources": [self.GetPath(a)],
+                       "deps": []
+                   }))
     self.WriteFile(b_metadata,
-                   '{"sources": ["%s"], "deps": []}\n' % self.GetPath(b))
+                   json.dumps({
+                       "sources": [self.GetPath(b)],
+                       "deps": []
+                   }))
     self.WriteFile(a, """\
         module a;
         struct Bar {};""")
diff --git a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
index d45ec586..d10d69c6 100644
--- a/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/stable_attribute_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
diff --git a/utils/ipc/mojo/public/tools/mojom/union_unittest.py b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
new file mode 100644
index 00000000..6b2525e5
--- /dev/null
+++ b/utils/ipc/mojo/public/tools/mojom/union_unittest.py
@@ -0,0 +1,44 @@ 
+# Copyright 2022 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from mojom_parser_test_case import MojomParserTestCase
+
+
+class UnionTest(MojomParserTestCase):
+  """Tests union parsing behavior."""
+
+  def testExtensibleMustHaveDefault(self):
+    """Verifies that extensible unions must have a default field."""
+    mojom = 'foo.mojom'
+    self.WriteFile(mojom, 'module foo; [Extensible] union U { bool x; };')
+    with self.assertRaisesRegexp(Exception, 'must specify a \[Default\]'):
+      self.ParseMojoms([mojom])
+
+  def testExtensibleSingleDefault(self):
+    """Verifies that extensible unions must not have multiple default fields."""
+    mojom = 'foo.mojom'
+    self.WriteFile(
+        mojom, """\
+               module foo;
+               [Extensible] union U {
+                 [Default] bool x;
+                 [Default] bool y;
+               };
+               """)
+    with self.assertRaisesRegexp(Exception, 'Multiple \[Default\] fields'):
+      self.ParseMojoms([mojom])
+
+  def testExtensibleDefaultTypeValid(self):
+    """Verifies that an extensible union's default field must be nullable or
+    integral type."""
+    mojom = 'foo.mojom'
+    self.WriteFile(
+        mojom, """\
+               module foo;
+               [Extensible] union U {
+                 [Default] handle<message_pipe> p;
+               };
+               """)
+    with self.assertRaisesRegexp(Exception, 'must be nullable or integral'):
+      self.ParseMojoms([mojom])
diff --git a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
index 65db4dc9..7b71ce65 100644
--- a/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
+++ b/utils/ipc/mojo/public/tools/mojom/version_compatibility_unittest.py
@@ -1,4 +1,4 @@ 
-# Copyright 2020 The Chromium Authors. All rights reserved.
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -23,7 +23,7 @@  class VersionCompatibilityTest(MojomParserTestCase):
 
     checker = module.BackwardCompatibilityChecker()
     compatibility_map = {}
-    for name in old.keys():
+    for name in old:
       compatibility_map[name] = checker.IsBackwardCompatible(
           new[name], old[name])
     return compatibility_map
@@ -60,40 +60,48 @@  class VersionCompatibilityTest(MojomParserTestCase):
     """Adding a value to an existing version is not allowed, even if the old
     enum was marked [Extensible]. Note that it is irrelevant whether or not the
     new enum is marked [Extensible]."""
-    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
-                                     'enum E { kFoo, kBar, kBaz };')
     self.assertNotBackwardCompatible(
-        '[Extensible] enum E { kFoo, kBar };',
-        '[Extensible] enum E { kFoo, kBar, kBaz };')
+        '[Extensible] enum E { [Default] kFoo, kBar };',
+        'enum E { kFoo, kBar, kBaz };')
+    self.assertNotBackwardCompatible(
+        '[Extensible] enum E { [Default] kFoo, kBar };',
+        '[Extensible] enum E { [Default] kFoo, kBar, kBaz };')
     self.assertNotBackwardCompatible(
-        '[Extensible] enum E { kFoo, [MinVersion=1] kBar };',
+        '[Extensible] enum E { [Default] kFoo, [MinVersion=1] kBar };',
         'enum E { kFoo, [MinVersion=1] kBar, [MinVersion=1] kBaz };')
 
   def testEnumValueRemoval(self):
     """Removal of an enum value is never valid even for [Extensible] enums."""
     self.assertNotBackwardCompatible('enum E { kFoo, kBar };',
                                      'enum E { kFoo };')
-    self.assertNotBackwardCompatible('[Extensible] enum E { kFoo, kBar };',
-                                     '[Extensible] enum E { kFoo };')
     self.assertNotBackwardCompatible(
-        '[Extensible] enum E { kA, [MinVersion=1] kB };',
-        '[Extensible] enum E { kA, };')
+        '[Extensible] enum E { [Default] kFoo, kBar };',
+        '[Extensible] enum E { [Default] kFoo };')
+    self.assertNotBackwardCompatible(
+        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
+        '[Extensible] enum E { [Default] kA, };')
     self.assertNotBackwardCompatible(
-        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=1] kZ };',
-        '[Extensible] enum E { kA, [MinVersion=1] kB };')
+        """[Extensible] enum E {
+          [Default] kA,
+          [MinVersion=1] kB,
+          [MinVersion=1] kZ };""",
+        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };')
 
   def testNewExtensibleEnumValueWithMinVersion(self):
     """Adding a new and properly [MinVersion]'d value to an [Extensible] enum
     is a backward-compatible change. Note that it is irrelevant whether or not
     the new enum is marked [Extensible]."""
-    self.assertBackwardCompatible('[Extensible] enum E { kA, kB };',
+    self.assertBackwardCompatible('[Extensible] enum E { [Default] kA, kB };',
                                   'enum E { kA, kB, [MinVersion=1] kC };')
     self.assertBackwardCompatible(
-        '[Extensible] enum E { kA, kB };',
-        '[Extensible] enum E { kA, kB, [MinVersion=1] kC };')
+        '[Extensible] enum E { [Default] kA, kB };',
+        '[Extensible] enum E { [Default] kA, kB, [MinVersion=1] kC };')
     self.assertBackwardCompatible(
-        '[Extensible] enum E { kA, [MinVersion=1] kB };',
-        '[Extensible] enum E { kA, [MinVersion=1] kB, [MinVersion=2] kC };')
+        '[Extensible] enum E { [Default] kA, [MinVersion=1] kB };',
+        """[Extensible] enum E {
+          [Default] kA,
+          [MinVersion=1] kB,
+          [MinVersion=2] kC };""")
 
   def testRenameEnumValue(self):
     """Renaming an enum value does not affect backward-compatibility. Only
@@ -161,14 +169,17 @@  class VersionCompatibilityTest(MojomParserTestCase):
         'struct S {}; struct T { S s; };',
         'struct S { [MinVersion=1] int32 x; }; struct T { S s; };')
     self.assertBackwardCompatible(
-        '[Extensible] enum E { kA }; struct S { E e; };',
-        '[Extensible] enum E { kA, [MinVersion=1] kB }; struct S { E e; };')
+        '[Extensible] enum E { [Default] kA }; struct S { E e; };',
+        """[Extensible] enum E {
+          [Default] kA,
+          [MinVersion=1] kB };
+          struct S { E e; };""")
     self.assertNotBackwardCompatible(
         'struct S {}; struct T { S s; };',
         'struct S { int32 x; }; struct T { S s; };')
     self.assertNotBackwardCompatible(
-        '[Extensible] enum E { kA }; struct S { E e; };',
-        '[Extensible] enum E { kA, kB }; struct S { E e; };')
+        '[Extensible] enum E { [Default] kA }; struct S { E e; };',
+        '[Extensible] enum E { [Default] kA, kB }; struct S { E e; };')
 
   def testNewStructFieldWithInvalidMinVersion(self):
     """Adding a new field using an existing MinVersion breaks backward-
@@ -305,14 +316,17 @@  class VersionCompatibilityTest(MojomParserTestCase):
         'struct S {}; union U { S s; };',
         'struct S { [MinVersion=1] int32 x; }; union U { S s; };')
     self.assertBackwardCompatible(
-        '[Extensible] enum E { kA }; union U { E e; };',
-        '[Extensible] enum E { kA, [MinVersion=1] kB }; union U { E e; };')
+        '[Extensible] enum E { [Default] kA }; union U { E e; };',
+        """[Extensible] enum E {
+          [Default] kA,
+          [MinVersion=1] kB };
+          union U { E e; };""")
     self.assertNotBackwardCompatible(
         'struct S {}; union U { S s; };',
         'struct S { int32 x; }; union U { S s; };')
     self.assertNotBackwardCompatible(
-        '[Extensible] enum E { kA }; union U { E e; };',
-        '[Extensible] enum E { kA, kB }; union U { E e; };')
+        '[Extensible] enum E { [Default] kA }; union U { E e; };',
+        '[Extensible] enum E { [Default] kA, kB }; union U { E e; };')
 
   def testNewUnionFieldWithInvalidMinVersion(self):
     """Adding a new field using an existing MinVersion breaks backward-
diff --git a/utils/ipc/mojo/public/tools/run_all_python_unittests.py b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
index b2010958..98bce18c 100755
--- a/utils/ipc/mojo/public/tools/run_all_python_unittests.py
+++ b/utils/ipc/mojo/public/tools/run_all_python_unittests.py
@@ -1,5 +1,5 @@ 
-#!/usr/bin/env python
-# Copyright 2020 The Chromium Authors. All rights reserved.
+#!/usr/bin/env python3
+# Copyright 2020 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -8,11 +8,13 @@  import sys
 
 _TOOLS_DIR = os.path.dirname(__file__)
 _MOJOM_DIR = os.path.join(_TOOLS_DIR, 'mojom')
+_BINDINGS_DIR = os.path.join(_TOOLS_DIR, 'bindings')
 _SRC_DIR = os.path.join(_TOOLS_DIR, os.path.pardir, os.path.pardir,
                         os.path.pardir)
 
 # Ensure that the mojom library is discoverable.
 sys.path.append(_MOJOM_DIR)
+sys.path.append(_BINDINGS_DIR)
 
 # Help Python find typ in //third_party/catapult/third_party/typ/
 sys.path.append(
@@ -21,7 +23,7 @@  import typ
 
 
 def Main():
-  return typ.main(top_level_dir=_MOJOM_DIR)
+  return typ.main(top_level_dirs=[_MOJOM_DIR, _BINDINGS_DIR])
 
 
 if __name__ == '__main__':
diff --git a/utils/ipc/tools/README b/utils/ipc/tools/README
index d5c24fc3..9a2979d3 100644
--- a/utils/ipc/tools/README
+++ b/utils/ipc/tools/README
@@ -1,4 +1,4 @@ 
 # SPDX-License-Identifier: CC0-1.0
 
-Files in this directory are imported from 9c138d992bfc of Chromium. Do not
+Files in this directory are imported from e2b2277a00e37 of Chromium. Do not
 modify them manually.
diff --git a/utils/ipc/tools/diagnosis/crbug_1001171.py b/utils/ipc/tools/diagnosis/crbug_1001171.py
index 478fb8c1..40900d10 100644
--- a/utils/ipc/tools/diagnosis/crbug_1001171.py
+++ b/utils/ipc/tools/diagnosis/crbug_1001171.py
@@ -1,4 +1,4 @@ 
-# Copyright 2019 The Chromium Authors. All rights reserved.
+# Copyright 2019 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.