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