blob: 6d247540630f939129360569937c21594336d664 [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>
410### webui::SetupWebUIDataSource() and webui::SetupBundledWebUIDataSource()
411
412These methods perform 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 these
414utilities instead of duplicating the configuration steps they perform elsewhere.
415Specific setup steps performed by these utilities include:
416
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.
425
426The version for non-bundled UIs (<code>SetupWebUIDataSource()</code>) also adds
427all resources in a GritResourceMap.
428
429The version for bundled UIs (<code>SetupBundledWebUIDataSource()</code>) adds
430a single specified bundled resource. Note that this version is only defined when
431the optimize_webui build flag is enabled.
432
Dan Beam079d5c12017-06-16 19:23:30433## Browser (C++) &rarr; Renderer (JS)
434
435<a name="AllowJavascript"></a>
436### WebUIMessageHandler::AllowJavascript()
437
Adam Langley81be0732019-03-06 18:38:45438A tab that has been used for settings UI may be reloaded, or may navigate to an
439external origin. In both cases, one does not want callbacks from C++ to
440Javascript to run. In the former case, the callbacks will occur when the
441Javascript doesn't expect them. In the latter case, sensitive information may be
442delivered to an untrusted origin.
443
444Therefore each message handler maintains
445[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
446that describes whether delivering callbacks to Javascript is currently
447appropriate. This boolean is set by calling `AllowJavascript`, which should be
448done when handling a call from Javascript, because that indicates that the page
449is ready for the subsequent callback. (See
450[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
451If the tab navigates or reloads,
452[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
453is called to clear the flag.
454
455Therefore, before each callback from C++ to Javascript, the flag must be tested
456by calling
457[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
458If false, then the callback must be dropped. (When the flag is false, calling
459[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
460will crash. See
461[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
462
463Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
464the case where an asynchronous operation is started, the settings page is
465reloaded, and the user triggers another operation using the original message
466handler. The `javascript_allowed_` boolean will be true, but the original
467callback should still be dropped because it relates to a operation that was
468discarded by the reload. (Reloading settings UI does _not_ cause message handler
469objects to be deleted.)
470
471Thus a message handler may override
472[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
473to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30474
475In the JS:
476
477```js
478window.onload = function() {
479 app.initialize();
480 chrome.send('startPilotLight');
481};
482```
483
484In the C++:
485
486```c++
487void OvenHandler::HandleStartPilotLight(cont base::ListValue* /*args*/) {
488 AllowJavascript();
489 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
490 GetOven()->StartPilotLight();
491}
492```
493
494<div class="note">
495Relying on the <code>'load'</code> event or browser-side navigation callbacks to
496detect page readiness omits <i>application-specific</i> initialization, and a
497custom <code>'initialized'</code> message is often necessary.
498</div>
499
500<a name="CallJavascriptFunction"></a>
501### WebUIMessageHandler::CallJavascriptFunction()
502
503When the browser process needs to tell the renderer/JS of an event or otherwise
504execute code, it can use `CallJavascriptFunction()`.
505
506<div class="note">
507Javascript must be <a href="#AllowJavascript">allowed</a> to use
508<code>CallJavscriptFunction()</code>.
509</div>
510
511```c++
512void OvenHandler::OnPilotLightExtinguished() {
513 CallJavascriptFunction("app.pilotLightExtinguished");
514}
515```
516
517This works by crafting a string to be evaluated in the renderer. Any arguments
518to the call are serialized to JSON and the parameter list is wrapped with
519
520```
521// See WebUI::GetJavascriptCall() for specifics:
522"functionCallName(" + argumentsAsJson + ")"
523```
524
525and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
526
527While this works, it implies that:
528
529* a global method must exist to successfully run the Javascript request
530* any method can be called with any parameter (far more access than required in
531 practice)
532
533^ These factors have resulted in less use of `CallJavascriptFunction()` in the
534webui codebase. This functionality can easily be accomplished with the following
535alternatives:
536
537* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
538 when an event occurs in C++ and is more loosely coupled (nothing blows up if
539 the event dispatch is ignored). JS subscribes to notifications via
540 [`cr.addWebUIListener`](#cr_addWebUIListener).
541* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
542 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
543 when Javascript requires a response to an inquiry about C++-canonical state
544 (i.e. "Is Autofill enabled?", "Is the user incognito?")
545
546<a name="FireWebUIListener"></a>
547### WebUIMessageHandler::FireWebUIListener()
548
549`FireWebUIListener()` is used to notify a registered set of listeners that an
550event has occurred. This is generally used for events that are not guaranteed to
551happen in timely manner, or may be caused to happen by unpredictable events
552(i.e. user actions).
553
554Here's some example to detect a change to Chrome's theme:
555
556```js
557cr.addWebUIListener("theme-changed", refreshThemeStyles);
558```
559
560This Javascript event listener can be triggered in C++ via:
561
562```c++
563void MyHandler::OnThemeChanged() {
564 FireWebUIListener("theme-changed");
565}
566```
567
568Because it's not clear when a user might want to change their theme nor what
569theme they'll choose, this is a good candidate for an event listener.
570
571If you simply need to get a response in Javascript from C++, consider using
572[`cr.sendWithPromise()`](#cr_sendWithPromise) and
573[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
574
575<a name="OnJavascriptAllowed"></a>
576### WebUIMessageHandler::OnJavascriptAllowed()
577
578`OnJavascriptDisallowed()` is a lifecycle method called in response to
579[`AllowJavascript()`](#AllowJavascript). It is a good place to register
580observers of global services or other callbacks that might call at unpredictable
581times.
582
583For example:
584
585```c++
586class MyHandler : public content::WebUIMessageHandler {
587 MyHandler() {
588 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
589 }
590 void OnGlobalServiceEvent() {
591 FireWebUIListener("global-thing-happened");
592 }
593};
594```
595
596Because browser-side C++ handlers are created before a renderer is ready, the
597above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
598before the renderer is ready, which may result in dropped updates or
599accidentally running Javascript in a renderer that has navigated to a new URL.
600
601A safer way to set up communication is:
602
603```c++
604class MyHandler : public content::WebUIMessageHandler {
605 public:
606 MyHandler() : observer_(this) {}
607 void OnJavascriptAllowed() override {
608 observer_.Add(GetGlobalService()); // <-- DO THIS.
609 }
610 void OnJavascriptDisallowed() override {
611 observer_.RemoveAll(); // <-- AND THIS.
612 }
613 ScopedObserver<MyHandler, GlobalService> observer_; // <-- ALSO HANDY.
614```
615when a renderer has been created and the
616document has loaded enough to signal to the C++ that it's ready to respond to
617messages.
618
619<a name="OnJavascriptDisallowed"></a>
620### WebUIMessageHandler::OnJavascriptDisallowed()
621
622`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
623it's safe to send JavaScript messsages to the renderer.
624
625There's a number of situations that result in this method being called:
626
627* renderer doesn't exist yet
628* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23629* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30630* tab refresh
631* renderer crash
632
633Though it's possible to programmatically disable Javascript, it's uncommon to
634need to do so.
635
636Because there's no single strategy that works for all cases of a renderer's
637state (i.e. queueing vs dropping messages), these lifecycle methods were
638introduced so a WebUI application can implement these decisions itself.
639
640Often, it makes sense to disconnect from observers in
641`OnJavascriptDisallowed()`:
642
643```c++
644void OvenHandler::OnJavascriptDisallowed() {
645 scoped_oven_observer_.RemoveAll()
646}
647```
648
649Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
650`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
651scoped observer that automatically unsubscribes on destruction but can also
652imperatively unsubscribe in `OnJavascriptDisallowed()`.
653
654<a name="RejectJavascriptCallback"></a>
655### WebUIMessageHandler::RejectJavascriptCallback()
656
657This method is called in response to
658[`cr.sendWithPromise()`](#cr_sendWithPromise) to reject the issued Promise. This
659runs the rejection (second) callback in the [Promise's
660executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
661and any
662[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
663callbacks in the chain.
664
665```c++
666void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23667 AllowJavascript();
668 if (!GetOven()->HasGas()) {
669 RejectJavascriptCallback(args->GetList()[0],
670 base::StringValue("need gas to cook the donuts!"));
671 }
Dan Beam079d5c12017-06-16 19:23:30672```
673
674This method is basically just a
675[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
676global "cr.webUIResponse" method with a success value of false.
677
678```c++
679// WebUIMessageHandler::RejectJavascriptCallback():
680CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:18681 response);
Dan Beam079d5c12017-06-16 19:23:30682```
683
684See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
685
686<a name="ResolveJavascriptCallback"></a>
687### WebUIMessageHandler::ResolveJavascriptCallback()
688
689This method is called in response to
690[`cr.sendWithPromise()`](#cr_sendWithPromise) to fulfill an issued Promise,
691often with a value. This results in runnings any fulfillment (first) callbacks
692in the associate Promise executor and any registered
693[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
694callbacks.
695
696So, given this JS code:
697
698```js
699cr.sendWithPromise('bakeDonuts').then(function(numDonutsBaked) {
700 shop.donuts += numDonutsBaked;
701});
702```
703
704Some handling C++ might do this:
705
706```c++
707void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23708 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:30709 double num_donuts_baked = GetOven()->BakeDonuts();
Michael Giuffrida14938292019-05-31 21:30:23710 ResolveJavascriptCallback(args->GetList()[0], num_donuts_baked);
Dan Beam079d5c12017-06-16 19:23:30711}
712```
713
714## Renderer (JS) &rarr; Browser (C++)
715
716<a name="chrome_send"></a>
717### chrome.send()
718
719When the JavaScript `window` object is created, a renderer is checked for [WebUI
720bindings](#bindings).
721
722```c++
723// RenderFrameImpl::DidClearWindowObject():
724if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
725 WebUIExtension::Install(frame_);
726```
727
728If the bindings exist, a global `chrome.send()` function is exposed to the
729renderer:
730
731```c++
732// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:35733v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:30734chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:18735 gin::CreateFunctionTemplate(
736 isolate, base::Bind(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:30737```
738
739The `chrome.send()` method takes a message name and argument list.
740
741```js
742chrome.send('messageName', [arg1, arg2, ...]);
743```
744
745The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:37746`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:30747
748```c++
749// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:37750render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
751 frame->GetDocument().Url(),
752 message, *content));
Dan Beam079d5c12017-06-16 19:23:30753```
754```c++
755// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:37756IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:30757```
758
759The browser-side code does a map lookup for the message name and calls the found
760callback with the deserialized arguments:
761
762```c++
763// WebUIImpl::ProcessWebUIMessage():
764message_callbacks_.find(message)->second.Run(&args);
765```
766
767<a name="cr_addWebUIListener">
768### cr.addWebUIListener()
769
770WebUI listeners are a convenient way for C++ to inform JavaScript of events.
771
772Older WebUI code exposed public methods for event notification, similar to how
773responses to [chrome.send()](#chrome_send) used to work. They both
Ian Barkley-Yeung4f4f71d2020-06-09 00:38:13774resulted in global namespace pollution, but it was additionally hard to stop
Dan Beam079d5c12017-06-16 19:23:30775listening for events in some cases. **cr.addWebUIListener** is preferred in new
776code.
777
778Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
779just like [cr.sendWithPromise()](#cr_sendWithPromise).
780
781```js
782// addWebUIListener():
783webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
784webUIListenerMap[eventName][createUid()] = callback;
785```
786
787The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
788with an event name and a variable number of arguments.
789
790```c++
791// WebUIMessageHandler:
792template <typename... Values>
793void FireWebUIListener(const std::string& event_name, const Values&... values) {
794 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
795 values...);
796}
797```
798
799C++ handlers call this `FireWebUIListener` method when an event occurs that
800should be communicated to the JavaScript running in a tab.
801
802```c++
803void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
804 FireWebUIListener("donuts-baked", base::FundamentalValue(num_donuts));
805}
806```
807
808JavaScript can listen for WebUI events via:
809
810```js
811var donutsReady = 0;
812cr.addWebUIListener('donuts-baked', function(numFreshlyBakedDonuts) {
813 donutsReady += numFreshlyBakedDonuts;
814});
815```
816
817<a name="cr_sendWithPromise"></a>
818### cr.sendWithPromise()
819
820`cr.sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
821triggering a message requires a response:
822
823```js
824chrome.send('getNumberOfDonuts'); // No easy way to get response!
825```
826
827In older WebUI pages, global methods were exposed simply so responses could be
828sent. **This is discouraged** as it pollutes the global namespace and is harder
829to make request specific or do from deeply nested code.
830
831In newer WebUI pages, you see code like this:
832
833```js
834cr.sendWithPromise('getNumberOfDonuts').then(function(numDonuts) {
835 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
836});
837```
838
839On the C++ side, the message registration is similar to
840[`chrome.send()`](#chrome_send) except that the first argument in the
841message handler's list is a callback ID. That ID is passed to
842`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
843JavaScript and calling the `then()` function.
844
845```c++
846void DonutHandler::HandleGetNumberOfDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23847 AllowJavascript();
848
849 const base::Value& callback_id = args->GetList()[0];
Dan Beam079d5c12017-06-16 19:23:30850 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Michael Giuffrida14938292019-05-31 21:30:23851 ResolveJavascriptCallback(callback_id, base::FundamentalValue(num_donuts));
Dan Beam079d5c12017-06-16 19:23:30852}
853```
854
855Under the covers, a map of `Promise`s are kept in JavaScript.
856
857The callback ID is just a namespaced, ever-increasing number. It's used to
858insert a `Promise` into the JS-side map when created.
859
860```js
861// cr.sendWithPromise():
862var id = methodName + '_' + uidCounter++;
863chromeSendResolverMap[id] = new PromiseResolver;
864chrome.send(methodName, [id].concat(args));
865```
866
867The corresponding number is used to look up a `Promise` and reject or resolve it
868when the outcome is known.
869
870```js
871// cr.webUIResponse():
872var resolver = chromeSendResolverMap[id];
873if (success)
874 resolver.resolve(response);
875else
876 resolver.reject(response);
877```
878
879This approach still relies on the C++ calling a globally exposed method, but
880reduces the surface to only a single global (`cr.webUIResponse`) instead of
881many. It also makes per-request responses easier, which is helpful when multiple
882are in flight.
883
Lukasz Anforowicz11e59532018-10-23 22:46:21884
885## Security considerations
886
887Because WebUI pages are highly privileged, they are often targets for attack,
888since taking control of a WebUI page can sometimes be sufficient to escape
889Chrome's sandbox. To make sure that the special powers granted to WebUI pages
890are safe, WebUI pages are restricted in what they can do:
891
892* WebUI pages cannot embed http/https resources or frames
893* WebUI pages cannot issue http/https fetches
894
895In the rare case that a WebUI page really needs to include web content, the safe
896way to do this is by using a `<webview>` tag. Using a `<webview>` tag is more
897secure than using an iframe for multiple reasons, even if Site Isolation and
898out-of-process iframes keep the web content out of the privileged WebUI process.
899
900First, the content inside the `<webview>` tag has a much reduced attack surface,
901since it does not have a window reference to its embedder or any other frames.
902Only postMessage channel is supported, and this needs to be initiated by the
903embedder, not the guest.
904
905Second, the content inside the `<webview>` tag is hosted in a separate
906StoragePartition. Thus, cookies and other persistent storage for both the WebUI
907page and other browser tabs are inaccessible to it.
908
909This greater level of isolation makes it safer to load possibly untrustworthy or
910compromised web content, reducing the risk of sandbox escapes.
911
912For an example of switching from iframe to webview tag see
913https://crrev.com/c/710738.
914
915
Dan Beam079d5c12017-06-16 19:23:30916## See also
917
Amos Limf916d572018-05-21 23:10:35918* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:30919* WebUI's HTML/CSS/JS code follows the [Chromium Web
920 Development Style Guide](../styleguide/web/web.md)
921
922
923<script>
924let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
925let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
926
927let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
928let hrefs = localLinks.map(a => a.href.split('#')[1]);
929
930hrefs.forEach(href => {
931 if (names.includes(href))
932 console.info('found: ' + href);
933 else
934 console.error('broken href: ' + href);
935})
936</script>