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