blob: 586b6713eed2619b5efb669f3b6a48e86f69e64d [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
91browser process lifecycle.
92
93```c++
94// ChromeBrowserMainParts::PreMainMessageLoopRunImpl():
95content::WebUIControllerFactory::RegisterFactory(
96 ChromeWebUIControllerFactory::GetInstance());
97```
98
99When a URL is requested, a new renderer is created to load the URL, and a
100corresponding class in the browser is set up to handle messages from the
101renderer to the browser (a `RenderFrameHost`).
102
103The URL of the request is inspected:
104
105```c++
106if (url.SchemeIs("chrome") && url.host_piece() == "donuts") // chrome://donuts
107 return &NewWebUI<DonutsUI>;
108return nullptr; // Not a known host; no special access.
109```
110
111and if a factory knows how to handle a host (returns a `WebUIFactoryFunction`),
112the navigation machinery [grants the renderer process WebUI
113bindings](#bindings) via the child security policy.
114
115```c++
116// RenderFrameHostImpl::AllowBindings():
117if (bindings_flags & BINDINGS_POLICY_WEB_UI) {
dbeam8b52edff2017-06-16 22:36:18118 ChildProcessSecurityPolicyImpl::GetInstance()->GrantWebUIBindings(
119 GetProcess()->GetID());
Dan Beam079d5c12017-06-16 19:23:30120}
121```
122
123The factory creates a [`WebUIController`](#WebUIController) for a tab.
124Here's an example:
125
126```c++
127// Controller for chrome://donuts.
128class DonutsUI : public content::WebUIController {
129 public:
130 DonutsUI(content::WebUI* web_ui) : content::WebUIController(web_ui) {
131 content::WebUIDataSource* source =
Rebekah Pottera8949422023-01-05 18:44:13132 content::WebUIDataSource::CreateAndAdd(
133 web_ui->GetWebContents()->GetBrowserContext(),
134 "donuts"); // "donuts" == hostname
Dan Beam079d5c12017-06-16 19:23:30135 source->AddString("mmmDonuts", "Mmm, donuts!"); // Translations.
rbpotterf50e0252020-09-14 16:38:33136 source->AddResourcePath("", IDR_DONUTS_HTML); // Home page.
Dan Beam079d5c12017-06-16 19:23:30137
138 // Handles messages from JavaScript to C++ via chrome.send().
Jeremy Romane0760a402018-03-02 18:19:40139 web_ui->AddMessageHandler(std::make_unique<OvenHandler>());
Dan Beam079d5c12017-06-16 19:23:30140 }
141};
142```
143
144If we assume the contents of `IDR_DONUTS_HTML` yields:
145
146```html
147<h1>$i18n{mmmDonuts}</h1>
148```
149
150Visiting `chrome://donuts` should show in something like:
151
152<div style="border: 1px solid black; padding: 10px;">
153<h1>Mmmm, donuts!</h1>
154</div>
155
156Delicious success.
157
Christopher Lam50ab1e92019-10-29 04:33:16158By default $i18n{} escapes strings for HTML. $i18nRaw{} can be used for
159translations that embed HTML, and $i18nPolymer{} can be used for Polymer
160bindings. See
161[this comment](https://bugs.chromium.org/p/chromium/issues/detail?id=1010815#c1)
162for more information.
163
Dan Beam079d5c12017-06-16 19:23:30164## C++ classes
165
166### WebUI
167
168`WebUI` is a high-level class and pretty much all HTML-based Chrome UIs have
169one. `WebUI` lives in the browser process, and is owned by a `RenderFrameHost`.
170`WebUI`s have a concrete implementation (`WebUIImpl`) in `content/` and are
171created in response to navigation events.
172
173A `WebUI` knows very little about the page it's showing, and it owns a
174[`WebUIController`](#WebUIController) that is set after creation based on the
175hostname of a requested URL.
176
177A `WebUI` *can* handle messages itself, but often defers these duties to
178separate [`WebUIMessageHandler`](#WebUIMessageHandler)s, which are generally
179designed for handling messages on certain topics.
180
181A `WebUI` can be created speculatively, and are generally fairly lightweight.
182Heavier duty stuff like hard initialization logic or accessing services that may
183have side effects are more commonly done in a
184[`WebUIController`](#WebUIController) or
185[`WebUIMessageHandler`s](#WebUIMessageHandler).
186
187`WebUI` are created synchronously on the UI thread in response to a URL request,
188and are re-used where possible between navigations (i.e. refreshing a page).
189Because they run in a separate process and can exist before a corresponding
190renderer process has been created, special care is required to communicate with
191the renderer if reliable message passing is required.
192
Dan Beam079d5c12017-06-16 19:23:30193### WebUIController
194
195A `WebUIController` is the brains of the operation, and is responsible for
196application-specific logic, setting up translations and resources, creating
197message handlers, and potentially responding to requests dynamically. In complex
198pages, logic is often split across multiple
199[`WebUIMessageHandler`s](#WebUIMessageHandler) instead of solely in the
200controller for organizational benefits.
201
202A `WebUIController` is owned by a [`WebUI`](#WebUI), and is created and set on
203an existing [`WebUI`](#WebUI) when the correct one is determined via URL
204inspection (i.e. chrome://settings creates a generic [`WebUI`](#WebUI) with a
205settings-specific `WebUIController`).
206
207### WebUIDataSource
208
rbpotterf50e0252020-09-14 16:38:33209The `WebUIDataSource` class provides a place for data to live for WebUI pages.
210
211Examples types of data stored in this class are:
212
213* static resources (i.e. .html files packed into bundles and pulled off of disk)
214* translations
215* dynamic feature values (i.e. whether a feature is enabled)
216
217Data sources are set up in the browser process (in C++) and are accessed by
218loading URLs from the renderer.
219
220Below is an example of a simple data source (in this case, Chrome's history
221page):
222
223```c++
Rebekah Pottera8949422023-01-05 18:44:13224content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
225 Profile::FromWebUI(web_ui), "history");
rbpotterf50e0252020-09-14 16:38:33226
227source->AddResourcePath("sign_in_promo.svg", IDR_HISTORY_SIGN_IN_PROMO_SVG);
228source->AddResourcePath("synced_tabs.html", IDR_HISTORY_SYNCED_TABS_HTML);
229
230source->AddString("title", IDS_HISTORY_TITLE);
231source->AddString("moreFromThisSite", IDS_HISTORY_MORE_FROM_THIS_SITE);
232
233source->AddBoolean("showDateRanges",
234 base::FeatureList::IsEnabled(features::kHistoryShowDateRanges));
235
236webui::SetupWebUIDataSource(
237 source, base::make_span(kHistoryResources, kHistoryResourcesSize),
238 kGeneratedPath, IDR_HISTORY_HISTORY_HTML);
rbpotterf50e0252020-09-14 16:38:33239```
240
241For more about each of the methods called on `WebUIDataSource` and the utility
242method that performs additional configuration, see [DataSources](#DataSources)
243and [WebUIDataSourceUtils](#WebUIDataSourceUtils)
244
Dan Beam079d5c12017-06-16 19:23:30245### WebUIMessageHandler
246
247Because some pages have many messages or share code that sends messages, message
248handling is often split into discrete classes called `WebUIMessageHandler`s.
249These handlers respond to specific invocations from JavaScript.
250
251So, the given C++ code:
252
253```c++
254void OvenHandler::RegisterMessages() {
Ayu Ishii33743432021-02-03 19:05:01255 web_ui()->RegisterMessageCallback(
256 "bakeDonuts",
257 base::BindRepeating(&OvenHandler::HandleBakeDonuts,
258 base::Unretained(this)));
Dan Beam079d5c12017-06-16 19:23:30259}
260
Moe Ahmadide5901862022-02-25 21:56:23261void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:23262 AllowJavascript();
263
Lei Zhang72347ebdd2021-11-16 16:40:02264 // IMPORTANT: Fully validate `args`.
cammie720e8acd2021-08-25 19:15:45265 CHECK_EQ(1u, args.size());
Lei Zhang72347ebdd2021-11-16 16:40:02266 int num_donuts = args[0].GetInt();
267 CHECK_GT(num_donuts, 0);
268 GetOven()->BakeDonuts(num_donuts);
Dan Beam079d5c12017-06-16 19:23:30269}
270```
271
272Can be triggered in JavaScript with this example code:
273
274```js
275$('bakeDonutsButton').onclick = function() {
276 chrome.send('bakeDonuts', [5]); // bake 5 donuts!
277};
278```
279
rbpotterf50e0252020-09-14 16:38:33280## Data Sources
281
Rebekah Pottera8949422023-01-05 18:44:13282### WebUIDataSource::CreateAndAdd()
rbpotterf50e0252020-09-14 16:38:33283
Rebekah Pottera8949422023-01-05 18:44:13284This is a factory method required to create and add a WebUIDataSource. The first
285argument to `Create()` is the browser context. The second argument is typically
286the host name of the page. The caller does not own the result.
rbpotterf50e0252020-09-14 16:38:33287
Rebekah Pottera8949422023-01-05 18:44:13288Additionally, calling `CreateAndAdd()` will overwrite any existing data source
rbpotterf50e0252020-09-14 16:38:33289with the same name.
290
291<div class="note">
292It's unsafe to keep references to a <code>WebUIDataSource</code> after calling
293<code>Add()</code>. Don't do this.
294</div>
295
rbpotterf50e0252020-09-14 16:38:33296### WebUIDataSource::AddLocalizedString()
297
298Using an int reference to a grit string (starts with "IDS" and lives in a .grd
299or .grdp file), adding a string with a key name will be possible to reference
300via the `$i18n{}` syntax (and will be replaced when requested) or later
301dynamically in JavaScript via `loadTimeData.getString()` (or `getStringF`).
302
Lei Zhang5b205082022-01-25 18:08:38303### WebUIDataSource::AddLocalizedStrings()
304
305Many Web UI data sources need to be set up with a large number of localized
306strings. Instead of repeatedly calling <code>AddLocalizedString()</code>, create
307an array of all the strings and use <code>AddLocalizedStrings()</code>:
308
309```c++
310 static constexpr webui::LocalizedString kStrings[] = {
311 // Localized strings (alphabetical order).
312 {"actionMenuDescription", IDS_HISTORY_ACTION_MENU_DESCRIPTION},
313 {"ariaRoleDescription", IDS_HISTORY_ARIA_ROLE_DESCRIPTION},
314 {"bookmarked", IDS_HISTORY_ENTRY_BOOKMARKED},
315 };
316 source->AddLocalizedStrings(kStrings);
317```
318
rbpotterf50e0252020-09-14 16:38:33319### WebUIDataSource::AddResourcePath()
320
321Using an int reference to a grit resource (starts with "IDR" and lives in a .grd
322or .grdp file), adds a resource to the UI with the specified path.
323
324It's generally a good idea to call <code>AddResourcePath()</code> with the empty
325path and a resource ID that should be served as the "catch all" resource to
326respond with. This resource will be served for requests like "chrome://history",
327or "chrome://history/pathThatDoesNotExist". It will not be served for requests
328that look like they are attempting to fetch a specific file, like
329"chrome://history/file\_that\_does\_not\_exist.js". This is so that if a user
330enters a typo when trying to load a subpage like "chrome://history/syncedTabs"
331they will be redirected to the main history page, instead of seeing an error,
332but incorrect imports in the source code will fail, so that they can be more
333easily found and corrected.
334
Lei Zhang5b205082022-01-25 18:08:38335### WebUIDataSource::AddResourcePaths()
336
337Similar to the localized strings, many Web UIs need to add a large number of
338resource paths. In this case, use <code>AddResourcePaths()</code> to
339replace repeated calls to <code>AddResourcePath()</code>.
340
341```c++
342 static constexpr webui::ResourcePath kResources[] = {
343 {"browser_api.js", IDR_BROWSER_API_JS},
344 {"constants.js", IDR_CONSTANTS_JS},
345 {"controller.js", IDR_CONTROLLER_JS},
346 };
347 source->AddResourcePaths(kResources);
348```
349
350The same method can be leveraged for cases that directly use constants defined
351by autogenerated grit resources map header files. For example, the autogenerated
352print\_preview\_resources\_map.h header defines a
353<code>webui::ResourcePath</code> array named <code>kPrintPreviewResources</code>
354and a <code>size\_t kPrintPreviewResourcesSize</code>. All the resources in this
355resource map can be added as follows:
356
357```c++
358 source->AddResourcePaths(
359 base::make_span(kPrintPreviewResources, kPrintPreviewResourcesSize));
360```
361
rbpotterf50e0252020-09-14 16:38:33362### WebUIDataSource::AddBoolean()
363
364Often a page needs to know whether a feature is enabled. This is a good use case
365for `WebUIDataSource::AddBoolean()`. Then, in the Javascript, one can write
366code like this:
367
368```js
369if (loadTimeData.getBoolean('myFeatureIsEnabled')) {
370 ...
371}
372```
373
374<div class="note">
375Data sources are not recreated on refresh, and therefore values that are dynamic
376(i.e. that can change while Chrome is running) may easily become stale. It may
rbpotteracc480cd2022-03-04 08:42:19377be preferable to use <code>sendWithPromise()</code> to initialize dynamic
rbpotterf50e0252020-09-14 16:38:33378values and call <code>FireWebUIListener()</code> to update them.
379
380If you really want or need to use <code>AddBoolean()</code> for a dynamic value,
381make sure to call <code>WebUIDataSource::Update()</code> when the value changes.
382</div>
383
rbpotterf50e0252020-09-14 16:38:33384## WebUI utils for working with data sources
385
386chrome/browser/ui/webui/webui\_util.\* contains a number of methods to simplify
387common configuration tasks.
388
Rebekah Potter5691cab2020-10-29 21:30:35389### webui::SetupWebUIDataSource()
rbpotterf50e0252020-09-14 16:38:33390
Rebekah Potter5691cab2020-10-29 21:30:35391This method performs common configuration tasks on a data source for a Web UI
392that uses JS modules. When creating a Web UI that uses JS modules, use this
393utility instead of duplicating the configuration steps it performs elsewhere.
394Specific setup steps include:
rbpotterf50e0252020-09-14 16:38:33395
396* Setting the content security policy to allow the data source to load only
397 resources from its own host (e.g. chrome://history), chrome://resources, and
398 chrome://test (used to load test files).
399* Enabling i18n template replacements by calling <code>UseStringsJs()</code> and
400 <code>EnableReplaceI18nInJS()</code> on the data source.
401* Adding the test loader files to the data source, so that test files can be
402 loaded as JS modules.
403* Setting the resource to load for the empty path.
Rebekah Potter5691cab2020-10-29 21:30:35404* Adding all resources from a GritResourceMap.
rbpotterf50e0252020-09-14 16:38:33405
Rebekah Potter65c7cae2022-12-15 22:19:49406## Browser (C++) and Renderer (JS) communication
Dan Beam079d5c12017-06-16 19:23:30407
Rebekah Potter65c7cae2022-12-15 22:19:49408### Mojo
409
410[Mojo](https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md)
411is used for IPC throughout Chromium, and should generally be used for new
412WebUIs to communicate between the browser (C++) and the renderer (JS/TS). To
413use Mojo, you will need to:
414
415* Write an interface definition for the JS/C++ interface in a mojom file
416* Add a build target in the BUILD.gn file to autogenerate C++ and TypeScript
417 code ("bindings").
418* Bind the interface on the C++ side and implement any methods to send or
419 receive information from TypeScript.
420* Add the TypeScript bindings file to your WebUI's <code>ts_library()</code>
421 and use them in your TypeScript code.
422
423#### Mojo Interface Definition
424Mojo interfaces are declared in mojom files. For WebUIs, these normally live
425alongside the C++ code in chrome/browser/ui/webui. For example:
426
427**chrome/browser/ui/webui/donuts/donuts.mojom**
428```
429module donuts.mojom;
430
431interface PageHandlerFactory {
432 CreatePageHandler(pending_remote<Page> page,
433 pending_receiver<PageHandler> handler);
434};
435
436// Called from TS side of chrome://donuts (Renderer -> Browser)
437interface PageHandler {
438 StartPilotLight();
439
440 BakeDonuts(uint32 num_donuts);
441
442 // Expects a response from the browser.
443 GetNumberOfDonuts() => (uint32 num_donuts);
444}
445
446// Called from C++ side of chrome://donuts. (Browser -> Renderer)
447interface Page {
448 DonutsBaked(uint32 num_donuts);
449}
450```
451
452#### BUILD.gn mojo target
453mojom() is the build rule used to generate mojo bindings. It can be set up as
454follows:
455
456**chrome/browser/ui/webui/donuts/BUILD.gn**
457```
458import("//mojo/public/tools/bindings/mojom.gni")
459
460mojom("mojo_bindings") {
461 sources = [ "donuts.mojom" ]
462 webui_module_path = "/"
463 use_typescript_sources = true
464}
465```
466
467#### Setting up C++ bindings
468The WebUIController class should inherit from ui::MojoWebUIController and
469from the PageHandlerFactory class defined in the mojom file.
470
471**chrome/browser/ui/webui/donuts/donuts_ui.h**
472```c++
473class DonutsPageHandler;
474
475class DonutsUI : public ui::MojoWebUIController,
476 public donuts::mojom::PageHandlerFactory {
477 public:
478 explicit DonutsUI(content::WebUI* web_ui);
479
480 DonutsUI(const DonutsUI&) = delete;
481 DonutsUI& operator=(const DonutsUI&) = delete;
482
483 ~DonutsUI() override;
484
485 // Instantiates the implementor of the mojom::PageHandlerFactory mojo
486 // interface passing the pending receiver that will be internally bound.
487 void BindInterface(
488 mojo::PendingReceiver<donuts::mojom::PageHandlerFactory> receiver);
489
490 private:
491 // donuts::mojom::PageHandlerFactory:
492 void CreatePageHandler(
493 mojo::PendingRemote<donuts::mojom::Page> page,
494 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver) override;
495
496 std::unique_ptr<DonutsPageHandler> page_handler_;
497
498 mojo::Receiver<donuts::mojom::PageHandlerFactory> page_factory_receiver_{
499 this};
500
501 WEB_UI_CONTROLLER_TYPE_DECL();
502};
503```
504
505**chrome/browser/ui/webui/donuts/donuts_ui.cc**
506```c++
507DonutsUI::DonutsUI(content::WebUI* web_ui)
508 : ui::MojoWebUIController(web_ui, true) {
509 // Normal constructor steps (e.g. setting up data source) go here.
510}
511
512WEB_UI_CONTROLLER_TYPE_IMPL(DonutsUI)
513
514DonutsUI::~DonutsUI() = default;
515
516void DonutsUI::BindInterface(
517 mojo::PendingReceiver<donuts::mojom::PageHandlerFactory> receiver) {
518 page_factory_receiver_.reset();
519 page_factory_receiver_.Bind(std::move(receiver));
520}
521
522void DonutsUI::CreatePageHandler(
523 mojo::PendingRemote<donuts::mojom::Page> page,
524 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver) {
525 DCHECK(page);
526 page_handler_ = std::make_unique<DonutsPageHandler>(
527 std::move(receiver), std::move(page));
528}
529```
530
531You also need to register the PageHandlerFactory to your controller in
532**chrome/browser/chrome_browser_interface_binders.cc**:
533```c++
534RegisterWebUIControllerInterfaceBinder<donuts::mojom::PageHandlerFactory,
535 DonutsUI>(map);
536```
537
538#### Using C++ bindings for communication
539The WebUI message handler should inherit from the Mojo PageHandler class.
540
541**chrome/browser/ui/webui/donuts/donuts_page_handler.h**
542```c++
543#include "chrome/browser/ui/webui/donuts/donuts.mojom.h"
544#include "mojo/public/cpp/bindings/receiver.h"
545#include "mojo/public/cpp/bindings/remote.h"
546
547class DonutsPageHandler : public donuts::mojom::PageHandler {
548 public:
549 DonutsPageHandler(
550 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver,
551 mojo::PendingRemote<donuts::mojom::Page> page);
552
553 DonutsPageHandler(const DonutsPageHandler&) = delete;
554 DonutsPageHandler& operator=(const DonutsPageHandler&) = delete;
555
556 ~DonutsPageHandler() override;
557
558 // Triggered by some outside event
559 void DonutsPageHandler::OnBakingDonutsFinished(uint32_t num_donuts);
560
561 // donuts::mojom::PageHandler:
562 void StartPilotLight() override;
563 void BakeDonuts(uint32_t num_donuts) override;
564 void GetNumberOfDonuts(GetNumberOfDonutsCallback callback) override;
565}
566```
567
568The message handler needs to implement all the methods on the PageHandler
569interface.
570
571**chrome/browser/ui/webui/donuts/donuts_page_handler.cc**
572```c++
573DonutsPageHandler::DonutsPageHandler(
574 mojo::PendingReceiver<donuts::mojom::PageHandler> receiver,
575 mojo::PendingRemote<donuts::mojom::Page> page)
576 : receiver_(this, std::move(receiver)),
577 page_(std::move(page)) {
578}
579
580DonutsPageHandler::~DonutsPageHandler() {
581 GetOven()->TurnOffGas();
582}
583
584// Triggered by outside asynchronous event; sends information to the renderer.
585void DonutsPageHandler::OnBakingDonutsFinished(uint32_t num_donuts) {
586 page_->DonutsBaked(num_donuts);
587}
588
589// Triggered by startPilotLight() call in TS.
590void DonutsPageHandler::StartPilotLight() {
591 GetOven()->StartPilotLight();
592}
593
594// Triggered by bakeDonuts() call in TS.
595void DonutsPageHandler::BakeDonuts(int32_t num_donuts) {
596 GetOven()->BakeDonuts();
597}
598
599// Triggered by getNumberOfDonuts() call in TS; sends a response back to the
600// renderer.
601void DonutsPageHandler::GetNumberOfDonuts(GetNumberOfDonutsCallback callback) {
602 uint32_t result = GetOven()->GetNumberOfDonuts();
603 std::move(callback).Run(result);
604}
605```
606
607#### Setting Up TypeScript bindings
608
609For WebUIs using the `build_webui()` rule, the TypeScript mojo bindings can be
610added to the build and served from the root (e.g.
611`chrome://donuts/donuts.mojom-webui.js`) by adding the following arguments to
612`build_webui()`:
613
614**chrome/browser/resources/donuts/BUILD.gn**
615```
616build_webui("build") {
617 # ... Other arguments go here
618 mojo_files_deps =
619 [ "//chrome/browser/ui/webui/donuts:mojo_bindings_ts__generator" ]
620 mojo_files = [
621 "$root_gen_dir/chrome/browser/ui/webui/donuts/donuts.mojom-webui.ts",
622 ]
623 # ... Other arguments can go here
624}
625```
626
627It is often helpful to wrap the TypeScript side of Mojo setup in a BrowserProxy
628class:
629
630**chrome/browser/resources/donuts/browser_proxy.ts**
631```js
632import {PageCallbackRouter, PageHandlerFactory, PageHandlerInterface, PageHandlerRemote} from './donuts.mojom-webui.js';
633
634class BrowserProxy {
635 callbackRouter: PageCallbackRouter;
636 handler: PageHandlerInterface;
637
638 constructor() {
639 this.callbackRouter = new PageCallbackRouter();
640
641 this.handler = new PageHandlerRemote();
642
643 const factory = PageHandlerFactory.getRemote();
644 factory.createPageHandler(
645 this.callbackRouter.$.bindNewPipeAndPassRemote(),
646 (this.handler as PageHandlerRemote).$.bindNewPipeAndPassReceiver());
647 }
648
649 static getInstance(): BrowserProxy {
650 return instance || (instance = new BrowserProxy());
651 }
652
653 static setInstance(obj: BrowserProxy) {
654 instance = obj;
655 }
656}
657
658let instance: BrowserProxy|null = null;
659```
660
661#### Using TypeScript bindings for communication
662The `callbackRouter` (`PageCallbackRouter`) can be used to add listeners for
663asynchronous events sent from the browser.
664
665The `handler` (`PageHandlerRemote`) can be used to send messages from the
666renderer to the browser. For interface methods that require a browser response,
667calling the method returns a promise. The promise will be resolved with the
668response from the browser.
669
670**chrome/browser/resources/donuts/donuts.ts**
671```js
672import {BrowserProxy} from './browser_proxy.js';
673
674let numDonutsBaked: number = 0;
675
676window.onload = function() {
677 // Other page initialization steps go here
678 const proxy = BrowserProxy.getInstance();
679 // Tells the browser to start the pilot light.
680 proxy.handler.startPilotLight();
681 // Adds a listener for the asynchronous "donutsBaked" event.
682 proxy.callbackRouter.donutsBaked.addListener(
683 (numDonuts: number) => {
684 numDonutsBaked += numDonuts;
685 });
686};
687
688function CheckNumberOfDonuts() {
689 // Requests the number of donuts from the browser, and alerts with the
690 // response.
691 BrowserProxy.getInstance().handler.getNumberOfDonuts().then(
692 (numDonuts: number) => {
693 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
694 });
695}
696
697function BakeDonuts(numDonuts: number) {
698 // Tells the browser to bake |numDonuts| donuts.
699 BrowserProxy.getInstance().handler.bakeDonuts(numDonuts);
700}
701```
702
703### Pre-Mojo alternative: chrome.send()/WebUIMessageHandler
704Most Chrome WebUIs were added before the introduction of Mojo, and use the
705older style WebUIMessageHandler + chrome.send() pattern. The following sections
706detail the methods in WebUIMessageHandler and the corresponding communication
707methods in TypeScript/JavaScript and how to use them.
708
709#### WebUIMessageHandler::AllowJavascript()
Dan Beam079d5c12017-06-16 19:23:30710
Adam Langley81be0732019-03-06 18:38:45711A tab that has been used for settings UI may be reloaded, or may navigate to an
712external origin. In both cases, one does not want callbacks from C++ to
713Javascript to run. In the former case, the callbacks will occur when the
714Javascript doesn't expect them. In the latter case, sensitive information may be
715delivered to an untrusted origin.
716
717Therefore each message handler maintains
718[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
719that describes whether delivering callbacks to Javascript is currently
720appropriate. This boolean is set by calling `AllowJavascript`, which should be
721done when handling a call from Javascript, because that indicates that the page
722is ready for the subsequent callback. (See
723[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
724If the tab navigates or reloads,
725[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
726is called to clear the flag.
727
728Therefore, before each callback from C++ to Javascript, the flag must be tested
729by calling
730[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
731If false, then the callback must be dropped. (When the flag is false, calling
732[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
733will crash. See
734[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
735
736Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
737the case where an asynchronous operation is started, the settings page is
738reloaded, and the user triggers another operation using the original message
739handler. The `javascript_allowed_` boolean will be true, but the original
740callback should still be dropped because it relates to a operation that was
741discarded by the reload. (Reloading settings UI does _not_ cause message handler
742objects to be deleted.)
743
744Thus a message handler may override
745[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
746to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30747
748In the JS:
749
750```js
751window.onload = function() {
752 app.initialize();
753 chrome.send('startPilotLight');
754};
755```
756
757In the C++:
758
759```c++
Lei Zhangf48bb60e2022-12-09 17:42:44760void OvenHandler::HandleStartPilotLight(const base::Value::List& /*args*/) {
Dan Beam079d5c12017-06-16 19:23:30761 AllowJavascript();
762 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
763 GetOven()->StartPilotLight();
764}
765```
766
767<div class="note">
768Relying on the <code>'load'</code> event or browser-side navigation callbacks to
769detect page readiness omits <i>application-specific</i> initialization, and a
770custom <code>'initialized'</code> message is often necessary.
771</div>
772
Rebekah Potter65c7cae2022-12-15 22:19:49773#### WebUIMessageHandler::CallJavascriptFunction()
Dan Beam079d5c12017-06-16 19:23:30774
775When the browser process needs to tell the renderer/JS of an event or otherwise
776execute code, it can use `CallJavascriptFunction()`.
777
778<div class="note">
779Javascript must be <a href="#AllowJavascript">allowed</a> to use
780<code>CallJavscriptFunction()</code>.
781</div>
782
783```c++
784void OvenHandler::OnPilotLightExtinguished() {
785 CallJavascriptFunction("app.pilotLightExtinguished");
786}
787```
788
789This works by crafting a string to be evaluated in the renderer. Any arguments
790to the call are serialized to JSON and the parameter list is wrapped with
791
792```
793// See WebUI::GetJavascriptCall() for specifics:
794"functionCallName(" + argumentsAsJson + ")"
795```
796
797and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
798
799While this works, it implies that:
800
801* a global method must exist to successfully run the Javascript request
802* any method can be called with any parameter (far more access than required in
803 practice)
804
805^ These factors have resulted in less use of `CallJavascriptFunction()` in the
806webui codebase. This functionality can easily be accomplished with the following
807alternatives:
808
809* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
810 when an event occurs in C++ and is more loosely coupled (nothing blows up if
811 the event dispatch is ignored). JS subscribes to notifications via
Rebekah Potter952290e2022-11-18 09:07:28812 [`addWebUiListener`](#addWebUiListener).
Dan Beam079d5c12017-06-16 19:23:30813* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
814 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
815 when Javascript requires a response to an inquiry about C++-canonical state
816 (i.e. "Is Autofill enabled?", "Is the user incognito?")
817
Rebekah Potter65c7cae2022-12-15 22:19:49818#### WebUIMessageHandler::FireWebUIListener()
Dan Beam079d5c12017-06-16 19:23:30819
820`FireWebUIListener()` is used to notify a registered set of listeners that an
821event has occurred. This is generally used for events that are not guaranteed to
822happen in timely manner, or may be caused to happen by unpredictable events
823(i.e. user actions).
824
825Here's some example to detect a change to Chrome's theme:
826
827```js
Rebekah Potter952290e2022-11-18 09:07:28828addWebUiListener("theme-changed", refreshThemeStyles);
Dan Beam079d5c12017-06-16 19:23:30829```
830
831This Javascript event listener can be triggered in C++ via:
832
833```c++
834void MyHandler::OnThemeChanged() {
835 FireWebUIListener("theme-changed");
836}
837```
838
839Because it's not clear when a user might want to change their theme nor what
840theme they'll choose, this is a good candidate for an event listener.
841
842If you simply need to get a response in Javascript from C++, consider using
rbpotteracc480cd2022-03-04 08:42:19843[`sendWithPromise()`](#sendWithPromise) and
Dan Beam079d5c12017-06-16 19:23:30844[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
845
Rebekah Potter65c7cae2022-12-15 22:19:49846#### WebUIMessageHandler::OnJavascriptAllowed()
Dan Beam079d5c12017-06-16 19:23:30847
848`OnJavascriptDisallowed()` is a lifecycle method called in response to
849[`AllowJavascript()`](#AllowJavascript). It is a good place to register
850observers of global services or other callbacks that might call at unpredictable
851times.
852
853For example:
854
855```c++
856class MyHandler : public content::WebUIMessageHandler {
857 MyHandler() {
858 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
859 }
860 void OnGlobalServiceEvent() {
861 FireWebUIListener("global-thing-happened");
862 }
863};
864```
865
866Because browser-side C++ handlers are created before a renderer is ready, the
867above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
868before the renderer is ready, which may result in dropped updates or
869accidentally running Javascript in a renderer that has navigated to a new URL.
870
871A safer way to set up communication is:
872
873```c++
874class MyHandler : public content::WebUIMessageHandler {
875 public:
Dan Beam079d5c12017-06-16 19:23:30876 void OnJavascriptAllowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17877 observation_.Observe(GetGlobalService()); // <-- DO THIS.
Dan Beam079d5c12017-06-16 19:23:30878 }
879 void OnJavascriptDisallowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17880 observation_.Reset(); // <-- AND THIS.
Dan Beam079d5c12017-06-16 19:23:30881 }
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17882 base::ScopedObservation<MyHandler, GlobalService> observation_{this}; // <-- ALSO HANDY.
Dan Beam079d5c12017-06-16 19:23:30883```
884when a renderer has been created and the
885document has loaded enough to signal to the C++ that it's ready to respond to
886messages.
887
Rebekah Potter65c7cae2022-12-15 22:19:49888#### WebUIMessageHandler::OnJavascriptDisallowed()
Dan Beam079d5c12017-06-16 19:23:30889
890`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
891it's safe to send JavaScript messsages to the renderer.
892
893There's a number of situations that result in this method being called:
894
895* renderer doesn't exist yet
896* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23897* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30898* tab refresh
899* renderer crash
900
901Though it's possible to programmatically disable Javascript, it's uncommon to
902need to do so.
903
904Because there's no single strategy that works for all cases of a renderer's
905state (i.e. queueing vs dropping messages), these lifecycle methods were
906introduced so a WebUI application can implement these decisions itself.
907
908Often, it makes sense to disconnect from observers in
909`OnJavascriptDisallowed()`:
910
911```c++
912void OvenHandler::OnJavascriptDisallowed() {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17913 scoped_oven_observation_.Reset()
Dan Beam079d5c12017-06-16 19:23:30914}
915```
916
917Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
918`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
919scoped observer that automatically unsubscribes on destruction but can also
920imperatively unsubscribe in `OnJavascriptDisallowed()`.
921
Rebekah Potter65c7cae2022-12-15 22:19:49922#### WebUIMessageHandler::RejectJavascriptCallback()
Dan Beam079d5c12017-06-16 19:23:30923
924This method is called in response to
rbpotteracc480cd2022-03-04 08:42:19925[`sendWithPromise()`](#sendWithPromise) to reject the issued Promise. This
Dan Beam079d5c12017-06-16 19:23:30926runs the rejection (second) callback in the [Promise's
927executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
928and any
929[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
930callbacks in the chain.
931
932```c++
Lei Zhangf48bb60e2022-12-09 17:42:44933void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:23934 AllowJavascript();
935 if (!GetOven()->HasGas()) {
Lei Zhangf48bb60e2022-12-09 17:42:44936 RejectJavascriptCallback(args[0],
Michael Giuffrida14938292019-05-31 21:30:23937 base::StringValue("need gas to cook the donuts!"));
938 }
Dan Beam079d5c12017-06-16 19:23:30939```
940
941This method is basically just a
942[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
943global "cr.webUIResponse" method with a success value of false.
944
945```c++
946// WebUIMessageHandler::RejectJavascriptCallback():
947CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:18948 response);
Dan Beam079d5c12017-06-16 19:23:30949```
950
951See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
952
Rebekah Potter65c7cae2022-12-15 22:19:49953#### WebUIMessageHandler::ResolveJavascriptCallback()
Dan Beam079d5c12017-06-16 19:23:30954
955This method is called in response to
rbpotteracc480cd2022-03-04 08:42:19956[`sendWithPromise()`](#sendWithPromise) to fulfill an issued Promise,
Dan Beam079d5c12017-06-16 19:23:30957often with a value. This results in runnings any fulfillment (first) callbacks
958in the associate Promise executor and any registered
959[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
960callbacks.
961
rbpotteracc480cd2022-03-04 08:42:19962So, given this TypeScript code:
Dan Beam079d5c12017-06-16 19:23:30963
964```js
Rebekah Potter65c7cae2022-12-15 22:19:49965sendWithPromise('bakeDonuts', [5]).then(function(numDonutsBaked: number) {
Dan Beam079d5c12017-06-16 19:23:30966 shop.donuts += numDonutsBaked;
967});
968```
969
970Some handling C++ might do this:
971
972```c++
Lei Zhangf48bb60e2022-12-09 17:42:44973void OvenHandler::HandleBakeDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:23974 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:30975 double num_donuts_baked = GetOven()->BakeDonuts();
Lei Zhangf48bb60e2022-12-09 17:42:44976 ResolveJavascriptCallback(args[0], base::Value(num_donuts_baked));
Dan Beam079d5c12017-06-16 19:23:30977}
978```
979
Rebekah Potter65c7cae2022-12-15 22:19:49980#### chrome.send()
Dan Beam079d5c12017-06-16 19:23:30981
982When the JavaScript `window` object is created, a renderer is checked for [WebUI
983bindings](#bindings).
984
985```c++
986// RenderFrameImpl::DidClearWindowObject():
987if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
988 WebUIExtension::Install(frame_);
989```
990
991If the bindings exist, a global `chrome.send()` function is exposed to the
992renderer:
993
994```c++
995// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:35996v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:30997chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:18998 gin::CreateFunctionTemplate(
Ayu Ishii33743432021-02-03 19:05:01999 isolate,
1000 base::BindRepeating(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:301001```
1002
1003The `chrome.send()` method takes a message name and argument list.
1004
1005```js
1006chrome.send('messageName', [arg1, arg2, ...]);
1007```
1008
1009The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:371010`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:301011
1012```c++
1013// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:371014render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
1015 frame->GetDocument().Url(),
1016 message, *content));
Dan Beam079d5c12017-06-16 19:23:301017```
1018```c++
1019// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:371020IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:301021```
1022
1023The browser-side code does a map lookup for the message name and calls the found
1024callback with the deserialized arguments:
1025
1026```c++
1027// WebUIImpl::ProcessWebUIMessage():
1028message_callbacks_.find(message)->second.Run(&args);
1029```
1030
Rebekah Potter65c7cae2022-12-15 22:19:491031#### addWebUiListener()
Dan Beam079d5c12017-06-16 19:23:301032
1033WebUI listeners are a convenient way for C++ to inform JavaScript of events.
1034
1035Older WebUI code exposed public methods for event notification, similar to how
1036responses to [chrome.send()](#chrome_send) used to work. They both
Ian Barkley-Yeung4f4f71d2020-06-09 00:38:131037resulted in global namespace pollution, but it was additionally hard to stop
Rebekah Potter952290e2022-11-18 09:07:281038listening for events in some cases. **addWebUiListener** is preferred in new
Dan Beam079d5c12017-06-16 19:23:301039code.
1040
1041Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
rbpotteracc480cd2022-03-04 08:42:191042just like [sendWithPromise()](#sendWithPromise).
1043
Rebekah Potter65c7cae2022-12-15 22:19:491044addWebUiListener can be imported from 'chrome://resources/js/cr.js'.
Dan Beam079d5c12017-06-16 19:23:301045
1046```js
Rebekah Potter952290e2022-11-18 09:07:281047// addWebUiListener():
Dan Beam079d5c12017-06-16 19:23:301048webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
1049webUIListenerMap[eventName][createUid()] = callback;
1050```
1051
1052The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
1053with an event name and a variable number of arguments.
1054
1055```c++
1056// WebUIMessageHandler:
1057template <typename... Values>
1058void FireWebUIListener(const std::string& event_name, const Values&... values) {
1059 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
1060 values...);
1061}
1062```
1063
1064C++ handlers call this `FireWebUIListener` method when an event occurs that
1065should be communicated to the JavaScript running in a tab.
1066
1067```c++
1068void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
Toby Huang97ce1d5d2021-07-13 01:38:581069 FireWebUIListener("donuts-baked", base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:301070}
1071```
1072
rbpotteracc480cd2022-03-04 08:42:191073TypeScript can listen for WebUI events via:
Dan Beam079d5c12017-06-16 19:23:301074
1075```js
rbpotteracc480cd2022-03-04 08:42:191076let donutsReady: number = 0;
Rebekah Potter952290e2022-11-18 09:07:281077addWebUiListener('donuts-baked', function(numFreshlyBakedDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:301078 donutsReady += numFreshlyBakedDonuts;
1079});
1080```
1081
Rebekah Potter65c7cae2022-12-15 22:19:491082#### sendWithPromise()
Dan Beam079d5c12017-06-16 19:23:301083
rbpotteracc480cd2022-03-04 08:42:191084`sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
Dan Beam079d5c12017-06-16 19:23:301085triggering a message requires a response:
1086
1087```js
1088chrome.send('getNumberOfDonuts'); // No easy way to get response!
1089```
1090
1091In older WebUI pages, global methods were exposed simply so responses could be
1092sent. **This is discouraged** as it pollutes the global namespace and is harder
1093to make request specific or do from deeply nested code.
1094
1095In newer WebUI pages, you see code like this:
1096
1097```js
rbpotteracc480cd2022-03-04 08:42:191098sendWithPromise('getNumberOfDonuts').then(function(numDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:301099 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
1100});
1101```
1102
Rebekah Potter952290e2022-11-18 09:07:281103Note that sendWithPromise can be imported from 'chrome://resources/js/cr.js';
rbpotteracc480cd2022-03-04 08:42:191104
Dan Beam079d5c12017-06-16 19:23:301105On the C++ side, the message registration is similar to
1106[`chrome.send()`](#chrome_send) except that the first argument in the
1107message handler's list is a callback ID. That ID is passed to
1108`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
rbpotteracc480cd2022-03-04 08:42:191109JavaScript/TypeScript and calling the `then()` function.
Dan Beam079d5c12017-06-16 19:23:301110
1111```c++
Lei Zhangf48bb60e2022-12-09 17:42:441112void DonutHandler::HandleGetNumberOfDonuts(const base::Value::List& args) {
Michael Giuffrida14938292019-05-31 21:30:231113 AllowJavascript();
1114
Lei Zhangf48bb60e2022-12-09 17:42:441115 const base::Value& callback_id = args[0];
Dan Beam079d5c12017-06-16 19:23:301116 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Toby Huang97ce1d5d2021-07-13 01:38:581117 ResolveJavascriptCallback(callback_id, base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:301118}
1119```
1120
1121Under the covers, a map of `Promise`s are kept in JavaScript.
1122
1123The callback ID is just a namespaced, ever-increasing number. It's used to
1124insert a `Promise` into the JS-side map when created.
1125
1126```js
rbpotteracc480cd2022-03-04 08:42:191127// sendWithPromise():
Dan Beam079d5c12017-06-16 19:23:301128var id = methodName + '_' + uidCounter++;
1129chromeSendResolverMap[id] = new PromiseResolver;
1130chrome.send(methodName, [id].concat(args));
1131```
1132
1133The corresponding number is used to look up a `Promise` and reject or resolve it
1134when the outcome is known.
1135
1136```js
1137// cr.webUIResponse():
1138var resolver = chromeSendResolverMap[id];
1139if (success)
1140 resolver.resolve(response);
1141else
1142 resolver.reject(response);
1143```
1144
1145This approach still relies on the C++ calling a globally exposed method, but
1146reduces the surface to only a single global (`cr.webUIResponse`) instead of
1147many. It also makes per-request responses easier, which is helpful when multiple
1148are in flight.
1149
Lukasz Anforowicz11e59532018-10-23 22:46:211150
1151## Security considerations
1152
1153Because WebUI pages are highly privileged, they are often targets for attack,
1154since taking control of a WebUI page can sometimes be sufficient to escape
1155Chrome's sandbox. To make sure that the special powers granted to WebUI pages
1156are safe, WebUI pages are restricted in what they can do:
1157
Nasko Oskov24fc53c52021-01-08 10:02:361158* WebUI pages cannot embed http/https resources
Lukasz Anforowicz11e59532018-10-23 22:46:211159* WebUI pages cannot issue http/https fetches
1160
1161In the rare case that a WebUI page really needs to include web content, the safe
Nasko Oskov24fc53c52021-01-08 10:02:361162way to do this is by using an `<iframe>` tag. Chrome's security model gives
1163process isolation between the WebUI and the web content. However, some extra
1164precautions need to be taken, because there are properties of the page that are
1165accessible cross-origin and malicious code can take advantage of such data to
1166attack the WebUI. Here are some things to keep in mind:
Lukasz Anforowicz11e59532018-10-23 22:46:211167
Nasko Oskov24fc53c52021-01-08 10:02:361168* The WebUI page can receive postMessage payloads from the web and should
1169 ensure it verifies any messages as they are not trustworthy.
1170* The entire frame tree is visible to the embedded web content, including
1171 ancestor origins.
1172* The web content runs in the same StoragePartition and Profile as the WebUI,
1173 which reflect where the WebUI page was loaded (e.g., the default profile,
1174 Incognito, etc). The corresponding user credentials will thus be available to
1175 the web content inside the WebUI, possibly showing the user as signed in.
Lukasz Anforowicz11e59532018-10-23 22:46:211176
Nasko Oskov24fc53c52021-01-08 10:02:361177Note: WebUIs have a default Content Security Policy which disallows embedding
1178any frames. If you want to include any web content in an <iframe> you will need
1179to update the policy for your WebUI. When doing so, allow only known origins and
1180avoid making the policy more permissive than strictly necessary.
Lukasz Anforowicz11e59532018-10-23 22:46:211181
Nasko Oskov24fc53c52021-01-08 10:02:361182Alternatively, a `<webview>` tag can be used, which runs in a separate
1183StoragePartition, a separate frame tree, and restricts postMessage communication
1184by default. However, `<webview>` does not support Site Isolation and
1185therefore it is not advisable to use for any sensitive content.
Lukasz Anforowicz11e59532018-10-23 22:46:211186
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351187## JavaScript Error Reporting
1188
Ian Barkley-Yeung43404622022-03-25 00:00:441189By default, errors in the JavaScript or TypeScript of a WebUI page will generate
1190error reports which appear in Google's internal [go/crash](http://go/crash)
1191reports page. These error reports will only be generated for Google Chrome
1192builds, not Chromium or other Chromium-based browsers.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351193
Ian Barkley-Yeung43404622022-03-25 00:00:441194Specifically, an error report will be generated when the JavaScript or
1195TypeScript for a WebUI-based chrome:// page does one of the following:
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351196* Generates an uncaught exception,
1197* Has a promise which is rejected, and no rejection handler is provided, or
1198* Calls `console.error()`.
1199
1200Such errors will appear alongside other crashes in the
Ian Barkley-Yeung43404622022-03-25 00:00:441201`product_name=Chrome_ChromeOS`, `product_name=Chrome_Lacros`, or
1202`product_name=Chrome_Linux` lists on [go/crash](http://go/crash).
1203
1204The signature of the error is the error message followed by the URL on which the
1205error appeared. For example, if chrome://settings/lazy_load.js throws a
1206TypeError with a message `Cannot read properties of null (reading 'select')` and
1207does not catch it, the magic signature would be
1208```
1209Uncaught TypeError: Cannot read properties of null (reading 'select') (chrome://settings)
1210```
1211To avoid spamming the system, only one error report with a given message will be
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351212generated per hour.
1213
1214If you are getting error reports for an expected condition, you can turn off the
Ian Barkley-Yeung43404622022-03-25 00:00:441215reports simply by changing `console.error()` into `console.warn()`. For
1216instance, if JavaScript is calling `console.error()` when the user tries to
1217connect to an unavailable WiFi network at the same time the page shows the user
1218an error message, the `console.error()` should be replaced with a
1219`console.warn()`.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351220
1221If you wish to get more control of the JavaScript error messages, for example
1222to change the product name or to add additional data, you may wish to switch to
1223using `CrashReportPrivate.reportError()`. If you do so, be sure to override
1224`WebUIController::IsJavascriptErrorReportingEnabled()` to return false for your
1225page; this will avoid generating redundant error reports.
1226
Ian Barkley-Yeung43404622022-03-25 00:00:441227### Are JavaScript errors actually crashes?
1228JavaScript errors are not "crashes" in the C++ sense. They do not stop a process
1229from running, they do not cause a "sad tab" page. Some tooling refers to them as
1230crashes because they are going through the same pipeline as the C++ crashes, and
1231that pipeline was originally designed to handle crashes.
1232
1233### How much impact does this JavaScript error have?
1234That depends on the JavaScript error. In some cases, the errors have no user
1235impact; for instance, the "unavailable WiFi network calling `console.error()`"
1236example above. In other cases, JavaScript errors may be serious errors that
1237block the user from completing critical user journeys. For example, if the
1238JavaScript is supposed to un-hide one of several variants of settings page, but
1239the JavaScript has an unhandled exception before un-hiding any of them, then
1240the user will see a blank page and be unable to change that setting.
1241
1242Because it is difficult to automatically determine the severity of a given
1243error, JavaScript errors are currently all classified as "WARNING" level when
1244computing stability metrics.
1245
1246### Known issues
12471. Error reporting is currently enabled only on ChromeOS (ash and Lacros) and
1248 Linux.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:3512492. Errors are only reported for chrome:// URLs.
12503. Unhandled promise rejections do not have a good stack.
12514. The line numbers and column numbers in the stacks are for the minified
1252 JavaScript and do not correspond to the line and column numbers of the
1253 original source files.
12545. Error messages with variable strings do not group well. For example, if the
1255 error message includes the name of a network, each network name will be its
1256 own signature.
1257
Rebekah Potter312e4552022-10-13 21:41:141258## Common TypeScript build issue: Missing dependencies
1259Similar to how builds can flakily fail when a C++ file adds an include without
1260updating the DEPS file appropriately, builds can flakily (or consistently) fail
1261if TypeScript code adds an import but doesn't update the dependencies for its
1262`ts_library()` target to include the library that contains that import. This
1263has caused confusion for both developers and sheriffs in the past.
1264
1265### Example Failure
1266The following is an example build flake that occurred due to the file
1267`personalization_app.ts` adding an import of `colors_css_updater.js`, but not
1268updating its dependencies appropriately:
1269
1270```
1271gen/ash/webui/personalization_app/resources/preprocessed/js/personalization_app.ts:38:39 - error TS2792: Cannot find module 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js'. Did you mean to set the 'moduleResolution' option to 'node', or to add aliases to the 'paths' option?
1272
127338 import {startColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
1274 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1275
1276
1277Found 1 error in gen/ash/webui/personalization_app/resources/preprocessed/js/personalization_app.ts:38
1278```
1279
1280### For Chromium Sheriffs
1281If you see a failure like the one in the example, there is a high chance that
1282the regression range given by automated tools will not include the CL that is
1283the root cause of the failure. There are 2 possible approaches to take to fix
1284the build. One is described below at "fixing the error" - typically these are 1
1285line fixes, but do require a few steps to identify the exact fix. An
1286alternative workaround is as follows:
12871. Note that the file that failed ("1 error in") is `personalization_app.ts`.
1288 Find this file in the repo: in this case, it was at
1289 `ash/webui/personalization_app/resources/js/personalization_app.ts`.
12902. Find the failed import in the repo (line 38, as noted by the bot failure).
12913. Use "Blame" in Chromium code search to find out what CL added this import
1292 line.
12934. Either contact the CL owner or try reverting the CL that made the addition.
1294
1295### Fixing the error
1296The [fix](https://chromium-review.googlesource.com/c/chromium/src/+/3952957)
1297for this example was just 1 line and was identified as follows:
1298
12991. Observe from this failure that the module that can't be found is
1300 `chrome://resources/cr_components/color_change_listener/colors_css_updater.js`.
13012. Find `colors_css_updater.ts` in the repository at
1302 `ui/webui/resources/cr_components/color_change_listener/colors_css_updater.ts`.
13033. Find the BUILD.gn file that compiles this TS file. The BUILD.gn file will in
1304 most cases be in the same folder as the TS file or one of its ancestors. In
1305 this case, it was
1306 `ui/webui/resources/cr_components/color_change_listener/BUILD.gn`.
13074. Observe the target name for the `ts_library()` target that compiled the file
1308 is `"build_ts"`, so the full target path is
1309 `//ui/webui/resources/cr_components/color_change_listener:build_ts`.
13105. Observe that the file where the import failed is `personalization_app.ts`,
1311 which is `ash/webui/personalization_app/resourcesjs/personalization_app.ts` in
1312 the repo.
13136. Find the `ts_library` target that compiles `personalization_app.ts` at
1314 `ash/webui/personalization_app/resources/BUILD.gn`.
13157. Observe that this target doesn't have the
1316 `//ui/webui/resources/cr_components/color_change_listener:build_ts` target
1317 listed in `deps`. Add the missing dependency there.
1318
1319Note that if `colors_css_updater.js` was actually checked into the repo as a
1320JavaScript file, steps 3, 4, and 7 would be slightly different as follows:
1321
13223. Find the BUILD.gn file that either copies or generates a
1323 `colors_css_updater.d.ts`. Generally, this will contain a
1324 `ts_definitions()` target, where the JS file is either passed as an input,
1325 or a target copying the checked in definitions file is a dependency.
13264. Observe the name of the target - usually `"generate_definitions"`.
13277. Look for this target in the `extra_deps` of the `ts_library()` target that
1328 depends on it. Add it to `extra_deps` if it's missing.
1329
1330### For developers - Prevent missing dependency build errors
1331When adding a new import (e.g. `import {FooSharedClass} from 'chrome://resources/foo/foo_shared.js';`) to a TypeScript file in your project:
13321. If the file in the repo is TypeScript (e.g.
1333 `ui/webui/resources/foo/foo_shared.ts`), find which `ts_library()` target
1334 compiles this file.
13352. If, for example, `ui/webui/resources/foo/BUILD.gn` contains:
1336 `ts_library("library")`, which has `foo_shared.ts` listed in its `in_files`,
1337 then add `//ui/webui/resources/foo:library` to your `ts_library()` target's
1338 deps as follows:
1339
1340```
1341ts_library("build_ts") {
1342 root_dir = my_root_dir
1343 out_dir = "$target_gen_dir/tsc"
1344 tsconfig_base = "tsconfig_base.json"
1345 deps = [
1346 "//ui/webui/resources:library",
1347 "//ui/webui/resources/foo:library", # This line is new
1348 ]
1349 in_files = my_project_ts_files
1350}
1351```
1352
1353Alternatively:
13541. If the file in the repo is JavaScript (i.e.
1355 `ui/webui/resources/foo/foo_shared.js`), look for which `ts_definitions()`
1356 target generates the corresponding `.d.ts` file or depends on a target
1357 copying a manually checked in `foo_shared.d.ts` file.
13582. If, for example, `ui/webui/resources/foo/BUILD.gn` contains
1359 `ts_definitions("generate_definitions")`, which lists `foo_shared.js` in
1360 `js_files` or alternatively depends on `:copy_definitions` which copies
1361 `foo_shared.d.ts`, then add `//ui/webui/resources/foo:generate_definitions`
1362 to your `ts_library()` target's `extra_deps` as follows:
1363
1364```
1365ts_library("build_ts") {
1366 root_dir = my_root_dir
1367 out_dir = "$target_gen_dir/tsc"
1368 tsconfig_base = "tsconfig_base.json"
1369 deps = [ "//ui/webui/resources:library" ]
1370
1371 # This line is new
1372 extra_deps = [ "//ui/webui/resources/foo:generate_definitions" ]
1373
1374 in_files = my_project_ts_files
1375}
1376```
1377
1378Note: If using the `build_webui()` wrapper rule, add the new dependency to
1379`ts_deps` (for a TypeScript file) or `ts_extra_deps` (for a JavaScript file
1380with definitions).
1381
1382Failure to follow these steps can lead to other developers hitting flaky build
1383errors and/or having their unrelated CLs reverted by sheriffs who aren't always
1384aware that the regression range given in automated tools may not contain the
1385true culprit for TypeScript related build flakes.
1386
Dan Beam079d5c12017-06-16 19:23:301387## See also
1388
Amos Limf916d572018-05-21 23:10:351389* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:301390* WebUI's HTML/CSS/JS code follows the [Chromium Web
1391 Development Style Guide](../styleguide/web/web.md)
1392
1393
1394<script>
1395let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
1396let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
1397
1398let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
1399let hrefs = localLinks.map(a => a.href.split('#')[1]);
1400
1401hrefs.forEach(href => {
1402 if (names.includes(href))
1403 console.info('found: ' + href);
1404 else
1405 console.error('broken href: ' + href);
1406})
1407</script>