blob: 3b9bd04cf1d4f5f9b23a233abefe6bde780039f3 [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.
137 source->SetDefaultResource(IDR_DONUTS_HTML); // Home page.
138 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
160## C++ classes
161
162### WebUI
163
164`WebUI` is a high-level class and pretty much all HTML-based Chrome UIs have
165one. `WebUI` lives in the browser process, and is owned by a `RenderFrameHost`.
166`WebUI`s have a concrete implementation (`WebUIImpl`) in `content/` and are
167created in response to navigation events.
168
169A `WebUI` knows very little about the page it's showing, and it owns a
170[`WebUIController`](#WebUIController) that is set after creation based on the
171hostname of a requested URL.
172
173A `WebUI` *can* handle messages itself, but often defers these duties to
174separate [`WebUIMessageHandler`](#WebUIMessageHandler)s, which are generally
175designed for handling messages on certain topics.
176
177A `WebUI` can be created speculatively, and are generally fairly lightweight.
178Heavier duty stuff like hard initialization logic or accessing services that may
179have side effects are more commonly done in a
180[`WebUIController`](#WebUIController) or
181[`WebUIMessageHandler`s](#WebUIMessageHandler).
182
183`WebUI` are created synchronously on the UI thread in response to a URL request,
184and are re-used where possible between navigations (i.e. refreshing a page).
185Because they run in a separate process and can exist before a corresponding
186renderer process has been created, special care is required to communicate with
187the renderer if reliable message passing is required.
188
189<a name="WebUIController"></a>
190### WebUIController
191
192A `WebUIController` is the brains of the operation, and is responsible for
193application-specific logic, setting up translations and resources, creating
194message handlers, and potentially responding to requests dynamically. In complex
195pages, logic is often split across multiple
196[`WebUIMessageHandler`s](#WebUIMessageHandler) instead of solely in the
197controller for organizational benefits.
198
199A `WebUIController` is owned by a [`WebUI`](#WebUI), and is created and set on
200an existing [`WebUI`](#WebUI) when the correct one is determined via URL
201inspection (i.e. chrome://settings creates a generic [`WebUI`](#WebUI) with a
202settings-specific `WebUIController`).
203
204### WebUIDataSource
205
206<a name="WebUIMessageHandler"></a>
207### WebUIMessageHandler
208
209Because some pages have many messages or share code that sends messages, message
210handling is often split into discrete classes called `WebUIMessageHandler`s.
211These handlers respond to specific invocations from JavaScript.
212
213So, the given C++ code:
214
215```c++
216void OvenHandler::RegisterMessages() {
217 web_ui()->RegisterMessageHandler("bakeDonuts",
218 base::Bind(&OvenHandler::HandleBakeDonuts, base::Unretained(this)));
219}
220
221void OverHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23222 AllowJavascript();
223
224 CHECK_EQ(1u, args->GetSize());
225 // JavaScript numbers are doubles.
226 double num_donuts = args->GetList()[0].GetDouble();
Dan Beam079d5c12017-06-16 19:23:30227 GetOven()->BakeDonuts(static_cast<int>(num_donuts));
228}
229```
230
231Can be triggered in JavaScript with this example code:
232
233```js
234$('bakeDonutsButton').onclick = function() {
235 chrome.send('bakeDonuts', [5]); // bake 5 donuts!
236};
237```
238
239## Browser (C++) &rarr; Renderer (JS)
240
241<a name="AllowJavascript"></a>
242### WebUIMessageHandler::AllowJavascript()
243
Adam Langley81be0732019-03-06 18:38:45244A tab that has been used for settings UI may be reloaded, or may navigate to an
245external origin. In both cases, one does not want callbacks from C++ to
246Javascript to run. In the former case, the callbacks will occur when the
247Javascript doesn't expect them. In the latter case, sensitive information may be
248delivered to an untrusted origin.
249
250Therefore each message handler maintains
251[a boolean](https://cs.chromium.org/search/?q=WebUIMessageHandler::javascript_allowed_)
252that describes whether delivering callbacks to Javascript is currently
253appropriate. This boolean is set by calling `AllowJavascript`, which should be
254done when handling a call from Javascript, because that indicates that the page
255is ready for the subsequent callback. (See
256[design doc](https://drive.google.com/open?id=1z1diKvwgMmn4YFzlW1kss0yHmo8yy68TN_FUhUzRz7Q).)
257If the tab navigates or reloads,
258[`DisallowJavascript`](https://cs.chromium.org/search/?q=WebUIMessageHandler::DisallowJavascript)
259is called to clear the flag.
260
261Therefore, before each callback from C++ to Javascript, the flag must be tested
262by calling
263[`IsJavascriptAllowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::IsJavascriptAllowed).
264If false, then the callback must be dropped. (When the flag is false, calling
265[`ResolveJavascriptCallback`](https://cs.chromium.org/search/?q=WebUIMessageHandler::ResolveJavascriptCallback)
266will crash. See
267[design doc](https://docs.google.com/document/d/1udXoW3aJL0-l5wrbsOg5bpYWB0qOCW5K7yXpv4tFeA8).)
268
269Also beware of [ABA](https://en.wikipedia.org/wiki/ABA_problem) issues: Consider
270the case where an asynchronous operation is started, the settings page is
271reloaded, and the user triggers another operation using the original message
272handler. The `javascript_allowed_` boolean will be true, but the original
273callback should still be dropped because it relates to a operation that was
274discarded by the reload. (Reloading settings UI does _not_ cause message handler
275objects to be deleted.)
276
277Thus a message handler may override
278[`OnJavascriptDisallowed`](https://cs.chromium.org/search/?q=WebUIMessageHandler::OnJavascriptDisallowed)
279to learn when pending callbacks should be canceled.
Dan Beam079d5c12017-06-16 19:23:30280
281In the JS:
282
283```js
284window.onload = function() {
285 app.initialize();
286 chrome.send('startPilotLight');
287};
288```
289
290In the C++:
291
292```c++
293void OvenHandler::HandleStartPilotLight(cont base::ListValue* /*args*/) {
294 AllowJavascript();
295 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
296 GetOven()->StartPilotLight();
297}
298```
299
300<div class="note">
301Relying on the <code>'load'</code> event or browser-side navigation callbacks to
302detect page readiness omits <i>application-specific</i> initialization, and a
303custom <code>'initialized'</code> message is often necessary.
304</div>
305
306<a name="CallJavascriptFunction"></a>
307### WebUIMessageHandler::CallJavascriptFunction()
308
309When the browser process needs to tell the renderer/JS of an event or otherwise
310execute code, it can use `CallJavascriptFunction()`.
311
312<div class="note">
313Javascript must be <a href="#AllowJavascript">allowed</a> to use
314<code>CallJavscriptFunction()</code>.
315</div>
316
317```c++
318void OvenHandler::OnPilotLightExtinguished() {
319 CallJavascriptFunction("app.pilotLightExtinguished");
320}
321```
322
323This works by crafting a string to be evaluated in the renderer. Any arguments
324to the call are serialized to JSON and the parameter list is wrapped with
325
326```
327// See WebUI::GetJavascriptCall() for specifics:
328"functionCallName(" + argumentsAsJson + ")"
329```
330
331and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
332
333While this works, it implies that:
334
335* a global method must exist to successfully run the Javascript request
336* any method can be called with any parameter (far more access than required in
337 practice)
338
339^ These factors have resulted in less use of `CallJavascriptFunction()` in the
340webui codebase. This functionality can easily be accomplished with the following
341alternatives:
342
343* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
344 when an event occurs in C++ and is more loosely coupled (nothing blows up if
345 the event dispatch is ignored). JS subscribes to notifications via
346 [`cr.addWebUIListener`](#cr_addWebUIListener).
347* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
348 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
349 when Javascript requires a response to an inquiry about C++-canonical state
350 (i.e. "Is Autofill enabled?", "Is the user incognito?")
351
352<a name="FireWebUIListener"></a>
353### WebUIMessageHandler::FireWebUIListener()
354
355`FireWebUIListener()` is used to notify a registered set of listeners that an
356event has occurred. This is generally used for events that are not guaranteed to
357happen in timely manner, or may be caused to happen by unpredictable events
358(i.e. user actions).
359
360Here's some example to detect a change to Chrome's theme:
361
362```js
363cr.addWebUIListener("theme-changed", refreshThemeStyles);
364```
365
366This Javascript event listener can be triggered in C++ via:
367
368```c++
369void MyHandler::OnThemeChanged() {
370 FireWebUIListener("theme-changed");
371}
372```
373
374Because it's not clear when a user might want to change their theme nor what
375theme they'll choose, this is a good candidate for an event listener.
376
377If you simply need to get a response in Javascript from C++, consider using
378[`cr.sendWithPromise()`](#cr_sendWithPromise) and
379[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
380
381<a name="OnJavascriptAllowed"></a>
382### WebUIMessageHandler::OnJavascriptAllowed()
383
384`OnJavascriptDisallowed()` is a lifecycle method called in response to
385[`AllowJavascript()`](#AllowJavascript). It is a good place to register
386observers of global services or other callbacks that might call at unpredictable
387times.
388
389For example:
390
391```c++
392class MyHandler : public content::WebUIMessageHandler {
393 MyHandler() {
394 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
395 }
396 void OnGlobalServiceEvent() {
397 FireWebUIListener("global-thing-happened");
398 }
399};
400```
401
402Because browser-side C++ handlers are created before a renderer is ready, the
403above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
404before the renderer is ready, which may result in dropped updates or
405accidentally running Javascript in a renderer that has navigated to a new URL.
406
407A safer way to set up communication is:
408
409```c++
410class MyHandler : public content::WebUIMessageHandler {
411 public:
412 MyHandler() : observer_(this) {}
413 void OnJavascriptAllowed() override {
414 observer_.Add(GetGlobalService()); // <-- DO THIS.
415 }
416 void OnJavascriptDisallowed() override {
417 observer_.RemoveAll(); // <-- AND THIS.
418 }
419 ScopedObserver<MyHandler, GlobalService> observer_; // <-- ALSO HANDY.
420```
421when a renderer has been created and the
422document has loaded enough to signal to the C++ that it's ready to respond to
423messages.
424
425<a name="OnJavascriptDisallowed"></a>
426### WebUIMessageHandler::OnJavascriptDisallowed()
427
428`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
429it's safe to send JavaScript messsages to the renderer.
430
431There's a number of situations that result in this method being called:
432
433* renderer doesn't exist yet
434* renderer exists but isn't ready
Michael Giuffrida14938292019-05-31 21:30:23435* renderer is ready but application-specific JS isn't ready yet
Dan Beam079d5c12017-06-16 19:23:30436* tab refresh
437* renderer crash
438
439Though it's possible to programmatically disable Javascript, it's uncommon to
440need to do so.
441
442Because there's no single strategy that works for all cases of a renderer's
443state (i.e. queueing vs dropping messages), these lifecycle methods were
444introduced so a WebUI application can implement these decisions itself.
445
446Often, it makes sense to disconnect from observers in
447`OnJavascriptDisallowed()`:
448
449```c++
450void OvenHandler::OnJavascriptDisallowed() {
451 scoped_oven_observer_.RemoveAll()
452}
453```
454
455Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
456`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
457scoped observer that automatically unsubscribes on destruction but can also
458imperatively unsubscribe in `OnJavascriptDisallowed()`.
459
460<a name="RejectJavascriptCallback"></a>
461### WebUIMessageHandler::RejectJavascriptCallback()
462
463This method is called in response to
464[`cr.sendWithPromise()`](#cr_sendWithPromise) to reject the issued Promise. This
465runs the rejection (second) callback in the [Promise's
466executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
467and any
468[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
469callbacks in the chain.
470
471```c++
472void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23473 AllowJavascript();
474 if (!GetOven()->HasGas()) {
475 RejectJavascriptCallback(args->GetList()[0],
476 base::StringValue("need gas to cook the donuts!"));
477 }
Dan Beam079d5c12017-06-16 19:23:30478```
479
480This method is basically just a
481[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
482global "cr.webUIResponse" method with a success value of false.
483
484```c++
485// WebUIMessageHandler::RejectJavascriptCallback():
486CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:18487 response);
Dan Beam079d5c12017-06-16 19:23:30488```
489
490See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
491
492<a name="ResolveJavascriptCallback"></a>
493### WebUIMessageHandler::ResolveJavascriptCallback()
494
495This method is called in response to
496[`cr.sendWithPromise()`](#cr_sendWithPromise) to fulfill an issued Promise,
497often with a value. This results in runnings any fulfillment (first) callbacks
498in the associate Promise executor and any registered
499[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
500callbacks.
501
502So, given this JS code:
503
504```js
505cr.sendWithPromise('bakeDonuts').then(function(numDonutsBaked) {
506 shop.donuts += numDonutsBaked;
507});
508```
509
510Some handling C++ might do this:
511
512```c++
513void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23514 AllowJavascript();
Dan Beam079d5c12017-06-16 19:23:30515 double num_donuts_baked = GetOven()->BakeDonuts();
Michael Giuffrida14938292019-05-31 21:30:23516 ResolveJavascriptCallback(args->GetList()[0], num_donuts_baked);
Dan Beam079d5c12017-06-16 19:23:30517}
518```
519
520## Renderer (JS) &rarr; Browser (C++)
521
522<a name="chrome_send"></a>
523### chrome.send()
524
525When the JavaScript `window` object is created, a renderer is checked for [WebUI
526bindings](#bindings).
527
528```c++
529// RenderFrameImpl::DidClearWindowObject():
530if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
531 WebUIExtension::Install(frame_);
532```
533
534If the bindings exist, a global `chrome.send()` function is exposed to the
535renderer:
536
537```c++
538// WebUIExtension::Install():
Dan Elphick258bbaf2019-02-01 17:37:35539v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
Dan Beam079d5c12017-06-16 19:23:30540chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:18541 gin::CreateFunctionTemplate(
542 isolate, base::Bind(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:30543```
544
545The `chrome.send()` method takes a message name and argument list.
546
547```js
548chrome.send('messageName', [arg1, arg2, ...]);
549```
550
551The message name and argument list are serialized to JSON and sent via the
Lukasz Anforowicz02923102017-10-09 18:11:37552`FrameHostMsg_WebUISend` IPC message from the renderer to the browser.
Dan Beam079d5c12017-06-16 19:23:30553
554```c++
555// In the renderer (WebUIExtension::Send()):
Lukasz Anforowicz02923102017-10-09 18:11:37556render_frame->Send(new FrameHostMsg_WebUISend(render_frame->GetRoutingID(),
557 frame->GetDocument().Url(),
558 message, *content));
Dan Beam079d5c12017-06-16 19:23:30559```
560```c++
561// In the browser (WebUIImpl::OnMessageReceived()):
Lukasz Anforowicz02923102017-10-09 18:11:37562IPC_MESSAGE_HANDLER(FrameHostMsg_WebUISend, OnWebUISend)
Dan Beam079d5c12017-06-16 19:23:30563```
564
565The browser-side code does a map lookup for the message name and calls the found
566callback with the deserialized arguments:
567
568```c++
569// WebUIImpl::ProcessWebUIMessage():
570message_callbacks_.find(message)->second.Run(&args);
571```
572
573<a name="cr_addWebUIListener">
574### cr.addWebUIListener()
575
576WebUI listeners are a convenient way for C++ to inform JavaScript of events.
577
578Older WebUI code exposed public methods for event notification, similar to how
579responses to [chrome.send()](#chrome_send) used to work. They both
580resulted in global namespace polution, but it was additionally hard to stop
581listening for events in some cases. **cr.addWebUIListener** is preferred in new
582code.
583
584Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
585just like [cr.sendWithPromise()](#cr_sendWithPromise).
586
587```js
588// addWebUIListener():
589webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
590webUIListenerMap[eventName][createUid()] = callback;
591```
592
593The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
594with an event name and a variable number of arguments.
595
596```c++
597// WebUIMessageHandler:
598template <typename... Values>
599void FireWebUIListener(const std::string& event_name, const Values&... values) {
600 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
601 values...);
602}
603```
604
605C++ handlers call this `FireWebUIListener` method when an event occurs that
606should be communicated to the JavaScript running in a tab.
607
608```c++
609void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
610 FireWebUIListener("donuts-baked", base::FundamentalValue(num_donuts));
611}
612```
613
614JavaScript can listen for WebUI events via:
615
616```js
617var donutsReady = 0;
618cr.addWebUIListener('donuts-baked', function(numFreshlyBakedDonuts) {
619 donutsReady += numFreshlyBakedDonuts;
620});
621```
622
623<a name="cr_sendWithPromise"></a>
624### cr.sendWithPromise()
625
626`cr.sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
627triggering a message requires a response:
628
629```js
630chrome.send('getNumberOfDonuts'); // No easy way to get response!
631```
632
633In older WebUI pages, global methods were exposed simply so responses could be
634sent. **This is discouraged** as it pollutes the global namespace and is harder
635to make request specific or do from deeply nested code.
636
637In newer WebUI pages, you see code like this:
638
639```js
640cr.sendWithPromise('getNumberOfDonuts').then(function(numDonuts) {
641 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
642});
643```
644
645On the C++ side, the message registration is similar to
646[`chrome.send()`](#chrome_send) except that the first argument in the
647message handler's list is a callback ID. That ID is passed to
648`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
649JavaScript and calling the `then()` function.
650
651```c++
652void DonutHandler::HandleGetNumberOfDonuts(const base::ListValue* args) {
Michael Giuffrida14938292019-05-31 21:30:23653 AllowJavascript();
654
655 const base::Value& callback_id = args->GetList()[0];
Dan Beam079d5c12017-06-16 19:23:30656 size_t num_donuts = GetOven()->GetNumberOfDonuts();
Michael Giuffrida14938292019-05-31 21:30:23657 ResolveJavascriptCallback(callback_id, base::FundamentalValue(num_donuts));
Dan Beam079d5c12017-06-16 19:23:30658}
659```
660
661Under the covers, a map of `Promise`s are kept in JavaScript.
662
663The callback ID is just a namespaced, ever-increasing number. It's used to
664insert a `Promise` into the JS-side map when created.
665
666```js
667// cr.sendWithPromise():
668var id = methodName + '_' + uidCounter++;
669chromeSendResolverMap[id] = new PromiseResolver;
670chrome.send(methodName, [id].concat(args));
671```
672
673The corresponding number is used to look up a `Promise` and reject or resolve it
674when the outcome is known.
675
676```js
677// cr.webUIResponse():
678var resolver = chromeSendResolverMap[id];
679if (success)
680 resolver.resolve(response);
681else
682 resolver.reject(response);
683```
684
685This approach still relies on the C++ calling a globally exposed method, but
686reduces the surface to only a single global (`cr.webUIResponse`) instead of
687many. It also makes per-request responses easier, which is helpful when multiple
688are in flight.
689
Lukasz Anforowicz11e59532018-10-23 22:46:21690
691## Security considerations
692
693Because WebUI pages are highly privileged, they are often targets for attack,
694since taking control of a WebUI page can sometimes be sufficient to escape
695Chrome's sandbox. To make sure that the special powers granted to WebUI pages
696are safe, WebUI pages are restricted in what they can do:
697
698* WebUI pages cannot embed http/https resources or frames
699* WebUI pages cannot issue http/https fetches
700
701In the rare case that a WebUI page really needs to include web content, the safe
702way to do this is by using a `<webview>` tag. Using a `<webview>` tag is more
703secure than using an iframe for multiple reasons, even if Site Isolation and
704out-of-process iframes keep the web content out of the privileged WebUI process.
705
706First, the content inside the `<webview>` tag has a much reduced attack surface,
707since it does not have a window reference to its embedder or any other frames.
708Only postMessage channel is supported, and this needs to be initiated by the
709embedder, not the guest.
710
711Second, the content inside the `<webview>` tag is hosted in a separate
712StoragePartition. Thus, cookies and other persistent storage for both the WebUI
713page and other browser tabs are inaccessible to it.
714
715This greater level of isolation makes it safer to load possibly untrustworthy or
716compromised web content, reducing the risk of sandbox escapes.
717
718For an example of switching from iframe to webview tag see
719https://crrev.com/c/710738.
720
721
Dan Beam079d5c12017-06-16 19:23:30722## See also
723
Amos Limf916d572018-05-21 23:10:35724* WebUI's C++ code follows the [Chromium C++ styleguide](../styleguide/c++/c++.md).
Dan Beam079d5c12017-06-16 19:23:30725* WebUI's HTML/CSS/JS code follows the [Chromium Web
726 Development Style Guide](../styleguide/web/web.md)
727
728
729<script>
730let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
731let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
732
733let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
734let hrefs = localLinks.map(a => a.href.split('#')[1]);
735
736hrefs.forEach(href => {
737 if (names.includes(href))
738 console.info('found: ' + href);
739 else
740 console.error('broken href: ' + href);
741})
742</script>