blob: 465dbc0cfe71a386d112e087279dc65e63a25cf4 [file] [log] [blame] [view]
Dan Beam079d5c12017-06-16 19:23:301<style>
2.note::before {
3 content: 'Note: ';
4 font-variant: small-caps;
5 font-style: italic;
6}
7
8.doc h1 {
9 margin: 0;
10}
11</style>
12
13# WebUI Explainer
14
15[TOC]
16
Dan Beam079d5c12017-06-16 19:23:3017## What is "WebUI"?
18
19"WebUI" is a term used to loosely describe **parts of Chrome's UI
20implemented with web technologies** (i.e. HTML, CSS, JavaScript).
21
22Examples of WebUI in Chromium:
23
24* Settings (chrome://settings)
25* History (chrome://history)
26* Downloads (chrome://downloads)
27
28<div class="note">
29Not all web-based UIs in Chrome have chrome:// URLs.
30</div>
31
32This document explains how WebUI works.
33
Dan Beam079d5c12017-06-16 19:23:3034## What's different from a web page?
35
36WebUIs are granted super powers so that they can manage Chrome itself. For
37example, it'd be very hard to implement the Settings UI without access to many
38different privacy and security sensitive services. Access to these services are
39not granted by default.
40
41Only special URLs are granted WebUI "bindings" via the child security process.
42
43Specifically, these bindings:
44
45* give a renderer access to load [`chrome:`](#chrome_urls) URLS
46 * this is helpful for shared libraries, i.e. `chrome://resources/`
47* allow the browser to execute arbitrary JavaScript in that renderer via
48 [`CallJavascriptFunction()`](#CallJavascriptFunction)
49* allow communicating from the renderer to the browser with
50 [`chrome.send()`](#chrome_send) and friends
51* ignore content settings regarding showing images or executing JavaScript
52
Dan Beam079d5c12017-06-16 19:23:3053## How `chrome:` URLs work
54
55<div class="note">
56A URL is of the format &lt;protocol&gt;://&lt;host&gt;/&lt;path&gt;.
57</div>
58
59A `chrome:` URL loads a file from disk, memory, or can respond dynamically.
60
61Because Chrome UIs generally need access to the browser (not just the current
62tab), much of the C++ that handles requests or takes actions lives in the
63browser process. The browser has many more privileges than a renderer (which is
64sandboxed and doesn't have file access), so access is only granted for certain
65URLs.
66
67### `chrome:` protocol
68
69Chrome recognizes a list of special protocols, which it registers while starting
70up.
71
72Examples:
73
James Lissiak28b21a62019-05-15 15:32:0474* devtools:
Dan Beam079d5c12017-06-16 19:23:3075* chrome-extensions:
Adam Langley81be0732019-03-06 18:38:4576* chrome:
Dan Beam079d5c12017-06-16 19:23:3077* file:
78* view-source:
79
80This document mainly cares about the **chrome:** protocol, but others can also
81be granted [WebUI bindings](#bindings) or have special
82properties.
83
84### `chrome:` hosts
85
86After registering the `chrome:` protocol, a set of factories are created. These
87factories contain a list of valid host names. A valid hostname generates a
88controller.
89
90In the case of `chrome:` URLs, these factories are registered early in the
Rebekah Potter017ed542023-11-15 16:15:3891browser process lifecycle. Before the first `WebUIConfig` is registered, the
92`WebUIConfigMap` instance is created. This map creates and registers a
93factory (`WebUIConfigMapWebUIControllerFactory`) in its constructor.
94This factory looks at the global `WebUIConfigMap`, which maps hosts to
95`WebUIConfig`s, to see if any of the configs handle the requested URL. It calls
96the method on the config to create the corresponding controller if it finds a
97config to handle the URL.
Dan Beam079d5c12017-06-16 19:23:3098
99```c++
100// ChromeBrowserMainParts::PreMainMessageLoopRunImpl():
Rebekah Potter017ed542023-11-15 16:15:38101
102// Legacy WebUIControllerFactory registration
Dan Beam079d5c12017-06-16 19:23:30103content::WebUIControllerFactory::RegisterFactory(
104 ChromeWebUIControllerFactory::GetInstance());
Rebekah Potter017ed542023-11-15 16:15:38105
106// Factory for all WebUIs using WebUIConfig will be created here.
107RegisterChromeWebUIConfigs();
108RegisterChromeUntrustedWebUIConfigs();
Dan Beam079d5c12017-06-16 19:23:30109```
110
111When a URL is requested, a new renderer is created to load the URL, and a
112corresponding class in the browser is set up to handle messages from the
113renderer to the browser (a `RenderFrameHost`).
114
Dan Beam079d5c12017-06-16 19:23:30115```c++
Rebekah Potter26b32f42023-10-31 20:36:47116auto* config = config_map_->GetConfig(browser_context, url);
117if (!config)
118 return nullptr; // Not a known host; no special access.
119
120return config->CreateWebUIController(web_ui, url);
Dan Beam079d5c12017-06-16 19:23:30121```
122
Rebekah Potter26b32f42023-10-31 20:36:47123Configs can be registered with the map by calling `map.AddWebUIConfig()` in
124`chrome_web_ui_configs.cc`:
125```c++
126map.AddWebUIConfig(std::make_unique<donuts::DonutsUIConfig>());
127
128```
129
130If a factory knows how to handle a host (returns a `WebUIFactoryFunction`),
Dan Beam079d5c12017-06-16 19:23:30131the navigation machinery [grants the renderer process WebUI
132bindings](#bindings) via the child security policy.
133
134```c++
135// RenderFrameHostImpl::AllowBindings():
Avi Drissman78865bbb2024-08-22 20:57:19136if (bindings_flags.Has(BindingsPolicyValue::kWebUi)) {
dbeam8b52edff2017-06-16 22:36:18137 ChildProcessSecurityPolicyImpl::GetInstance()->GrantWebUIBindings(
Emily Andrewsd15fd762024-12-10 20:41:54138 GetProcess()->GetDeprecatedID());
Dan Beam079d5c12017-06-16 19:23:30139}
140```
141
Rebekah Potter26b32f42023-10-31 20:36:47142The factory creates a [`WebUIController`](#WebUIController) for a tab using
143the WebUIConfig.
144
Mickey Burks463e6f52024-07-02 11:45:05145Here's an example using the DefaultWebUIConfig:
Dan Beam079d5c12017-06-16 19:23:30146
147```c++
Mickey Burks463e6f52024-07-02 11:45:05148class DonutsUI;
149
150// This would go in chrome/common/webui_url_constants.cc
151namespace chrome {
152const char kChromeUIDonutsHost[] = "donuts";
153} // namespace chrome
154
Rebekah Potter26b32f42023-10-31 20:36:47155// Config for chrome://donuts
Mickey Burks463e6f52024-07-02 11:45:05156class DonutsUIConfig : public content::DefaultWebUIConfig<DonutsUI> {
157 public:
158 DonutsUIConfig()
159 : DefaultWebUIConfig(content::kChromeUIScheme,
160 chrome::kChromeUIDonutsHost) {}
161};
Rebekah Potter26b32f42023-10-31 20:36:47162
Dan Beam079d5c12017-06-16 19:23:30163// Controller for chrome://donuts.
164class DonutsUI : public content::WebUIController {
165 public:
166 DonutsUI(content::WebUI* web_ui) : content::WebUIController(web_ui) {
167 content::WebUIDataSource* source =
Rebekah Pottera8949422023-01-05 18:44:13168 content::WebUIDataSource::CreateAndAdd(
169 web_ui->GetWebContents()->GetBrowserContext(),
170 "donuts"); // "donuts" == hostname
Dan Beam079d5c12017-06-16 19:23:30171 source->AddString("mmmDonuts", "Mmm, donuts!"); // Translations.
rbpotterf50e0252020-09-14 16:38:33172 source->AddResourcePath("", IDR_DONUTS_HTML); // Home page.
Dan Beam079d5c12017-06-16 19:23:30173
174 // Handles messages from JavaScript to C++ via chrome.send().
Jeremy Romane0760a402018-03-02 18:19:40175 web_ui->AddMessageHandler(std::make_unique<OvenHandler>());
Dan Beam079d5c12017-06-16 19:23:30176 }
177};
178```
179
180If we assume the contents of `IDR_DONUTS_HTML` yields:
181
182```html
183<h1>$i18n{mmmDonuts}</h1>
184```
185
186Visiting `chrome://donuts` should show in something like:
187
188<div style="border: 1px solid black; padding: 10px;">
189<h1>Mmmm, donuts!</h1>
190</div>
191
192Delicious success.
193
Christopher Lam50ab1e92019-10-29 04:33:16194By default $i18n{} escapes strings for HTML. $i18nRaw{} can be used for
195translations that embed HTML, and $i18nPolymer{} can be used for Polymer
196bindings. See
197[this comment](https://bugs.chromium.org/p/chromium/issues/detail?id=1010815#c1)
198for more information.
199
Dan Beam079d5c12017-06-16 19:23:30200## C++ classes
201
202### WebUI
203
204`WebUI` is a high-level class and pretty much all HTML-based Chrome UIs have
205one. `WebUI` lives in the browser process, and is owned by a `RenderFrameHost`.
206`WebUI`s have a concrete implementation (`WebUIImpl`) in `content/` and are
207created in response to navigation events.
208
209A `WebUI` knows very little about the page it's showing, and it owns a
210[`WebUIController`](#WebUIController) that is set after creation based on the
211hostname of a requested URL.
212
213A `WebUI` *can* handle messages itself, but often defers these duties to
214separate [`WebUIMessageHandler`](#WebUIMessageHandler)s, which are generally
215designed for handling messages on certain topics.
216
217A `WebUI` can be created speculatively, and are generally fairly lightweight.
218Heavier duty stuff like hard initialization logic or accessing services that may
219have side effects are more commonly done in a
220[`WebUIController`](#WebUIController) or
221[`WebUIMessageHandler`s](#WebUIMessageHandler).
222
223`WebUI` are created synchronously on the UI thread in response to a URL request,
dpapad32c8dd92025-01-22 19:05:16224and are reused where possible between navigations (i.e. refreshing a page).
Dan Beam079d5c12017-06-16 19:23:30225Because they run in a separate process and can exist before a corresponding
226renderer process has been created, special care is required to communicate with
227the renderer if reliable message passing is required.
228
Rebekah Potter26b32f42023-10-31 20:36:47229### WebUIConfig
230A `WebUIConfig` contains minimal possible logic and information for determining
231whether a certain subclass of `WebUIController` should be created for a given
232URL.
233
234A `WebUIConfig` holds information about the host and scheme (`chrome://` or
Carlos Knippschilde084bd22024-09-19 18:50:54235[`chrome-untrusted://`](chrome_untrusted.md)) that the controller serves.
Rebekah Potter26b32f42023-10-31 20:36:47236
237A `WebUIConfig` may contain logic to check if the WebUI is enabled for a given
238`BrowserContext` and url (e.g., if relevant feature flags are enabled/disabled,
239if the url path is valid, etc).
240
241A `WebUIConfig` can invoke the `WebUIController`'s constructor in its
242`CreateWebUIControllerForURL` method.
243
244`WebUIConfig`s are created at startup when factories are registered, so should
245be lightweight.
246
Dan Beam079d5c12017-06-16 19:23:30247### WebUIController
248
249A `WebUIController` is the brains of the operation, and is responsible for
250application-specific logic, setting up translations and resources, creating
251message handlers, and potentially responding to requests dynamically. In complex
252pages, logic is often split across multiple
253[`WebUIMessageHandler`s](#WebUIMessageHandler) instead of solely in the
254controller for organizational benefits.
255
256A `WebUIController` is owned by a [`WebUI`](#WebUI), and is created and set on
Rebekah Potter26b32f42023-10-31 20:36:47257an existing [`WebUI`](#WebUI) when the corresponding `WebUIConfig` is found in
258the map matching the URL, or when the correct controller is determined via URL
259inspection in `ChromeWebUIControllerFactory`. (i.e. chrome://settings creates
260a generic [`WebUI`](#WebUI) with a settings-specific `WebUIController`).
Dan Beam079d5c12017-06-16 19:23:30261
262### WebUIDataSource
263
rbpotterf50e0252020-09-14 16:38:33264The `WebUIDataSource` class provides a place for data to live for WebUI pages.
265
266Examples types of data stored in this class are:
267
268* static resources (i.e. .html files packed into bundles and pulled off of disk)
269* translations
270* dynamic feature values (i.e. whether a feature is enabled)
271
272Data sources are set up in the browser process (in C++) and are accessed by
273loading URLs from the renderer.
274
275Below is an example of a simple data source (in this case, Chrome's history
276page):
277
278```c++
Rebekah Pottera8949422023-01-05 18:44:13279content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
280 Profile::FromWebUI(web_ui), "history");
rbpotterf50e0252020-09-14 16:38:33281
282source->AddResourcePath("sign_in_promo.svg", IDR_HISTORY_SIGN_IN_PROMO_SVG);
283source->AddResourcePath("synced_tabs.html", IDR_HISTORY_SYNCED_TABS_HTML);
284
285source->AddString("title", IDS_HISTORY_TITLE);
286source->AddString("moreFromThisSite", IDS_HISTORY_MORE_FROM_THIS_SITE);
287
288source->AddBoolean("showDateRanges",
289 base::FeatureList::IsEnabled(features::kHistoryShowDateRanges));
290
291webui::SetupWebUIDataSource(
Mickey Burkse714ad672024-10-16 20:40:54292 source, base::span<const webui::ResourcePath>(kHistoryResources),
rbpotterf50e0252020-09-14 16:38:33293 kGeneratedPath, IDR_HISTORY_HISTORY_HTML);
rbpotterf50e0252020-09-14 16:38:33294```
295
296For more about each of the methods called on `WebUIDataSource` and the utility
297method that performs additional configuration, see [DataSources](#DataSources)
298and [WebUIDataSourceUtils](#WebUIDataSourceUtils)
299
Dan Beam079d5c12017-06-16 19:23:30300### WebUIMessageHandler
301
302Because some pages have many messages or share code that sends messages, message
303handling is often split into discrete classes called `WebUIMessageHandler`s.
304These handlers respond to specific invocations from JavaScript.
305
306So, the given C++ code:
307
308```c++
309void OvenHandler::RegisterMessages() {
Ayu Ishii33743432021-02-03 19:05:01310 web_ui()->RegisterMessageCallback(
311 "bakeDonuts",
312 base::BindRepeating(&OvenHandler::HandleBakeDonuts,
313 base::Unretained(this)));
Dan Beam079d5c12017-06-16 19:23:30314}
315
Moe Ahmadide5901862022-02-25 21:56:23316void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:23317 AllowJavascript();
318
Lei Zhang72347ebdd2021-11-16 16:40:02319 // IMPORTANT: Fully validate `args`.
cammie720e8acd2021-08-25 19:15:45320 CHECK_EQ(1u, args.size());
Lei Zhang72347ebdd2021-11-16 16:40:02321 int num_donuts = args[0].GetInt();
322 CHECK_GT(num_donuts, 0);
323 GetOven()->BakeDonuts(num_donuts);
Dan Beam079d5c12017-06-16 19:23:30324}
325```
326
327Can be triggered in JavaScript with this example code:
328
329```js
330$('bakeDonutsButton').onclick = function() {
331 chrome.send('bakeDonuts', [5]); // bake 5 donuts!
332};
333```
334
rbpotterf50e0252020-09-14 16:38:33335## Data Sources
336
Rebekah Pottera8949422023-01-05 18:44:13337### WebUIDataSource::CreateAndAdd()
rbpotterf50e0252020-09-14 16:38:33338
Rebekah Pottera8949422023-01-05 18:44:13339This is a factory method required to create and add a WebUIDataSource. The first
340argument to `Create()` is the browser context. The second argument is typically
341the host name of the page. The caller does not own the result.
rbpotterf50e0252020-09-14 16:38:33342
Rebekah Pottera8949422023-01-05 18:44:13343Additionally, calling `CreateAndAdd()` will overwrite any existing data source
rbpotterf50e0252020-09-14 16:38:33344with the same name.
345
346<div class="note">
347It's unsafe to keep references to a <code>WebUIDataSource</code> after calling
348<code>Add()</code>. Don't do this.
349</div>
350
rbpotterf50e0252020-09-14 16:38:33351### WebUIDataSource::AddLocalizedString()
352
353Using an int reference to a grit string (starts with "IDS" and lives in a .grd
354or .grdp file), adding a string with a key name will be possible to reference
355via the `$i18n{}` syntax (and will be replaced when requested) or later
356dynamically in JavaScript via `loadTimeData.getString()` (or `getStringF`).
357
Lei Zhang5b205082022-01-25 18:08:38358### WebUIDataSource::AddLocalizedStrings()
359
360Many Web UI data sources need to be set up with a large number of localized
361strings. Instead of repeatedly calling <code>AddLocalizedString()</code>, create
362an array of all the strings and use <code>AddLocalizedStrings()</code>:
363
364```c++
365 static constexpr webui::LocalizedString kStrings[] = {
366 // Localized strings (alphabetical order).
367 {"actionMenuDescription", IDS_HISTORY_ACTION_MENU_DESCRIPTION},
368 {"ariaRoleDescription", IDS_HISTORY_ARIA_ROLE_DESCRIPTION},
369 {"bookmarked", IDS_HISTORY_ENTRY_BOOKMARKED},
370 };
371 source->AddLocalizedStrings(kStrings);
372```
373
rbpotterf50e0252020-09-14 16:38:33374### WebUIDataSource::AddResourcePath()
375
376Using an int reference to a grit resource (starts with "IDR" and lives in a .grd
377or .grdp file), adds a resource to the UI with the specified path.
378
379It's generally a good idea to call <code>AddResourcePath()</code> with the empty
380path and a resource ID that should be served as the "catch all" resource to
381respond with. This resource will be served for requests like "chrome://history",
382or "chrome://history/pathThatDoesNotExist". It will not be served for requests
383that look like they are attempting to fetch a specific file, like
384"chrome://history/file\_that\_does\_not\_exist.js". This is so that if a user
385enters a typo when trying to load a subpage like "chrome://history/syncedTabs"
386they will be redirected to the main history page, instead of seeing an error,
387but incorrect imports in the source code will fail, so that they can be more
388easily found and corrected.
389
Lei Zhang5b205082022-01-25 18:08:38390### WebUIDataSource::AddResourcePaths()
391
392Similar to the localized strings, many Web UIs need to add a large number of
393resource paths. In this case, use <code>AddResourcePaths()</code> to
394replace repeated calls to <code>AddResourcePath()</code>.
395
396```c++
397 static constexpr webui::ResourcePath kResources[] = {
398 {"browser_api.js", IDR_BROWSER_API_JS},
399 {"constants.js", IDR_CONSTANTS_JS},
400 {"controller.js", IDR_CONTROLLER_JS},
401 };
402 source->AddResourcePaths(kResources);
403```
404
405The same method can be leveraged for cases that directly use constants defined
406by autogenerated grit resources map header files. For example, the autogenerated
407print\_preview\_resources\_map.h header defines a
408<code>webui::ResourcePath</code> array named <code>kPrintPreviewResources</code>
409and a <code>size\_t kPrintPreviewResourcesSize</code>. All the resources in this
410resource map can be added as follows:
411
412```c++
413 source->AddResourcePaths(
Mickey Burkse714ad672024-10-16 20:40:54414 base::span<const webui::ResourcePath>(kPrintPreviewResources),
Lei Zhang5b205082022-01-25 18:08:38415```
416
rbpotterf50e0252020-09-14 16:38:33417### WebUIDataSource::AddBoolean()
418
419Often a page needs to know whether a feature is enabled. This is a good use case
420for `WebUIDataSource::AddBoolean()`. Then, in the Javascript, one can write
421code like this:
422
423```js
424if (loadTimeData.getBoolean('myFeatureIsEnabled')) {
425 ...
426}
427```
428
429<div class="note">
430Data sources are not recreated on refresh, and therefore values that are dynamic
431(i.e. that can change while Chrome is running) may easily become stale. It may
rbpotteracc480cd2022-03-04 08:42:19432be preferable to use <code>sendWithPromise()</code> to initialize dynamic
rbpotterf50e0252020-09-14 16:38:33433values and call <code>FireWebUIListener()</code> to update them.
434
435If you really want or need to use <code>AddBoolean()</code> for a dynamic value,
436make sure to call <code>WebUIDataSource::Update()</code> when the value changes.
437</div>
438
rbpotterf50e0252020-09-14 16:38:33439## WebUI utils for working with data sources
440
441chrome/browser/ui/webui/webui\_util.\* contains a number of methods to simplify
442common configuration tasks.
443
Rebekah Potter5691cab2020-10-29 21:30:35444### webui::SetupWebUIDataSource()
rbpotterf50e0252020-09-14 16:38:33445
Rebekah Potter5691cab2020-10-29 21:30:35446This method performs common configuration tasks on a data source for a Web UI
447that uses JS modules. When creating a Web UI that uses JS modules, use this
448utility instead of duplicating the configuration steps it performs elsewhere.
449Specific setup steps include:
rbpotterf50e0252020-09-14 16:38:33450
451* Setting the content security policy to allow the data source to load only
452 resources from its own host (e.g. chrome://history), chrome://resources, and
Rebekah Potter1ebb97f2023-10-25 15:38:45453 chrome://webui-test (used to serve test files).
rbpotterf50e0252020-09-14 16:38:33454* Enabling i18n template replacements by calling <code>UseStringsJs()</code> and
455 <code>EnableReplaceI18nInJS()</code> on the data source.
456* Adding the test loader files to the data source, so that test files can be
457 loaded as JS modules.
458* Setting the resource to load for the empty path.
Rebekah Potter5691cab2020-10-29 21:30:35459* Adding all resources from a GritResourceMap.
rbpotterf50e0252020-09-14 16:38:33460
Rebekah Potter65c7cae2022-12-15 22:19:49461## Browser (C++) and Renderer (JS) communication
Dan Beam079d5c12017-06-16 19:23:30462
Rebekah Potter65c7cae2022-12-15 22:19:49463### Mojo
464
465[Mojo](https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md)
466is used for IPC throughout Chromium, and should generally be used for new
467WebUIs to communicate between the browser (C++) and the renderer (JS/TS). To
468use Mojo, you will need to:
469
470* Write an interface definition for the JS/C++ interface in a mojom file
471* Add a build target in the BUILD.gn file to autogenerate C++ and TypeScript
472 code ("bindings").
473* Bind the interface on the C++ side and implement any methods to send or
474 receive information from TypeScript.
475* Add the TypeScript bindings file to your WebUI's <code>ts_library()</code>
476 and use them in your TypeScript code.
477
478#### Mojo Interface Definition
479Mojo interfaces are declared in mojom files. For WebUIs, these normally live
480alongside the C++ code in chrome/browser/ui/webui. For example:
481
482**chrome/browser/ui/webui/donuts/donuts.mojom**
483```
484module donuts.mojom;
485
Rebekah Potter26b32f42023-10-31 20:36:47486// Factory ensures that the Page and PageHandler interfaces are always created
487// together without requiring an initialization call from the WebUI to the
488// handler.
Rebekah Potter65c7cae2022-12-15 22:19:49489interface PageHandlerFactory {
490 CreatePageHandler(pending_remote<Page> page,
491 pending_receiver<PageHandler> handler);
492};
493
494// Called from TS side of chrome://donuts (Renderer -> Browser)
495interface PageHandler {
496 StartPilotLight();
497
498 BakeDonuts(uint32 num_donuts);
499
500 // Expects a response from the browser.
501 GetNumberOfDonuts() => (uint32 num_donuts);
Kevin Graney1a0030f2023-10-24 23:31:17502};
Rebekah Potter65c7cae2022-12-15 22:19:49503
504// Called from C++ side of chrome://donuts. (Browser -> Renderer)
505interface Page {
506 DonutsBaked(uint32 num_donuts);
Kevin Graney1a0030f2023-10-24 23:31:17507};
Rebekah Potter65c7cae2022-12-15 22:19:49508```
509
510#### BUILD.gn mojo target
511mojom() is the build rule used to generate mojo bindings. It can be set up as
512follows:
513
514**chrome/browser/ui/webui/donuts/BUILD.gn**
dpapad994a9392025-03-25 00:21:43515```python
Rebekah Potter65c7cae2022-12-15 22:19:49516import("//mojo/public/tools/bindings/mojom.gni")
517
518mojom("mojo_bindings") {
519 sources = [ "donuts.mojom" ]
520 webui_module_path = "/"
Rebekah Potter65c7cae2022-12-15 22:19:49521}
522```
523
524#### Setting up C++ bindings
525The WebUIController class should inherit from ui::MojoWebUIController and
526from the PageHandlerFactory class defined in the mojom file.
527
528**chrome/browser/ui/webui/donuts/donuts_ui.h**
529```c++
530class DonutsPageHandler;
531
532class DonutsUI : public ui::MojoWebUIController,
533 public donuts::mojom::PageHandlerFactory {
534 public:
535 explicit DonutsUI(content::WebUI* web_ui);
536
537 DonutsUI(const DonutsUI&) = delete;
538 DonutsUI& operator=(const DonutsUI&) = delete;
539
540 ~DonutsUI() override;
541
542 // Instantiates the implementor of the mojom::PageHandlerFactory mojo
543 // interface passing the pending receiver that will be internally bound.
544 void BindInterface(
545 mojo::PendingReceiver<donuts::mojom::PageHandlerFactory> receiver);
546
547 private:
548 // donuts::mojom::PageHandlerFactory:
549 void CreatePageHandler(
550 mojo::PendingRemote<donuts::mojom::Page> page,
551 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver) override;
552
553 std::unique_ptr<DonutsPageHandler> page_handler_;
554
555 mojo::Receiver<donuts::mojom::PageHandlerFactory> page_factory_receiver_{
556 this};
557
558 WEB_UI_CONTROLLER_TYPE_DECL();
559};
560```
561
562**chrome/browser/ui/webui/donuts/donuts_ui.cc**
563```c++
564DonutsUI::DonutsUI(content::WebUI* web_ui)
565 : ui::MojoWebUIController(web_ui, true) {
566 // Normal constructor steps (e.g. setting up data source) go here.
567}
568
569WEB_UI_CONTROLLER_TYPE_IMPL(DonutsUI)
570
571DonutsUI::~DonutsUI() = default;
572
573void DonutsUI::BindInterface(
574 mojo::PendingReceiver<donuts::mojom::PageHandlerFactory> receiver) {
575 page_factory_receiver_.reset();
576 page_factory_receiver_.Bind(std::move(receiver));
577}
578
579void DonutsUI::CreatePageHandler(
580 mojo::PendingRemote<donuts::mojom::Page> page,
581 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver) {
582 DCHECK(page);
583 page_handler_ = std::make_unique<DonutsPageHandler>(
584 std::move(receiver), std::move(page));
585}
586```
587
588You also need to register the PageHandlerFactory to your controller in
Jacob Stanley40be5eb2025-03-19 17:06:55589**chrome/browser/chrome_browser_interface_binders_webui.cc**:
Rebekah Potter65c7cae2022-12-15 22:19:49590```c++
591RegisterWebUIControllerInterfaceBinder<donuts::mojom::PageHandlerFactory,
592 DonutsUI>(map);
593```
594
595#### Using C++ bindings for communication
596The WebUI message handler should inherit from the Mojo PageHandler class.
597
598**chrome/browser/ui/webui/donuts/donuts_page_handler.h**
599```c++
600#include "chrome/browser/ui/webui/donuts/donuts.mojom.h"
601#include "mojo/public/cpp/bindings/receiver.h"
602#include "mojo/public/cpp/bindings/remote.h"
603
604class DonutsPageHandler : public donuts::mojom::PageHandler {
605 public:
606 DonutsPageHandler(
607 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver,
608 mojo::PendingRemote<donuts::mojom::Page> page);
609
610 DonutsPageHandler(const DonutsPageHandler&) = delete;
611 DonutsPageHandler& operator=(const DonutsPageHandler&) = delete;
612
613 ~DonutsPageHandler() override;
614
615 // Triggered by some outside event
Mickey Burks463e6f52024-07-02 11:45:05616 void OnBakingDonutsFinished(uint32_t num_donuts);
Rebekah Potter65c7cae2022-12-15 22:19:49617
618 // donuts::mojom::PageHandler:
619 void StartPilotLight() override;
620 void BakeDonuts(uint32_t num_donuts) override;
621 void GetNumberOfDonuts(GetNumberOfDonutsCallback callback) override;
Mickey Burks463e6f52024-07-02 11:45:05622
623 private:
624 mojo::Receiver<donuts::mojom::PageHandler> receiver_;
625 mojo::Remote<donuts::mojom::Page> page_;
626};
Rebekah Potter65c7cae2022-12-15 22:19:49627```
628
629The message handler needs to implement all the methods on the PageHandler
630interface.
631
632**chrome/browser/ui/webui/donuts/donuts_page_handler.cc**
633```c++
634DonutsPageHandler::DonutsPageHandler(
635 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver,
636 mojo::PendingRemote<donuts::mojom::Page> page)
637 : receiver_(this, std::move(receiver)),
638 page_(std::move(page)) {
639}
640
641DonutsPageHandler::~DonutsPageHandler() {
642 GetOven()->TurnOffGas();
643}
644
645// Triggered by outside asynchronous event; sends information to the renderer.
646void DonutsPageHandler::OnBakingDonutsFinished(uint32_t num_donuts) {
647 page_->DonutsBaked(num_donuts);
648}
649
650// Triggered by startPilotLight() call in TS.
651void DonutsPageHandler::StartPilotLight() {
652 GetOven()->StartPilotLight();
653}
654
655// Triggered by bakeDonuts() call in TS.
Mickey Burks463e6f52024-07-02 11:45:05656void DonutsPageHandler::BakeDonuts(uint32_t num_donuts) {
Rebekah Potter65c7cae2022-12-15 22:19:49657 GetOven()->BakeDonuts();
658}
659
660// Triggered by getNumberOfDonuts() call in TS; sends a response back to the
661// renderer.
662void DonutsPageHandler::GetNumberOfDonuts(GetNumberOfDonutsCallback callback) {
663 uint32_t result = GetOven()->GetNumberOfDonuts();
664 std::move(callback).Run(result);
665}
666```
667
668#### Setting Up TypeScript bindings
669
670For WebUIs using the `build_webui()` rule, the TypeScript mojo bindings can be
671added to the build and served from the root (e.g.
672`chrome://donuts/donuts.mojom-webui.js`) by adding the following arguments to
673`build_webui()`:
674
675**chrome/browser/resources/donuts/BUILD.gn**
dpapad994a9392025-03-25 00:21:43676```python
Mickey Burks463e6f52024-07-02 11:45:05677import("//ui/webui/resources/tools/build_webui.gni")
678
Rebekah Potter65c7cae2022-12-15 22:19:49679build_webui("build") {
Mickey Burks463e6f52024-07-02 11:45:05680 grd_prefix = "donuts"
681
682 # You will add these files in the next step:
dpapadbb3ecad2025-04-10 19:30:39683 ts_files = [
Mickey Burks463e6f52024-07-02 11:45:05684 "donuts.ts",
685 "browser_proxy.ts",
686 ]
687
Ryan Sultanembd4c2a02024-09-26 16:50:39688 ts_deps = [ "//ui/webui/resources/mojo:build_ts" ]
689
Rebekah Potter65c7cae2022-12-15 22:19:49690 mojo_files_deps =
691 [ "//chrome/browser/ui/webui/donuts:mojo_bindings_ts__generator" ]
692 mojo_files = [
693 "$root_gen_dir/chrome/browser/ui/webui/donuts/donuts.mojom-webui.ts",
694 ]
Rebekah Potter65c7cae2022-12-15 22:19:49695}
696```
697
698It is often helpful to wrap the TypeScript side of Mojo setup in a BrowserProxy
699class:
700
701**chrome/browser/resources/donuts/browser_proxy.ts**
702```js
Mickey Burks463e6f52024-07-02 11:45:05703import {PageCallbackRouter, PageHandlerFactory, PageHandlerRemote} from './donuts.mojom-webui.js';
704import type {PageHandlerInterface} from './donuts.mojom-webui.js';
Rebekah Potter65c7cae2022-12-15 22:19:49705
Mickey Burks463e6f52024-07-02 11:45:05706// Exporting the interface helps when creating a TestBrowserProxy wrapper.
707export interface BrowserProxy {
708 callbackRouter: PageCallbackRouter;
709 handler: PageHandlerInterface;
710}
711
712export class BrowserProxyImpl implements BrowserProxy {
Rebekah Potter65c7cae2022-12-15 22:19:49713 callbackRouter: PageCallbackRouter;
714 handler: PageHandlerInterface;
715
Mickey Burks463e6f52024-07-02 11:45:05716 private constructor() {
Rebekah Potter65c7cae2022-12-15 22:19:49717 this.callbackRouter = new PageCallbackRouter();
Rebekah Potter65c7cae2022-12-15 22:19:49718 this.handler = new PageHandlerRemote();
Mickey Burks463e6f52024-07-02 11:45:05719 PageHandlerFactory.getRemote().createPageHandler(
Rebekah Potter65c7cae2022-12-15 22:19:49720 this.callbackRouter.$.bindNewPipeAndPassRemote(),
721 (this.handler as PageHandlerRemote).$.bindNewPipeAndPassReceiver());
722 }
723
724 static getInstance(): BrowserProxy {
725 return instance || (instance = new BrowserProxy());
726 }
727
Mickey Burks463e6f52024-07-02 11:45:05728 static setInstance(proxy: BrowserProxy) {
729 instance = proxy;
Rebekah Potter65c7cae2022-12-15 22:19:49730 }
731}
732
733let instance: BrowserProxy|null = null;
734```
735
736#### Using TypeScript bindings for communication
737The `callbackRouter` (`PageCallbackRouter`) can be used to add listeners for
738asynchronous events sent from the browser.
739
740The `handler` (`PageHandlerRemote`) can be used to send messages from the
741renderer to the browser. For interface methods that require a browser response,
742calling the method returns a promise. The promise will be resolved with the
743response from the browser.
744
745**chrome/browser/resources/donuts/donuts.ts**
dpapad994a9392025-03-25 00:21:43746```ts
Mickey Burks463e6f52024-07-02 11:45:05747import {BrowserProxyImpl} from './browser_proxy.js';
Rebekah Potter65c7cae2022-12-15 22:19:49748
749let numDonutsBaked: number = 0;
750
751window.onload = function() {
752 // Other page initialization steps go here
Mickey Burks463e6f52024-07-02 11:45:05753 const proxy = BrowserProxyImpl.getInstance();
Rebekah Potter65c7cae2022-12-15 22:19:49754 // Tells the browser to start the pilot light.
755 proxy.handler.startPilotLight();
756 // Adds a listener for the asynchronous "donutsBaked" event.
757 proxy.callbackRouter.donutsBaked.addListener(
758 (numDonuts: number) => {
759 numDonutsBaked += numDonuts;
760 });
761};
762
763function CheckNumberOfDonuts() {
764 // Requests the number of donuts from the browser, and alerts with the
765 // response.
Mickey Burks463e6f52024-07-02 11:45:05766 BrowserProxyImpl.getInstance().handler.getNumberOfDonuts().then(
Rebekah Potter65c7cae2022-12-15 22:19:49767 (numDonuts: number) => {
768 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
769 });
770}
771
772function BakeDonuts(numDonuts: number) {
773 // Tells the browser to bake |numDonuts| donuts.
Mickey Burks463e6f52024-07-02 11:45:05774 BrowserProxyImpl.getInstance().handler.bakeDonuts(numDonuts);
Rebekah Potter65c7cae2022-12-15 22:19:49775}
776```
777
778### Pre-Mojo alternative: chrome.send()/WebUIMessageHandler
779Most Chrome WebUIs were added before the introduction of Mojo, and use the
780older style WebUIMessageHandler + chrome.send() pattern. The following sections
781detail the methods in WebUIMessageHandler and the corresponding communication
782methods in TypeScript/JavaScript and how to use them.
783
784#### WebUIMessageHandler::AllowJavascript()
Dan Beam079d5c12017-06-16 19:23:30785
Adam Langley81be0732019-03-06 18:38:45786A tab that has been used for settings UI may be reloaded, or may navigate to an
787external origin. In both cases, one does not want callbacks from C++ to
788Javascript to run. In the former case, the callbacks will occur when the
789Javascript doesn't expect them. In the latter case, sensitive information may be
790delivered to an untrusted origin.
791
792Therefore each message handler maintains
793[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
794that describes whether delivering callbacks to Javascript is currently
795appropriate. This boolean is set by calling `AllowJavascript`, which should be
796done when handling a call from Javascript, because that indicates that the page
797is ready for the subsequent callback. (See
798[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
799If the tab navigates or reloads,
800[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
801is called to clear the flag.
802
803Therefore, before each callback from C++ to Javascript, the flag must be tested
804by calling
805[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
806If false, then the callback must be dropped. (When the flag is false, calling
807[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
808will crash. See
809[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
810
811Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
812the case where an asynchronous operation is started, the settings page is
813reloaded, and the user triggers another operation using the original message
814handler. The `javascript_allowed_` boolean will be true, but the original
815callback should still be dropped because it relates to a operation that was
816discarded by the reload. (Reloading settings UI does _not_ cause message handler
817objects to be deleted.)
818
819Thus a message handler may override
820[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
821to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30822
823In the JS:
824
825```js
826window.onload = function() {
827 app.initialize();
828 chrome.send('startPilotLight');
829};
830```
831
832In the C++:
833
834```c++
Lei Zhangf48bb60e2022-12-09 17:42:44835void OvenHandler::HandleStartPilotLight(const base::Value::List& /*args*/) {
Dan Beam079d5c12017-06-16 19:23:30836 AllowJavascript();
837 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
838 GetOven()->StartPilotLight();
839}
840```
841
842<div class="note">
843Relying on the <code>'load'</code> event or browser-side navigation callbacks to
844detect page readiness omits <i>application-specific</i> initialization, and a
845custom <code>'initialized'</code> message is often necessary.
846</div>
847
Rebekah Potter65c7cae2022-12-15 22:19:49848#### WebUIMessageHandler::CallJavascriptFunction()
Dan Beam079d5c12017-06-16 19:23:30849
850When the browser process needs to tell the renderer/JS of an event or otherwise
851execute code, it can use `CallJavascriptFunction()`.
852
853<div class="note">
854Javascript must be <a href="#AllowJavascript">allowed</a> to use
dpapad32c8dd92025-01-22 19:05:16855<code>CallJavascriptFunction()</code>.
Dan Beam079d5c12017-06-16 19:23:30856</div>
857
858```c++
859void OvenHandler::OnPilotLightExtinguished() {
860 CallJavascriptFunction("app.pilotLightExtinguished");
861}
862```
863
864This works by crafting a string to be evaluated in the renderer. Any arguments
865to the call are serialized to JSON and the parameter list is wrapped with
866
dpapad994a9392025-03-25 00:21:43867```c++
Dan Beam079d5c12017-06-16 19:23:30868// See WebUI::GetJavascriptCall() for specifics:
869"functionCallName(" + argumentsAsJson + ")"
870```
871
872and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
873
874While this works, it implies that:
875
876* a global method must exist to successfully run the Javascript request
877* any method can be called with any parameter (far more access than required in
878 practice)
879
880^ These factors have resulted in less use of `CallJavascriptFunction()` in the
881webui codebase. This functionality can easily be accomplished with the following
882alternatives:
883
884* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
885 when an event occurs in C++ and is more loosely coupled (nothing blows up if
886 the event dispatch is ignored). JS subscribes to notifications via
Rebekah Potter952290e2022-11-18 09:07:28887 [`addWebUiListener`](#addWebUiListener).
Dan Beam079d5c12017-06-16 19:23:30888* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
889 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
890 when Javascript requires a response to an inquiry about C++-canonical state
891 (i.e. "Is Autofill enabled?", "Is the user incognito?")
892
Rebekah Potter65c7cae2022-12-15 22:19:49893#### WebUIMessageHandler::FireWebUIListener()
Dan Beam079d5c12017-06-16 19:23:30894
895`FireWebUIListener()` is used to notify a registered set of listeners that an
896event has occurred. This is generally used for events that are not guaranteed to
897happen in timely manner, or may be caused to happen by unpredictable events
898(i.e. user actions).
899
900Here's some example to detect a change to Chrome's theme:
901
902```js
Rebekah Potter952290e2022-11-18 09:07:28903addWebUiListener("theme-changed", refreshThemeStyles);
Dan Beam079d5c12017-06-16 19:23:30904```
905
906This Javascript event listener can be triggered in C++ via:
907
908```c++
909void MyHandler::OnThemeChanged() {
910 FireWebUIListener("theme-changed");
911}
912```
913
914Because it's not clear when a user might want to change their theme nor what
915theme they'll choose, this is a good candidate for an event listener.
916
917If you simply need to get a response in Javascript from C++, consider using
rbpotteracc480cd2022-03-04 08:42:19918[`sendWithPromise()`](#sendWithPromise) and
Dan Beam079d5c12017-06-16 19:23:30919[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
920
Rebekah Potter65c7cae2022-12-15 22:19:49921#### WebUIMessageHandler::OnJavascriptAllowed()
Dan Beam079d5c12017-06-16 19:23:30922
923`OnJavascriptDisallowed()` is a lifecycle method called in response to
924[`AllowJavascript()`](#AllowJavascript). It is a good place to register
925observers of global services or other callbacks that might call at unpredictable
926times.
927
928For example:
929
930```c++
931class MyHandler : public content::WebUIMessageHandler {
932 MyHandler() {
933 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
934 }
935 void OnGlobalServiceEvent() {
936 FireWebUIListener("global-thing-happened");
937 }
938};
939```
940
941Because browser-side C++ handlers are created before a renderer is ready, the
942above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
943before the renderer is ready, which may result in dropped updates or
944accidentally running Javascript in a renderer that has navigated to a new URL.
945
946A safer way to set up communication is:
947
948```c++
949class MyHandler : public content::WebUIMessageHandler {
950 public:
Dan Beam079d5c12017-06-16 19:23:30951 void OnJavascriptAllowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17952 observation_.Observe(GetGlobalService()); // <-- DO THIS.
Dan Beam079d5c12017-06-16 19:23:30953 }
954 void OnJavascriptDisallowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17955 observation_.Reset(); // <-- AND THIS.
Dan Beam079d5c12017-06-16 19:23:30956 }
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17957 base::ScopedObservation<MyHandler, GlobalService> observation_{this}; // <-- ALSO HANDY.
Dan Beam079d5c12017-06-16 19:23:30958```
959when a renderer has been created and the
960document has loaded enough to signal to the C++ that it's ready to respond to
961messages.
962
Rebekah Potter65c7cae2022-12-15 22:19:49963#### WebUIMessageHandler::OnJavascriptDisallowed()
Dan Beam079d5c12017-06-16 19:23:30964
965`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
dpapad32c8dd92025-01-22 19:05:16966it's safe to send JavaScript messages to the renderer.
Dan Beam079d5c12017-06-16 19:23:30967
968There's a number of situations that result in this method being called:
969
970* renderer doesn't exist yet
971* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23972* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30973* tab refresh
974* renderer crash
975
976Though it's possible to programmatically disable Javascript, it's uncommon to
977need to do so.
978
979Because there's no single strategy that works for all cases of a renderer's
980state (i.e. queueing vs dropping messages), these lifecycle methods were
981introduced so a WebUI application can implement these decisions itself.
982
983Often, it makes sense to disconnect from observers in
984`OnJavascriptDisallowed()`:
985
986```c++
987void OvenHandler::OnJavascriptDisallowed() {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17988 scoped_oven_observation_.Reset()
Dan Beam079d5c12017-06-16 19:23:30989}
990```
991
992Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
993`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
994scoped observer that automatically unsubscribes on destruction but can also
995imperatively unsubscribe in `OnJavascriptDisallowed()`.
996
Rebekah Potter65c7cae2022-12-15 22:19:49997#### WebUIMessageHandler::RejectJavascriptCallback()
Dan Beam079d5c12017-06-16 19:23:30998
999This method is called in response to
rbpotteracc480cd2022-03-04 08:42:191000[`sendWithPromise()`](#sendWithPromise) to reject the issued Promise. This
Dan Beam079d5c12017-06-16 19:23:301001runs the rejection (second) callback in the [Promise's
1002executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
1003and any
1004[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
1005callbacks in the chain.
1006
1007```c++
Lei Zhangf48bb60e2022-12-09 17:42:441008void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:231009 AllowJavascript();
1010 if (!GetOven()->HasGas()) {
Lei Zhangf48bb60e2022-12-09 17:42:441011 RejectJavascriptCallback(args[0],
Michael Giuffrida14938292019-05-31 21:30:231012 base::StringValue("need gas to cook the donuts!"));
1013 }
Dan Beam079d5c12017-06-16 19:23:301014```
1015
1016This method is basically just a
1017[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
1018global "cr.webUIResponse" method with a success value of false.
1019
1020```c++
1021// WebUIMessageHandler::RejectJavascriptCallback():
1022CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:181023 response);
Dan Beam079d5c12017-06-16 19:23:301024```
1025
1026See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
1027
Rebekah Potter65c7cae2022-12-15 22:19:491028#### WebUIMessageHandler::ResolveJavascriptCallback()
Dan Beam079d5c12017-06-16 19:23:301029
1030This method is called in response to
rbpotteracc480cd2022-03-04 08:42:191031[`sendWithPromise()`](#sendWithPromise) to fulfill an issued Promise,
Dan Beam079d5c12017-06-16 19:23:301032often with a value. This results in runnings any fulfillment (first) callbacks
1033in the associate Promise executor and any registered
1034[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
1035callbacks.
1036
rbpotteracc480cd2022-03-04 08:42:191037So, given this TypeScript code:
Dan Beam079d5c12017-06-16 19:23:301038
1039```js
Rebekah Potter65c7cae2022-12-15 22:19:491040sendWithPromise('bakeDonuts', [5]).then(function(numDonutsBaked: number) {
Dan Beam079d5c12017-06-16 19:23:301041 shop.donuts += numDonutsBaked;
1042});
1043```
1044
1045Some handling C++ might do this:
1046
1047```c++
Lei Zhangf48bb60e2022-12-09 17:42:441048void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:231049 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:301050 double num_donuts_baked = GetOven()->BakeDonuts();
Lei Zhangf48bb60e2022-12-09 17:42:441051 ResolveJavascriptCallback(args[0], base::Value(num_donuts_baked));
Dan Beam079d5c12017-06-16 19:23:301052}
1053```
1054
Rebekah Potter65c7cae2022-12-15 22:19:491055#### chrome.send()
Dan Beam079d5c12017-06-16 19:23:301056
1057When the JavaScript `window` object is created, a renderer is checked for [WebUI
1058bindings](#bindings).
1059
1060```c++
1061// RenderFrameImpl::DidClearWindowObject():
Avi Drissman78865bbb2024-08-22 20:57:191062if (enabled_bindings_.Has(BindingsPolicyValue::kWebUi))
Dan Beam079d5c12017-06-16 19:23:301063 WebUIExtension::Install(frame_);
1064```
1065
1066If the bindings exist, a global `chrome.send()` function is exposed to the
1067renderer:
1068
1069```c++
1070// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:351071v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:301072chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:181073 gin::CreateFunctionTemplate(
Ayu Ishii33743432021-02-03 19:05:011074 isolate,
1075 base::BindRepeating(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:301076```
1077
1078The `chrome.send()` method takes a message name and argument list.
1079
1080```js
1081chrome.send('messageName', [arg1, arg2, ...]);
1082```
1083
1084The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:371085`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:301086
1087```c++
1088// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:371089render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
1090 frame->GetDocument().Url(),
1091 message, *content));
Dan Beam079d5c12017-06-16 19:23:301092```
1093```c++
1094// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:371095IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:301096```
1097
1098The browser-side code does a map lookup for the message name and calls the found
1099callback with the deserialized arguments:
1100
1101```c++
1102// WebUIImpl::ProcessWebUIMessage():
1103message_callbacks_.find(message)->second.Run(&args);
1104```
1105
Rebekah Potter65c7cae2022-12-15 22:19:491106#### addWebUiListener()
Dan Beam079d5c12017-06-16 19:23:301107
1108WebUI listeners are a convenient way for C++ to inform JavaScript of events.
1109
1110Older WebUI code exposed public methods for event notification, similar to how
1111responses to [chrome.send()](#chrome_send) used to work. They both
Ian Barkley-Yeung4f4f71d2020-06-09 00:38:131112resulted in global namespace pollution, but it was additionally hard to stop
Rebekah Potter952290e2022-11-18 09:07:281113listening for events in some cases. **addWebUiListener** is preferred in new
Dan Beam079d5c12017-06-16 19:23:301114code.
1115
1116Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
rbpotteracc480cd2022-03-04 08:42:191117just like [sendWithPromise()](#sendWithPromise).
1118
Rebekah Potter65c7cae2022-12-15 22:19:491119addWebUiListener can be imported from 'chrome://resources/js/cr.js'.
Dan Beam079d5c12017-06-16 19:23:301120
1121```js
Rebekah Potter952290e2022-11-18 09:07:281122// addWebUiListener():
Dan Beam079d5c12017-06-16 19:23:301123webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
1124webUIListenerMap[eventName][createUid()] = callback;
1125```
1126
1127The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
1128with an event name and a variable number of arguments.
1129
1130```c++
1131// WebUIMessageHandler:
1132template <typename... Values>
1133void FireWebUIListener(const std::string& event_name, const Values&... values) {
1134 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
1135 values...);
1136}
1137```
1138
1139C++ handlers call this `FireWebUIListener` method when an event occurs that
1140should be communicated to the JavaScript running in a tab.
1141
1142```c++
1143void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
Toby Huang97ce1d5d2021-07-13 01:38:581144 FireWebUIListener("donuts-baked", base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:301145}
1146```
1147
rbpotteracc480cd2022-03-04 08:42:191148TypeScript can listen for WebUI events via:
Dan Beam079d5c12017-06-16 19:23:301149
1150```js
rbpotteracc480cd2022-03-04 08:42:191151let donutsReady: number = 0;
Rebekah Potter952290e2022-11-18 09:07:281152addWebUiListener('donuts-baked', function(numFreshlyBakedDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:301153 donutsReady += numFreshlyBakedDonuts;
1154});
1155```
1156
Rebekah Potter65c7cae2022-12-15 22:19:491157#### sendWithPromise()
Dan Beam079d5c12017-06-16 19:23:301158
rbpotteracc480cd2022-03-04 08:42:191159`sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
Dan Beam079d5c12017-06-16 19:23:301160triggering a message requires a response:
1161
1162```js
1163chrome.send('getNumberOfDonuts'); // No easy way to get response!
1164```
1165
1166In older WebUI pages, global methods were exposed simply so responses could be
1167sent. **This is discouraged** as it pollutes the global namespace and is harder
1168to make request specific or do from deeply nested code.
1169
1170In newer WebUI pages, you see code like this:
1171
1172```js
rbpotteracc480cd2022-03-04 08:42:191173sendWithPromise('getNumberOfDonuts').then(function(numDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:301174 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
1175});
1176```
1177
Rebekah Potter952290e2022-11-18 09:07:281178Note that sendWithPromise can be imported from 'chrome://resources/js/cr.js';
rbpotteracc480cd2022-03-04 08:42:191179
Dan Beam079d5c12017-06-16 19:23:301180On the C++ side, the message registration is similar to
1181[`chrome.send()`](#chrome_send) except that the first argument in the
1182message handler's list is a callback ID. That ID is passed to
1183`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
rbpotteracc480cd2022-03-04 08:42:191184JavaScript/TypeScript and calling the `then()` function.
Dan Beam079d5c12017-06-16 19:23:301185
1186```c++
Lei Zhangf48bb60e2022-12-09 17:42:441187void DonutHandler::HandleGetNumberOfDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:231188 AllowJavascript();
1189
Lei Zhangf48bb60e2022-12-09 17:42:441190 const base::Value& callback_id = args[0];
Dan Beam079d5c12017-06-16 19:23:301191 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Toby Huang97ce1d5d2021-07-13 01:38:581192 ResolveJavascriptCallback(callback_id, base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:301193}
1194```
1195
1196Under the covers, a map of `Promise`s are kept in JavaScript.
1197
1198The callback ID is just a namespaced, ever-increasing number. It's used to
1199insert a `Promise` into the JS-side map when created.
1200
1201```js
rbpotteracc480cd2022-03-04 08:42:191202// sendWithPromise():
Dan Beam079d5c12017-06-16 19:23:301203var id = methodName + '_' + uidCounter++;
1204chromeSendResolverMap[id] = new PromiseResolver;
1205chrome.send(methodName, [id].concat(args));
1206```
1207
1208The corresponding number is used to look up a `Promise` and reject or resolve it
1209when the outcome is known.
1210
1211```js
1212// cr.webUIResponse():
1213var resolver = chromeSendResolverMap[id];
1214if (success)
1215 resolver.resolve(response);
1216else
1217 resolver.reject(response);
1218```
1219
1220This approach still relies on the C++ calling a globally exposed method, but
1221reduces the surface to only a single global (`cr.webUIResponse`) instead of
1222many. It also makes per-request responses easier, which is helpful when multiple
1223are in flight.
1224
Lukasz Anforowicz11e59532018-10-23 22:46:211225
1226## Security considerations
1227
1228Because WebUI pages are highly privileged, they are often targets for attack,
1229since taking control of a WebUI page can sometimes be sufficient to escape
1230Chrome's sandbox. To make sure that the special powers granted to WebUI pages
1231are safe, WebUI pages are restricted in what they can do:
1232
Nasko Oskov24fc53c52021-01-08 10:02:361233* WebUI pages cannot embed http/https resources
Lukasz Anforowicz11e59532018-10-23 22:46:211234* WebUI pages cannot issue http/https fetches
1235
1236In the rare case that a WebUI page really needs to include web content, the safe
Nasko Oskov24fc53c52021-01-08 10:02:361237way to do this is by using an `<iframe>` tag. Chrome's security model gives
1238process isolation between the WebUI and the web content. However, some extra
1239precautions need to be taken, because there are properties of the page that are
1240accessible cross-origin and malicious code can take advantage of such data to
1241attack the WebUI. Here are some things to keep in mind:
Lukasz Anforowicz11e59532018-10-23 22:46:211242
Nasko Oskov24fc53c52021-01-08 10:02:361243* The WebUI page can receive postMessage payloads from the web and should
1244 ensure it verifies any messages as they are not trustworthy.
1245* The entire frame tree is visible to the embedded web content, including
1246 ancestor origins.
1247* The web content runs in the same StoragePartition and Profile as the WebUI,
1248 which reflect where the WebUI page was loaded (e.g., the default profile,
1249 Incognito, etc). The corresponding user credentials will thus be available to
1250 the web content inside the WebUI, possibly showing the user as signed in.
Lukasz Anforowicz11e59532018-10-23 22:46:211251
Nasko Oskov24fc53c52021-01-08 10:02:361252Note: WebUIs have a default Content Security Policy which disallows embedding
1253any frames. If you want to include any web content in an <iframe> you will need
1254to update the policy for your WebUI. When doing so, allow only known origins and
1255avoid making the policy more permissive than strictly necessary.
Lukasz Anforowicz11e59532018-10-23 22:46:211256
Nasko Oskov24fc53c52021-01-08 10:02:361257Alternatively, a `<webview>` tag can be used, which runs in a separate
1258StoragePartition, a separate frame tree, and restricts postMessage communication
Alex Moshchuk031f7832023-04-04 16:59:071259by default. Note that `<webview>` is only available on desktop platforms.
Lukasz Anforowicz11e59532018-10-23 22:46:211260
Keren Zhu24f106e62024-12-12 21:06:391261## Error Reporting
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351262
Keren Zhu24f106e62024-12-12 21:06:391263By default, DevTools error messages in a WebUI page will generate error reports
1264and will appear in Google's internal [go/crash](http://go/crash) reports page.
1265These error reports will only be generated for Google Chrome builds, not
1266Chromium or other Chromium-based browsers.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351267
Keren Zhu24f106e62024-12-12 21:06:391268Specifically, an error report will be generated when a WebUI-based chrome://
1269page does one of the following:
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351270* Generates an uncaught exception,
1271* Has a promise which is rejected, and no rejection handler is provided, or
1272* Calls `console.error()`.
Keren Zhu24f106e62024-12-12 21:06:391273* Generates any other [error level](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/mojom/devtools/console_message.mojom;l=11;drc=047c7dc4ee1ce908d7fea38ca063fa2f80f92c77)
1274DevTools message, e.g. bad websocket.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351275
Keren Zhu24f106e62024-12-12 21:06:391276Such errors will appear alongside other crashes on [go/crash](http://go/crash)
1277with a product data type of [JavascriptError](https://crash.corp.google.com/browse?q=product_name%3D%27Chrome_Mac%27+AND+EXISTS+%28SELECT+1+FROM+UNNEST%28productdata%29+WHERE+key%3D%27type%27+AND+value%3D%27JavascriptError%27%29&stbtiq=&reportid=&index=0).
1278They will also appear locally on the chrome://crashes page.
Ian Barkley-Yeung43404622022-03-25 00:00:441279
1280The signature of the error is the error message followed by the URL on which the
1281error appeared. For example, if chrome://settings/lazy_load.js throws a
1282TypeError with a message `Cannot read properties of null (reading 'select')` and
Avi Drissman78865bbb2024-08-22 20:57:191283does not catch it, the magic signature would be
Ian Barkley-Yeung43404622022-03-25 00:00:441284```
1285Uncaught TypeError: Cannot read properties of null (reading 'select') (chrome://settings)
1286```
1287To avoid spamming the system, only one error report with a given message will be
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351288generated per hour.
1289
1290If you are getting error reports for an expected condition, you can turn off the
Ian Barkley-Yeung43404622022-03-25 00:00:441291reports simply by changing `console.error()` into `console.warn()`. For
1292instance, if JavaScript is calling `console.error()` when the user tries to
1293connect to an unavailable WiFi network at the same time the page shows the user
1294an error message, the `console.error()` should be replaced with a
1295`console.warn()`.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351296
1297If you wish to get more control of the JavaScript error messages, for example
1298to change the product name or to add additional data, you may wish to switch to
1299using `CrashReportPrivate.reportError()`. If you do so, be sure to override
1300`WebUIController::IsJavascriptErrorReportingEnabled()` to return false for your
1301page; this will avoid generating redundant error reports.
1302
Ian Barkley-Yeung43404622022-03-25 00:00:441303### Are JavaScript errors actually crashes?
1304JavaScript errors are not "crashes" in the C++ sense. They do not stop a process
1305from running, they do not cause a "sad tab" page. Some tooling refers to them as
1306crashes because they are going through the same pipeline as the C++ crashes, and
1307that pipeline was originally designed to handle crashes.
1308
1309### How much impact does this JavaScript error have?
1310That depends on the JavaScript error. In some cases, the errors have no user
1311impact; for instance, the "unavailable WiFi network calling `console.error()`"
1312example above. In other cases, JavaScript errors may be serious errors that
1313block the user from completing critical user journeys. For example, if the
1314JavaScript is supposed to un-hide one of several variants of settings page, but
1315the JavaScript has an unhandled exception before un-hiding any of them, then
1316the user will see a blank page and be unable to change that setting.
1317
1318Because it is difficult to automatically determine the severity of a given
1319error, JavaScript errors are currently all classified as "WARNING" level when
1320computing stability metrics.
1321
1322### Known issues
Keren Zhu24f106e62024-12-12 21:06:3913231. Android does not support error reporting.
13242. Errors are only reported for chrome:// URLs, except for pages that don't
1325create a WebUI object (chrome://dino and chrome://network-error). Other schemes,
1326e.g. chrome-untrusted:// and chrome-extension:// are not supported either.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:3513273. Unhandled promise rejections do not have a good stack.
13284. The line numbers and column numbers in the stacks are for the minified
1329 JavaScript and do not correspond to the line and column numbers of the
1330 original source files.
13315. Error messages with variable strings do not group well. For example, if the
1332 error message includes the name of a network, each network name will be its
1333 own signature.
1334
Dan Beam079d5c12017-06-16 19:23:301335## See also
1336
dpapad09934792025-01-23 16:59:501337* WebUI's C++ code follows the [Chromium C++ styleguide](../../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:301338* WebUI's HTML/CSS/JS code follows the [Chromium Web
dpapad09934792025-01-23 16:59:501339 Development Style Guide](../../styleguide/web/web.md)
Rebekah Potter1ebb97f2023-10-25 15:38:451340* Adding tests for WebUI pages: [Testing WebUI](./testing_webui.md)
Hubert Chao6f79e2c2024-04-04 14:14:311341* Demo WebUI widgets at `chrome://webui-gallery` (and source at
1342 [chrome/browser/resources/webui_gallery/](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/resources/webui_gallery/))
Dan Beam079d5c12017-06-16 19:23:301343
1344
1345<script>
1346let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
1347let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
1348
1349let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
1350let hrefs = localLinks.map(a => a.href.split('#')[1]);
1351
1352hrefs.forEach(href => {
1353 if (names.includes(href))
1354 console.info('found: ' + href);
1355 else
1356 console.error('broken href: ' + href);
1357})
1358</script>