blob: 816ec5cb7937a2a8815650895b5f3f420e1fb7c0 [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);
Kevin Graney1a0030f2023-10-24 23:31:17444};
Rebekah Potter65c7cae2022-12-15 22:19:49445
446// Called from C++ side of chrome://donuts. (Browser -> Renderer)
447interface Page {
448 DonutsBaked(uint32 num_donuts);
Kevin Graney1a0030f2023-10-24 23:31:17449};
Rebekah Potter65c7cae2022-12-15 22:19:49450```
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
Alex Moshchuk031f7832023-04-04 16:59:071184by default. Note that `<webview>` is only available on desktop platforms.
Lukasz Anforowicz11e59532018-10-23 22:46:211185
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351186## JavaScript Error Reporting
1187
Ian Barkley-Yeung43404622022-03-25 00:00:441188By default, errors in the JavaScript or TypeScript of a WebUI page will generate
1189error reports which appear in Google's internal [go/crash](http://go/crash)
1190reports page. These error reports will only be generated for Google Chrome
1191builds, not Chromium or other Chromium-based browsers.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351192
Ian Barkley-Yeung43404622022-03-25 00:00:441193Specifically, an error report will be generated when the JavaScript or
1194TypeScript for a WebUI-based chrome:// page does one of the following:
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351195* Generates an uncaught exception,
1196* Has a promise which is rejected, and no rejection handler is provided, or
1197* Calls `console.error()`.
1198
1199Such errors will appear alongside other crashes in the
Ian Barkley-Yeung43404622022-03-25 00:00:441200`product_name=Chrome_ChromeOS`, `product_name=Chrome_Lacros`, or
1201`product_name=Chrome_Linux` lists on [go/crash](http://go/crash).
1202
1203The signature of the error is the error message followed by the URL on which the
1204error appeared. For example, if chrome://settings/lazy_load.js throws a
1205TypeError with a message `Cannot read properties of null (reading 'select')` and
1206does not catch it, the magic signature would be
1207```
1208Uncaught TypeError: Cannot read properties of null (reading 'select') (chrome://settings)
1209```
1210To avoid spamming the system, only one error report with a given message will be
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351211generated per hour.
1212
1213If you are getting error reports for an expected condition, you can turn off the
Ian Barkley-Yeung43404622022-03-25 00:00:441214reports simply by changing `console.error()` into `console.warn()`. For
1215instance, if JavaScript is calling `console.error()` when the user tries to
1216connect to an unavailable WiFi network at the same time the page shows the user
1217an error message, the `console.error()` should be replaced with a
1218`console.warn()`.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:351219
1220If you wish to get more control of the JavaScript error messages, for example
1221to change the product name or to add additional data, you may wish to switch to
1222using `CrashReportPrivate.reportError()`. If you do so, be sure to override
1223`WebUIController::IsJavascriptErrorReportingEnabled()` to return false for your
1224page; this will avoid generating redundant error reports.
1225
Ian Barkley-Yeung43404622022-03-25 00:00:441226### Are JavaScript errors actually crashes?
1227JavaScript errors are not "crashes" in the C++ sense. They do not stop a process
1228from running, they do not cause a "sad tab" page. Some tooling refers to them as
1229crashes because they are going through the same pipeline as the C++ crashes, and
1230that pipeline was originally designed to handle crashes.
1231
1232### How much impact does this JavaScript error have?
1233That depends on the JavaScript error. In some cases, the errors have no user
1234impact; for instance, the "unavailable WiFi network calling `console.error()`"
1235example above. In other cases, JavaScript errors may be serious errors that
1236block the user from completing critical user journeys. For example, if the
1237JavaScript is supposed to un-hide one of several variants of settings page, but
1238the JavaScript has an unhandled exception before un-hiding any of them, then
1239the user will see a blank page and be unable to change that setting.
1240
1241Because it is difficult to automatically determine the severity of a given
1242error, JavaScript errors are currently all classified as "WARNING" level when
1243computing stability metrics.
1244
1245### Known issues
12461. Error reporting is currently enabled only on ChromeOS (ash and Lacros) and
1247 Linux.
Ian Barkley-Yeung20a8ff72021-07-01 01:06:3512482. Errors are only reported for chrome:// URLs.
12493. Unhandled promise rejections do not have a good stack.
12504. The line numbers and column numbers in the stacks are for the minified
1251 JavaScript and do not correspond to the line and column numbers of the
1252 original source files.
12535. Error messages with variable strings do not group well. For example, if the
1254 error message includes the name of a network, each network name will be its
1255 own signature.
1256
Dan Beam079d5c12017-06-16 19:23:301257## See also
1258
Amos Limf916d572018-05-21 23:10:351259* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:301260* WebUI's HTML/CSS/JS code follows the [Chromium Web
1261 Development Style Guide](../styleguide/web/web.md)
1262
1263
1264<script>
1265let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
1266let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
1267
1268let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
1269let hrefs = localLinks.map(a => a.href.split('#')[1]);
1270
1271hrefs.forEach(href => {
1272 if (names.includes(href))
1273 console.info('found: ' + href);
1274 else
1275 console.error('broken href: ' + href);
1276})
1277</script>