blob: 7487560e296682f643dfab3b75df89e4d1b9a8f1 [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 =
132 content::WebUIDataSource::Create("donuts"); // "donuts" == hostname
133 source->AddString("mmmDonuts", "Mmm, donuts!"); // Translations.
rbpotterf50e0252020-09-14 16:38:33134 source->AddResourcePath("", IDR_DONUTS_HTML); // Home page.
Dan Beam079d5c12017-06-16 19:23:30135 content::WebUIDataSource::Add(source);
136
137 // Handles messages from JavaScript to C++ via chrome.send().
Jeremy Romane0760a402018-03-02 18:19:40138 web_ui->AddMessageHandler(std::make_unique<OvenHandler>());
Dan Beam079d5c12017-06-16 19:23:30139 }
140};
141```
142
143If we assume the contents of `IDR_DONUTS_HTML` yields:
144
145```html
146<h1>$i18n{mmmDonuts}</h1>
147```
148
149Visiting `chrome://donuts` should show in something like:
150
151<div style="border: 1px solid black; padding: 10px;">
152<h1>Mmmm, donuts!</h1>
153</div>
154
155Delicious success.
156
Christopher Lam50ab1e92019-10-29 04:33:16157By default $i18n{} escapes strings for HTML. $i18nRaw{} can be used for
158translations that embed HTML, and $i18nPolymer{} can be used for Polymer
159bindings. See
160[this comment](https://bugs.chromium.org/p/chromium/issues/detail?id=1010815#c1)
161for more information.
162
Dan Beam079d5c12017-06-16 19:23:30163## C++ classes
164
165### WebUI
166
167`WebUI` is a high-level class and pretty much all HTML-based Chrome UIs have
168one. `WebUI` lives in the browser process, and is owned by a `RenderFrameHost`.
169`WebUI`s have a concrete implementation (`WebUIImpl`) in `content/` and are
170created in response to navigation events.
171
172A `WebUI` knows very little about the page it's showing, and it owns a
173[`WebUIController`](#WebUIController) that is set after creation based on the
174hostname of a requested URL.
175
176A `WebUI` *can* handle messages itself, but often defers these duties to
177separate [`WebUIMessageHandler`](#WebUIMessageHandler)s, which are generally
178designed for handling messages on certain topics.
179
180A `WebUI` can be created speculatively, and are generally fairly lightweight.
181Heavier duty stuff like hard initialization logic or accessing services that may
182have side effects are more commonly done in a
183[`WebUIController`](#WebUIController) or
184[`WebUIMessageHandler`s](#WebUIMessageHandler).
185
186`WebUI` are created synchronously on the UI thread in response to a URL request,
187and are re-used where possible between navigations (i.e. refreshing a page).
188Because they run in a separate process and can exist before a corresponding
189renderer process has been created, special care is required to communicate with
190the renderer if reliable message passing is required.
191
Dan Beam079d5c12017-06-16 19:23:30192### WebUIController
193
194A `WebUIController` is the brains of the operation, and is responsible for
195application-specific logic, setting up translations and resources, creating
196message handlers, and potentially responding to requests dynamically. In complex
197pages, logic is often split across multiple
198[`WebUIMessageHandler`s](#WebUIMessageHandler) instead of solely in the
199controller for organizational benefits.
200
201A `WebUIController` is owned by a [`WebUI`](#WebUI), and is created and set on
202an existing [`WebUI`](#WebUI) when the correct one is determined via URL
203inspection (i.e. chrome://settings creates a generic [`WebUI`](#WebUI) with a
204settings-specific `WebUIController`).
205
206### WebUIDataSource
207
rbpotterf50e0252020-09-14 16:38:33208The `WebUIDataSource` class provides a place for data to live for WebUI pages.
209
210Examples types of data stored in this class are:
211
212* static resources (i.e. .html files packed into bundles and pulled off of disk)
213* translations
214* dynamic feature values (i.e. whether a feature is enabled)
215
216Data sources are set up in the browser process (in C++) and are accessed by
217loading URLs from the renderer.
218
219Below is an example of a simple data source (in this case, Chrome's history
220page):
221
222```c++
223content::WebUIDataSource* source = content::WebUIDataSource::Create("history");
224
225source->AddResourcePath("sign_in_promo.svg", IDR_HISTORY_SIGN_IN_PROMO_SVG);
226source->AddResourcePath("synced_tabs.html", IDR_HISTORY_SYNCED_TABS_HTML);
227
228source->AddString("title", IDS_HISTORY_TITLE);
229source->AddString("moreFromThisSite", IDS_HISTORY_MORE_FROM_THIS_SITE);
230
231source->AddBoolean("showDateRanges",
232 base::FeatureList::IsEnabled(features::kHistoryShowDateRanges));
233
234webui::SetupWebUIDataSource(
235 source, base::make_span(kHistoryResources, kHistoryResourcesSize),
236 kGeneratedPath, IDR_HISTORY_HISTORY_HTML);
237
238content::WebUIDataSource::Add(source);
239```
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
rbpotterf50e0252020-09-14 16:38:33282### WebUIDataSource::Create()
283
284This is a factory method required to create a WebUIDataSource instance. The
285argument to `Create()` is typically the host name of the page. Caller owns the
286result.
287
rbpotterf50e0252020-09-14 16:38:33288### WebUIDataSource::Add()
289
290Once you've created and added some things to a data source, it'll need to be
291"added". This means transferring ownership. In practice, the data source is
292created in the browser process on the UI thread and transferred to the IO
293thread. Additionally, calling `Add()` will overwrite any existing data source
294with the same name.
295
296<div class="note">
297It's unsafe to keep references to a <code>WebUIDataSource</code> after calling
298<code>Add()</code>. Don't do this.
299</div>
300
rbpotterf50e0252020-09-14 16:38:33301### WebUIDataSource::AddLocalizedString()
302
303Using an int reference to a grit string (starts with "IDS" and lives in a .grd
304or .grdp file), adding a string with a key name will be possible to reference
305via the `$i18n{}` syntax (and will be replaced when requested) or later
306dynamically in JavaScript via `loadTimeData.getString()` (or `getStringF`).
307
Lei Zhang5b205082022-01-25 18:08:38308### WebUIDataSource::AddLocalizedStrings()
309
310Many Web UI data sources need to be set up with a large number of localized
311strings. Instead of repeatedly calling <code>AddLocalizedString()</code>, create
312an array of all the strings and use <code>AddLocalizedStrings()</code>:
313
314```c++
315 static constexpr webui::LocalizedString kStrings[] = {
316 // Localized strings (alphabetical order).
317 {"actionMenuDescription", IDS_HISTORY_ACTION_MENU_DESCRIPTION},
318 {"ariaRoleDescription", IDS_HISTORY_ARIA_ROLE_DESCRIPTION},
319 {"bookmarked", IDS_HISTORY_ENTRY_BOOKMARKED},
320 };
321 source->AddLocalizedStrings(kStrings);
322```
323
rbpotterf50e0252020-09-14 16:38:33324### WebUIDataSource::AddResourcePath()
325
326Using an int reference to a grit resource (starts with "IDR" and lives in a .grd
327or .grdp file), adds a resource to the UI with the specified path.
328
329It's generally a good idea to call <code>AddResourcePath()</code> with the empty
330path and a resource ID that should be served as the "catch all" resource to
331respond with. This resource will be served for requests like "chrome://history",
332or "chrome://history/pathThatDoesNotExist". It will not be served for requests
333that look like they are attempting to fetch a specific file, like
334"chrome://history/file\_that\_does\_not\_exist.js". This is so that if a user
335enters a typo when trying to load a subpage like "chrome://history/syncedTabs"
336they will be redirected to the main history page, instead of seeing an error,
337but incorrect imports in the source code will fail, so that they can be more
338easily found and corrected.
339
Lei Zhang5b205082022-01-25 18:08:38340### WebUIDataSource::AddResourcePaths()
341
342Similar to the localized strings, many Web UIs need to add a large number of
343resource paths. In this case, use <code>AddResourcePaths()</code> to
344replace repeated calls to <code>AddResourcePath()</code>.
345
346```c++
347 static constexpr webui::ResourcePath kResources[] = {
348 {"browser_api.js", IDR_BROWSER_API_JS},
349 {"constants.js", IDR_CONSTANTS_JS},
350 {"controller.js", IDR_CONTROLLER_JS},
351 };
352 source->AddResourcePaths(kResources);
353```
354
355The same method can be leveraged for cases that directly use constants defined
356by autogenerated grit resources map header files. For example, the autogenerated
357print\_preview\_resources\_map.h header defines a
358<code>webui::ResourcePath</code> array named <code>kPrintPreviewResources</code>
359and a <code>size\_t kPrintPreviewResourcesSize</code>. All the resources in this
360resource map can be added as follows:
361
362```c++
363 source->AddResourcePaths(
364 base::make_span(kPrintPreviewResources, kPrintPreviewResourcesSize));
365```
366
rbpotterf50e0252020-09-14 16:38:33367### WebUIDataSource::AddBoolean()
368
369Often a page needs to know whether a feature is enabled. This is a good use case
370for `WebUIDataSource::AddBoolean()`. Then, in the Javascript, one can write
371code like this:
372
373```js
374if (loadTimeData.getBoolean('myFeatureIsEnabled')) {
375 ...
376}
377```
378
379<div class="note">
380Data sources are not recreated on refresh, and therefore values that are dynamic
381(i.e. that can change while Chrome is running) may easily become stale. It may
rbpotteracc480cd2022-03-04 08:42:19382be preferable to use <code>sendWithPromise()</code> to initialize dynamic
rbpotterf50e0252020-09-14 16:38:33383values and call <code>FireWebUIListener()</code> to update them.
384
385If you really want or need to use <code>AddBoolean()</code> for a dynamic value,
386make sure to call <code>WebUIDataSource::Update()</code> when the value changes.
387</div>
388
rbpotterf50e0252020-09-14 16:38:33389## WebUI utils for working with data sources
390
391chrome/browser/ui/webui/webui\_util.\* contains a number of methods to simplify
392common configuration tasks.
393
Rebekah Potter5691cab2020-10-29 21:30:35394### webui::SetupWebUIDataSource()
rbpotterf50e0252020-09-14 16:38:33395
Rebekah Potter5691cab2020-10-29 21:30:35396This method performs common configuration tasks on a data source for a Web UI
397that uses JS modules. When creating a Web UI that uses JS modules, use this
398utility instead of duplicating the configuration steps it performs elsewhere.
399Specific setup steps include:
rbpotterf50e0252020-09-14 16:38:33400
401* Setting the content security policy to allow the data source to load only
402 resources from its own host (e.g. chrome://history), chrome://resources, and
403 chrome://test (used to load test files).
404* Enabling i18n template replacements by calling <code>UseStringsJs()</code> and
405 <code>EnableReplaceI18nInJS()</code> on the data source.
406* Adding the test loader files to the data source, so that test files can be
407 loaded as JS modules.
408* Setting the resource to load for the empty path.
Rebekah Potter5691cab2020-10-29 21:30:35409* Adding all resources from a GritResourceMap.
rbpotterf50e0252020-09-14 16:38:33410
Dan Beam079d5c12017-06-16 19:23:30411## Browser (C++) &rarr; Renderer (JS)
412
Dan Beam079d5c12017-06-16 19:23:30413### WebUIMessageHandler::AllowJavascript()
414
Adam Langley81be0732019-03-06 18:38:45415A tab that has been used for settings UI may be reloaded, or may navigate to an
416external origin. In both cases, one does not want callbacks from C++ to
417Javascript to run. In the former case, the callbacks will occur when the
418Javascript doesn't expect them. In the latter case, sensitive information may be
419delivered to an untrusted origin.
420
421Therefore each message handler maintains
422[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
423that describes whether delivering callbacks to Javascript is currently
424appropriate. This boolean is set by calling `AllowJavascript`, which should be
425done when handling a call from Javascript, because that indicates that the page
426is ready for the subsequent callback. (See
427[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
428If the tab navigates or reloads,
429[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
430is called to clear the flag.
431
432Therefore, before each callback from C++ to Javascript, the flag must be tested
433by calling
434[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
435If false, then the callback must be dropped. (When the flag is false, calling
436[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
437will crash. See
438[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
439
440Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
441the case where an asynchronous operation is started, the settings page is
442reloaded, and the user triggers another operation using the original message
443handler. The `javascript_allowed_` boolean will be true, but the original
444callback should still be dropped because it relates to a operation that was
445discarded by the reload. (Reloading settings UI does _not_ cause message handler
446objects to be deleted.)
447
448Thus a message handler may override
449[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
450to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30451
452In the JS:
453
454```js
455window.onload = function() {
456 app.initialize();
457 chrome.send('startPilotLight');
458};
459```
460
461In the C++:
462
463```c++
464void OvenHandler::HandleStartPilotLight(cont base::ListValue* /*args*/) {
465 AllowJavascript();
466 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
467 GetOven()->StartPilotLight();
468}
469```
470
471<div class="note">
472Relying on the <code>'load'</code> event or browser-side navigation callbacks to
473detect page readiness omits <i>application-specific</i> initialization, and a
474custom <code>'initialized'</code> message is often necessary.
475</div>
476
Dan Beam079d5c12017-06-16 19:23:30477### WebUIMessageHandler::CallJavascriptFunction()
478
479When the browser process needs to tell the renderer/JS of an event or otherwise
480execute code, it can use `CallJavascriptFunction()`.
481
482<div class="note">
483Javascript must be <a href="#AllowJavascript">allowed</a> to use
484<code>CallJavscriptFunction()</code>.
485</div>
486
487```c++
488void OvenHandler::OnPilotLightExtinguished() {
489 CallJavascriptFunction("app.pilotLightExtinguished");
490}
491```
492
493This works by crafting a string to be evaluated in the renderer. Any arguments
494to the call are serialized to JSON and the parameter list is wrapped with
495
496```
497// See WebUI::GetJavascriptCall() for specifics:
498"functionCallName(" + argumentsAsJson + ")"
499```
500
501and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
502
503While this works, it implies that:
504
505* a global method must exist to successfully run the Javascript request
506* any method can be called with any parameter (far more access than required in
507 practice)
508
509^ These factors have resulted in less use of `CallJavascriptFunction()` in the
510webui codebase. This functionality can easily be accomplished with the following
511alternatives:
512
513* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
514 when an event occurs in C++ and is more loosely coupled (nothing blows up if
515 the event dispatch is ignored). JS subscribes to notifications via
rbpotteracc480cd2022-03-04 08:42:19516 [`addWebUIListener`](#addWebUIListener).
Dan Beam079d5c12017-06-16 19:23:30517* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
518 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
519 when Javascript requires a response to an inquiry about C++-canonical state
520 (i.e. "Is Autofill enabled?", "Is the user incognito?")
521
Dan Beam079d5c12017-06-16 19:23:30522### WebUIMessageHandler::FireWebUIListener()
523
524`FireWebUIListener()` is used to notify a registered set of listeners that an
525event has occurred. This is generally used for events that are not guaranteed to
526happen in timely manner, or may be caused to happen by unpredictable events
527(i.e. user actions).
528
529Here's some example to detect a change to Chrome's theme:
530
531```js
rbpotteracc480cd2022-03-04 08:42:19532addWebUIListener("theme-changed", refreshThemeStyles);
Dan Beam079d5c12017-06-16 19:23:30533```
534
535This Javascript event listener can be triggered in C++ via:
536
537```c++
538void MyHandler::OnThemeChanged() {
539 FireWebUIListener("theme-changed");
540}
541```
542
543Because it's not clear when a user might want to change their theme nor what
544theme they'll choose, this is a good candidate for an event listener.
545
546If you simply need to get a response in Javascript from C++, consider using
rbpotteracc480cd2022-03-04 08:42:19547[`sendWithPromise()`](#sendWithPromise) and
Dan Beam079d5c12017-06-16 19:23:30548[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
549
Dan Beam079d5c12017-06-16 19:23:30550### WebUIMessageHandler::OnJavascriptAllowed()
551
552`OnJavascriptDisallowed()` is a lifecycle method called in response to
553[`AllowJavascript()`](#AllowJavascript). It is a good place to register
554observers of global services or other callbacks that might call at unpredictable
555times.
556
557For example:
558
559```c++
560class MyHandler : public content::WebUIMessageHandler {
561 MyHandler() {
562 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
563 }
564 void OnGlobalServiceEvent() {
565 FireWebUIListener("global-thing-happened");
566 }
567};
568```
569
570Because browser-side C++ handlers are created before a renderer is ready, the
571above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
572before the renderer is ready, which may result in dropped updates or
573accidentally running Javascript in a renderer that has navigated to a new URL.
574
575A safer way to set up communication is:
576
577```c++
578class MyHandler : public content::WebUIMessageHandler {
579 public:
Dan Beam079d5c12017-06-16 19:23:30580 void OnJavascriptAllowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17581 observation_.Observe(GetGlobalService()); // <-- DO THIS.
Dan Beam079d5c12017-06-16 19:23:30582 }
583 void OnJavascriptDisallowed() override {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17584 observation_.Reset(); // <-- AND THIS.
Dan Beam079d5c12017-06-16 19:23:30585 }
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17586 base::ScopedObservation<MyHandler, GlobalService> observation_{this}; // <-- ALSO HANDY.
Dan Beam079d5c12017-06-16 19:23:30587```
588when a renderer has been created and the
589document has loaded enough to signal to the C++ that it's ready to respond to
590messages.
591
Dan Beam079d5c12017-06-16 19:23:30592### WebUIMessageHandler::OnJavascriptDisallowed()
593
594`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
595it's safe to send JavaScript messsages to the renderer.
596
597There's a number of situations that result in this method being called:
598
599* renderer doesn't exist yet
600* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23601* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30602* tab refresh
603* renderer crash
604
605Though it's possible to programmatically disable Javascript, it's uncommon to
606need to do so.
607
608Because there's no single strategy that works for all cases of a renderer's
609state (i.e. queueing vs dropping messages), these lifecycle methods were
610introduced so a WebUI application can implement these decisions itself.
611
612Often, it makes sense to disconnect from observers in
613`OnJavascriptDisallowed()`:
614
615```c++
616void OvenHandler::OnJavascriptDisallowed() {
Sigurdur Asgeirssonfb9a9f72021-05-20 20:45:17617 scoped_oven_observation_.Reset()
Dan Beam079d5c12017-06-16 19:23:30618}
619```
620
621Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
622`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
623scoped observer that automatically unsubscribes on destruction but can also
624imperatively unsubscribe in `OnJavascriptDisallowed()`.
625
Dan Beam079d5c12017-06-16 19:23:30626### WebUIMessageHandler::RejectJavascriptCallback()
627
628This method is called in response to
rbpotteracc480cd2022-03-04 08:42:19629[`sendWithPromise()`](#sendWithPromise) to reject the issued Promise. This
Dan Beam079d5c12017-06-16 19:23:30630runs the rejection (second) callback in the [Promise's
631executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
632and any
633[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
634callbacks in the chain.
635
636```c++
637void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23638 AllowJavascript();
639 if (!GetOven()->HasGas()) {
640 RejectJavascriptCallback(args->GetList()[0],
641 base::StringValue("need gas to cook the donuts!"));
642 }
Dan Beam079d5c12017-06-16 19:23:30643```
644
645This method is basically just a
646[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
647global "cr.webUIResponse" method with a success value of false.
648
649```c++
650// WebUIMessageHandler::RejectJavascriptCallback():
651CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:18652 response);
Dan Beam079d5c12017-06-16 19:23:30653```
654
655See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
656
Dan Beam079d5c12017-06-16 19:23:30657### WebUIMessageHandler::ResolveJavascriptCallback()
658
659This method is called in response to
rbpotteracc480cd2022-03-04 08:42:19660[`sendWithPromise()`](#sendWithPromise) to fulfill an issued Promise,
Dan Beam079d5c12017-06-16 19:23:30661often with a value. This results in runnings any fulfillment (first) callbacks
662in the associate Promise executor and any registered
663[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
664callbacks.
665
rbpotteracc480cd2022-03-04 08:42:19666So, given this TypeScript code:
Dan Beam079d5c12017-06-16 19:23:30667
668```js
rbpotteracc480cd2022-03-04 08:42:19669sendWithPromise('bakeDonuts').then(function(numDonutsBaked: number) {
Dan Beam079d5c12017-06-16 19:23:30670 shop.donuts += numDonutsBaked;
671});
672```
673
674Some handling C++ might do this:
675
676```c++
677void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23678 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:30679 double num_donuts_baked = GetOven()->BakeDonuts();
Toby Huang97ce1d5d2021-07-13 01:38:58680 ResolveJavascriptCallback(args->GetList()[0], base::Value(num_donuts_baked));
Dan Beam079d5c12017-06-16 19:23:30681}
682```
683
684## Renderer (JS) &rarr; Browser (C++)
685
Dan Beam079d5c12017-06-16 19:23:30686### chrome.send()
687
688When the JavaScript `window` object is created, a renderer is checked for [WebUI
689bindings](#bindings).
690
691```c++
692// RenderFrameImpl::DidClearWindowObject():
693if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
694 WebUIExtension::Install(frame_);
695```
696
697If the bindings exist, a global `chrome.send()` function is exposed to the
698renderer:
699
700```c++
701// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:35702v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:30703chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:18704 gin::CreateFunctionTemplate(
Ayu Ishii33743432021-02-03 19:05:01705 isolate,
706 base::BindRepeating(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:30707```
708
709The `chrome.send()` method takes a message name and argument list.
710
711```js
712chrome.send('messageName', [arg1, arg2, ...]);
713```
714
715The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:37716`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:30717
718```c++
719// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:37720render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
721 frame->GetDocument().Url(),
722 message, *content));
Dan Beam079d5c12017-06-16 19:23:30723```
724```c++
725// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:37726IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:30727```
728
729The browser-side code does a map lookup for the message name and calls the found
730callback with the deserialized arguments:
731
732```c++
733// WebUIImpl::ProcessWebUIMessage():
734message_callbacks_.find(message)->second.Run(&args);
735```
736
rbpotteracc480cd2022-03-04 08:42:19737### addWebUIListener()
Dan Beam079d5c12017-06-16 19:23:30738
739WebUI listeners are a convenient way for C++ to inform JavaScript of events.
740
741Older WebUI code exposed public methods for event notification, similar to how
742responses to [chrome.send()](#chrome_send) used to work. They both
Ian Barkley-Yeung4f4f71d2020-06-09 00:38:13743resulted in global namespace pollution, but it was additionally hard to stop
Dan Beam079d5c12017-06-16 19:23:30744listening for events in some cases. **cr.addWebUIListener** is preferred in new
745code.
746
747Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
rbpotteracc480cd2022-03-04 08:42:19748just like [sendWithPromise()](#sendWithPromise).
749
750addWebUIListener can be imported from 'chrome://resources/js/cr.m.js'.
Dan Beam079d5c12017-06-16 19:23:30751
752```js
753// addWebUIListener():
754webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
755webUIListenerMap[eventName][createUid()] = callback;
756```
757
758The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
759with an event name and a variable number of arguments.
760
761```c++
762// WebUIMessageHandler:
763template <typename... Values>
764void FireWebUIListener(const std::string& event_name, const Values&... values) {
765 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
766 values...);
767}
768```
769
770C++ handlers call this `FireWebUIListener` method when an event occurs that
771should be communicated to the JavaScript running in a tab.
772
773```c++
774void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
Toby Huang97ce1d5d2021-07-13 01:38:58775 FireWebUIListener("donuts-baked", base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:30776}
777```
778
rbpotteracc480cd2022-03-04 08:42:19779TypeScript can listen for WebUI events via:
Dan Beam079d5c12017-06-16 19:23:30780
781```js
rbpotteracc480cd2022-03-04 08:42:19782let donutsReady: number = 0;
783addWebUIListener('donuts-baked', function(numFreshlyBakedDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:30784 donutsReady += numFreshlyBakedDonuts;
785});
786```
787
rbpotteracc480cd2022-03-04 08:42:19788### sendWithPromise()
Dan Beam079d5c12017-06-16 19:23:30789
rbpotteracc480cd2022-03-04 08:42:19790`sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
Dan Beam079d5c12017-06-16 19:23:30791triggering a message requires a response:
792
793```js
794chrome.send('getNumberOfDonuts'); // No easy way to get response!
795```
796
797In older WebUI pages, global methods were exposed simply so responses could be
798sent. **This is discouraged** as it pollutes the global namespace and is harder
799to make request specific or do from deeply nested code.
800
801In newer WebUI pages, you see code like this:
802
803```js
rbpotteracc480cd2022-03-04 08:42:19804sendWithPromise('getNumberOfDonuts').then(function(numDonuts: number) {
Dan Beam079d5c12017-06-16 19:23:30805 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
806});
807```
808
rbpotteracc480cd2022-03-04 08:42:19809Note that sendWithPromise can be imported from 'chrome://resources/js/cr.m.js';
810
Dan Beam079d5c12017-06-16 19:23:30811On the C++ side, the message registration is similar to
812[`chrome.send()`](#chrome_send) except that the first argument in the
813message handler's list is a callback ID. That ID is passed to
814`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
rbpotteracc480cd2022-03-04 08:42:19815JavaScript/TypeScript and calling the `then()` function.
Dan Beam079d5c12017-06-16 19:23:30816
817```c++
818void DonutHandler::HandleGetNumberOfDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23819 AllowJavascript();
820
821 const base::Value& callback_id = args->GetList()[0];
Dan Beam079d5c12017-06-16 19:23:30822 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Toby Huang97ce1d5d2021-07-13 01:38:58823 ResolveJavascriptCallback(callback_id, base::Value(num_donuts));
Dan Beam079d5c12017-06-16 19:23:30824}
825```
826
827Under the covers, a map of `Promise`s are kept in JavaScript.
828
829The callback ID is just a namespaced, ever-increasing number. It's used to
830insert a `Promise` into the JS-side map when created.
831
832```js
rbpotteracc480cd2022-03-04 08:42:19833// sendWithPromise():
Dan Beam079d5c12017-06-16 19:23:30834var id = methodName + '_' + uidCounter++;
835chromeSendResolverMap[id] = new PromiseResolver;
836chrome.send(methodName, [id].concat(args));
837```
838
839The corresponding number is used to look up a `Promise` and reject or resolve it
840when the outcome is known.
841
842```js
843// cr.webUIResponse():
844var resolver = chromeSendResolverMap[id];
845if (success)
846 resolver.resolve(response);
847else
848 resolver.reject(response);
849```
850
851This approach still relies on the C++ calling a globally exposed method, but
852reduces the surface to only a single global (`cr.webUIResponse`) instead of
853many. It also makes per-request responses easier, which is helpful when multiple
854are in flight.
855
Lukasz Anforowicz11e59532018-10-23 22:46:21856
857## Security considerations
858
859Because WebUI pages are highly privileged, they are often targets for attack,
860since taking control of a WebUI page can sometimes be sufficient to escape
861Chrome's sandbox. To make sure that the special powers granted to WebUI pages
862are safe, WebUI pages are restricted in what they can do:
863
Nasko Oskov24fc53c52021-01-08 10:02:36864* WebUI pages cannot embed http/https resources
Lukasz Anforowicz11e59532018-10-23 22:46:21865* WebUI pages cannot issue http/https fetches
866
867In the rare case that a WebUI page really needs to include web content, the safe
Nasko Oskov24fc53c52021-01-08 10:02:36868way to do this is by using an `<iframe>` tag. Chrome's security model gives
869process isolation between the WebUI and the web content. However, some extra
870precautions need to be taken, because there are properties of the page that are
871accessible cross-origin and malicious code can take advantage of such data to
872attack the WebUI. Here are some things to keep in mind:
Lukasz Anforowicz11e59532018-10-23 22:46:21873
Nasko Oskov24fc53c52021-01-08 10:02:36874* The WebUI page can receive postMessage payloads from the web and should
875 ensure it verifies any messages as they are not trustworthy.
876* The entire frame tree is visible to the embedded web content, including
877 ancestor origins.
878* The web content runs in the same StoragePartition and Profile as the WebUI,
879 which reflect where the WebUI page was loaded (e.g., the default profile,
880 Incognito, etc). The corresponding user credentials will thus be available to
881 the web content inside the WebUI, possibly showing the user as signed in.
Lukasz Anforowicz11e59532018-10-23 22:46:21882
Nasko Oskov24fc53c52021-01-08 10:02:36883Note: WebUIs have a default Content Security Policy which disallows embedding
884any frames. If you want to include any web content in an <iframe> you will need
885to update the policy for your WebUI. When doing so, allow only known origins and
886avoid making the policy more permissive than strictly necessary.
Lukasz Anforowicz11e59532018-10-23 22:46:21887
Nasko Oskov24fc53c52021-01-08 10:02:36888Alternatively, a `<webview>` tag can be used, which runs in a separate
889StoragePartition, a separate frame tree, and restricts postMessage communication
890by default. However, `<webview>` does not support Site Isolation and
891therefore it is not advisable to use for any sensitive content.
Lukasz Anforowicz11e59532018-10-23 22:46:21892
Ian Barkley-Yeung20a8ff72021-07-01 01:06:35893## JavaScript Error Reporting
894
895By default, errors in the JavaScript of a WebUI page will generate error reports
896which appear in Google's internal crash/ reports page. These error reports will
897only be generated for Google Chrome builds, not Chromium or other Chromium-based
898browsers.
899
900Specifically, an error report will be generated when the JavaScript for a
901WebUI-based chrome:// page does one of the following:
902* Generates an uncaught exception,
903* Has a promise which is rejected, and no rejection handler is provided, or
904* Calls `console.error()`.
905
906Such errors will appear alongside other crashes in the
907`product_name=Chrome_ChromeOS` or `product_name=Chrome_Linux` lists on crash/.
908The signature of the error is simply the error message. To avoid
909spamming the system, only one error report with a given message will be
910generated per hour.
911
912If you are getting error reports for an expected condition, you can turn off the
913reports simply by changing `console.error()` into `console.warn()`.
914
915If you wish to get more control of the JavaScript error messages, for example
916to change the product name or to add additional data, you may wish to switch to
917using `CrashReportPrivate.reportError()`. If you do so, be sure to override
918`WebUIController::IsJavascriptErrorReportingEnabled()` to return false for your
919page; this will avoid generating redundant error reports.
920
921Known issues:
9221. Error reporting is currently enabled only on ChromeOS and Linux.
9232. Errors are only reported for chrome:// URLs.
9243. Unhandled promise rejections do not have a good stack.
9254. The line numbers and column numbers in the stacks are for the minified
926 JavaScript and do not correspond to the line and column numbers of the
927 original source files.
9285. Error messages with variable strings do not group well. For example, if the
929 error message includes the name of a network, each network name will be its
930 own signature.
931
Lukasz Anforowicz11e59532018-10-23 22:46:21932
Dan Beam079d5c12017-06-16 19:23:30933## See also
934
Amos Limf916d572018-05-21 23:10:35935* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:30936* WebUI's HTML/CSS/JS code follows the [Chromium Web
937 Development Style Guide](../styleguide/web/web.md)
938
939
940<script>
941let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
942let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
943
944let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
945let hrefs = localLinks.map(a => a.href.split('#')[1]);
946
947hrefs.forEach(href => {
948 if (names.includes(href))
949 console.info('found: ' + href);
950 else
951 console.error('broken href: ' + href);
952})
953</script>