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