blob: 66f97629d4788af6bb937861ee7f704150cd3fe2 [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
17<a name="What_is_webui"></a>
18## What is "WebUI"?
19
20"WebUI" is a term used to loosely describe **parts of Chrome's UI
21implemented with web technologies** (i.e. HTML, CSS, JavaScript).
22
23Examples of WebUI in Chromium:
24
25* Settings (chrome://settings)
26* History (chrome://history)
27* Downloads (chrome://downloads)
28
29<div class="note">
30Not all web-based UIs in Chrome have chrome:// URLs.
31</div>
32
33This document explains how WebUI works.
34
35<a name="bindings"></a>
36## What's different from a web page?
37
38WebUIs are granted super powers so that they can manage Chrome itself. For
39example, it'd be very hard to implement the Settings UI without access to many
40different privacy and security sensitive services. Access to these services are
41not granted by default.
42
43Only special URLs are granted WebUI "bindings" via the child security process.
44
45Specifically, these bindings:
46
47* give a renderer access to load [`chrome:`](#chrome_urls) URLS
48 * this is helpful for shared libraries, i.e. `chrome://resources/`
49* allow the browser to execute arbitrary JavaScript in that renderer via
50 [`CallJavascriptFunction()`](#CallJavascriptFunction)
51* allow communicating from the renderer to the browser with
52 [`chrome.send()`](#chrome_send) and friends
53* ignore content settings regarding showing images or executing JavaScript
54
55<a name="chrome_urls"></a>
56## How `chrome:` URLs work
57
58<div class="note">
59A URL is of the format &lt;protocol&gt;://&lt;host&gt;/&lt;path&gt;.
60</div>
61
62A `chrome:` URL loads a file from disk, memory, or can respond dynamically.
63
64Because Chrome UIs generally need access to the browser (not just the current
65tab), much of the C++ that handles requests or takes actions lives in the
66browser process. The browser has many more privileges than a renderer (which is
67sandboxed and doesn't have file access), so access is only granted for certain
68URLs.
69
70### `chrome:` protocol
71
72Chrome recognizes a list of special protocols, which it registers while starting
73up.
74
75Examples:
76
James Lissiak28b21a62019-05-15 15:32:0477* devtools:
Dan Beam079d5c12017-06-16 19:23:3078* chrome-extensions:
Adam Langley81be0732019-03-06 18:38:4579* chrome:
Dan Beam079d5c12017-06-16 19:23:3080* file:
81* view-source:
82
83This document mainly cares about the **chrome:** protocol, but others can also
84be granted [WebUI bindings](#bindings) or have special
85properties.
86
87### `chrome:` hosts
88
89After registering the `chrome:` protocol, a set of factories are created. These
90factories contain a list of valid host names. A valid hostname generates a
91controller.
92
93In the case of `chrome:` URLs, these factories are registered early in the
94browser process lifecycle.
95
96```c++
97// ChromeBrowserMainParts::PreMainMessageLoopRunImpl():
98content::WebUIControllerFactory::RegisterFactory(
99 ChromeWebUIControllerFactory::GetInstance());
100```
101
102When a URL is requested, a new renderer is created to load the URL, and a
103corresponding class in the browser is set up to handle messages from the
104renderer to the browser (a `RenderFrameHost`).
105
106The URL of the request is inspected:
107
108```c++
109if (url.SchemeIs("chrome") && url.host_piece() == "donuts") // chrome://donuts
110 return &NewWebUI<DonutsUI>;
111return nullptr; // Not a known host; no special access.
112```
113
114and if a factory knows how to handle a host (returns a `WebUIFactoryFunction`),
115the navigation machinery [grants the renderer process WebUI
116bindings](#bindings) via the child security policy.
117
118```c++
119// RenderFrameHostImpl::AllowBindings():
120if (bindings_flags & BINDINGS_POLICY_WEB_UI) {
dbeam8b52edff2017-06-16 22:36:18121 ChildProcessSecurityPolicyImpl::GetInstance()->GrantWebUIBindings(
122 GetProcess()->GetID());
Dan Beam079d5c12017-06-16 19:23:30123}
124```
125
126The factory creates a [`WebUIController`](#WebUIController) for a tab.
127Here's an example:
128
129```c++
130// Controller for chrome://donuts.
131class DonutsUI : public content::WebUIController {
132 public:
133 DonutsUI(content::WebUI* web_ui) : content::WebUIController(web_ui) {
134 content::WebUIDataSource* source =
135 content::WebUIDataSource::Create("donuts"); // "donuts" == hostname
136 source->AddString("mmmDonuts", "Mmm, donuts!"); // Translations.
rbpotterf50e0252020-09-14 16:38:33137 source->AddResourcePath("", IDR_DONUTS_HTML); // Home page.
Dan Beam079d5c12017-06-16 19:23:30138 content::WebUIDataSource::Add(source);
139
140 // Handles messages from JavaScript to C++ via chrome.send().
Jeremy Romane0760a402018-03-02 18:19:40141 web_ui->AddMessageHandler(std::make_unique<OvenHandler>());
Dan Beam079d5c12017-06-16 19:23:30142 }
143};
144```
145
146If we assume the contents of `IDR_DONUTS_HTML` yields:
147
148```html
149<h1>$i18n{mmmDonuts}</h1>
150```
151
152Visiting `chrome://donuts` should show in something like:
153
154<div style="border: 1px solid black; padding: 10px;">
155<h1>Mmmm, donuts!</h1>
156</div>
157
158Delicious success.
159
Christopher Lam50ab1e92019-10-29 04:33:16160By default $i18n{} escapes strings for HTML. $i18nRaw{} can be used for
161translations that embed HTML, and $i18nPolymer{} can be used for Polymer
162bindings. See
163[this comment](https://bugs.chromium.org/p/chromium/issues/detail?id=1010815#c1)
164for more information.
165
Dan Beam079d5c12017-06-16 19:23:30166## C++ classes
167
168### WebUI
169
170`WebUI` is a high-level class and pretty much all HTML-based Chrome UIs have
171one. `WebUI` lives in the browser process, and is owned by a `RenderFrameHost`.
172`WebUI`s have a concrete implementation (`WebUIImpl`) in `content/` and are
173created in response to navigation events.
174
175A `WebUI` knows very little about the page it's showing, and it owns a
176[`WebUIController`](#WebUIController) that is set after creation based on the
177hostname of a requested URL.
178
179A `WebUI` *can* handle messages itself, but often defers these duties to
180separate [`WebUIMessageHandler`](#WebUIMessageHandler)s, which are generally
181designed for handling messages on certain topics.
182
183A `WebUI` can be created speculatively, and are generally fairly lightweight.
184Heavier duty stuff like hard initialization logic or accessing services that may
185have side effects are more commonly done in a
186[`WebUIController`](#WebUIController) or
187[`WebUIMessageHandler`s](#WebUIMessageHandler).
188
189`WebUI` are created synchronously on the UI thread in response to a URL request,
190and are re-used where possible between navigations (i.e. refreshing a page).
191Because they run in a separate process and can exist before a corresponding
192renderer process has been created, special care is required to communicate with
193the renderer if reliable message passing is required.
194
195<a name="WebUIController"></a>
196### WebUIController
197
198A `WebUIController` is the brains of the operation, and is responsible for
199application-specific logic, setting up translations and resources, creating
200message handlers, and potentially responding to requests dynamically. In complex
201pages, logic is often split across multiple
202[`WebUIMessageHandler`s](#WebUIMessageHandler) instead of solely in the
203controller for organizational benefits.
204
205A `WebUIController` is owned by a [`WebUI`](#WebUI), and is created and set on
206an existing [`WebUI`](#WebUI) when the correct one is determined via URL
207inspection (i.e. chrome://settings creates a generic [`WebUI`](#WebUI) with a
208settings-specific `WebUIController`).
209
rbpotterf50e0252020-09-14 16:38:33210<a name="WebUIDataSource"></a>
Dan Beam079d5c12017-06-16 19:23:30211### WebUIDataSource
212
rbpotterf50e0252020-09-14 16:38:33213The `WebUIDataSource` class provides a place for data to live for WebUI pages.
214
215Examples types of data stored in this class are:
216
217* static resources (i.e. .html files packed into bundles and pulled off of disk)
218* translations
219* dynamic feature values (i.e. whether a feature is enabled)
220
221Data sources are set up in the browser process (in C++) and are accessed by
222loading URLs from the renderer.
223
224Below is an example of a simple data source (in this case, Chrome's history
225page):
226
227```c++
228content::WebUIDataSource* source = content::WebUIDataSource::Create("history");
229
230source->AddResourcePath("sign_in_promo.svg", IDR_HISTORY_SIGN_IN_PROMO_SVG);
231source->AddResourcePath("synced_tabs.html", IDR_HISTORY_SYNCED_TABS_HTML);
232
233source->AddString("title", IDS_HISTORY_TITLE);
234source->AddString("moreFromThisSite", IDS_HISTORY_MORE_FROM_THIS_SITE);
235
236source->AddBoolean("showDateRanges",
237 base::FeatureList::IsEnabled(features::kHistoryShowDateRanges));
238
239webui::SetupWebUIDataSource(
240 source, base::make_span(kHistoryResources, kHistoryResourcesSize),
241 kGeneratedPath, IDR_HISTORY_HISTORY_HTML);
242
243content::WebUIDataSource::Add(source);
244```
245
246For more about each of the methods called on `WebUIDataSource` and the utility
247method that performs additional configuration, see [DataSources](#DataSources)
248and [WebUIDataSourceUtils](#WebUIDataSourceUtils)
249
Dan Beam079d5c12017-06-16 19:23:30250<a name="WebUIMessageHandler"></a>
251### WebUIMessageHandler
252
253Because some pages have many messages or share code that sends messages, message
254handling is often split into discrete classes called `WebUIMessageHandler`s.
255These handlers respond to specific invocations from JavaScript.
256
257So, the given C++ code:
258
259```c++
260void OvenHandler::RegisterMessages() {
Ayu Ishii33743432021-02-03 19:05:01261 web_ui()->RegisterMessageCallback(
262 "bakeDonuts",
263 base::BindRepeating(&OvenHandler::HandleBakeDonuts,
264 base::Unretained(this)));
Dan Beam079d5c12017-06-16 19:23:30265}
266
Jarryd21f7ba72019-08-07 19:59:45267void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23268 AllowJavascript();
269
270 CHECK_EQ(1u, args->GetSize());
271 // JavaScript numbers are doubles.
272 double num_donuts = args->GetList()[0].GetDouble();
Dan Beam079d5c12017-06-16 19:23:30273 GetOven()->BakeDonuts(static_cast<int>(num_donuts));
274}
275```
276
277Can be triggered in JavaScript with this example code:
278
279```js
280$('bakeDonutsButton').onclick = function() {
281 chrome.send('bakeDonuts', [5]); // bake 5 donuts!
282};
283```
284
rbpotterf50e0252020-09-14 16:38:33285<a name="DataSources">
286## Data Sources
287
288<a name="Create"></a>
289### WebUIDataSource::Create()
290
291This is a factory method required to create a WebUIDataSource instance. The
292argument to `Create()` is typically the host name of the page. Caller owns the
293result.
294
295<a name="Add"></a>
296### WebUIDataSource::Add()
297
298Once you've created and added some things to a data source, it'll need to be
299"added". This means transferring ownership. In practice, the data source is
300created in the browser process on the UI thread and transferred to the IO
301thread. Additionally, calling `Add()` will overwrite any existing data source
302with the same name.
303
304<div class="note">
305It's unsafe to keep references to a <code>WebUIDataSource</code> after calling
306<code>Add()</code>. Don't do this.
307</div>
308
309<a name="AddLocalizedString"></a>
310### WebUIDataSource::AddLocalizedString()
311
312Using an int reference to a grit string (starts with "IDS" and lives in a .grd
313or .grdp file), adding a string with a key name will be possible to reference
314via the `$i18n{}` syntax (and will be replaced when requested) or later
315dynamically in JavaScript via `loadTimeData.getString()` (or `getStringF`).
316
317<a name="AddResourcePath"></a>
318### WebUIDataSource::AddResourcePath()
319
320Using an int reference to a grit resource (starts with "IDR" and lives in a .grd
321or .grdp file), adds a resource to the UI with the specified path.
322
323It's generally a good idea to call <code>AddResourcePath()</code> with the empty
324path and a resource ID that should be served as the "catch all" resource to
325respond with. This resource will be served for requests like "chrome://history",
326or "chrome://history/pathThatDoesNotExist". It will not be served for requests
327that look like they are attempting to fetch a specific file, like
328"chrome://history/file\_that\_does\_not\_exist.js". This is so that if a user
329enters a typo when trying to load a subpage like "chrome://history/syncedTabs"
330they will be redirected to the main history page, instead of seeing an error,
331but incorrect imports in the source code will fail, so that they can be more
332easily found and corrected.
333
334<a name="AddBoolean"></a>
335### WebUIDataSource::AddBoolean()
336
337Often a page needs to know whether a feature is enabled. This is a good use case
338for `WebUIDataSource::AddBoolean()`. Then, in the Javascript, one can write
339code like this:
340
341```js
342if (loadTimeData.getBoolean('myFeatureIsEnabled')) {
343 ...
344}
345```
346
347<div class="note">
348Data sources are not recreated on refresh, and therefore values that are dynamic
349(i.e. that can change while Chrome is running) may easily become stale. It may
350be preferable to use <code>cr.sendWithPromise()</code> to initialize dynamic
351values and call <code>FireWebUIListener()</code> to update them.
352
353If you really want or need to use <code>AddBoolean()</code> for a dynamic value,
354make sure to call <code>WebUIDataSource::Update()</code> when the value changes.
355</div>
356
357<a name="WebUIDataSourceUtils"></a>
358## WebUI utils for working with data sources
359
360chrome/browser/ui/webui/webui\_util.\* contains a number of methods to simplify
361common configuration tasks.
362
363<a name="AddLocalizedStringsBulk"></a>
dpapadf61285c2021-02-09 21:56:39364### WebUIDataSource::AddLocalizedStrings()
rbpotterf50e0252020-09-14 16:38:33365
366Many Web UI data sources need to be set up with a large number of localized
367strings. Instead of repeatedly calling <code>AddLocalizedString()</code>, create
dpapadf61285c2021-02-09 21:56:39368an array of all the strings and use <code>AddLocalizedStrings()</code>:
rbpotterf50e0252020-09-14 16:38:33369
370```c++
371 static constexpr webui::LocalizedString kStrings[] = {
372 // Localized strings (alphabetical order).
373 {"actionMenuDescription", IDS_HISTORY_ACTION_MENU_DESCRIPTION},
374 {"ariaRoleDescription", IDS_HISTORY_ARIA_ROLE_DESCRIPTION},
375 {"bookmarked", IDS_HISTORY_ENTRY_BOOKMARKED},
376 };
dpapadf61285c2021-02-09 21:56:39377 source->AddLocalizedStrings(kStrings);
rbpotterf50e0252020-09-14 16:38:33378```
379
dpapad71310e5a72021-02-16 19:59:19380<a name="AddResourcePaths"></a>
381### WebUIDataSource::AddResourcePaths()
rbpotterf50e0252020-09-14 16:38:33382
383Similar to the localized strings, many Web UIs need to add a large number of
dpapad71310e5a72021-02-16 19:59:19384resource paths. In this case, use <code>AddResourcePaths()</code> to
385replace repeated calls to <code>AddResourcePath()</code>.
rbpotterf50e0252020-09-14 16:38:33386
387```c++
388 static constexpr webui::ResourcePath kPdfResources[] = {
389 {"pdf/browser_api.js", IDR_PDF_BROWSER_API_JS},
390 {"pdf/constants.js", IDR_PDF_CONSTANTS_JS},
391 {"pdf/controller.js", IDR_PDF_CONTROLLER_JS},
392 };
dpapad71310e5a72021-02-16 19:59:19393 source->AddResourcePaths(kStrings);
rbpotterf50e0252020-09-14 16:38:33394```
395
dpapad71310e5a72021-02-16 19:59:19396The same method can be leveraged for cases that directly use constants defined
397by autogenerated grit resources map header files. For example, the autogenerated
398print\_preview\_resources\_map.h header defines a
399<code>webui::ResourcePath</code> array named <code>kPrintPreviewResources</code>
400and a <code>size\_t kPrintPreviewResourcesSize</code>. All the resources in this
rbpotterf50e0252020-09-14 16:38:33401resource map can be added as follows:
402
403```c++
dpapad71310e5a72021-02-16 19:59:19404 source->AddResourcePaths(
rbpotterf50e0252020-09-14 16:38:33405 base::make_span(kPrintPreviewResources, kPrintPreviewResourcesSize));
406```
407
408<a name="SetupWebUIDataSource"></a>
Rebekah Potter5691cab2020-10-29 21:30:35409### webui::SetupWebUIDataSource()
rbpotterf50e0252020-09-14 16:38:33410
Rebekah Potter5691cab2020-10-29 21:30:35411This method performs common configuration tasks on a data source for a Web UI
412that uses JS modules. When creating a Web UI that uses JS modules, use this
413utility instead of duplicating the configuration steps it performs elsewhere.
414Specific setup steps include:
rbpotterf50e0252020-09-14 16:38:33415
416* Setting the content security policy to allow the data source to load only
417 resources from its own host (e.g. chrome://history), chrome://resources, and
418 chrome://test (used to load test files).
419* Enabling i18n template replacements by calling <code>UseStringsJs()</code> and
420 <code>EnableReplaceI18nInJS()</code> on the data source.
421* Adding the test loader files to the data source, so that test files can be
422 loaded as JS modules.
423* Setting the resource to load for the empty path.
Rebekah Potter5691cab2020-10-29 21:30:35424* Adding all resources from a GritResourceMap.
rbpotterf50e0252020-09-14 16:38:33425
Dan Beam079d5c12017-06-16 19:23:30426## Browser (C++) &rarr; Renderer (JS)
427
428<a name="AllowJavascript"></a>
429### WebUIMessageHandler::AllowJavascript()
430
Adam Langley81be0732019-03-06 18:38:45431A tab that has been used for settings UI may be reloaded, or may navigate to an
432external origin. In both cases, one does not want callbacks from C++ to
433Javascript to run. In the former case, the callbacks will occur when the
434Javascript doesn't expect them. In the latter case, sensitive information may be
435delivered to an untrusted origin.
436
437Therefore each message handler maintains
438[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
439that describes whether delivering callbacks to Javascript is currently
440appropriate. This boolean is set by calling `AllowJavascript`, which should be
441done when handling a call from Javascript, because that indicates that the page
442is ready for the subsequent callback. (See
443[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
444If the tab navigates or reloads,
445[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
446is called to clear the flag.
447
448Therefore, before each callback from C++ to Javascript, the flag must be tested
449by calling
450[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
451If false, then the callback must be dropped. (When the flag is false, calling
452[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
453will crash. See
454[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
455
456Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
457the case where an asynchronous operation is started, the settings page is
458reloaded, and the user triggers another operation using the original message
459handler. The `javascript_allowed_` boolean will be true, but the original
460callback should still be dropped because it relates to a operation that was
461discarded by the reload. (Reloading settings UI does _not_ cause message handler
462objects to be deleted.)
463
464Thus a message handler may override
465[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
466to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30467
468In the JS:
469
470```js
471window.onload = function() {
472 app.initialize();
473 chrome.send('startPilotLight');
474};
475```
476
477In the C++:
478
479```c++
480void OvenHandler::HandleStartPilotLight(cont base::ListValue* /*args*/) {
481 AllowJavascript();
482 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
483 GetOven()->StartPilotLight();
484}
485```
486
487<div class="note">
488Relying on the <code>'load'</code> event or browser-side navigation callbacks to
489detect page readiness omits <i>application-specific</i> initialization, and a
490custom <code>'initialized'</code> message is often necessary.
491</div>
492
493<a name="CallJavascriptFunction"></a>
494### WebUIMessageHandler::CallJavascriptFunction()
495
496When the browser process needs to tell the renderer/JS of an event or otherwise
497execute code, it can use `CallJavascriptFunction()`.
498
499<div class="note">
500Javascript must be <a href="#AllowJavascript">allowed</a> to use
501<code>CallJavscriptFunction()</code>.
502</div>
503
504```c++
505void OvenHandler::OnPilotLightExtinguished() {
506 CallJavascriptFunction("app.pilotLightExtinguished");
507}
508```
509
510This works by crafting a string to be evaluated in the renderer. Any arguments
511to the call are serialized to JSON and the parameter list is wrapped with
512
513```
514// See WebUI::GetJavascriptCall() for specifics:
515"functionCallName(" + argumentsAsJson + ")"
516```
517
518and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
519
520While this works, it implies that:
521
522* a global method must exist to successfully run the Javascript request
523* any method can be called with any parameter (far more access than required in
524 practice)
525
526^ These factors have resulted in less use of `CallJavascriptFunction()` in the
527webui codebase. This functionality can easily be accomplished with the following
528alternatives:
529
530* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
531 when an event occurs in C++ and is more loosely coupled (nothing blows up if
532 the event dispatch is ignored). JS subscribes to notifications via
533 [`cr.addWebUIListener`](#cr_addWebUIListener).
534* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
535 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
536 when Javascript requires a response to an inquiry about C++-canonical state
537 (i.e. "Is Autofill enabled?", "Is the user incognito?")
538
539<a name="FireWebUIListener"></a>
540### WebUIMessageHandler::FireWebUIListener()
541
542`FireWebUIListener()` is used to notify a registered set of listeners that an
543event has occurred. This is generally used for events that are not guaranteed to
544happen in timely manner, or may be caused to happen by unpredictable events
545(i.e. user actions).
546
547Here's some example to detect a change to Chrome's theme:
548
549```js
550cr.addWebUIListener("theme-changed", refreshThemeStyles);
551```
552
553This Javascript event listener can be triggered in C++ via:
554
555```c++
556void MyHandler::OnThemeChanged() {
557 FireWebUIListener("theme-changed");
558}
559```
560
561Because it's not clear when a user might want to change their theme nor what
562theme they'll choose, this is a good candidate for an event listener.
563
564If you simply need to get a response in Javascript from C++, consider using
565[`cr.sendWithPromise()`](#cr_sendWithPromise) and
566[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
567
568<a name="OnJavascriptAllowed"></a>
569### WebUIMessageHandler::OnJavascriptAllowed()
570
571`OnJavascriptDisallowed()` is a lifecycle method called in response to
572[`AllowJavascript()`](#AllowJavascript). It is a good place to register
573observers of global services or other callbacks that might call at unpredictable
574times.
575
576For example:
577
578```c++
579class MyHandler : public content::WebUIMessageHandler {
580 MyHandler() {
581 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
582 }
583 void OnGlobalServiceEvent() {
584 FireWebUIListener("global-thing-happened");
585 }
586};
587```
588
589Because browser-side C++ handlers are created before a renderer is ready, the
590above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
591before the renderer is ready, which may result in dropped updates or
592accidentally running Javascript in a renderer that has navigated to a new URL.
593
594A safer way to set up communication is:
595
596```c++
597class MyHandler : public content::WebUIMessageHandler {
598 public:
Dan Beam079d5c12017-06-16 19:23:30599 void OnJavascriptAllowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17600 observation_.Observe(GetGlobalService()); // <-- DO THIS.
Dan Beam079d5c12017-06-16 19:23:30601 }
602 void OnJavascriptDisallowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17603 observation_.Reset(); // <-- AND THIS.
Dan Beam079d5c12017-06-16 19:23:30604 }
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17605 base::ScopedObservation<MyHandler, GlobalService> observation_{this}; // <-- ALSO HANDY.
Dan Beam079d5c12017-06-16 19:23:30606```
607when a renderer has been created and the
608document has loaded enough to signal to the C++ that it's ready to respond to
609messages.
610
611<a name="OnJavascriptDisallowed"></a>
612### WebUIMessageHandler::OnJavascriptDisallowed()
613
614`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
615it's safe to send JavaScript messsages to the renderer.
616
617There's a number of situations that result in this method being called:
618
619* renderer doesn't exist yet
620* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23621* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30622* tab refresh
623* renderer crash
624
625Though it's possible to programmatically disable Javascript, it's uncommon to
626need to do so.
627
628Because there's no single strategy that works for all cases of a renderer's
629state (i.e. queueing vs dropping messages), these lifecycle methods were
630introduced so a WebUI application can implement these decisions itself.
631
632Often, it makes sense to disconnect from observers in
633`OnJavascriptDisallowed()`:
634
635```c++
636void OvenHandler::OnJavascriptDisallowed() {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17637 scoped_oven_observation_.Reset()
Dan Beam079d5c12017-06-16 19:23:30638}
639```
640
641Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
642`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
643scoped observer that automatically unsubscribes on destruction but can also
644imperatively unsubscribe in `OnJavascriptDisallowed()`.
645
646<a name="RejectJavascriptCallback"></a>
647### WebUIMessageHandler::RejectJavascriptCallback()
648
649This method is called in response to
650[`cr.sendWithPromise()`](#cr_sendWithPromise) to reject the issued Promise. This
651runs the rejection (second) callback in the [Promise's
652executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
653and any
654[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
655callbacks in the chain.
656
657```c++
658void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23659 AllowJavascript();
660 if (!GetOven()->HasGas()) {
661 RejectJavascriptCallback(args->GetList()[0],
662 base::StringValue("need gas to cook the donuts!"));
663 }
Dan Beam079d5c12017-06-16 19:23:30664```
665
666This method is basically just a
667[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
668global "cr.webUIResponse" method with a success value of false.
669
670```c++
671// WebUIMessageHandler::RejectJavascriptCallback():
672CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:18673 response);
Dan Beam079d5c12017-06-16 19:23:30674```
675
676See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
677
678<a name="ResolveJavascriptCallback"></a>
679### WebUIMessageHandler::ResolveJavascriptCallback()
680
681This method is called in response to
682[`cr.sendWithPromise()`](#cr_sendWithPromise) to fulfill an issued Promise,
683often with a value. This results in runnings any fulfillment (first) callbacks
684in the associate Promise executor and any registered
685[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
686callbacks.
687
688So, given this JS code:
689
690```js
691cr.sendWithPromise('bakeDonuts').then(function(numDonutsBaked) {
692 shop.donuts += numDonutsBaked;
693});
694```
695
696Some handling C++ might do this:
697
698```c++
699void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23700 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:30701 double num_donuts_baked = GetOven()->BakeDonuts();
Michael Giuffrida14938292019-05-31 21:30:23702 ResolveJavascriptCallback(args->GetList()[0], num_donuts_baked);
Dan Beam079d5c12017-06-16 19:23:30703}
704```
705
706## Renderer (JS) &rarr; Browser (C++)
707
708<a name="chrome_send"></a>
709### chrome.send()
710
711When the JavaScript `window` object is created, a renderer is checked for [WebUI
712bindings](#bindings).
713
714```c++
715// RenderFrameImpl::DidClearWindowObject():
716if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
717 WebUIExtension::Install(frame_);
718```
719
720If the bindings exist, a global `chrome.send()` function is exposed to the
721renderer:
722
723```c++
724// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:35725v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:30726chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:18727 gin::CreateFunctionTemplate(
Ayu Ishii33743432021-02-03 19:05:01728 isolate,
729 base::BindRepeating(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:30730```
731
732The `chrome.send()` method takes a message name and argument list.
733
734```js
735chrome.send('messageName', [arg1, arg2, ...]);
736```
737
738The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:37739`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:30740
741```c++
742// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:37743render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
744 frame->GetDocument().Url(),
745 message, *content));
Dan Beam079d5c12017-06-16 19:23:30746```
747```c++
748// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:37749IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:30750```
751
752The browser-side code does a map lookup for the message name and calls the found
753callback with the deserialized arguments:
754
755```c++
756// WebUIImpl::ProcessWebUIMessage():
757message_callbacks_.find(message)->second.Run(&args);
758```
759
760<a name="cr_addWebUIListener">
761### cr.addWebUIListener()
762
763WebUI listeners are a convenient way for C++ to inform JavaScript of events.
764
765Older WebUI code exposed public methods for event notification, similar to how
766responses to [chrome.send()](#chrome_send) used to work. They both
Ian Barkley-Yeung4f4f71d2020-06-09 00:38:13767resulted in global namespace pollution, but it was additionally hard to stop
Dan Beam079d5c12017-06-16 19:23:30768listening for events in some cases. **cr.addWebUIListener** is preferred in new
769code.
770
771Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
772just like [cr.sendWithPromise()](#cr_sendWithPromise).
773
774```js
775// addWebUIListener():
776webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
777webUIListenerMap[eventName][createUid()] = callback;
778```
779
780The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
781with an event name and a variable number of arguments.
782
783```c++
784// WebUIMessageHandler:
785template <typename... Values>
786void FireWebUIListener(const std::string& event_name, const Values&... values) {
787 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
788 values...);
789}
790```
791
792C++ handlers call this `FireWebUIListener` method when an event occurs that
793should be communicated to the JavaScript running in a tab.
794
795```c++
796void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
797 FireWebUIListener("donuts-baked", base::FundamentalValue(num_donuts));
798}
799```
800
801JavaScript can listen for WebUI events via:
802
803```js
804var donutsReady = 0;
805cr.addWebUIListener('donuts-baked', function(numFreshlyBakedDonuts) {
806 donutsReady += numFreshlyBakedDonuts;
807});
808```
809
810<a name="cr_sendWithPromise"></a>
811### cr.sendWithPromise()
812
813`cr.sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
814triggering a message requires a response:
815
816```js
817chrome.send('getNumberOfDonuts'); // No easy way to get response!
818```
819
820In older WebUI pages, global methods were exposed simply so responses could be
821sent. **This is discouraged** as it pollutes the global namespace and is harder
822to make request specific or do from deeply nested code.
823
824In newer WebUI pages, you see code like this:
825
826```js
827cr.sendWithPromise('getNumberOfDonuts').then(function(numDonuts) {
828 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
829});
830```
831
832On the C++ side, the message registration is similar to
833[`chrome.send()`](#chrome_send) except that the first argument in the
834message handler's list is a callback ID. That ID is passed to
835`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
836JavaScript and calling the `then()` function.
837
838```c++
839void DonutHandler::HandleGetNumberOfDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23840 AllowJavascript();
841
842 const base::Value& callback_id = args->GetList()[0];
Dan Beam079d5c12017-06-16 19:23:30843 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Michael Giuffrida14938292019-05-31 21:30:23844 ResolveJavascriptCallback(callback_id, base::FundamentalValue(num_donuts));
Dan Beam079d5c12017-06-16 19:23:30845}
846```
847
848Under the covers, a map of `Promise`s are kept in JavaScript.
849
850The callback ID is just a namespaced, ever-increasing number. It's used to
851insert a `Promise` into the JS-side map when created.
852
853```js
854// cr.sendWithPromise():
855var id = methodName + '_' + uidCounter++;
856chromeSendResolverMap[id] = new PromiseResolver;
857chrome.send(methodName, [id].concat(args));
858```
859
860The corresponding number is used to look up a `Promise` and reject or resolve it
861when the outcome is known.
862
863```js
864// cr.webUIResponse():
865var resolver = chromeSendResolverMap[id];
866if (success)
867 resolver.resolve(response);
868else
869 resolver.reject(response);
870```
871
872This approach still relies on the C++ calling a globally exposed method, but
873reduces the surface to only a single global (`cr.webUIResponse`) instead of
874many. It also makes per-request responses easier, which is helpful when multiple
875are in flight.
876
Lukasz Anforowicz11e59532018-10-23 22:46:21877
878## Security considerations
879
880Because WebUI pages are highly privileged, they are often targets for attack,
881since taking control of a WebUI page can sometimes be sufficient to escape
882Chrome's sandbox. To make sure that the special powers granted to WebUI pages
883are safe, WebUI pages are restricted in what they can do:
884
Nasko Oskov24fc53c52021-01-08 10:02:36885* WebUI pages cannot embed http/https resources
Lukasz Anforowicz11e59532018-10-23 22:46:21886* WebUI pages cannot issue http/https fetches
887
888In the rare case that a WebUI page really needs to include web content, the safe
Nasko Oskov24fc53c52021-01-08 10:02:36889way to do this is by using an `<iframe>` tag. Chrome's security model gives
890process isolation between the WebUI and the web content. However, some extra
891precautions need to be taken, because there are properties of the page that are
892accessible cross-origin and malicious code can take advantage of such data to
893attack the WebUI. Here are some things to keep in mind:
Lukasz Anforowicz11e59532018-10-23 22:46:21894
Nasko Oskov24fc53c52021-01-08 10:02:36895* The WebUI page can receive postMessage payloads from the web and should
896 ensure it verifies any messages as they are not trustworthy.
897* The entire frame tree is visible to the embedded web content, including
898 ancestor origins.
899* The web content runs in the same StoragePartition and Profile as the WebUI,
900 which reflect where the WebUI page was loaded (e.g., the default profile,
901 Incognito, etc). The corresponding user credentials will thus be available to
902 the web content inside the WebUI, possibly showing the user as signed in.
Lukasz Anforowicz11e59532018-10-23 22:46:21903
Nasko Oskov24fc53c52021-01-08 10:02:36904Note: WebUIs have a default Content Security Policy which disallows embedding
905any frames. If you want to include any web content in an <iframe> you will need
906to update the policy for your WebUI. When doing so, allow only known origins and
907avoid making the policy more permissive than strictly necessary.
Lukasz Anforowicz11e59532018-10-23 22:46:21908
Nasko Oskov24fc53c52021-01-08 10:02:36909Alternatively, a `<webview>` tag can be used, which runs in a separate
910StoragePartition, a separate frame tree, and restricts postMessage communication
911by default. However, `<webview>` does not support Site Isolation and
912therefore it is not advisable to use for any sensitive content.
Lukasz Anforowicz11e59532018-10-23 22:46:21913
914
Dan Beam079d5c12017-06-16 19:23:30915## See also
916
Amos Limf916d572018-05-21 23:10:35917* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:30918* WebUI's HTML/CSS/JS code follows the [Chromium Web
919 Development Style Guide](../styleguide/web/web.md)
920
921
922<script>
923let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
924let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
925
926let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
927let hrefs = localLinks.map(a => a.href.split('#')[1]);
928
929hrefs.forEach(href => {
930 if (names.includes(href))
931 console.info('found: ' + href);
932 else
933 console.error('broken href: ' + href);
934})
935</script>