blob: df98e413e0bbd1bfa059e210398172be067dc444 [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
380<a name="AddResourcePathsBulk"></a>
381### webui::AddResourcePathsBulk()
382
383Similar to the localized strings, many Web UIs need to add a large number of
384resource paths. In this case, use <code>AddResourcePathsBulk()</code> to
385replace repeated calls to <code>AddResourcePath()</code>. There are two
386versions. One works almost identically to the strings case:
387
388```c++
389 static constexpr webui::ResourcePath kPdfResources[] = {
390 {"pdf/browser_api.js", IDR_PDF_BROWSER_API_JS},
391 {"pdf/constants.js", IDR_PDF_CONSTANTS_JS},
392 {"pdf/controller.js", IDR_PDF_CONTROLLER_JS},
393 };
394 webui::AddResourcePathsBulk(source, kStrings);
395```
396
397The second version instead accepts a span of <code>GritResourceMap</code> so
398that it can directly use constants defined by autogenerated grit resources map
399header files. For example, the autogenerated print\_preview\_resources\_map.h
400header defines a <code>GritResourceMap</code> named
401<code>kPrintPreviewResources</code> and a
402<code>size\_t kPrintPreviewResourcesSize</code>. All the resources in this
403resource map can be added as follows:
404
405```c++
406 webui::AddResourcePathsBulk(
407 source,
408 base::make_span(kPrintPreviewResources, kPrintPreviewResourcesSize));
409```
410
411<a name="SetupWebUIDataSource"></a>
Rebekah Potter5691cab2020-10-29 21:30:35412### webui::SetupWebUIDataSource()
rbpotterf50e0252020-09-14 16:38:33413
Rebekah Potter5691cab2020-10-29 21:30:35414This method performs common configuration tasks on a data source for a Web UI
415that uses JS modules. When creating a Web UI that uses JS modules, use this
416utility instead of duplicating the configuration steps it performs elsewhere.
417Specific setup steps include:
rbpotterf50e0252020-09-14 16:38:33418
419* Setting the content security policy to allow the data source to load only
420 resources from its own host (e.g. chrome://history), chrome://resources, and
421 chrome://test (used to load test files).
422* Enabling i18n template replacements by calling <code>UseStringsJs()</code> and
423 <code>EnableReplaceI18nInJS()</code> on the data source.
424* Adding the test loader files to the data source, so that test files can be
425 loaded as JS modules.
426* Setting the resource to load for the empty path.
Rebekah Potter5691cab2020-10-29 21:30:35427* Adding all resources from a GritResourceMap.
rbpotterf50e0252020-09-14 16:38:33428
Dan Beam079d5c12017-06-16 19:23:30429## Browser (C++) &rarr; Renderer (JS)
430
431<a name="AllowJavascript"></a>
432### WebUIMessageHandler::AllowJavascript()
433
Adam Langley81be0732019-03-06 18:38:45434A tab that has been used for settings UI may be reloaded, or may navigate to an
435external origin. In both cases, one does not want callbacks from C++ to
436Javascript to run. In the former case, the callbacks will occur when the
437Javascript doesn't expect them. In the latter case, sensitive information may be
438delivered to an untrusted origin.
439
440Therefore each message handler maintains
441[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
442that describes whether delivering callbacks to Javascript is currently
443appropriate. This boolean is set by calling `AllowJavascript`, which should be
444done when handling a call from Javascript, because that indicates that the page
445is ready for the subsequent callback. (See
446[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
447If the tab navigates or reloads,
448[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
449is called to clear the flag.
450
451Therefore, before each callback from C++ to Javascript, the flag must be tested
452by calling
453[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
454If false, then the callback must be dropped. (When the flag is false, calling
455[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
456will crash. See
457[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
458
459Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
460the case where an asynchronous operation is started, the settings page is
461reloaded, and the user triggers another operation using the original message
462handler. The `javascript_allowed_` boolean will be true, but the original
463callback should still be dropped because it relates to a operation that was
464discarded by the reload. (Reloading settings UI does _not_ cause message handler
465objects to be deleted.)
466
467Thus a message handler may override
468[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
469to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30470
471In the JS:
472
473```js
474window.onload = function() {
475 app.initialize();
476 chrome.send('startPilotLight');
477};
478```
479
480In the C++:
481
482```c++
483void OvenHandler::HandleStartPilotLight(cont base::ListValue* /*args*/) {
484 AllowJavascript();
485 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
486 GetOven()->StartPilotLight();
487}
488```
489
490<div class="note">
491Relying on the <code>'load'</code> event or browser-side navigation callbacks to
492detect page readiness omits <i>application-specific</i> initialization, and a
493custom <code>'initialized'</code> message is often necessary.
494</div>
495
496<a name="CallJavascriptFunction"></a>
497### WebUIMessageHandler::CallJavascriptFunction()
498
499When the browser process needs to tell the renderer/JS of an event or otherwise
500execute code, it can use `CallJavascriptFunction()`.
501
502<div class="note">
503Javascript must be <a href="#AllowJavascript">allowed</a> to use
504<code>CallJavscriptFunction()</code>.
505</div>
506
507```c++
508void OvenHandler::OnPilotLightExtinguished() {
509 CallJavascriptFunction("app.pilotLightExtinguished");
510}
511```
512
513This works by crafting a string to be evaluated in the renderer. Any arguments
514to the call are serialized to JSON and the parameter list is wrapped with
515
516```
517// See WebUI::GetJavascriptCall() for specifics:
518"functionCallName(" + argumentsAsJson + ")"
519```
520
521and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
522
523While this works, it implies that:
524
525* a global method must exist to successfully run the Javascript request
526* any method can be called with any parameter (far more access than required in
527 practice)
528
529^ These factors have resulted in less use of `CallJavascriptFunction()` in the
530webui codebase. This functionality can easily be accomplished with the following
531alternatives:
532
533* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
534 when an event occurs in C++ and is more loosely coupled (nothing blows up if
535 the event dispatch is ignored). JS subscribes to notifications via
536 [`cr.addWebUIListener`](#cr_addWebUIListener).
537* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
538 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
539 when Javascript requires a response to an inquiry about C++-canonical state
540 (i.e. "Is Autofill enabled?", "Is the user incognito?")
541
542<a name="FireWebUIListener"></a>
543### WebUIMessageHandler::FireWebUIListener()
544
545`FireWebUIListener()` is used to notify a registered set of listeners that an
546event has occurred. This is generally used for events that are not guaranteed to
547happen in timely manner, or may be caused to happen by unpredictable events
548(i.e. user actions).
549
550Here's some example to detect a change to Chrome's theme:
551
552```js
553cr.addWebUIListener("theme-changed", refreshThemeStyles);
554```
555
556This Javascript event listener can be triggered in C++ via:
557
558```c++
559void MyHandler::OnThemeChanged() {
560 FireWebUIListener("theme-changed");
561}
562```
563
564Because it's not clear when a user might want to change their theme nor what
565theme they'll choose, this is a good candidate for an event listener.
566
567If you simply need to get a response in Javascript from C++, consider using
568[`cr.sendWithPromise()`](#cr_sendWithPromise) and
569[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
570
571<a name="OnJavascriptAllowed"></a>
572### WebUIMessageHandler::OnJavascriptAllowed()
573
574`OnJavascriptDisallowed()` is a lifecycle method called in response to
575[`AllowJavascript()`](#AllowJavascript). It is a good place to register
576observers of global services or other callbacks that might call at unpredictable
577times.
578
579For example:
580
581```c++
582class MyHandler : public content::WebUIMessageHandler {
583 MyHandler() {
584 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
585 }
586 void OnGlobalServiceEvent() {
587 FireWebUIListener("global-thing-happened");
588 }
589};
590```
591
592Because browser-side C++ handlers are created before a renderer is ready, the
593above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
594before the renderer is ready, which may result in dropped updates or
595accidentally running Javascript in a renderer that has navigated to a new URL.
596
597A safer way to set up communication is:
598
599```c++
600class MyHandler : public content::WebUIMessageHandler {
601 public:
602 MyHandler() : observer_(this) {}
603 void OnJavascriptAllowed() override {
604 observer_.Add(GetGlobalService()); // <-- DO THIS.
605 }
606 void OnJavascriptDisallowed() override {
607 observer_.RemoveAll(); // <-- AND THIS.
608 }
609 ScopedObserver<MyHandler, GlobalService> observer_; // <-- ALSO HANDY.
610```
611when a renderer has been created and the
612document has loaded enough to signal to the C++ that it's ready to respond to
613messages.
614
615<a name="OnJavascriptDisallowed"></a>
616### WebUIMessageHandler::OnJavascriptDisallowed()
617
618`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
619it's safe to send JavaScript messsages to the renderer.
620
621There's a number of situations that result in this method being called:
622
623* renderer doesn't exist yet
624* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23625* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30626* tab refresh
627* renderer crash
628
629Though it's possible to programmatically disable Javascript, it's uncommon to
630need to do so.
631
632Because there's no single strategy that works for all cases of a renderer's
633state (i.e. queueing vs dropping messages), these lifecycle methods were
634introduced so a WebUI application can implement these decisions itself.
635
636Often, it makes sense to disconnect from observers in
637`OnJavascriptDisallowed()`:
638
639```c++
640void OvenHandler::OnJavascriptDisallowed() {
641 scoped_oven_observer_.RemoveAll()
642}
643```
644
645Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
646`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
647scoped observer that automatically unsubscribes on destruction but can also
648imperatively unsubscribe in `OnJavascriptDisallowed()`.
649
650<a name="RejectJavascriptCallback"></a>
651### WebUIMessageHandler::RejectJavascriptCallback()
652
653This method is called in response to
654[`cr.sendWithPromise()`](#cr_sendWithPromise) to reject the issued Promise. This
655runs the rejection (second) callback in the [Promise's
656executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
657and any
658[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
659callbacks in the chain.
660
661```c++
662void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23663 AllowJavascript();
664 if (!GetOven()->HasGas()) {
665 RejectJavascriptCallback(args->GetList()[0],
666 base::StringValue("need gas to cook the donuts!"));
667 }
Dan Beam079d5c12017-06-16 19:23:30668```
669
670This method is basically just a
671[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
672global "cr.webUIResponse" method with a success value of false.
673
674```c++
675// WebUIMessageHandler::RejectJavascriptCallback():
676CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:18677 response);
Dan Beam079d5c12017-06-16 19:23:30678```
679
680See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
681
682<a name="ResolveJavascriptCallback"></a>
683### WebUIMessageHandler::ResolveJavascriptCallback()
684
685This method is called in response to
686[`cr.sendWithPromise()`](#cr_sendWithPromise) to fulfill an issued Promise,
687often with a value. This results in runnings any fulfillment (first) callbacks
688in the associate Promise executor and any registered
689[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
690callbacks.
691
692So, given this JS code:
693
694```js
695cr.sendWithPromise('bakeDonuts').then(function(numDonutsBaked) {
696 shop.donuts += numDonutsBaked;
697});
698```
699
700Some handling C++ might do this:
701
702```c++
703void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23704 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:30705 double num_donuts_baked = GetOven()->BakeDonuts();
Michael Giuffrida14938292019-05-31 21:30:23706 ResolveJavascriptCallback(args->GetList()[0], num_donuts_baked);
Dan Beam079d5c12017-06-16 19:23:30707}
708```
709
710## Renderer (JS) &rarr; Browser (C++)
711
712<a name="chrome_send"></a>
713### chrome.send()
714
715When the JavaScript `window` object is created, a renderer is checked for [WebUI
716bindings](#bindings).
717
718```c++
719// RenderFrameImpl::DidClearWindowObject():
720if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
721 WebUIExtension::Install(frame_);
722```
723
724If the bindings exist, a global `chrome.send()` function is exposed to the
725renderer:
726
727```c++
728// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:35729v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:30730chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:18731 gin::CreateFunctionTemplate(
Ayu Ishii33743432021-02-03 19:05:01732 isolate,
733 base::BindRepeating(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:30734```
735
736The `chrome.send()` method takes a message name and argument list.
737
738```js
739chrome.send('messageName', [arg1, arg2, ...]);
740```
741
742The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:37743`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:30744
745```c++
746// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:37747render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
748 frame->GetDocument().Url(),
749 message, *content));
Dan Beam079d5c12017-06-16 19:23:30750```
751```c++
752// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:37753IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:30754```
755
756The browser-side code does a map lookup for the message name and calls the found
757callback with the deserialized arguments:
758
759```c++
760// WebUIImpl::ProcessWebUIMessage():
761message_callbacks_.find(message)->second.Run(&args);
762```
763
764<a name="cr_addWebUIListener">
765### cr.addWebUIListener()
766
767WebUI listeners are a convenient way for C++ to inform JavaScript of events.
768
769Older WebUI code exposed public methods for event notification, similar to how
770responses to [chrome.send()](#chrome_send) used to work. They both
Ian Barkley-Yeung4f4f71d2020-06-09 00:38:13771resulted in global namespace pollution, but it was additionally hard to stop
Dan Beam079d5c12017-06-16 19:23:30772listening for events in some cases. **cr.addWebUIListener** is preferred in new
773code.
774
775Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
776just like [cr.sendWithPromise()](#cr_sendWithPromise).
777
778```js
779// addWebUIListener():
780webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
781webUIListenerMap[eventName][createUid()] = callback;
782```
783
784The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
785with an event name and a variable number of arguments.
786
787```c++
788// WebUIMessageHandler:
789template <typename... Values>
790void FireWebUIListener(const std::string& event_name, const Values&... values) {
791 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
792 values...);
793}
794```
795
796C++ handlers call this `FireWebUIListener` method when an event occurs that
797should be communicated to the JavaScript running in a tab.
798
799```c++
800void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
801 FireWebUIListener("donuts-baked", base::FundamentalValue(num_donuts));
802}
803```
804
805JavaScript can listen for WebUI events via:
806
807```js
808var donutsReady = 0;
809cr.addWebUIListener('donuts-baked', function(numFreshlyBakedDonuts) {
810 donutsReady += numFreshlyBakedDonuts;
811});
812```
813
814<a name="cr_sendWithPromise"></a>
815### cr.sendWithPromise()
816
817`cr.sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
818triggering a message requires a response:
819
820```js
821chrome.send('getNumberOfDonuts'); // No easy way to get response!
822```
823
824In older WebUI pages, global methods were exposed simply so responses could be
825sent. **This is discouraged** as it pollutes the global namespace and is harder
826to make request specific or do from deeply nested code.
827
828In newer WebUI pages, you see code like this:
829
830```js
831cr.sendWithPromise('getNumberOfDonuts').then(function(numDonuts) {
832 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
833});
834```
835
836On the C++ side, the message registration is similar to
837[`chrome.send()`](#chrome_send) except that the first argument in the
838message handler's list is a callback ID. That ID is passed to
839`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
840JavaScript and calling the `then()` function.
841
842```c++
843void DonutHandler::HandleGetNumberOfDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23844 AllowJavascript();
845
846 const base::Value& callback_id = args->GetList()[0];
Dan Beam079d5c12017-06-16 19:23:30847 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Michael Giuffrida14938292019-05-31 21:30:23848 ResolveJavascriptCallback(callback_id, base::FundamentalValue(num_donuts));
Dan Beam079d5c12017-06-16 19:23:30849}
850```
851
852Under the covers, a map of `Promise`s are kept in JavaScript.
853
854The callback ID is just a namespaced, ever-increasing number. It's used to
855insert a `Promise` into the JS-side map when created.
856
857```js
858// cr.sendWithPromise():
859var id = methodName + '_' + uidCounter++;
860chromeSendResolverMap[id] = new PromiseResolver;
861chrome.send(methodName, [id].concat(args));
862```
863
864The corresponding number is used to look up a `Promise` and reject or resolve it
865when the outcome is known.
866
867```js
868// cr.webUIResponse():
869var resolver = chromeSendResolverMap[id];
870if (success)
871 resolver.resolve(response);
872else
873 resolver.reject(response);
874```
875
876This approach still relies on the C++ calling a globally exposed method, but
877reduces the surface to only a single global (`cr.webUIResponse`) instead of
878many. It also makes per-request responses easier, which is helpful when multiple
879are in flight.
880
Lukasz Anforowicz11e59532018-10-23 22:46:21881
882## Security considerations
883
884Because WebUI pages are highly privileged, they are often targets for attack,
885since taking control of a WebUI page can sometimes be sufficient to escape
886Chrome's sandbox. To make sure that the special powers granted to WebUI pages
887are safe, WebUI pages are restricted in what they can do:
888
Nasko Oskov24fc53c52021-01-08 10:02:36889* WebUI pages cannot embed http/https resources
Lukasz Anforowicz11e59532018-10-23 22:46:21890* WebUI pages cannot issue http/https fetches
891
892In the rare case that a WebUI page really needs to include web content, the safe
Nasko Oskov24fc53c52021-01-08 10:02:36893way to do this is by using an `<iframe>` tag. Chrome's security model gives
894process isolation between the WebUI and the web content. However, some extra
895precautions need to be taken, because there are properties of the page that are
896accessible cross-origin and malicious code can take advantage of such data to
897attack the WebUI. Here are some things to keep in mind:
Lukasz Anforowicz11e59532018-10-23 22:46:21898
Nasko Oskov24fc53c52021-01-08 10:02:36899* The WebUI page can receive postMessage payloads from the web and should
900 ensure it verifies any messages as they are not trustworthy.
901* The entire frame tree is visible to the embedded web content, including
902 ancestor origins.
903* The web content runs in the same StoragePartition and Profile as the WebUI,
904 which reflect where the WebUI page was loaded (e.g., the default profile,
905 Incognito, etc). The corresponding user credentials will thus be available to
906 the web content inside the WebUI, possibly showing the user as signed in.
Lukasz Anforowicz11e59532018-10-23 22:46:21907
Nasko Oskov24fc53c52021-01-08 10:02:36908Note: WebUIs have a default Content Security Policy which disallows embedding
909any frames. If you want to include any web content in an <iframe> you will need
910to update the policy for your WebUI. When doing so, allow only known origins and
911avoid making the policy more permissive than strictly necessary.
Lukasz Anforowicz11e59532018-10-23 22:46:21912
Nasko Oskov24fc53c52021-01-08 10:02:36913Alternatively, a `<webview>` tag can be used, which runs in a separate
914StoragePartition, a separate frame tree, and restricts postMessage communication
915by default. However, `<webview>` does not support Site Isolation and
916therefore it is not advisable to use for any sensitive content.
Lukasz Anforowicz11e59532018-10-23 22:46:21917
918
Dan Beam079d5c12017-06-16 19:23:30919## See also
920
Amos Limf916d572018-05-21 23:10:35921* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:30922* WebUI's HTML/CSS/JS code follows the [Chromium Web
923 Development Style Guide](../styleguide/web/web.md)
924
925
926<script>
927let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
928let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
929
930let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
931let hrefs = localLinks.map(a => a.href.split('#')[1]);
932
933hrefs.forEach(href => {
934 if (names.includes(href))
935 console.info('found: ' + href);
936 else
937 console.error('broken href: ' + href);
938})
939</script>