blob: 97602a8cce3c9a726d70fae134399e5c41e9ace6 [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(
138 GetProcess()->GetID());
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,
224and are re-used where possible between navigations (i.e. refreshing a page).
225Because 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
235`chrome-untrusted://`) that the controller serves.
236
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(
292 source, base::make_span(kHistoryResources, kHistoryResourcesSize),
293 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(
414 base::make_span(kPrintPreviewResources, kPrintPreviewResourcesSize));
415```
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**
515```
516import("//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
589**chrome/browser/chrome_browser_interface_binders.cc**:
590```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**
676```
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:
683 non_web_component_files = [
684 "donuts.ts",
685 "browser_proxy.ts",
686 ]
687
Rebekah Potter65c7cae2022-12-15 22:19:49688 mojo_files_deps =
689 [ "//chrome/browser/ui/webui/donuts:mojo_bindings_ts__generator" ]
690 mojo_files = [
691 "$root_gen_dir/chrome/browser/ui/webui/donuts/donuts.mojom-webui.ts",
692 ]
Rebekah Potter65c7cae2022-12-15 22:19:49693}
694```
695
696It is often helpful to wrap the TypeScript side of Mojo setup in a BrowserProxy
697class:
698
699**chrome/browser/resources/donuts/browser_proxy.ts**
700```js
Mickey Burks463e6f52024-07-02 11:45:05701import {PageCallbackRouter, PageHandlerFactory, PageHandlerRemote} from './donuts.mojom-webui.js';
702import type {PageHandlerInterface} from './donuts.mojom-webui.js';
Rebekah Potter65c7cae2022-12-15 22:19:49703
Mickey Burks463e6f52024-07-02 11:45:05704// Exporting the interface helps when creating a TestBrowserProxy wrapper.
705export interface BrowserProxy {
706 callbackRouter: PageCallbackRouter;
707 handler: PageHandlerInterface;
708}
709
710export class BrowserProxyImpl implements BrowserProxy {
Rebekah Potter65c7cae2022-12-15 22:19:49711 callbackRouter: PageCallbackRouter;
712 handler: PageHandlerInterface;
713
Mickey Burks463e6f52024-07-02 11:45:05714 private constructor() {
Rebekah Potter65c7cae2022-12-15 22:19:49715 this.callbackRouter = new PageCallbackRouter();
Rebekah Potter65c7cae2022-12-15 22:19:49716 this.handler = new PageHandlerRemote();
Mickey Burks463e6f52024-07-02 11:45:05717 PageHandlerFactory.getRemote().createPageHandler(
Rebekah Potter65c7cae2022-12-15 22:19:49718 this.callbackRouter.$.bindNewPipeAndPassRemote(),
719 (this.handler as PageHandlerRemote).$.bindNewPipeAndPassReceiver());
720 }
721
722 static getInstance(): BrowserProxy {
723 return instance || (instance = new BrowserProxy());
724 }
725
Mickey Burks463e6f52024-07-02 11:45:05726 static setInstance(proxy: BrowserProxy) {
727 instance = proxy;
Rebekah Potter65c7cae2022-12-15 22:19:49728 }
729}
730
731let instance: BrowserProxy|null = null;
732```
733
734#### Using TypeScript bindings for communication
735The `callbackRouter` (`PageCallbackRouter`) can be used to add listeners for
736asynchronous events sent from the browser.
737
738The `handler` (`PageHandlerRemote`) can be used to send messages from the
739renderer to the browser. For interface methods that require a browser response,
740calling the method returns a promise. The promise will be resolved with the
741response from the browser.
742
743**chrome/browser/resources/donuts/donuts.ts**
744```js
Mickey Burks463e6f52024-07-02 11:45:05745import {BrowserProxyImpl} from './browser_proxy.js';
Rebekah Potter65c7cae2022-12-15 22:19:49746
747let numDonutsBaked: number = 0;
748
749window.onload = function() {
750 // Other page initialization steps go here
Mickey Burks463e6f52024-07-02 11:45:05751 const proxy = BrowserProxyImpl.getInstance();
Rebekah Potter65c7cae2022-12-15 22:19:49752 // Tells the browser to start the pilot light.
753 proxy.handler.startPilotLight();
754 // Adds a listener for the asynchronous "donutsBaked" event.
755 proxy.callbackRouter.donutsBaked.addListener(
756 (numDonuts: number) => {
757 numDonutsBaked += numDonuts;
758 });
759};
760
761function CheckNumberOfDonuts() {
762 // Requests the number of donuts from the browser, and alerts with the
763 // response.
Mickey Burks463e6f52024-07-02 11:45:05764 BrowserProxyImpl.getInstance().handler.getNumberOfDonuts().then(
Rebekah Potter65c7cae2022-12-15 22:19:49765 (numDonuts: number) => {
766 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
767 });
768}
769
770function BakeDonuts(numDonuts: number) {
771 // Tells the browser to bake |numDonuts| donuts.
Mickey Burks463e6f52024-07-02 11:45:05772 BrowserProxyImpl.getInstance().handler.bakeDonuts(numDonuts);
Rebekah Potter65c7cae2022-12-15 22:19:49773}
774```
775
776### Pre-Mojo alternative: chrome.send()/WebUIMessageHandler
777Most Chrome WebUIs were added before the introduction of Mojo, and use the
778older style WebUIMessageHandler + chrome.send() pattern. The following sections
779detail the methods in WebUIMessageHandler and the corresponding communication
780methods in TypeScript/JavaScript and how to use them.
781
782#### WebUIMessageHandler::AllowJavascript()
Dan Beam079d5c12017-06-16 19:23:30783
Adam Langley81be0732019-03-06 18:38:45784A tab that has been used for settings UI may be reloaded, or may navigate to an
785external origin. In both cases, one does not want callbacks from C++ to
786Javascript to run. In the former case, the callbacks will occur when the
787Javascript doesn't expect them. In the latter case, sensitive information may be
788delivered to an untrusted origin.
789
790Therefore each message handler maintains
791[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
792that describes whether delivering callbacks to Javascript is currently
793appropriate. This boolean is set by calling `AllowJavascript`, which should be
794done when handling a call from Javascript, because that indicates that the page
795is ready for the subsequent callback. (See
796[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
797If the tab navigates or reloads,
798[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
799is called to clear the flag.
800
801Therefore, before each callback from C++ to Javascript, the flag must be tested
802by calling
803[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
804If false, then the callback must be dropped. (When the flag is false, calling
805[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
806will crash. See
807[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
808
809Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
810the case where an asynchronous operation is started, the settings page is
811reloaded, and the user triggers another operation using the original message
812handler. The `javascript_allowed_` boolean will be true, but the original
813callback should still be dropped because it relates to a operation that was
814discarded by the reload. (Reloading settings UI does _not_ cause message handler
815objects to be deleted.)
816
817Thus a message handler may override
818[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
819to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30820
821In the JS:
822
823```js
824window.onload = function() {
825 app.initialize();
826 chrome.send('startPilotLight');
827};
828```
829
830In the C++:
831
832```c++
Lei Zhangf48bb60e2022-12-09 17:42:44833void OvenHandler::HandleStartPilotLight(const base::Value::List& /*args*/) {
Dan Beam079d5c12017-06-16 19:23:30834 AllowJavascript();
835 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
836 GetOven()->StartPilotLight();
837}
838```
839
840<div class="note">
841Relying on the <code>'load'</code> event or browser-side navigation callbacks to
842detect page readiness omits <i>application-specific</i> initialization, and a
843custom <code>'initialized'</code> message is often necessary.
844</div>
845
Rebekah Potter65c7cae2022-12-15 22:19:49846#### WebUIMessageHandler::CallJavascriptFunction()
Dan Beam079d5c12017-06-16 19:23:30847
848When the browser process needs to tell the renderer/JS of an event or otherwise
849execute code, it can use `CallJavascriptFunction()`.
850
851<div class="note">
852Javascript must be <a href="#AllowJavascript">allowed</a> to use
853<code>CallJavscriptFunction()</code>.
854</div>
855
856```c++
857void OvenHandler::OnPilotLightExtinguished() {
858 CallJavascriptFunction("app.pilotLightExtinguished");
859}
860```
861
862This works by crafting a string to be evaluated in the renderer. Any arguments
863to the call are serialized to JSON and the parameter list is wrapped with
864
865```
866// See WebUI::GetJavascriptCall() for specifics:
867"functionCallName(" + argumentsAsJson + ")"
868```
869
870and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
871
872While this works, it implies that:
873
874* a global method must exist to successfully run the Javascript request
875* any method can be called with any parameter (far more access than required in
876 practice)
877
878^ These factors have resulted in less use of `CallJavascriptFunction()` in the
879webui codebase. This functionality can easily be accomplished with the following
880alternatives:
881
882* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
883 when an event occurs in C++ and is more loosely coupled (nothing blows up if
884 the event dispatch is ignored). JS subscribes to notifications via
Rebekah Potter952290e2022-11-18 09:07:28885 [`addWebUiListener`](#addWebUiListener).
Dan Beam079d5c12017-06-16 19:23:30886* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
887 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
888 when Javascript requires a response to an inquiry about C++-canonical state
889 (i.e. "Is Autofill enabled?", "Is the user incognito?")
890
Rebekah Potter65c7cae2022-12-15 22:19:49891#### WebUIMessageHandler::FireWebUIListener()
Dan Beam079d5c12017-06-16 19:23:30892
893`FireWebUIListener()` is used to notify a registered set of listeners that an
894event has occurred. This is generally used for events that are not guaranteed to
895happen in timely manner, or may be caused to happen by unpredictable events
896(i.e. user actions).
897
898Here's some example to detect a change to Chrome's theme:
899
900```js
Rebekah Potter952290e2022-11-18 09:07:28901addWebUiListener("theme-changed", refreshThemeStyles);
Dan Beam079d5c12017-06-16 19:23:30902```
903
904This Javascript event listener can be triggered in C++ via:
905
906```c++
907void MyHandler::OnThemeChanged() {
908 FireWebUIListener("theme-changed");
909}
910```
911
912Because it's not clear when a user might want to change their theme nor what
913theme they'll choose, this is a good candidate for an event listener.
914
915If you simply need to get a response in Javascript from C++, consider using
rbpotteracc480cd2022-03-04 08:42:19916[`sendWithPromise()`](#sendWithPromise) and
Dan Beam079d5c12017-06-16 19:23:30917[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
918
Rebekah Potter65c7cae2022-12-15 22:19:49919#### WebUIMessageHandler::OnJavascriptAllowed()
Dan Beam079d5c12017-06-16 19:23:30920
921`OnJavascriptDisallowed()` is a lifecycle method called in response to
922[`AllowJavascript()`](#AllowJavascript). It is a good place to register
923observers of global services or other callbacks that might call at unpredictable
924times.
925
926For example:
927
928```c++
929class MyHandler : public content::WebUIMessageHandler {
930 MyHandler() {
931 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
932 }
933 void OnGlobalServiceEvent() {
934 FireWebUIListener("global-thing-happened");
935 }
936};
937```
938
939Because browser-side C++ handlers are created before a renderer is ready, the
940above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
941before the renderer is ready, which may result in dropped updates or
942accidentally running Javascript in a renderer that has navigated to a new URL.
943
944A safer way to set up communication is:
945
946```c++
947class MyHandler : public content::WebUIMessageHandler {
948 public:
Dan Beam079d5c12017-06-16 19:23:30949 void OnJavascriptAllowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17950 observation_.Observe(GetGlobalService()); // <-- DO THIS.
Dan Beam079d5c12017-06-16 19:23:30951 }
952 void OnJavascriptDisallowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17953 observation_.Reset(); // <-- AND THIS.
Dan Beam079d5c12017-06-16 19:23:30954 }
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17955 base::ScopedObservation<MyHandler, GlobalService> observation_{this}; // <-- ALSO HANDY.
Dan Beam079d5c12017-06-16 19:23:30956```
957when a renderer has been created and the
958document has loaded enough to signal to the C++ that it's ready to respond to
959messages.
960
Rebekah Potter65c7cae2022-12-15 22:19:49961#### WebUIMessageHandler::OnJavascriptDisallowed()
Dan Beam079d5c12017-06-16 19:23:30962
963`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
964it's safe to send JavaScript messsages to the renderer.
965
966There's a number of situations that result in this method being called:
967
968* renderer doesn't exist yet
969* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23970* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30971* tab refresh
972* renderer crash
973
974Though it's possible to programmatically disable Javascript, it's uncommon to
975need to do so.
976
977Because there's no single strategy that works for all cases of a renderer's
978state (i.e. queueing vs dropping messages), these lifecycle methods were
979introduced so a WebUI application can implement these decisions itself.
980
981Often, it makes sense to disconnect from observers in
982`OnJavascriptDisallowed()`:
983
984```c++
985void OvenHandler::OnJavascriptDisallowed() {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17986 scoped_oven_observation_.Reset()
Dan Beam079d5c12017-06-16 19:23:30987}
988```
989
990Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
991`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
992scoped observer that automatically unsubscribes on destruction but can also
993imperatively unsubscribe in `OnJavascriptDisallowed()`.
994
Rebekah Potter65c7cae2022-12-15 22:19:49995#### WebUIMessageHandler::RejectJavascriptCallback()
Dan Beam079d5c12017-06-16 19:23:30996
997This method is called in response to
rbpotteracc480cd2022-03-04 08:42:19998[`sendWithPromise()`](#sendWithPromise) to reject the issued Promise. This
Dan Beam079d5c12017-06-16 19:23:30999runs the rejection (second) callback in the [Promise's
1000executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
1001and any
1002[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
1003callbacks in the chain.
1004
1005```c++
Lei Zhangf48bb60e2022-12-09 17:42:441006void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:231007 AllowJavascript();
1008 if (!GetOven()->HasGas()) {
Lei Zhangf48bb60e2022-12-09 17:42:441009 RejectJavascriptCallback(args[0],
Michael Giuffrida14938292019-05-31 21:30:231010 base::StringValue("need gas to cook the donuts!"));
1011 }
Dan Beam079d5c12017-06-16 19:23:301012```
1013
1014This method is basically just a
1015[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
1016global "cr.webUIResponse" method with a success value of false.
1017
1018```c++
1019// WebUIMessageHandler::RejectJavascriptCallback():
1020CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:181021 response);
Dan Beam079d5c12017-06-16 19:23:301022```
1023
1024See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
1025
Rebekah Potter65c7cae2022-12-15 22:19:491026#### WebUIMessageHandler::ResolveJavascriptCallback()
Dan Beam079d5c12017-06-16 19:23:301027
1028This method is called in response to
rbpotteracc480cd2022-03-04 08:42:191029[`sendWithPromise()`](#sendWithPromise) to fulfill an issued Promise,
Dan Beam079d5c12017-06-16 19:23:301030often with a value. This results in runnings any fulfillment (first) callbacks
1031in the associate Promise executor and any registered
1032[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
1033callbacks.
1034
rbpotteracc480cd2022-03-04 08:42:191035So, given this TypeScript code:
Dan Beam079d5c12017-06-16 19:23:301036
1037```js
Rebekah Potter65c7cae2022-12-15 22:19:491038sendWithPromise('bakeDonuts', [5]).then(function(numDonutsBaked: number) {
Dan Beam079d5c12017-06-16 19:23:301039 shop.donuts += numDonutsBaked;
1040});
1041```
1042
1043Some handling C++ might do this:
1044
1045```c++
Lei Zhangf48bb60e2022-12-09 17:42:441046void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:231047 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:301048 double num_donuts_baked = GetOven()->BakeDonuts();
Lei Zhangf48bb60e2022-12-09 17:42:441049 ResolveJavascriptCallback(args[0], base::Value(num_donuts_baked));
Dan Beam079d5c12017-06-16 19:23:301050}
1051```
1052
Rebekah Potter65c7cae2022-12-15 22:19:491053#### chrome.send()
Dan Beam079d5c12017-06-16 19:23:301054
1055When the JavaScript `window` object is created, a renderer is checked for [WebUI
1056bindings](#bindings).
1057
1058```c++
1059// RenderFrameImpl::DidClearWindowObject():
Avi Drissman78865bbb2024-08-22 20:57:191060if (enabled_bindings_.Has(BindingsPolicyValue::kWebUi))
Dan Beam079d5c12017-06-16 19:23:301061 WebUIExtension::Install(frame_);
1062```
1063
1064If the bindings exist, a global `chrome.send()` function is exposed to the
1065renderer:
1066
1067```c++
1068// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:351069v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:301070chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:181071 gin::CreateFunctionTemplate(
Ayu Ishii33743432021-02-03 19:05:011072 isolate,
1073 base::BindRepeating(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:301074```
1075
1076The `chrome.send()` method takes a message name and argument list.
1077
1078```js
1079chrome.send('messageName', [arg1, arg2, ...]);
1080```
1081
1082The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:371083`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:301084
1085```c++
1086// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:371087render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
1088 frame->GetDocument().Url(),
1089 message, *content));
Dan Beam079d5c12017-06-16 19:23:301090```
1091```c++
1092// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:371093IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:301094```
1095
1096The browser-side code does a map lookup for the message name and calls the found
1097callback with the deserialized arguments:
1098
1099```c++
1100// WebUIImpl::ProcessWebUIMessage():
1101message_callbacks_.find(message)->second.Run(&args);
1102```
1103
Rebekah Potter65c7cae2022-12-15 22:19:491104#### addWebUiListener()
Dan Beam079d5c12017-06-16 19:23:301105
1106WebUI listeners are a convenient way for C++ to inform JavaScript of events.
1107
1108Older WebUI code exposed public methods for event notification, similar to how
1109responses to [chrome.send()](#chrome_send) used to work. They both
Ian Barkley-Yeung4f4f71d2020-06-09 00:38:131110resulted in global namespace pollution, but it was additionally hard to stop
Rebekah Potter952290e2022-11-18 09:07:281111listening for events in some cases. **addWebUiListener** is preferred in new
Dan Beam079d5c12017-06-16 19:23:301112code.
1113
1114Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
rbpotteracc480cd2022-03-04 08:42:191115just like [sendWithPromise()](#sendWithPromise).
1116
Rebekah Potter65c7cae2022-12-15 22:19:491117addWebUiListener can be imported from 'chrome://resources/js/cr.js'.
Dan Beam079d5c12017-06-16 19:23:301118
1119```js
Rebekah Potter952290e2022-11-18 09:07:281120// addWebUiListener():
Dan Beam079d5c12017-06-16 19:23:301121webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
1122webUIListenerMap[eventName][createUid()] = callback;
1123```
1124
1125The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
1126with an event name and a variable number of arguments.
1127
1128```c++
1129// WebUIMessageHandler:
1130template <typename... Values>
1131void FireWebUIListener(const std::string& event_name, const Values&... values) {
1132 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
1133 values...);
1134}
1135```
1136
1137C++ handlers call this `FireWebUIListener` method when an event occurs that
1138should be communicated to the JavaScript running in a tab.
1139
1140```c++
1141void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
Toby Huang97ce1d5d2021-07-13 01:38:581142 FireWebUIListener("donuts-baked", base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:301143}
1144```
1145
rbpotteracc480cd2022-03-04 08:42:191146TypeScript can listen for WebUI events via:
Dan Beam079d5c12017-06-16 19:23:301147
1148```js
rbpotteracc480cd2022-03-04 08:42:191149let donutsReady: number = 0;
Rebekah Potter952290e2022-11-18 09:07:281150addWebUiListener('donuts-baked', function(numFreshlyBakedDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:301151 donutsReady += numFreshlyBakedDonuts;
1152});
1153```
1154
Rebekah Potter65c7cae2022-12-15 22:19:491155#### sendWithPromise()
Dan Beam079d5c12017-06-16 19:23:301156
rbpotteracc480cd2022-03-04 08:42:191157`sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
Dan Beam079d5c12017-06-16 19:23:301158triggering a message requires a response:
1159
1160```js
1161chrome.send('getNumberOfDonuts'); // No easy way to get response!
1162```
1163
1164In older WebUI pages, global methods were exposed simply so responses could be
1165sent. **This is discouraged** as it pollutes the global namespace and is harder
1166to make request specific or do from deeply nested code.
1167
1168In newer WebUI pages, you see code like this:
1169
1170```js
rbpotteracc480cd2022-03-04 08:42:191171sendWithPromise('getNumberOfDonuts').then(function(numDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:301172 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
1173});
1174```
1175
Rebekah Potter952290e2022-11-18 09:07:281176Note that sendWithPromise can be imported from 'chrome://resources/js/cr.js';
rbpotteracc480cd2022-03-04 08:42:191177
Dan Beam079d5c12017-06-16 19:23:301178On the C++ side, the message registration is similar to
1179[`chrome.send()`](#chrome_send) except that the first argument in the
1180message handler's list is a callback ID. That ID is passed to
1181`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
rbpotteracc480cd2022-03-04 08:42:191182JavaScript/TypeScript and calling the `then()` function.
Dan Beam079d5c12017-06-16 19:23:301183
1184```c++
Lei Zhangf48bb60e2022-12-09 17:42:441185void DonutHandler::HandleGetNumberOfDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:231186 AllowJavascript();
1187
Lei Zhangf48bb60e2022-12-09 17:42:441188 const base::Value& callback_id = args[0];
Dan Beam079d5c12017-06-16 19:23:301189 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Toby Huang97ce1d5d2021-07-13 01:38:581190 ResolveJavascriptCallback(callback_id, base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:301191}
1192```
1193
1194Under the covers, a map of `Promise`s are kept in JavaScript.
1195
1196The callback ID is just a namespaced, ever-increasing number. It's used to
1197insert a `Promise` into the JS-side map when created.
1198
1199```js
rbpotteracc480cd2022-03-04 08:42:191200// sendWithPromise():
Dan Beam079d5c12017-06-16 19:23:301201var id = methodName + '_' + uidCounter++;
1202chromeSendResolverMap[id] = new PromiseResolver;
1203chrome.send(methodName, [id].concat(args));
1204```
1205
1206The corresponding number is used to look up a `Promise` and reject or resolve it
1207when the outcome is known.
1208
1209```js
1210// cr.webUIResponse():
1211var resolver = chromeSendResolverMap[id];
1212if (success)
1213 resolver.resolve(response);
1214else
1215 resolver.reject(response);
1216```
1217
1218This approach still relies on the C++ calling a globally exposed method, but
1219reduces the surface to only a single global (`cr.webUIResponse`) instead of
1220many. It also makes per-request responses easier, which is helpful when multiple
1221are in flight.
1222
Lukasz Anforowicz11e59532018-10-23 22:46:211223
1224## Security considerations
1225
1226Because WebUI pages are highly privileged, they are often targets for attack,
1227since taking control of a WebUI page can sometimes be sufficient to escape
1228Chrome's sandbox. To make sure that the special powers granted to WebUI pages
1229are safe, WebUI pages are restricted in what they can do:
1230
Nasko Oskov24fc53c52021-01-08 10:02:361231* WebUI pages cannot embed http/https resources
Lukasz Anforowicz11e59532018-10-23 22:46:211232* WebUI pages cannot issue http/https fetches
1233
1234In the rare case that a WebUI page really needs to include web content, the safe
Nasko Oskov24fc53c52021-01-08 10:02:361235way to do this is by using an `<iframe>` tag. Chrome's security model gives
1236process isolation between the WebUI and the web content. However, some extra
1237precautions need to be taken, because there are properties of the page that are
1238accessible cross-origin and malicious code can take advantage of such data to
1239attack the WebUI. Here are some things to keep in mind:
Lukasz Anforowicz11e59532018-10-23 22:46:211240
Nasko Oskov24fc53c52021-01-08 10:02:361241* The WebUI page can receive postMessage payloads from the web and should
1242 ensure it verifies any messages as they are not trustworthy.
1243* The entire frame tree is visible to the embedded web content, including
1244 ancestor origins.
1245* The web content runs in the same StoragePartition and Profile as the WebUI,
1246 which reflect where the WebUI page was loaded (e.g., the default profile,
1247 Incognito, etc). The corresponding user credentials will thus be available to
1248 the web content inside the WebUI, possibly showing the user as signed in.
Lukasz Anforowicz11e59532018-10-23 22:46:211249
Nasko Oskov24fc53c52021-01-08 10:02:361250Note: WebUIs have a default Content Security Policy which disallows embedding
1251any frames. If you want to include any web content in an <iframe> you will need
1252to update the policy for your WebUI. When doing so, allow only known origins and
1253avoid making the policy more permissive than strictly necessary.
Lukasz Anforowicz11e59532018-10-23 22:46:211254
Nasko Oskov24fc53c52021-01-08 10:02:361255Alternatively, a `<webview>` tag can be used, which runs in a separate
1256StoragePartition, a separate frame tree, and restricts postMessage communication
Alex Moshchuk031f7832023-04-04 16:59:071257by default. Note that `<webview>` is only available on desktop platforms.
Lukasz Anforowicz11e59532018-10-23 22:46:211258
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351259## JavaScript Error Reporting
1260
Ian Barkley-Yeung43404622022-03-25 00:00:441261By default, errors in the JavaScript or TypeScript of a WebUI page will generate
1262error reports which appear in Google's internal [go/crash](http://go/crash)
1263reports page. These error reports will only be generated for Google Chrome
1264builds, not Chromium or other Chromium-based browsers.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351265
Ian Barkley-Yeung43404622022-03-25 00:00:441266Specifically, an error report will be generated when the JavaScript or
1267TypeScript for a WebUI-based chrome:// page does one of the following:
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351268* Generates an uncaught exception,
1269* Has a promise which is rejected, and no rejection handler is provided, or
1270* Calls `console.error()`.
1271
1272Such errors will appear alongside other crashes in the
Ian Barkley-Yeung43404622022-03-25 00:00:441273`product_name=Chrome_ChromeOS`, `product_name=Chrome_Lacros`, or
1274`product_name=Chrome_Linux` lists on [go/crash](http://go/crash).
1275
1276The signature of the error is the error message followed by the URL on which the
1277error appeared. For example, if chrome://settings/lazy_load.js throws a
1278TypeError with a message `Cannot read properties of null (reading 'select')` and
Avi Drissman78865bbb2024-08-22 20:57:191279does not catch it, the magic signature would be
Ian Barkley-Yeung43404622022-03-25 00:00:441280```
1281Uncaught TypeError: Cannot read properties of null (reading 'select') (chrome://settings)
1282```
1283To avoid spamming the system, only one error report with a given message will be
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351284generated per hour.
1285
1286If you are getting error reports for an expected condition, you can turn off the
Ian Barkley-Yeung43404622022-03-25 00:00:441287reports simply by changing `console.error()` into `console.warn()`. For
1288instance, if JavaScript is calling `console.error()` when the user tries to
1289connect to an unavailable WiFi network at the same time the page shows the user
1290an error message, the `console.error()` should be replaced with a
1291`console.warn()`.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351292
1293If you wish to get more control of the JavaScript error messages, for example
1294to change the product name or to add additional data, you may wish to switch to
1295using `CrashReportPrivate.reportError()`. If you do so, be sure to override
1296`WebUIController::IsJavascriptErrorReportingEnabled()` to return false for your
1297page; this will avoid generating redundant error reports.
1298
Ian Barkley-Yeung43404622022-03-25 00:00:441299### Are JavaScript errors actually crashes?
1300JavaScript errors are not "crashes" in the C++ sense. They do not stop a process
1301from running, they do not cause a "sad tab" page. Some tooling refers to them as
1302crashes because they are going through the same pipeline as the C++ crashes, and
1303that pipeline was originally designed to handle crashes.
1304
1305### How much impact does this JavaScript error have?
1306That depends on the JavaScript error. In some cases, the errors have no user
1307impact; for instance, the "unavailable WiFi network calling `console.error()`"
1308example above. In other cases, JavaScript errors may be serious errors that
1309block the user from completing critical user journeys. For example, if the
1310JavaScript is supposed to un-hide one of several variants of settings page, but
1311the JavaScript has an unhandled exception before un-hiding any of them, then
1312the user will see a blank page and be unable to change that setting.
1313
1314Because it is difficult to automatically determine the severity of a given
1315error, JavaScript errors are currently all classified as "WARNING" level when
1316computing stability metrics.
1317
1318### Known issues
13191. Error reporting is currently enabled only on ChromeOS (ash and Lacros) and
1320 Linux.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:3513212. Errors are only reported for chrome:// URLs.
13223. Unhandled promise rejections do not have a good stack.
13234. The line numbers and column numbers in the stacks are for the minified
1324 JavaScript and do not correspond to the line and column numbers of the
1325 original source files.
13265. Error messages with variable strings do not group well. For example, if the
1327 error message includes the name of a network, each network name will be its
1328 own signature.
1329
Dan Beam079d5c12017-06-16 19:23:301330## See also
1331
Amos Limf916d572018-05-21 23:10:351332* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:301333* WebUI's HTML/CSS/JS code follows the [Chromium Web
1334 Development Style Guide](../styleguide/web/web.md)
Rebekah Potter1ebb97f2023-10-25 15:38:451335* Adding tests for WebUI pages: [Testing WebUI](./testing_webui.md)
Hubert Chao6f79e2c2024-04-04 14:14:311336* Demo WebUI widgets at `chrome://webui-gallery` (and source at
1337 [chrome/browser/resources/webui_gallery/](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/resources/webui_gallery/))
Dan Beam079d5c12017-06-16 19:23:301338
1339
1340<script>
1341let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
1342let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
1343
1344let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
1345let hrefs = localLinks.map(a => a.href.split('#')[1]);
1346
1347hrefs.forEach(href => {
1348 if (names.includes(href))
1349 console.info('found: ' + href);
1350 else
1351 console.error('broken href: ' + href);
1352})
1353</script>