1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175

docs / webui_code_sharing.md [blame]

<style>
.doc h1 {
  margin: 0;
}

.doc h3,
.doc h4 {
  font-weight: bold;
}

.doc h4 {
  font-style: italic;
}
</style>

# **Sharing Code in WebUI**

## **Summary Diagram**

The following diagram summarizes the correct location for TypeScript/CSS/HTML
WebUI code that is used by 2 or more WebUIs. Details on how to determine which
category a specific piece of code belongs to follow.

![WebUI code sharing diagram](images/webui_code_sharing.png)

## **Step 1: Determine whether the code should be widely or narrowly shared.**

**Widely shared code** should be located in `ui/webui/resources/` and will be
served from `chrome://resources` and `chrome-untrusted://resources` at runtime.
Code in these folders can be used by all UIs in Chrome (trusted and untrusted).
As a result, code in these folders **should be general purpose, and make sense
for any Chrome UI to use, not just UIs with specific properties**.

**Examples of general purpose code:**

*   Core UI elements like `cr_checkbox.ts` (lots of UIs have use for checkboxes)
*   Widely useful utilities like `assert.ts` (lots of UIs need to
    `assert(<some condition>) `or `assertNotReached()`)

***Rule of thumb: If code is needed by 3+ different WebUI surfaces, this
is often a good indicator that it is sufficiently general purpose to be widely
shared.***

**Narrowly shared code** should live in a specific folder that is a sibling of
folders that need to use it. For example, code in
`chrome/browser/resources/settings_shared` is used by `c/b/resources/settings`
and `c/b/resources/password_manager`. Narrowly shared libraries are packaged
with a `build_webui()` rule. UIs that need these libraries add a dependency on
the generated `ts_library()` target, and add the files from the generated `.grd`
to their data source.  Narrowly shared code is served from a designated path
from the individual UIs that use it (e.g. `chrome://settings` and
`chrome://password-manager` both serve code from `settings_shared` from
`/shared/settings/`) and it is only available to these UIs, and not to all
WebUIs in Chrome.

**Examples of code that multiple UIs use, but is not general purpose and
therefore is narrowly shared:**

*   UI code that is only useful for UIs that are in the Side Panel (belongs in
    `chrome/browser/resources/side_panel/shared`)
*   UI code that is only useful for UIs that have access to the
    `settingsPrivate` API and use the settings “prefs” mechanism (belongs in
    `chrome/browser/resources/settings_shared`)
*   UI code for viewing a PDF document (belongs in
    `chrome/browser/resources/pdf`)

## **How to add widely shared code**

The organization of `ui/webui/resources` subfolders is as follows:

**`js`**:
Used for general purpose utilities and some browser proxies.
Not for UI elements; should not depend on Polymer or Lit.

**`cr_elements`**:
Used for UI elements, styles, and mixins that meet the following requirements:
* Do not use `$i18n` replacements or the `I18nMixin`.
* Do not use `chrome.send`, Mojo, or extension APIs
For more details see the [cr_elements README](https://chromium.googlesource.com/chromium/src/+/main/ui/webui/resources/cr_elements/README.md)

**`cr_components`**:
Used for more complex UI elements or components that are widely shared, but
don’t fit the requirements for cr_elements. For more details see the
[cr_components README](https://chromium.googlesource.com/chromium/src/+/main/ui/webui/resources/cr_components/README.md)

**Note: All widely shared code in `ui/webui/resources` should have unit tests
at the time it is added to this folder**. Since the code is widely shared, it
is likely many developers from different teams will need to make changes, and
unit tests reduce the chance of such changes introducing regressions.
Regressions in shared code are also more likely to be high impact, since they
can impact many different UIs.

## **How to add narrowly shared code**

First, you will need to **create a folder for your narrowly shared code to
reside in**. This folder should be a sibling of the highest level folder(s)
that need to use it. Examples:

*   `chrome/browser/resources/settings/` and
    `chrome/browser/resources/password_manager/` share code in
    `chrome/browser/resources/settings_shared/`.
*   `chrome/browser/resources/side_panel/customize_chrome/` and
    `chrome/browser/resources/side_panel/bookmarks/` share code in
    `chrome/browser/resources/side_panel/shared`.

Second, you need to **set up a `build_webui()` target in your new shared
library folder**. This is largely the same as setting up a `build_webui()`
target in any other folder, with some important possible differences below:

*    `grd_resource_path_prefix` should be set to a path that you want all
     UIs sharing the code to import these files from at runtime. For example,
     the target in `settings_shared` sets this to `shared/settings`, and all
     settings code importing these files imports them from
     `/shared/settings/`.
*    `ts_composite` must be set to true, because other library targets will
     depend on the shared code.
*    `webui_context_type` should be set based on all UIs using the code. In
     particular, if both trusted and untrusted UIs will use the shared
     library, this should be set to `relative` so that no absolute `chrome://`
     or `chrome-untrusted://` paths are allowed (since such paths will not
     work in both trusted and untrusted contexts).

Third, **add dependencies and corresponding path mappings** in the build
targets that depend on the new shared library. This is best demonstrated with
an example:

```
build_webui("build_my_webui") {
  grd_prefix = "my_webui"
  # Other params here
  ts_deps = [
    "../foo_shared:build_ts",  # Limited shared library at ../foo_shared
    "//ui/webui/resources/js:build_ts",
  ]
  # Map to the output directory of the `ts_library` build rule for
  # chrome/browser/resources/foo_shared.
  ts_path_mappings = [
    "/foo_shared/*|" + rebase_path(
        "$root_gen_dir/chrome/browser/resources/foo_shared/tsc/*",
        target_gen_dir) ]
  # other stuff goes here
}
```

Fourth, **ensure that any UIs using the shared library will have access to the
resources at runtime**. This has a few steps:
*   Update `tools/gritsettings/resource_ids.spec` with an entry for the new
    generated shared `.grd` file.
*   Add a dependency on the shared library's generated `resources` target.
    This should typically be added in the same target or group that depends
    on the `resources` target(s) of the code relying on the library (often
    in `chrome/browser/resources/BUILD.gn`).
*   Add the generated `.pak` file in `chrome_paks.gni`.
*   Register the shared library's resources in any data source that needs to
    serve them at runtime (see example code below) using
    `WebUIDataSource::AddResourcePaths()`.

```cpp
#include "chrome/grit/foo_shared_resources.h"
#include "chrome/grit/bar_shared_resources.h"
// ...
HelloWorldUI::HelloWorldUI(content::WebUI* web_ui) {
  // ...
  // Add selected resources from foo_shared
  static constexpr webui::ResourcePath kResources[] = {
      {"foo_shared/foo_shared.css.js", IDR_FOO_SHARED_FOO_SHARED_CSS_JS},
      {"foo_shared/foo_shared_vars.css.js",
       IDR_FOO_SHARED_FOO_SHARED_VARS_CSS_JS},
  };
  source->AddResourcePaths(kResources);

  // Add all shared resources from bar_shared
  source->AddResourcePaths(kBarSharedResources);
}
```