blob: f62e208a4c79ebc850c6760cd0c4f1dda26b4ce6 [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() {
261 web_ui()->RegisterMessageHandler("bakeDonuts",
262 base::Bind(&OvenHandler::HandleBakeDonuts, base::Unretained(this)));
263}
264
Jarryd21f7ba72019-08-07 19:59:45265void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23266 AllowJavascript();
267
268 CHECK_EQ(1u, args->GetSize());
269 // JavaScript numbers are doubles.
270 double num_donuts = args->GetList()[0].GetDouble();
Dan Beam079d5c12017-06-16 19:23:30271 GetOven()->BakeDonuts(static_cast<int>(num_donuts));
272}
273```
274
275Can be triggered in JavaScript with this example code:
276
277```js
278$('bakeDonutsButton').onclick = function() {
279 chrome.send('bakeDonuts', [5]); // bake 5 donuts!
280};
281```
282
rbpotterf50e0252020-09-14 16:38:33283<a name="DataSources">
284## Data Sources
285
286<a name="Create"></a>
287### WebUIDataSource::Create()
288
289This is a factory method required to create a WebUIDataSource instance. The
290argument to `Create()` is typically the host name of the page. Caller owns the
291result.
292
293<a name="Add"></a>
294### WebUIDataSource::Add()
295
296Once you've created and added some things to a data source, it'll need to be
297"added". This means transferring ownership. In practice, the data source is
298created in the browser process on the UI thread and transferred to the IO
299thread. Additionally, calling `Add()` will overwrite any existing data source
300with the same name.
301
302<div class="note">
303It's unsafe to keep references to a <code>WebUIDataSource</code> after calling
304<code>Add()</code>. Don't do this.
305</div>
306
307<a name="AddLocalizedString"></a>
308### WebUIDataSource::AddLocalizedString()
309
310Using an int reference to a grit string (starts with "IDS" and lives in a .grd
311or .grdp file), adding a string with a key name will be possible to reference
312via the `$i18n{}` syntax (and will be replaced when requested) or later
313dynamically in JavaScript via `loadTimeData.getString()` (or `getStringF`).
314
315<a name="AddResourcePath"></a>
316### WebUIDataSource::AddResourcePath()
317
318Using an int reference to a grit resource (starts with "IDR" and lives in a .grd
319or .grdp file), adds a resource to the UI with the specified path.
320
321It's generally a good idea to call <code>AddResourcePath()</code> with the empty
322path and a resource ID that should be served as the "catch all" resource to
323respond with. This resource will be served for requests like "chrome://history",
324or "chrome://history/pathThatDoesNotExist". It will not be served for requests
325that look like they are attempting to fetch a specific file, like
326"chrome://history/file\_that\_does\_not\_exist.js". This is so that if a user
327enters a typo when trying to load a subpage like "chrome://history/syncedTabs"
328they will be redirected to the main history page, instead of seeing an error,
329but incorrect imports in the source code will fail, so that they can be more
330easily found and corrected.
331
332<a name="AddBoolean"></a>
333### WebUIDataSource::AddBoolean()
334
335Often a page needs to know whether a feature is enabled. This is a good use case
336for `WebUIDataSource::AddBoolean()`. Then, in the Javascript, one can write
337code like this:
338
339```js
340if (loadTimeData.getBoolean('myFeatureIsEnabled')) {
341 ...
342}
343```
344
345<div class="note">
346Data sources are not recreated on refresh, and therefore values that are dynamic
347(i.e. that can change while Chrome is running) may easily become stale. It may
348be preferable to use <code>cr.sendWithPromise()</code> to initialize dynamic
349values and call <code>FireWebUIListener()</code> to update them.
350
351If you really want or need to use <code>AddBoolean()</code> for a dynamic value,
352make sure to call <code>WebUIDataSource::Update()</code> when the value changes.
353</div>
354
355<a name="WebUIDataSourceUtils"></a>
356## WebUI utils for working with data sources
357
358chrome/browser/ui/webui/webui\_util.\* contains a number of methods to simplify
359common configuration tasks.
360
361<a name="AddLocalizedStringsBulk"></a>
362### webui::AddLocalizedStringsBulk()
363
364Many Web UI data sources need to be set up with a large number of localized
365strings. Instead of repeatedly calling <code>AddLocalizedString()</code>, create
366an array of all the strings and use <code>AddLocalizedStringsBulk()</code>:
367
368```c++
369 static constexpr webui::LocalizedString kStrings[] = {
370 // Localized strings (alphabetical order).
371 {"actionMenuDescription", IDS_HISTORY_ACTION_MENU_DESCRIPTION},
372 {"ariaRoleDescription", IDS_HISTORY_ARIA_ROLE_DESCRIPTION},
373 {"bookmarked", IDS_HISTORY_ENTRY_BOOKMARKED},
374 };
375 AddLocalizedStringsBulk(source, kStrings);
376```
377
378<a name="AddResourcePathsBulk"></a>
379### webui::AddResourcePathsBulk()
380
381Similar to the localized strings, many Web UIs need to add a large number of
382resource paths. In this case, use <code>AddResourcePathsBulk()</code> to
383replace repeated calls to <code>AddResourcePath()</code>. There are two
384versions. One works almost identically to the strings case:
385
386```c++
387 static constexpr webui::ResourcePath kPdfResources[] = {
388 {"pdf/browser_api.js", IDR_PDF_BROWSER_API_JS},
389 {"pdf/constants.js", IDR_PDF_CONSTANTS_JS},
390 {"pdf/controller.js", IDR_PDF_CONTROLLER_JS},
391 };
392 webui::AddResourcePathsBulk(source, kStrings);
393```
394
395The second version instead accepts a span of <code>GritResourceMap</code> so
396that it can directly use constants defined by autogenerated grit resources map
397header files. For example, the autogenerated print\_preview\_resources\_map.h
398header defines a <code>GritResourceMap</code> named
399<code>kPrintPreviewResources</code> and a
400<code>size\_t kPrintPreviewResourcesSize</code>. All the resources in this
401resource map can be added as follows:
402
403```c++
404 webui::AddResourcePathsBulk(
405 source,
406 base::make_span(kPrintPreviewResources, kPrintPreviewResourcesSize));
407```
408
409<a name="SetupWebUIDataSource"></a>
Rebekah Potter5691cab2020-10-29 21:30:35410### webui::SetupWebUIDataSource()
rbpotterf50e0252020-09-14 16:38:33411
Rebekah Potter5691cab2020-10-29 21:30:35412This method performs common configuration tasks on a data source for a Web UI
413that uses JS modules. When creating a Web UI that uses JS modules, use this
414utility instead of duplicating the configuration steps it performs elsewhere.
415Specific setup steps include:
rbpotterf50e0252020-09-14 16:38:33416
417* Setting the content security policy to allow the data source to load only
418 resources from its own host (e.g. chrome://history), chrome://resources, and
419 chrome://test (used to load test files).
420* Enabling i18n template replacements by calling <code>UseStringsJs()</code> and
421 <code>EnableReplaceI18nInJS()</code> on the data source.
422* Adding the test loader files to the data source, so that test files can be
423 loaded as JS modules.
424* Setting the resource to load for the empty path.
Rebekah Potter5691cab2020-10-29 21:30:35425* Adding all resources from a GritResourceMap.
rbpotterf50e0252020-09-14 16:38:33426
Dan Beam079d5c12017-06-16 19:23:30427## Browser (C++) &rarr; Renderer (JS)
428
429<a name="AllowJavascript"></a>
430### WebUIMessageHandler::AllowJavascript()
431
Adam Langley81be0732019-03-06 18:38:45432A tab that has been used for settings UI may be reloaded, or may navigate to an
433external origin. In both cases, one does not want callbacks from C++ to
434Javascript to run. In the former case, the callbacks will occur when the
435Javascript doesn't expect them. In the latter case, sensitive information may be
436delivered to an untrusted origin.
437
438Therefore each message handler maintains
439[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
440that describes whether delivering callbacks to Javascript is currently
441appropriate. This boolean is set by calling `AllowJavascript`, which should be
442done when handling a call from Javascript, because that indicates that the page
443is ready for the subsequent callback. (See
444[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
445If the tab navigates or reloads,
446[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
447is called to clear the flag.
448
449Therefore, before each callback from C++ to Javascript, the flag must be tested
450by calling
451[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
452If false, then the callback must be dropped. (When the flag is false, calling
453[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
454will crash. See
455[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
456
457Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
458the case where an asynchronous operation is started, the settings page is
459reloaded, and the user triggers another operation using the original message
460handler. The `javascript_allowed_` boolean will be true, but the original
461callback should still be dropped because it relates to a operation that was
462discarded by the reload. (Reloading settings UI does _not_ cause message handler
463objects to be deleted.)
464
465Thus a message handler may override
466[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
467to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30468
469In the JS:
470
471```js
472window.onload = function() {
473 app.initialize();
474 chrome.send('startPilotLight');
475};
476```
477
478In the C++:
479
480```c++
481void OvenHandler::HandleStartPilotLight(cont base::ListValue* /*args*/) {
482 AllowJavascript();
483 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
484 GetOven()->StartPilotLight();
485}
486```
487
488<div class="note">
489Relying on the <code>'load'</code> event or browser-side navigation callbacks to
490detect page readiness omits <i>application-specific</i> initialization, and a
491custom <code>'initialized'</code> message is often necessary.
492</div>
493
494<a name="CallJavascriptFunction"></a>
495### WebUIMessageHandler::CallJavascriptFunction()
496
497When the browser process needs to tell the renderer/JS of an event or otherwise
498execute code, it can use `CallJavascriptFunction()`.
499
500<div class="note">
501Javascript must be <a href="#AllowJavascript">allowed</a> to use
502<code>CallJavscriptFunction()</code>.
503</div>
504
505```c++
506void OvenHandler::OnPilotLightExtinguished() {
507 CallJavascriptFunction("app.pilotLightExtinguished");
508}
509```
510
511This works by crafting a string to be evaluated in the renderer. Any arguments
512to the call are serialized to JSON and the parameter list is wrapped with
513
514```
515// See WebUI::GetJavascriptCall() for specifics:
516"functionCallName(" + argumentsAsJson + ")"
517```
518
519and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
520
521While this works, it implies that:
522
523* a global method must exist to successfully run the Javascript request
524* any method can be called with any parameter (far more access than required in
525 practice)
526
527^ These factors have resulted in less use of `CallJavascriptFunction()` in the
528webui codebase. This functionality can easily be accomplished with the following
529alternatives:
530
531* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
532 when an event occurs in C++ and is more loosely coupled (nothing blows up if
533 the event dispatch is ignored). JS subscribes to notifications via
534 [`cr.addWebUIListener`](#cr_addWebUIListener).
535* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
536 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
537 when Javascript requires a response to an inquiry about C++-canonical state
538 (i.e. "Is Autofill enabled?", "Is the user incognito?")
539
540<a name="FireWebUIListener"></a>
541### WebUIMessageHandler::FireWebUIListener()
542
543`FireWebUIListener()` is used to notify a registered set of listeners that an
544event has occurred. This is generally used for events that are not guaranteed to
545happen in timely manner, or may be caused to happen by unpredictable events
546(i.e. user actions).
547
548Here's some example to detect a change to Chrome's theme:
549
550```js
551cr.addWebUIListener("theme-changed", refreshThemeStyles);
552```
553
554This Javascript event listener can be triggered in C++ via:
555
556```c++
557void MyHandler::OnThemeChanged() {
558 FireWebUIListener("theme-changed");
559}
560```
561
562Because it's not clear when a user might want to change their theme nor what
563theme they'll choose, this is a good candidate for an event listener.
564
565If you simply need to get a response in Javascript from C++, consider using
566[`cr.sendWithPromise()`](#cr_sendWithPromise) and
567[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
568
569<a name="OnJavascriptAllowed"></a>
570### WebUIMessageHandler::OnJavascriptAllowed()
571
572`OnJavascriptDisallowed()` is a lifecycle method called in response to
573[`AllowJavascript()`](#AllowJavascript). It is a good place to register
574observers of global services or other callbacks that might call at unpredictable
575times.
576
577For example:
578
579```c++
580class MyHandler : public content::WebUIMessageHandler {
581 MyHandler() {
582 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
583 }
584 void OnGlobalServiceEvent() {
585 FireWebUIListener("global-thing-happened");
586 }
587};
588```
589
590Because browser-side C++ handlers are created before a renderer is ready, the
591above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
592before the renderer is ready, which may result in dropped updates or
593accidentally running Javascript in a renderer that has navigated to a new URL.
594
595A safer way to set up communication is:
596
597```c++
598class MyHandler : public content::WebUIMessageHandler {
599 public:
600 MyHandler() : observer_(this) {}
601 void OnJavascriptAllowed() override {
602 observer_.Add(GetGlobalService()); // <-- DO THIS.
603 }
604 void OnJavascriptDisallowed() override {
605 observer_.RemoveAll(); // <-- AND THIS.
606 }
607 ScopedObserver<MyHandler, GlobalService> observer_; // <-- ALSO HANDY.
608```
609when a renderer has been created and the
610document has loaded enough to signal to the C++ that it's ready to respond to
611messages.
612
613<a name="OnJavascriptDisallowed"></a>
614### WebUIMessageHandler::OnJavascriptDisallowed()
615
616`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
617it's safe to send JavaScript messsages to the renderer.
618
619There's a number of situations that result in this method being called:
620
621* renderer doesn't exist yet
622* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23623* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30624* tab refresh
625* renderer crash
626
627Though it's possible to programmatically disable Javascript, it's uncommon to
628need to do so.
629
630Because there's no single strategy that works for all cases of a renderer's
631state (i.e. queueing vs dropping messages), these lifecycle methods were
632introduced so a WebUI application can implement these decisions itself.
633
634Often, it makes sense to disconnect from observers in
635`OnJavascriptDisallowed()`:
636
637```c++
638void OvenHandler::OnJavascriptDisallowed() {
639 scoped_oven_observer_.RemoveAll()
640}
641```
642
643Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
644`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
645scoped observer that automatically unsubscribes on destruction but can also
646imperatively unsubscribe in `OnJavascriptDisallowed()`.
647
648<a name="RejectJavascriptCallback"></a>
649### WebUIMessageHandler::RejectJavascriptCallback()
650
651This method is called in response to
652[`cr.sendWithPromise()`](#cr_sendWithPromise) to reject the issued Promise. This
653runs the rejection (second) callback in the [Promise's
654executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
655and any
656[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
657callbacks in the chain.
658
659```c++
660void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23661 AllowJavascript();
662 if (!GetOven()->HasGas()) {
663 RejectJavascriptCallback(args->GetList()[0],
664 base::StringValue("need gas to cook the donuts!"));
665 }
Dan Beam079d5c12017-06-16 19:23:30666```
667
668This method is basically just a
669[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
670global "cr.webUIResponse" method with a success value of false.
671
672```c++
673// WebUIMessageHandler::RejectJavascriptCallback():
674CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:18675 response);
Dan Beam079d5c12017-06-16 19:23:30676```
677
678See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
679
680<a name="ResolveJavascriptCallback"></a>
681### WebUIMessageHandler::ResolveJavascriptCallback()
682
683This method is called in response to
684[`cr.sendWithPromise()`](#cr_sendWithPromise) to fulfill an issued Promise,
685often with a value. This results in runnings any fulfillment (first) callbacks
686in the associate Promise executor and any registered
687[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
688callbacks.
689
690So, given this JS code:
691
692```js
693cr.sendWithPromise('bakeDonuts').then(function(numDonutsBaked) {
694 shop.donuts += numDonutsBaked;
695});
696```
697
698Some handling C++ might do this:
699
700```c++
701void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23702 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:30703 double num_donuts_baked = GetOven()->BakeDonuts();
Michael Giuffrida14938292019-05-31 21:30:23704 ResolveJavascriptCallback(args->GetList()[0], num_donuts_baked);
Dan Beam079d5c12017-06-16 19:23:30705}
706```
707
708## Renderer (JS) &rarr; Browser (C++)
709
710<a name="chrome_send"></a>
711### chrome.send()
712
713When the JavaScript `window` object is created, a renderer is checked for [WebUI
714bindings](#bindings).
715
716```c++
717// RenderFrameImpl::DidClearWindowObject():
718if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
719 WebUIExtension::Install(frame_);
720```
721
722If the bindings exist, a global `chrome.send()` function is exposed to the
723renderer:
724
725```c++
726// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:35727v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:30728chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:18729 gin::CreateFunctionTemplate(
730 isolate, base::Bind(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:30731```
732
733The `chrome.send()` method takes a message name and argument list.
734
735```js
736chrome.send('messageName', [arg1, arg2, ...]);
737```
738
739The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:37740`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:30741
742```c++
743// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:37744render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
745 frame->GetDocument().Url(),
746 message, *content));
Dan Beam079d5c12017-06-16 19:23:30747```
748```c++
749// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:37750IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:30751```
752
753The browser-side code does a map lookup for the message name and calls the found
754callback with the deserialized arguments:
755
756```c++
757// WebUIImpl::ProcessWebUIMessage():
758message_callbacks_.find(message)->second.Run(&args);
759```
760
761<a name="cr_addWebUIListener">
762### cr.addWebUIListener()
763
764WebUI listeners are a convenient way for C++ to inform JavaScript of events.
765
766Older WebUI code exposed public methods for event notification, similar to how
767responses to [chrome.send()](#chrome_send) used to work. They both
Ian Barkley-Yeung4f4f71d2020-06-09 00:38:13768resulted in global namespace pollution, but it was additionally hard to stop
Dan Beam079d5c12017-06-16 19:23:30769listening for events in some cases. **cr.addWebUIListener** is preferred in new
770code.
771
772Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
773just like [cr.sendWithPromise()](#cr_sendWithPromise).
774
775```js
776// addWebUIListener():
777webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
778webUIListenerMap[eventName][createUid()] = callback;
779```
780
781The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
782with an event name and a variable number of arguments.
783
784```c++
785// WebUIMessageHandler:
786template <typename... Values>
787void FireWebUIListener(const std::string& event_name, const Values&... values) {
788 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
789 values...);
790}
791```
792
793C++ handlers call this `FireWebUIListener` method when an event occurs that
794should be communicated to the JavaScript running in a tab.
795
796```c++
797void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
798 FireWebUIListener("donuts-baked", base::FundamentalValue(num_donuts));
799}
800```
801
802JavaScript can listen for WebUI events via:
803
804```js
805var donutsReady = 0;
806cr.addWebUIListener('donuts-baked', function(numFreshlyBakedDonuts) {
807 donutsReady += numFreshlyBakedDonuts;
808});
809```
810
811<a name="cr_sendWithPromise"></a>
812### cr.sendWithPromise()
813
814`cr.sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
815triggering a message requires a response:
816
817```js
818chrome.send('getNumberOfDonuts'); // No easy way to get response!
819```
820
821In older WebUI pages, global methods were exposed simply so responses could be
822sent. **This is discouraged** as it pollutes the global namespace and is harder
823to make request specific or do from deeply nested code.
824
825In newer WebUI pages, you see code like this:
826
827```js
828cr.sendWithPromise('getNumberOfDonuts').then(function(numDonuts) {
829 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
830});
831```
832
833On the C++ side, the message registration is similar to
834[`chrome.send()`](#chrome_send) except that the first argument in the
835message handler's list is a callback ID. That ID is passed to
836`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
837JavaScript and calling the `then()` function.
838
839```c++
840void DonutHandler::HandleGetNumberOfDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23841 AllowJavascript();
842
843 const base::Value& callback_id = args->GetList()[0];
Dan Beam079d5c12017-06-16 19:23:30844 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Michael Giuffrida14938292019-05-31 21:30:23845 ResolveJavascriptCallback(callback_id, base::FundamentalValue(num_donuts));
Dan Beam079d5c12017-06-16 19:23:30846}
847```
848
849Under the covers, a map of `Promise`s are kept in JavaScript.
850
851The callback ID is just a namespaced, ever-increasing number. It's used to
852insert a `Promise` into the JS-side map when created.
853
854```js
855// cr.sendWithPromise():
856var id = methodName + '_' + uidCounter++;
857chromeSendResolverMap[id] = new PromiseResolver;
858chrome.send(methodName, [id].concat(args));
859```
860
861The corresponding number is used to look up a `Promise` and reject or resolve it
862when the outcome is known.
863
864```js
865// cr.webUIResponse():
866var resolver = chromeSendResolverMap[id];
867if (success)
868 resolver.resolve(response);
869else
870 resolver.reject(response);
871```
872
873This approach still relies on the C++ calling a globally exposed method, but
874reduces the surface to only a single global (`cr.webUIResponse`) instead of
875many. It also makes per-request responses easier, which is helpful when multiple
876are in flight.
877
Lukasz Anforowicz11e59532018-10-23 22:46:21878
879## Security considerations
880
881Because WebUI pages are highly privileged, they are often targets for attack,
882since taking control of a WebUI page can sometimes be sufficient to escape
883Chrome's sandbox. To make sure that the special powers granted to WebUI pages
884are safe, WebUI pages are restricted in what they can do:
885
886* WebUI pages cannot embed http/https resources or frames
887* WebUI pages cannot issue http/https fetches
888
889In the rare case that a WebUI page really needs to include web content, the safe
890way to do this is by using a `<webview>` tag. Using a `<webview>` tag is more
891secure than using an iframe for multiple reasons, even if Site Isolation and
892out-of-process iframes keep the web content out of the privileged WebUI process.
893
894First, the content inside the `<webview>` tag has a much reduced attack surface,
895since it does not have a window reference to its embedder or any other frames.
896Only postMessage channel is supported, and this needs to be initiated by the
897embedder, not the guest.
898
899Second, the content inside the `<webview>` tag is hosted in a separate
900StoragePartition. Thus, cookies and other persistent storage for both the WebUI
901page and other browser tabs are inaccessible to it.
902
903This greater level of isolation makes it safer to load possibly untrustworthy or
904compromised web content, reducing the risk of sandbox escapes.
905
906For an example of switching from iframe to webview tag see
907https://crrev.com/c/710738.
908
909
Dan Beam079d5c12017-06-16 19:23:30910## See also
911
Amos Limf916d572018-05-21 23:10:35912* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:30913* WebUI's HTML/CSS/JS code follows the [Chromium Web
914 Development Style Guide](../styleguide/web/web.md)
915
916
917<script>
918let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
919let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
920
921let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
922let hrefs = localLinks.map(a => a.href.split('#')[1]);
923
924hrefs.forEach(href => {
925 if (names.includes(href))
926 console.info('found: ' + href);
927 else
928 console.error('broken href: ' + href);
929})
930</script>