blob: bbde551f2124b7be376e41fc51962cb44985b2c8 [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():
136if (bindings_flags & BINDINGS_POLICY_WEB_UI) {
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
Dan Beam079d5c12017-06-16 19:23:30145Here's an example:
146
147```c++
Rebekah Potter26b32f42023-10-31 20:36:47148// Config for chrome://donuts
149DonutsUIConfig::DonutsUIConfig()
150 : WebUIConfig(content::kChromeUIScheme, chrome::kChromeUIDonutsHost) {}
151
152DonutsUIConfig::~DonutsUIConfig() = default;
153
154std::unique_ptr<content::WebUIController>
155DonutsUIConfig::CreateWebUIController(content::WebUI* web_ui,
156 const GURL& url) {
157 return std::make_unique<DonutsUI>(web_ui);
158}
159
Dan Beam079d5c12017-06-16 19:23:30160// Controller for chrome://donuts.
161class DonutsUI : public content::WebUIController {
162 public:
163 DonutsUI(content::WebUI* web_ui) : content::WebUIController(web_ui) {
164 content::WebUIDataSource* source =
Rebekah Pottera8949422023-01-05 18:44:13165 content::WebUIDataSource::CreateAndAdd(
166 web_ui->GetWebContents()->GetBrowserContext(),
167 "donuts"); // "donuts" == hostname
Dan Beam079d5c12017-06-16 19:23:30168 source->AddString("mmmDonuts", "Mmm, donuts!"); // Translations.
rbpotterf50e0252020-09-14 16:38:33169 source->AddResourcePath("", IDR_DONUTS_HTML); // Home page.
Dan Beam079d5c12017-06-16 19:23:30170
171 // Handles messages from JavaScript to C++ via chrome.send().
Jeremy Romane0760a402018-03-02 18:19:40172 web_ui->AddMessageHandler(std::make_unique<OvenHandler>());
Dan Beam079d5c12017-06-16 19:23:30173 }
174};
175```
176
177If we assume the contents of `IDR_DONUTS_HTML` yields:
178
179```html
180<h1>$i18n{mmmDonuts}</h1>
181```
182
183Visiting `chrome://donuts` should show in something like:
184
185<div style="border: 1px solid black; padding: 10px;">
186<h1>Mmmm, donuts!</h1>
187</div>
188
189Delicious success.
190
Christopher Lam50ab1e92019-10-29 04:33:16191By default $i18n{} escapes strings for HTML. $i18nRaw{} can be used for
192translations that embed HTML, and $i18nPolymer{} can be used for Polymer
193bindings. See
194[this comment](https://bugs.chromium.org/p/chromium/issues/detail?id=1010815#c1)
195for more information.
196
Dan Beam079d5c12017-06-16 19:23:30197## C++ classes
198
199### WebUI
200
201`WebUI` is a high-level class and pretty much all HTML-based Chrome UIs have
202one. `WebUI` lives in the browser process, and is owned by a `RenderFrameHost`.
203`WebUI`s have a concrete implementation (`WebUIImpl`) in `content/` and are
204created in response to navigation events.
205
206A `WebUI` knows very little about the page it's showing, and it owns a
207[`WebUIController`](#WebUIController) that is set after creation based on the
208hostname of a requested URL.
209
210A `WebUI` *can* handle messages itself, but often defers these duties to
211separate [`WebUIMessageHandler`](#WebUIMessageHandler)s, which are generally
212designed for handling messages on certain topics.
213
214A `WebUI` can be created speculatively, and are generally fairly lightweight.
215Heavier duty stuff like hard initialization logic or accessing services that may
216have side effects are more commonly done in a
217[`WebUIController`](#WebUIController) or
218[`WebUIMessageHandler`s](#WebUIMessageHandler).
219
220`WebUI` are created synchronously on the UI thread in response to a URL request,
221and are re-used where possible between navigations (i.e. refreshing a page).
222Because they run in a separate process and can exist before a corresponding
223renderer process has been created, special care is required to communicate with
224the renderer if reliable message passing is required.
225
Rebekah Potter26b32f42023-10-31 20:36:47226### WebUIConfig
227A `WebUIConfig` contains minimal possible logic and information for determining
228whether a certain subclass of `WebUIController` should be created for a given
229URL.
230
231A `WebUIConfig` holds information about the host and scheme (`chrome://` or
232`chrome-untrusted://`) that the controller serves.
233
234A `WebUIConfig` may contain logic to check if the WebUI is enabled for a given
235`BrowserContext` and url (e.g., if relevant feature flags are enabled/disabled,
236if the url path is valid, etc).
237
238A `WebUIConfig` can invoke the `WebUIController`'s constructor in its
239`CreateWebUIControllerForURL` method.
240
241`WebUIConfig`s are created at startup when factories are registered, so should
242be lightweight.
243
Dan Beam079d5c12017-06-16 19:23:30244### WebUIController
245
246A `WebUIController` is the brains of the operation, and is responsible for
247application-specific logic, setting up translations and resources, creating
248message handlers, and potentially responding to requests dynamically. In complex
249pages, logic is often split across multiple
250[`WebUIMessageHandler`s](#WebUIMessageHandler) instead of solely in the
251controller for organizational benefits.
252
253A `WebUIController` is owned by a [`WebUI`](#WebUI), and is created and set on
Rebekah Potter26b32f42023-10-31 20:36:47254an existing [`WebUI`](#WebUI) when the corresponding `WebUIConfig` is found in
255the map matching the URL, or when the correct controller is determined via URL
256inspection in `ChromeWebUIControllerFactory`. (i.e. chrome://settings creates
257a generic [`WebUI`](#WebUI) with a settings-specific `WebUIController`).
Dan Beam079d5c12017-06-16 19:23:30258
259### WebUIDataSource
260
rbpotterf50e0252020-09-14 16:38:33261The `WebUIDataSource` class provides a place for data to live for WebUI pages.
262
263Examples types of data stored in this class are:
264
265* static resources (i.e. .html files packed into bundles and pulled off of disk)
266* translations
267* dynamic feature values (i.e. whether a feature is enabled)
268
269Data sources are set up in the browser process (in C++) and are accessed by
270loading URLs from the renderer.
271
272Below is an example of a simple data source (in this case, Chrome's history
273page):
274
275```c++
Rebekah Pottera8949422023-01-05 18:44:13276content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
277 Profile::FromWebUI(web_ui), "history");
rbpotterf50e0252020-09-14 16:38:33278
279source->AddResourcePath("sign_in_promo.svg", IDR_HISTORY_SIGN_IN_PROMO_SVG);
280source->AddResourcePath("synced_tabs.html", IDR_HISTORY_SYNCED_TABS_HTML);
281
282source->AddString("title", IDS_HISTORY_TITLE);
283source->AddString("moreFromThisSite", IDS_HISTORY_MORE_FROM_THIS_SITE);
284
285source->AddBoolean("showDateRanges",
286 base::FeatureList::IsEnabled(features::kHistoryShowDateRanges));
287
288webui::SetupWebUIDataSource(
289 source, base::make_span(kHistoryResources, kHistoryResourcesSize),
290 kGeneratedPath, IDR_HISTORY_HISTORY_HTML);
rbpotterf50e0252020-09-14 16:38:33291```
292
293For more about each of the methods called on `WebUIDataSource` and the utility
294method that performs additional configuration, see [DataSources](#DataSources)
295and [WebUIDataSourceUtils](#WebUIDataSourceUtils)
296
Dan Beam079d5c12017-06-16 19:23:30297### WebUIMessageHandler
298
299Because some pages have many messages or share code that sends messages, message
300handling is often split into discrete classes called `WebUIMessageHandler`s.
301These handlers respond to specific invocations from JavaScript.
302
303So, the given C++ code:
304
305```c++
306void OvenHandler::RegisterMessages() {
Ayu Ishii33743432021-02-03 19:05:01307 web_ui()->RegisterMessageCallback(
308 "bakeDonuts",
309 base::BindRepeating(&OvenHandler::HandleBakeDonuts,
310 base::Unretained(this)));
Dan Beam079d5c12017-06-16 19:23:30311}
312
Moe Ahmadide5901862022-02-25 21:56:23313void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:23314 AllowJavascript();
315
Lei Zhang72347ebdd2021-11-16 16:40:02316 // IMPORTANT: Fully validate `args`.
cammie720e8acd2021-08-25 19:15:45317 CHECK_EQ(1u, args.size());
Lei Zhang72347ebdd2021-11-16 16:40:02318 int num_donuts = args[0].GetInt();
319 CHECK_GT(num_donuts, 0);
320 GetOven()->BakeDonuts(num_donuts);
Dan Beam079d5c12017-06-16 19:23:30321}
322```
323
324Can be triggered in JavaScript with this example code:
325
326```js
327$('bakeDonutsButton').onclick = function() {
328 chrome.send('bakeDonuts', [5]); // bake 5 donuts!
329};
330```
331
rbpotterf50e0252020-09-14 16:38:33332## Data Sources
333
Rebekah Pottera8949422023-01-05 18:44:13334### WebUIDataSource::CreateAndAdd()
rbpotterf50e0252020-09-14 16:38:33335
Rebekah Pottera8949422023-01-05 18:44:13336This is a factory method required to create and add a WebUIDataSource. The first
337argument to `Create()` is the browser context. The second argument is typically
338the host name of the page. The caller does not own the result.
rbpotterf50e0252020-09-14 16:38:33339
Rebekah Pottera8949422023-01-05 18:44:13340Additionally, calling `CreateAndAdd()` will overwrite any existing data source
rbpotterf50e0252020-09-14 16:38:33341with the same name.
342
343<div class="note">
344It's unsafe to keep references to a <code>WebUIDataSource</code> after calling
345<code>Add()</code>. Don't do this.
346</div>
347
rbpotterf50e0252020-09-14 16:38:33348### WebUIDataSource::AddLocalizedString()
349
350Using an int reference to a grit string (starts with "IDS" and lives in a .grd
351or .grdp file), adding a string with a key name will be possible to reference
352via the `$i18n{}` syntax (and will be replaced when requested) or later
353dynamically in JavaScript via `loadTimeData.getString()` (or `getStringF`).
354
Lei Zhang5b205082022-01-25 18:08:38355### WebUIDataSource::AddLocalizedStrings()
356
357Many Web UI data sources need to be set up with a large number of localized
358strings. Instead of repeatedly calling <code>AddLocalizedString()</code>, create
359an array of all the strings and use <code>AddLocalizedStrings()</code>:
360
361```c++
362 static constexpr webui::LocalizedString kStrings[] = {
363 // Localized strings (alphabetical order).
364 {"actionMenuDescription", IDS_HISTORY_ACTION_MENU_DESCRIPTION},
365 {"ariaRoleDescription", IDS_HISTORY_ARIA_ROLE_DESCRIPTION},
366 {"bookmarked", IDS_HISTORY_ENTRY_BOOKMARKED},
367 };
368 source->AddLocalizedStrings(kStrings);
369```
370
rbpotterf50e0252020-09-14 16:38:33371### WebUIDataSource::AddResourcePath()
372
373Using an int reference to a grit resource (starts with "IDR" and lives in a .grd
374or .grdp file), adds a resource to the UI with the specified path.
375
376It's generally a good idea to call <code>AddResourcePath()</code> with the empty
377path and a resource ID that should be served as the "catch all" resource to
378respond with. This resource will be served for requests like "chrome://history",
379or "chrome://history/pathThatDoesNotExist". It will not be served for requests
380that look like they are attempting to fetch a specific file, like
381"chrome://history/file\_that\_does\_not\_exist.js". This is so that if a user
382enters a typo when trying to load a subpage like "chrome://history/syncedTabs"
383they will be redirected to the main history page, instead of seeing an error,
384but incorrect imports in the source code will fail, so that they can be more
385easily found and corrected.
386
Lei Zhang5b205082022-01-25 18:08:38387### WebUIDataSource::AddResourcePaths()
388
389Similar to the localized strings, many Web UIs need to add a large number of
390resource paths. In this case, use <code>AddResourcePaths()</code> to
391replace repeated calls to <code>AddResourcePath()</code>.
392
393```c++
394 static constexpr webui::ResourcePath kResources[] = {
395 {"browser_api.js", IDR_BROWSER_API_JS},
396 {"constants.js", IDR_CONSTANTS_JS},
397 {"controller.js", IDR_CONTROLLER_JS},
398 };
399 source->AddResourcePaths(kResources);
400```
401
402The same method can be leveraged for cases that directly use constants defined
403by autogenerated grit resources map header files. For example, the autogenerated
404print\_preview\_resources\_map.h header defines a
405<code>webui::ResourcePath</code> array named <code>kPrintPreviewResources</code>
406and a <code>size\_t kPrintPreviewResourcesSize</code>. All the resources in this
407resource map can be added as follows:
408
409```c++
410 source->AddResourcePaths(
411 base::make_span(kPrintPreviewResources, kPrintPreviewResourcesSize));
412```
413
rbpotterf50e0252020-09-14 16:38:33414### WebUIDataSource::AddBoolean()
415
416Often a page needs to know whether a feature is enabled. This is a good use case
417for `WebUIDataSource::AddBoolean()`. Then, in the Javascript, one can write
418code like this:
419
420```js
421if (loadTimeData.getBoolean('myFeatureIsEnabled')) {
422 ...
423}
424```
425
426<div class="note">
427Data sources are not recreated on refresh, and therefore values that are dynamic
428(i.e. that can change while Chrome is running) may easily become stale. It may
rbpotteracc480cd2022-03-04 08:42:19429be preferable to use <code>sendWithPromise()</code> to initialize dynamic
rbpotterf50e0252020-09-14 16:38:33430values and call <code>FireWebUIListener()</code> to update them.
431
432If you really want or need to use <code>AddBoolean()</code> for a dynamic value,
433make sure to call <code>WebUIDataSource::Update()</code> when the value changes.
434</div>
435
rbpotterf50e0252020-09-14 16:38:33436## WebUI utils for working with data sources
437
438chrome/browser/ui/webui/webui\_util.\* contains a number of methods to simplify
439common configuration tasks.
440
Rebekah Potter5691cab2020-10-29 21:30:35441### webui::SetupWebUIDataSource()
rbpotterf50e0252020-09-14 16:38:33442
Rebekah Potter5691cab2020-10-29 21:30:35443This method performs common configuration tasks on a data source for a Web UI
444that uses JS modules. When creating a Web UI that uses JS modules, use this
445utility instead of duplicating the configuration steps it performs elsewhere.
446Specific setup steps include:
rbpotterf50e0252020-09-14 16:38:33447
448* Setting the content security policy to allow the data source to load only
449 resources from its own host (e.g. chrome://history), chrome://resources, and
Rebekah Potter1ebb97f2023-10-25 15:38:45450 chrome://webui-test (used to serve test files).
rbpotterf50e0252020-09-14 16:38:33451* Enabling i18n template replacements by calling <code>UseStringsJs()</code> and
452 <code>EnableReplaceI18nInJS()</code> on the data source.
453* Adding the test loader files to the data source, so that test files can be
454 loaded as JS modules.
455* Setting the resource to load for the empty path.
Rebekah Potter5691cab2020-10-29 21:30:35456* Adding all resources from a GritResourceMap.
rbpotterf50e0252020-09-14 16:38:33457
Rebekah Potter65c7cae2022-12-15 22:19:49458## Browser (C++) and Renderer (JS) communication
Dan Beam079d5c12017-06-16 19:23:30459
Rebekah Potter65c7cae2022-12-15 22:19:49460### Mojo
461
462[Mojo](https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md)
463is used for IPC throughout Chromium, and should generally be used for new
464WebUIs to communicate between the browser (C++) and the renderer (JS/TS). To
465use Mojo, you will need to:
466
467* Write an interface definition for the JS/C++ interface in a mojom file
468* Add a build target in the BUILD.gn file to autogenerate C++ and TypeScript
469 code ("bindings").
470* Bind the interface on the C++ side and implement any methods to send or
471 receive information from TypeScript.
472* Add the TypeScript bindings file to your WebUI's <code>ts_library()</code>
473 and use them in your TypeScript code.
474
475#### Mojo Interface Definition
476Mojo interfaces are declared in mojom files. For WebUIs, these normally live
477alongside the C++ code in chrome/browser/ui/webui. For example:
478
479**chrome/browser/ui/webui/donuts/donuts.mojom**
480```
481module donuts.mojom;
482
Rebekah Potter26b32f42023-10-31 20:36:47483// Factory ensures that the Page and PageHandler interfaces are always created
484// together without requiring an initialization call from the WebUI to the
485// handler.
Rebekah Potter65c7cae2022-12-15 22:19:49486interface PageHandlerFactory {
487 CreatePageHandler(pending_remote<Page> page,
488 pending_receiver<PageHandler> handler);
489};
490
491// Called from TS side of chrome://donuts (Renderer -> Browser)
492interface PageHandler {
493 StartPilotLight();
494
495 BakeDonuts(uint32 num_donuts);
496
497 // Expects a response from the browser.
498 GetNumberOfDonuts() => (uint32 num_donuts);
Kevin Graney1a0030f2023-10-24 23:31:17499};
Rebekah Potter65c7cae2022-12-15 22:19:49500
501// Called from C++ side of chrome://donuts. (Browser -> Renderer)
502interface Page {
503 DonutsBaked(uint32 num_donuts);
Kevin Graney1a0030f2023-10-24 23:31:17504};
Rebekah Potter65c7cae2022-12-15 22:19:49505```
506
507#### BUILD.gn mojo target
508mojom() is the build rule used to generate mojo bindings. It can be set up as
509follows:
510
511**chrome/browser/ui/webui/donuts/BUILD.gn**
512```
513import("//mojo/public/tools/bindings/mojom.gni")
514
515mojom("mojo_bindings") {
516 sources = [ "donuts.mojom" ]
517 webui_module_path = "/"
Rebekah Potter65c7cae2022-12-15 22:19:49518}
519```
520
521#### Setting up C++ bindings
522The WebUIController class should inherit from ui::MojoWebUIController and
523from the PageHandlerFactory class defined in the mojom file.
524
525**chrome/browser/ui/webui/donuts/donuts_ui.h**
526```c++
527class DonutsPageHandler;
528
529class DonutsUI : public ui::MojoWebUIController,
530 public donuts::mojom::PageHandlerFactory {
531 public:
532 explicit DonutsUI(content::WebUI* web_ui);
533
534 DonutsUI(const DonutsUI&) = delete;
535 DonutsUI& operator=(const DonutsUI&) = delete;
536
537 ~DonutsUI() override;
538
539 // Instantiates the implementor of the mojom::PageHandlerFactory mojo
540 // interface passing the pending receiver that will be internally bound.
541 void BindInterface(
542 mojo::PendingReceiver<donuts::mojom::PageHandlerFactory> receiver);
543
544 private:
545 // donuts::mojom::PageHandlerFactory:
546 void CreatePageHandler(
547 mojo::PendingRemote<donuts::mojom::Page> page,
548 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver) override;
549
550 std::unique_ptr<DonutsPageHandler> page_handler_;
551
552 mojo::Receiver<donuts::mojom::PageHandlerFactory> page_factory_receiver_{
553 this};
554
555 WEB_UI_CONTROLLER_TYPE_DECL();
556};
557```
558
559**chrome/browser/ui/webui/donuts/donuts_ui.cc**
560```c++
561DonutsUI::DonutsUI(content::WebUI* web_ui)
562 : ui::MojoWebUIController(web_ui, true) {
563 // Normal constructor steps (e.g. setting up data source) go here.
564}
565
566WEB_UI_CONTROLLER_TYPE_IMPL(DonutsUI)
567
568DonutsUI::~DonutsUI() = default;
569
570void DonutsUI::BindInterface(
571 mojo::PendingReceiver<donuts::mojom::PageHandlerFactory> receiver) {
572 page_factory_receiver_.reset();
573 page_factory_receiver_.Bind(std::move(receiver));
574}
575
576void DonutsUI::CreatePageHandler(
577 mojo::PendingRemote<donuts::mojom::Page> page,
578 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver) {
579 DCHECK(page);
580 page_handler_ = std::make_unique<DonutsPageHandler>(
581 std::move(receiver), std::move(page));
582}
583```
584
585You also need to register the PageHandlerFactory to your controller in
586**chrome/browser/chrome_browser_interface_binders.cc**:
587```c++
588RegisterWebUIControllerInterfaceBinder<donuts::mojom::PageHandlerFactory,
589 DonutsUI>(map);
590```
591
592#### Using C++ bindings for communication
593The WebUI message handler should inherit from the Mojo PageHandler class.
594
595**chrome/browser/ui/webui/donuts/donuts_page_handler.h**
596```c++
597#include "chrome/browser/ui/webui/donuts/donuts.mojom.h"
598#include "mojo/public/cpp/bindings/receiver.h"
599#include "mojo/public/cpp/bindings/remote.h"
600
601class DonutsPageHandler : public donuts::mojom::PageHandler {
602 public:
603 DonutsPageHandler(
604 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver,
605 mojo::PendingRemote<donuts::mojom::Page> page);
606
607 DonutsPageHandler(const DonutsPageHandler&) = delete;
608 DonutsPageHandler& operator=(const DonutsPageHandler&) = delete;
609
610 ~DonutsPageHandler() override;
611
612 // Triggered by some outside event
613 void DonutsPageHandler::OnBakingDonutsFinished(uint32_t num_donuts);
614
615 // donuts::mojom::PageHandler:
616 void StartPilotLight() override;
617 void BakeDonuts(uint32_t num_donuts) override;
618 void GetNumberOfDonuts(GetNumberOfDonutsCallback callback) override;
619}
620```
621
622The message handler needs to implement all the methods on the PageHandler
623interface.
624
625**chrome/browser/ui/webui/donuts/donuts_page_handler.cc**
626```c++
627DonutsPageHandler::DonutsPageHandler(
628 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver,
629 mojo::PendingRemote<donuts::mojom::Page> page)
630 : receiver_(this, std::move(receiver)),
631 page_(std::move(page)) {
632}
633
634DonutsPageHandler::~DonutsPageHandler() {
635 GetOven()->TurnOffGas();
636}
637
638// Triggered by outside asynchronous event; sends information to the renderer.
639void DonutsPageHandler::OnBakingDonutsFinished(uint32_t num_donuts) {
640 page_->DonutsBaked(num_donuts);
641}
642
643// Triggered by startPilotLight() call in TS.
644void DonutsPageHandler::StartPilotLight() {
645 GetOven()->StartPilotLight();
646}
647
648// Triggered by bakeDonuts() call in TS.
649void DonutsPageHandler::BakeDonuts(int32_t num_donuts) {
650 GetOven()->BakeDonuts();
651}
652
653// Triggered by getNumberOfDonuts() call in TS; sends a response back to the
654// renderer.
655void DonutsPageHandler::GetNumberOfDonuts(GetNumberOfDonutsCallback callback) {
656 uint32_t result = GetOven()->GetNumberOfDonuts();
657 std::move(callback).Run(result);
658}
659```
660
661#### Setting Up TypeScript bindings
662
663For WebUIs using the `build_webui()` rule, the TypeScript mojo bindings can be
664added to the build and served from the root (e.g.
665`chrome://donuts/donuts.mojom-webui.js`) by adding the following arguments to
666`build_webui()`:
667
668**chrome/browser/resources/donuts/BUILD.gn**
669```
670build_webui("build") {
671 # ... Other arguments go here
672 mojo_files_deps =
673 [ "//chrome/browser/ui/webui/donuts:mojo_bindings_ts__generator" ]
674 mojo_files = [
675 "$root_gen_dir/chrome/browser/ui/webui/donuts/donuts.mojom-webui.ts",
676 ]
677 # ... Other arguments can go here
678}
679```
680
681It is often helpful to wrap the TypeScript side of Mojo setup in a BrowserProxy
682class:
683
684**chrome/browser/resources/donuts/browser_proxy.ts**
685```js
686import {PageCallbackRouter, PageHandlerFactory, PageHandlerInterface, PageHandlerRemote} from './donuts.mojom-webui.js';
687
688class BrowserProxy {
689 callbackRouter: PageCallbackRouter;
690 handler: PageHandlerInterface;
691
692 constructor() {
693 this.callbackRouter = new PageCallbackRouter();
694
695 this.handler = new PageHandlerRemote();
696
697 const factory = PageHandlerFactory.getRemote();
698 factory.createPageHandler(
699 this.callbackRouter.$.bindNewPipeAndPassRemote(),
700 (this.handler as PageHandlerRemote).$.bindNewPipeAndPassReceiver());
701 }
702
703 static getInstance(): BrowserProxy {
704 return instance || (instance = new BrowserProxy());
705 }
706
707 static setInstance(obj: BrowserProxy) {
708 instance = obj;
709 }
710}
711
712let instance: BrowserProxy|null = null;
713```
714
715#### Using TypeScript bindings for communication
716The `callbackRouter` (`PageCallbackRouter`) can be used to add listeners for
717asynchronous events sent from the browser.
718
719The `handler` (`PageHandlerRemote`) can be used to send messages from the
720renderer to the browser. For interface methods that require a browser response,
721calling the method returns a promise. The promise will be resolved with the
722response from the browser.
723
724**chrome/browser/resources/donuts/donuts.ts**
725```js
726import {BrowserProxy} from './browser_proxy.js';
727
728let numDonutsBaked: number = 0;
729
730window.onload = function() {
731 // Other page initialization steps go here
732 const proxy = BrowserProxy.getInstance();
733 // Tells the browser to start the pilot light.
734 proxy.handler.startPilotLight();
735 // Adds a listener for the asynchronous "donutsBaked" event.
736 proxy.callbackRouter.donutsBaked.addListener(
737 (numDonuts: number) => {
738 numDonutsBaked += numDonuts;
739 });
740};
741
742function CheckNumberOfDonuts() {
743 // Requests the number of donuts from the browser, and alerts with the
744 // response.
745 BrowserProxy.getInstance().handler.getNumberOfDonuts().then(
746 (numDonuts: number) => {
747 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
748 });
749}
750
751function BakeDonuts(numDonuts: number) {
752 // Tells the browser to bake |numDonuts| donuts.
753 BrowserProxy.getInstance().handler.bakeDonuts(numDonuts);
754}
755```
756
757### Pre-Mojo alternative: chrome.send()/WebUIMessageHandler
758Most Chrome WebUIs were added before the introduction of Mojo, and use the
759older style WebUIMessageHandler + chrome.send() pattern. The following sections
760detail the methods in WebUIMessageHandler and the corresponding communication
761methods in TypeScript/JavaScript and how to use them.
762
763#### WebUIMessageHandler::AllowJavascript()
Dan Beam079d5c12017-06-16 19:23:30764
Adam Langley81be0732019-03-06 18:38:45765A tab that has been used for settings UI may be reloaded, or may navigate to an
766external origin. In both cases, one does not want callbacks from C++ to
767Javascript to run. In the former case, the callbacks will occur when the
768Javascript doesn't expect them. In the latter case, sensitive information may be
769delivered to an untrusted origin.
770
771Therefore each message handler maintains
772[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
773that describes whether delivering callbacks to Javascript is currently
774appropriate. This boolean is set by calling `AllowJavascript`, which should be
775done when handling a call from Javascript, because that indicates that the page
776is ready for the subsequent callback. (See
777[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
778If the tab navigates or reloads,
779[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
780is called to clear the flag.
781
782Therefore, before each callback from C++ to Javascript, the flag must be tested
783by calling
784[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
785If false, then the callback must be dropped. (When the flag is false, calling
786[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
787will crash. See
788[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
789
790Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
791the case where an asynchronous operation is started, the settings page is
792reloaded, and the user triggers another operation using the original message
793handler. The `javascript_allowed_` boolean will be true, but the original
794callback should still be dropped because it relates to a operation that was
795discarded by the reload. (Reloading settings UI does _not_ cause message handler
796objects to be deleted.)
797
798Thus a message handler may override
799[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
800to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30801
802In the JS:
803
804```js
805window.onload = function() {
806 app.initialize();
807 chrome.send('startPilotLight');
808};
809```
810
811In the C++:
812
813```c++
Lei Zhangf48bb60e2022-12-09 17:42:44814void OvenHandler::HandleStartPilotLight(const base::Value::List& /*args*/) {
Dan Beam079d5c12017-06-16 19:23:30815 AllowJavascript();
816 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
817 GetOven()->StartPilotLight();
818}
819```
820
821<div class="note">
822Relying on the <code>'load'</code> event or browser-side navigation callbacks to
823detect page readiness omits <i>application-specific</i> initialization, and a
824custom <code>'initialized'</code> message is often necessary.
825</div>
826
Rebekah Potter65c7cae2022-12-15 22:19:49827#### WebUIMessageHandler::CallJavascriptFunction()
Dan Beam079d5c12017-06-16 19:23:30828
829When the browser process needs to tell the renderer/JS of an event or otherwise
830execute code, it can use `CallJavascriptFunction()`.
831
832<div class="note">
833Javascript must be <a href="#AllowJavascript">allowed</a> to use
834<code>CallJavscriptFunction()</code>.
835</div>
836
837```c++
838void OvenHandler::OnPilotLightExtinguished() {
839 CallJavascriptFunction("app.pilotLightExtinguished");
840}
841```
842
843This works by crafting a string to be evaluated in the renderer. Any arguments
844to the call are serialized to JSON and the parameter list is wrapped with
845
846```
847// See WebUI::GetJavascriptCall() for specifics:
848"functionCallName(" + argumentsAsJson + ")"
849```
850
851and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
852
853While this works, it implies that:
854
855* a global method must exist to successfully run the Javascript request
856* any method can be called with any parameter (far more access than required in
857 practice)
858
859^ These factors have resulted in less use of `CallJavascriptFunction()` in the
860webui codebase. This functionality can easily be accomplished with the following
861alternatives:
862
863* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
864 when an event occurs in C++ and is more loosely coupled (nothing blows up if
865 the event dispatch is ignored). JS subscribes to notifications via
Rebekah Potter952290e2022-11-18 09:07:28866 [`addWebUiListener`](#addWebUiListener).
Dan Beam079d5c12017-06-16 19:23:30867* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
868 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
869 when Javascript requires a response to an inquiry about C++-canonical state
870 (i.e. "Is Autofill enabled?", "Is the user incognito?")
871
Rebekah Potter65c7cae2022-12-15 22:19:49872#### WebUIMessageHandler::FireWebUIListener()
Dan Beam079d5c12017-06-16 19:23:30873
874`FireWebUIListener()` is used to notify a registered set of listeners that an
875event has occurred. This is generally used for events that are not guaranteed to
876happen in timely manner, or may be caused to happen by unpredictable events
877(i.e. user actions).
878
879Here's some example to detect a change to Chrome's theme:
880
881```js
Rebekah Potter952290e2022-11-18 09:07:28882addWebUiListener("theme-changed", refreshThemeStyles);
Dan Beam079d5c12017-06-16 19:23:30883```
884
885This Javascript event listener can be triggered in C++ via:
886
887```c++
888void MyHandler::OnThemeChanged() {
889 FireWebUIListener("theme-changed");
890}
891```
892
893Because it's not clear when a user might want to change their theme nor what
894theme they'll choose, this is a good candidate for an event listener.
895
896If you simply need to get a response in Javascript from C++, consider using
rbpotteracc480cd2022-03-04 08:42:19897[`sendWithPromise()`](#sendWithPromise) and
Dan Beam079d5c12017-06-16 19:23:30898[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
899
Rebekah Potter65c7cae2022-12-15 22:19:49900#### WebUIMessageHandler::OnJavascriptAllowed()
Dan Beam079d5c12017-06-16 19:23:30901
902`OnJavascriptDisallowed()` is a lifecycle method called in response to
903[`AllowJavascript()`](#AllowJavascript). It is a good place to register
904observers of global services or other callbacks that might call at unpredictable
905times.
906
907For example:
908
909```c++
910class MyHandler : public content::WebUIMessageHandler {
911 MyHandler() {
912 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
913 }
914 void OnGlobalServiceEvent() {
915 FireWebUIListener("global-thing-happened");
916 }
917};
918```
919
920Because browser-side C++ handlers are created before a renderer is ready, the
921above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
922before the renderer is ready, which may result in dropped updates or
923accidentally running Javascript in a renderer that has navigated to a new URL.
924
925A safer way to set up communication is:
926
927```c++
928class MyHandler : public content::WebUIMessageHandler {
929 public:
Dan Beam079d5c12017-06-16 19:23:30930 void OnJavascriptAllowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17931 observation_.Observe(GetGlobalService()); // <-- DO THIS.
Dan Beam079d5c12017-06-16 19:23:30932 }
933 void OnJavascriptDisallowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17934 observation_.Reset(); // <-- AND THIS.
Dan Beam079d5c12017-06-16 19:23:30935 }
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17936 base::ScopedObservation<MyHandler, GlobalService> observation_{this}; // <-- ALSO HANDY.
Dan Beam079d5c12017-06-16 19:23:30937```
938when a renderer has been created and the
939document has loaded enough to signal to the C++ that it's ready to respond to
940messages.
941
Rebekah Potter65c7cae2022-12-15 22:19:49942#### WebUIMessageHandler::OnJavascriptDisallowed()
Dan Beam079d5c12017-06-16 19:23:30943
944`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
945it's safe to send JavaScript messsages to the renderer.
946
947There's a number of situations that result in this method being called:
948
949* renderer doesn't exist yet
950* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23951* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30952* tab refresh
953* renderer crash
954
955Though it's possible to programmatically disable Javascript, it's uncommon to
956need to do so.
957
958Because there's no single strategy that works for all cases of a renderer's
959state (i.e. queueing vs dropping messages), these lifecycle methods were
960introduced so a WebUI application can implement these decisions itself.
961
962Often, it makes sense to disconnect from observers in
963`OnJavascriptDisallowed()`:
964
965```c++
966void OvenHandler::OnJavascriptDisallowed() {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17967 scoped_oven_observation_.Reset()
Dan Beam079d5c12017-06-16 19:23:30968}
969```
970
971Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
972`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
973scoped observer that automatically unsubscribes on destruction but can also
974imperatively unsubscribe in `OnJavascriptDisallowed()`.
975
Rebekah Potter65c7cae2022-12-15 22:19:49976#### WebUIMessageHandler::RejectJavascriptCallback()
Dan Beam079d5c12017-06-16 19:23:30977
978This method is called in response to
rbpotteracc480cd2022-03-04 08:42:19979[`sendWithPromise()`](#sendWithPromise) to reject the issued Promise. This
Dan Beam079d5c12017-06-16 19:23:30980runs the rejection (second) callback in the [Promise's
981executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
982and any
983[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
984callbacks in the chain.
985
986```c++
Lei Zhangf48bb60e2022-12-09 17:42:44987void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:23988 AllowJavascript();
989 if (!GetOven()->HasGas()) {
Lei Zhangf48bb60e2022-12-09 17:42:44990 RejectJavascriptCallback(args[0],
Michael Giuffrida14938292019-05-31 21:30:23991 base::StringValue("need gas to cook the donuts!"));
992 }
Dan Beam079d5c12017-06-16 19:23:30993```
994
995This method is basically just a
996[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
997global "cr.webUIResponse" method with a success value of false.
998
999```c++
1000// WebUIMessageHandler::RejectJavascriptCallback():
1001CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:181002 response);
Dan Beam079d5c12017-06-16 19:23:301003```
1004
1005See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
1006
Rebekah Potter65c7cae2022-12-15 22:19:491007#### WebUIMessageHandler::ResolveJavascriptCallback()
Dan Beam079d5c12017-06-16 19:23:301008
1009This method is called in response to
rbpotteracc480cd2022-03-04 08:42:191010[`sendWithPromise()`](#sendWithPromise) to fulfill an issued Promise,
Dan Beam079d5c12017-06-16 19:23:301011often with a value. This results in runnings any fulfillment (first) callbacks
1012in the associate Promise executor and any registered
1013[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
1014callbacks.
1015
rbpotteracc480cd2022-03-04 08:42:191016So, given this TypeScript code:
Dan Beam079d5c12017-06-16 19:23:301017
1018```js
Rebekah Potter65c7cae2022-12-15 22:19:491019sendWithPromise('bakeDonuts', [5]).then(function(numDonutsBaked: number) {
Dan Beam079d5c12017-06-16 19:23:301020 shop.donuts += numDonutsBaked;
1021});
1022```
1023
1024Some handling C++ might do this:
1025
1026```c++
Lei Zhangf48bb60e2022-12-09 17:42:441027void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:231028 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:301029 double num_donuts_baked = GetOven()->BakeDonuts();
Lei Zhangf48bb60e2022-12-09 17:42:441030 ResolveJavascriptCallback(args[0], base::Value(num_donuts_baked));
Dan Beam079d5c12017-06-16 19:23:301031}
1032```
1033
Rebekah Potter65c7cae2022-12-15 22:19:491034#### chrome.send()
Dan Beam079d5c12017-06-16 19:23:301035
1036When the JavaScript `window` object is created, a renderer is checked for [WebUI
1037bindings](#bindings).
1038
1039```c++
1040// RenderFrameImpl::DidClearWindowObject():
1041if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
1042 WebUIExtension::Install(frame_);
1043```
1044
1045If the bindings exist, a global `chrome.send()` function is exposed to the
1046renderer:
1047
1048```c++
1049// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:351050v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:301051chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:181052 gin::CreateFunctionTemplate(
Ayu Ishii33743432021-02-03 19:05:011053 isolate,
1054 base::BindRepeating(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:301055```
1056
1057The `chrome.send()` method takes a message name and argument list.
1058
1059```js
1060chrome.send('messageName', [arg1, arg2, ...]);
1061```
1062
1063The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:371064`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:301065
1066```c++
1067// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:371068render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
1069 frame->GetDocument().Url(),
1070 message, *content));
Dan Beam079d5c12017-06-16 19:23:301071```
1072```c++
1073// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:371074IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:301075```
1076
1077The browser-side code does a map lookup for the message name and calls the found
1078callback with the deserialized arguments:
1079
1080```c++
1081// WebUIImpl::ProcessWebUIMessage():
1082message_callbacks_.find(message)->second.Run(&args);
1083```
1084
Rebekah Potter65c7cae2022-12-15 22:19:491085#### addWebUiListener()
Dan Beam079d5c12017-06-16 19:23:301086
1087WebUI listeners are a convenient way for C++ to inform JavaScript of events.
1088
1089Older WebUI code exposed public methods for event notification, similar to how
1090responses to [chrome.send()](#chrome_send) used to work. They both
Ian Barkley-Yeung4f4f71d2020-06-09 00:38:131091resulted in global namespace pollution, but it was additionally hard to stop
Rebekah Potter952290e2022-11-18 09:07:281092listening for events in some cases. **addWebUiListener** is preferred in new
Dan Beam079d5c12017-06-16 19:23:301093code.
1094
1095Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
rbpotteracc480cd2022-03-04 08:42:191096just like [sendWithPromise()](#sendWithPromise).
1097
Rebekah Potter65c7cae2022-12-15 22:19:491098addWebUiListener can be imported from 'chrome://resources/js/cr.js'.
Dan Beam079d5c12017-06-16 19:23:301099
1100```js
Rebekah Potter952290e2022-11-18 09:07:281101// addWebUiListener():
Dan Beam079d5c12017-06-16 19:23:301102webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
1103webUIListenerMap[eventName][createUid()] = callback;
1104```
1105
1106The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
1107with an event name and a variable number of arguments.
1108
1109```c++
1110// WebUIMessageHandler:
1111template <typename... Values>
1112void FireWebUIListener(const std::string& event_name, const Values&... values) {
1113 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
1114 values...);
1115}
1116```
1117
1118C++ handlers call this `FireWebUIListener` method when an event occurs that
1119should be communicated to the JavaScript running in a tab.
1120
1121```c++
1122void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
Toby Huang97ce1d5d2021-07-13 01:38:581123 FireWebUIListener("donuts-baked", base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:301124}
1125```
1126
rbpotteracc480cd2022-03-04 08:42:191127TypeScript can listen for WebUI events via:
Dan Beam079d5c12017-06-16 19:23:301128
1129```js
rbpotteracc480cd2022-03-04 08:42:191130let donutsReady: number = 0;
Rebekah Potter952290e2022-11-18 09:07:281131addWebUiListener('donuts-baked', function(numFreshlyBakedDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:301132 donutsReady += numFreshlyBakedDonuts;
1133});
1134```
1135
Rebekah Potter65c7cae2022-12-15 22:19:491136#### sendWithPromise()
Dan Beam079d5c12017-06-16 19:23:301137
rbpotteracc480cd2022-03-04 08:42:191138`sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
Dan Beam079d5c12017-06-16 19:23:301139triggering a message requires a response:
1140
1141```js
1142chrome.send('getNumberOfDonuts'); // No easy way to get response!
1143```
1144
1145In older WebUI pages, global methods were exposed simply so responses could be
1146sent. **This is discouraged** as it pollutes the global namespace and is harder
1147to make request specific or do from deeply nested code.
1148
1149In newer WebUI pages, you see code like this:
1150
1151```js
rbpotteracc480cd2022-03-04 08:42:191152sendWithPromise('getNumberOfDonuts').then(function(numDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:301153 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
1154});
1155```
1156
Rebekah Potter952290e2022-11-18 09:07:281157Note that sendWithPromise can be imported from 'chrome://resources/js/cr.js';
rbpotteracc480cd2022-03-04 08:42:191158
Dan Beam079d5c12017-06-16 19:23:301159On the C++ side, the message registration is similar to
1160[`chrome.send()`](#chrome_send) except that the first argument in the
1161message handler's list is a callback ID. That ID is passed to
1162`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
rbpotteracc480cd2022-03-04 08:42:191163JavaScript/TypeScript and calling the `then()` function.
Dan Beam079d5c12017-06-16 19:23:301164
1165```c++
Lei Zhangf48bb60e2022-12-09 17:42:441166void DonutHandler::HandleGetNumberOfDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:231167 AllowJavascript();
1168
Lei Zhangf48bb60e2022-12-09 17:42:441169 const base::Value& callback_id = args[0];
Dan Beam079d5c12017-06-16 19:23:301170 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Toby Huang97ce1d5d2021-07-13 01:38:581171 ResolveJavascriptCallback(callback_id, base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:301172}
1173```
1174
1175Under the covers, a map of `Promise`s are kept in JavaScript.
1176
1177The callback ID is just a namespaced, ever-increasing number. It's used to
1178insert a `Promise` into the JS-side map when created.
1179
1180```js
rbpotteracc480cd2022-03-04 08:42:191181// sendWithPromise():
Dan Beam079d5c12017-06-16 19:23:301182var id = methodName + '_' + uidCounter++;
1183chromeSendResolverMap[id] = new PromiseResolver;
1184chrome.send(methodName, [id].concat(args));
1185```
1186
1187The corresponding number is used to look up a `Promise` and reject or resolve it
1188when the outcome is known.
1189
1190```js
1191// cr.webUIResponse():
1192var resolver = chromeSendResolverMap[id];
1193if (success)
1194 resolver.resolve(response);
1195else
1196 resolver.reject(response);
1197```
1198
1199This approach still relies on the C++ calling a globally exposed method, but
1200reduces the surface to only a single global (`cr.webUIResponse`) instead of
1201many. It also makes per-request responses easier, which is helpful when multiple
1202are in flight.
1203
Lukasz Anforowicz11e59532018-10-23 22:46:211204
1205## Security considerations
1206
1207Because WebUI pages are highly privileged, they are often targets for attack,
1208since taking control of a WebUI page can sometimes be sufficient to escape
1209Chrome's sandbox. To make sure that the special powers granted to WebUI pages
1210are safe, WebUI pages are restricted in what they can do:
1211
Nasko Oskov24fc53c52021-01-08 10:02:361212* WebUI pages cannot embed http/https resources
Lukasz Anforowicz11e59532018-10-23 22:46:211213* WebUI pages cannot issue http/https fetches
1214
1215In the rare case that a WebUI page really needs to include web content, the safe
Nasko Oskov24fc53c52021-01-08 10:02:361216way to do this is by using an `<iframe>` tag. Chrome's security model gives
1217process isolation between the WebUI and the web content. However, some extra
1218precautions need to be taken, because there are properties of the page that are
1219accessible cross-origin and malicious code can take advantage of such data to
1220attack the WebUI. Here are some things to keep in mind:
Lukasz Anforowicz11e59532018-10-23 22:46:211221
Nasko Oskov24fc53c52021-01-08 10:02:361222* The WebUI page can receive postMessage payloads from the web and should
1223 ensure it verifies any messages as they are not trustworthy.
1224* The entire frame tree is visible to the embedded web content, including
1225 ancestor origins.
1226* The web content runs in the same StoragePartition and Profile as the WebUI,
1227 which reflect where the WebUI page was loaded (e.g., the default profile,
1228 Incognito, etc). The corresponding user credentials will thus be available to
1229 the web content inside the WebUI, possibly showing the user as signed in.
Lukasz Anforowicz11e59532018-10-23 22:46:211230
Nasko Oskov24fc53c52021-01-08 10:02:361231Note: WebUIs have a default Content Security Policy which disallows embedding
1232any frames. If you want to include any web content in an <iframe> you will need
1233to update the policy for your WebUI. When doing so, allow only known origins and
1234avoid making the policy more permissive than strictly necessary.
Lukasz Anforowicz11e59532018-10-23 22:46:211235
Nasko Oskov24fc53c52021-01-08 10:02:361236Alternatively, a `<webview>` tag can be used, which runs in a separate
1237StoragePartition, a separate frame tree, and restricts postMessage communication
Alex Moshchuk031f7832023-04-04 16:59:071238by default. Note that `<webview>` is only available on desktop platforms.
Lukasz Anforowicz11e59532018-10-23 22:46:211239
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351240## JavaScript Error Reporting
1241
Ian Barkley-Yeung43404622022-03-25 00:00:441242By default, errors in the JavaScript or TypeScript of a WebUI page will generate
1243error reports which appear in Google's internal [go/crash](http://go/crash)
1244reports page. These error reports will only be generated for Google Chrome
1245builds, not Chromium or other Chromium-based browsers.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351246
Ian Barkley-Yeung43404622022-03-25 00:00:441247Specifically, an error report will be generated when the JavaScript or
1248TypeScript for a WebUI-based chrome:// page does one of the following:
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351249* Generates an uncaught exception,
1250* Has a promise which is rejected, and no rejection handler is provided, or
1251* Calls `console.error()`.
1252
1253Such errors will appear alongside other crashes in the
Ian Barkley-Yeung43404622022-03-25 00:00:441254`product_name=Chrome_ChromeOS`, `product_name=Chrome_Lacros`, or
1255`product_name=Chrome_Linux` lists on [go/crash](http://go/crash).
1256
1257The signature of the error is the error message followed by the URL on which the
1258error appeared. For example, if chrome://settings/lazy_load.js throws a
1259TypeError with a message `Cannot read properties of null (reading 'select')` and
1260does not catch it, the magic signature would be
1261```
1262Uncaught TypeError: Cannot read properties of null (reading 'select') (chrome://settings)
1263```
1264To avoid spamming the system, only one error report with a given message will be
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351265generated per hour.
1266
1267If you are getting error reports for an expected condition, you can turn off the
Ian Barkley-Yeung43404622022-03-25 00:00:441268reports simply by changing `console.error()` into `console.warn()`. For
1269instance, if JavaScript is calling `console.error()` when the user tries to
1270connect to an unavailable WiFi network at the same time the page shows the user
1271an error message, the `console.error()` should be replaced with a
1272`console.warn()`.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351273
1274If you wish to get more control of the JavaScript error messages, for example
1275to change the product name or to add additional data, you may wish to switch to
1276using `CrashReportPrivate.reportError()`. If you do so, be sure to override
1277`WebUIController::IsJavascriptErrorReportingEnabled()` to return false for your
1278page; this will avoid generating redundant error reports.
1279
Ian Barkley-Yeung43404622022-03-25 00:00:441280### Are JavaScript errors actually crashes?
1281JavaScript errors are not "crashes" in the C++ sense. They do not stop a process
1282from running, they do not cause a "sad tab" page. Some tooling refers to them as
1283crashes because they are going through the same pipeline as the C++ crashes, and
1284that pipeline was originally designed to handle crashes.
1285
1286### How much impact does this JavaScript error have?
1287That depends on the JavaScript error. In some cases, the errors have no user
1288impact; for instance, the "unavailable WiFi network calling `console.error()`"
1289example above. In other cases, JavaScript errors may be serious errors that
1290block the user from completing critical user journeys. For example, if the
1291JavaScript is supposed to un-hide one of several variants of settings page, but
1292the JavaScript has an unhandled exception before un-hiding any of them, then
1293the user will see a blank page and be unable to change that setting.
1294
1295Because it is difficult to automatically determine the severity of a given
1296error, JavaScript errors are currently all classified as "WARNING" level when
1297computing stability metrics.
1298
1299### Known issues
13001. Error reporting is currently enabled only on ChromeOS (ash and Lacros) and
1301 Linux.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:3513022. Errors are only reported for chrome:// URLs.
13033. Unhandled promise rejections do not have a good stack.
13044. The line numbers and column numbers in the stacks are for the minified
1305 JavaScript and do not correspond to the line and column numbers of the
1306 original source files.
13075. Error messages with variable strings do not group well. For example, if the
1308 error message includes the name of a network, each network name will be its
1309 own signature.
1310
Dan Beam079d5c12017-06-16 19:23:301311## See also
1312
Amos Limf916d572018-05-21 23:10:351313* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:301314* WebUI's HTML/CSS/JS code follows the [Chromium Web
1315 Development Style Guide](../styleguide/web/web.md)
Rebekah Potter1ebb97f2023-10-25 15:38:451316* Adding tests for WebUI pages: [Testing WebUI](./testing_webui.md)
Hubert Chao6f79e2c2024-04-04 14:14:311317* Demo WebUI widgets at `chrome://webui-gallery` (and source at
1318 [chrome/browser/resources/webui_gallery/](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/resources/webui_gallery/))
Dan Beam079d5c12017-06-16 19:23:301319
1320
1321<script>
1322let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
1323let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
1324
1325let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
1326let hrefs = localLinks.map(a => a.href.split('#')[1]);
1327
1328hrefs.forEach(href => {
1329 if (names.includes(href))
1330 console.info('found: ' + href);
1331 else
1332 console.error('broken href: ' + href);
1333})
1334</script>