blob: e9a9284305e83424148dca53a7ca4f8541ae0dbe [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
77* chrome-devtools:
78* chrome-extensions:
79* chrome:
80* 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().
141 web_ui->AddMessageHandler(base::MakeUnique<OvenHandler>());
142 }
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
241This method determines whether browser &rarr; renderer communication is allowed.
242It is called in response to a signal from JavaScript that the page is ready to
243communicate.
244
245In the JS:
246
247```js
248window.onload = function() {
249 app.initialize();
250 chrome.send('startPilotLight');
251};
252```
253
254In the C++:
255
256```c++
257void OvenHandler::HandleStartPilotLight(cont base::ListValue* /*args*/) {
258 AllowJavascript();
259 // CallJavascriptFunction() and FireWebUIListener() are now safe to do.
260 GetOven()->StartPilotLight();
261}
262```
263
264<div class="note">
265Relying on the <code>'load'</code> event or browser-side navigation callbacks to
266detect page readiness omits <i>application-specific</i> initialization, and a
267custom <code>'initialized'</code> message is often necessary.
268</div>
269
270<a name="CallJavascriptFunction"></a>
271### WebUIMessageHandler::CallJavascriptFunction()
272
273When the browser process needs to tell the renderer/JS of an event or otherwise
274execute code, it can use `CallJavascriptFunction()`.
275
276<div class="note">
277Javascript must be <a href="#AllowJavascript">allowed</a> to use
278<code>CallJavscriptFunction()</code>.
279</div>
280
281```c++
282void OvenHandler::OnPilotLightExtinguished() {
283 CallJavascriptFunction("app.pilotLightExtinguished");
284}
285```
286
287This works by crafting a string to be evaluated in the renderer. Any arguments
288to the call are serialized to JSON and the parameter list is wrapped with
289
290```
291// See WebUI::GetJavascriptCall() for specifics:
292"functionCallName(" + argumentsAsJson + ")"
293```
294
295and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message.
296
297While this works, it implies that:
298
299* a global method must exist to successfully run the Javascript request
300* any method can be called with any parameter (far more access than required in
301 practice)
302
303^ These factors have resulted in less use of `CallJavascriptFunction()` in the
304webui codebase. This functionality can easily be accomplished with the following
305alternatives:
306
307* [`FireWebUIListener()`](#FireWebUIListener) allows easily notifying the page
308 when an event occurs in C++ and is more loosely coupled (nothing blows up if
309 the event dispatch is ignored). JS subscribes to notifications via
310 [`cr.addWebUIListener`](#cr_addWebUIListener).
311* [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) and
312 [`RejectJavascriptCallback`](#RejectJavascriptCallback) are useful
313 when Javascript requires a response to an inquiry about C++-canonical state
314 (i.e. "Is Autofill enabled?", "Is the user incognito?")
315
316<a name="FireWebUIListener"></a>
317### WebUIMessageHandler::FireWebUIListener()
318
319`FireWebUIListener()` is used to notify a registered set of listeners that an
320event has occurred. This is generally used for events that are not guaranteed to
321happen in timely manner, or may be caused to happen by unpredictable events
322(i.e. user actions).
323
324Here's some example to detect a change to Chrome's theme:
325
326```js
327cr.addWebUIListener("theme-changed", refreshThemeStyles);
328```
329
330This Javascript event listener can be triggered in C++ via:
331
332```c++
333void MyHandler::OnThemeChanged() {
334 FireWebUIListener("theme-changed");
335}
336```
337
338Because it's not clear when a user might want to change their theme nor what
339theme they'll choose, this is a good candidate for an event listener.
340
341If you simply need to get a response in Javascript from C++, consider using
342[`cr.sendWithPromise()`](#cr_sendWithPromise) and
343[`ResolveJavascriptCallback`](#ResolveJavascriptCallback).
344
345<a name="OnJavascriptAllowed"></a>
346### WebUIMessageHandler::OnJavascriptAllowed()
347
348`OnJavascriptDisallowed()` is a lifecycle method called in response to
349[`AllowJavascript()`](#AllowJavascript). It is a good place to register
350observers of global services or other callbacks that might call at unpredictable
351times.
352
353For example:
354
355```c++
356class MyHandler : public content::WebUIMessageHandler {
357 MyHandler() {
358 GetGlobalService()->AddObserver(this); // <-- DON'T DO THIS.
359 }
360 void OnGlobalServiceEvent() {
361 FireWebUIListener("global-thing-happened");
362 }
363};
364```
365
366Because browser-side C++ handlers are created before a renderer is ready, the
367above code may result in calling [`FireWebUIListener`](#FireWebUIListener)
368before the renderer is ready, which may result in dropped updates or
369accidentally running Javascript in a renderer that has navigated to a new URL.
370
371A safer way to set up communication is:
372
373```c++
374class MyHandler : public content::WebUIMessageHandler {
375 public:
376 MyHandler() : observer_(this) {}
377 void OnJavascriptAllowed() override {
378 observer_.Add(GetGlobalService()); // <-- DO THIS.
379 }
380 void OnJavascriptDisallowed() override {
381 observer_.RemoveAll(); // <-- AND THIS.
382 }
383 ScopedObserver<MyHandler, GlobalService> observer_; // <-- ALSO HANDY.
384```
385when a renderer has been created and the
386document has loaded enough to signal to the C++ that it's ready to respond to
387messages.
388
389<a name="OnJavascriptDisallowed"></a>
390### WebUIMessageHandler::OnJavascriptDisallowed()
391
392`OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether
393it's safe to send JavaScript messsages to the renderer.
394
395There's a number of situations that result in this method being called:
396
397* renderer doesn't exist yet
398* renderer exists but isn't ready
399* renderer is ready but application-specifici JS isn't ready yet
400* tab refresh
401* renderer crash
402
403Though it's possible to programmatically disable Javascript, it's uncommon to
404need to do so.
405
406Because there's no single strategy that works for all cases of a renderer's
407state (i.e. queueing vs dropping messages), these lifecycle methods were
408introduced so a WebUI application can implement these decisions itself.
409
410Often, it makes sense to disconnect from observers in
411`OnJavascriptDisallowed()`:
412
413```c++
414void OvenHandler::OnJavascriptDisallowed() {
415 scoped_oven_observer_.RemoveAll()
416}
417```
418
419Because `OnJavascriptDisallowed()` is not guaranteed to be called before a
420`WebUIMessageHandler`'s destructor, it is often advisable to use some form of
421scoped observer that automatically unsubscribes on destruction but can also
422imperatively unsubscribe in `OnJavascriptDisallowed()`.
423
424<a name="RejectJavascriptCallback"></a>
425### WebUIMessageHandler::RejectJavascriptCallback()
426
427This method is called in response to
428[`cr.sendWithPromise()`](#cr_sendWithPromise) to reject the issued Promise. This
429runs the rejection (second) callback in the [Promise's
430executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
431and any
432[`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
433callbacks in the chain.
434
435```c++
436void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
437base::Value* callback_id;
438args->Get(0, &callback_id);
439if (!GetOven()->HasGas()) {
440 RejectJavascriptCallback(callback_id,
441 base::StringValue("need gas to cook the donuts!"));
442}
443```
444
445This method is basically just a
446[`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a
447global "cr.webUIResponse" method with a success value of false.
448
449```c++
450// WebUIMessageHandler::RejectJavascriptCallback():
451CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false),
dbeam8b52edff2017-06-16 22:36:18452 response);
Dan Beam079d5c12017-06-16 19:23:30453```
454
455See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback)
456
457<a name="ResolveJavascriptCallback"></a>
458### WebUIMessageHandler::ResolveJavascriptCallback()
459
460This method is called in response to
461[`cr.sendWithPromise()`](#cr_sendWithPromise) to fulfill an issued Promise,
462often with a value. This results in runnings any fulfillment (first) callbacks
463in the associate Promise executor and any registered
464[`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then)
465callbacks.
466
467So, given this JS code:
468
469```js
470cr.sendWithPromise('bakeDonuts').then(function(numDonutsBaked) {
471 shop.donuts += numDonutsBaked;
472});
473```
474
475Some handling C++ might do this:
476
477```c++
478void OvenHandler::HandleBakeDonuts(const base::ListValue* args) {
479 base::Value* callback_id;
480 args->Get(0, &callback_id);
481 double num_donuts_baked = GetOven()->BakeDonuts();
482 ResolveJavascriptCallback(*callback_id, num_donuts_baked);
483}
484```
485
486## Renderer (JS) &rarr; Browser (C++)
487
488<a name="chrome_send"></a>
489### chrome.send()
490
491When the JavaScript `window` object is created, a renderer is checked for [WebUI
492bindings](#bindings).
493
494```c++
495// RenderFrameImpl::DidClearWindowObject():
496if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI)
497 WebUIExtension::Install(frame_);
498```
499
500If the bindings exist, a global `chrome.send()` function is exposed to the
501renderer:
502
503```c++
504// WebUIExtension::Install():
505v8::Local<v8::Object> chrome =
dbeam8b52edff2017-06-16 22:36:18506 GetOrCreateChromeObject(isolate, context->Global());
Dan Beam079d5c12017-06-16 19:23:30507chrome->Set(gin::StringToSymbol(isolate, "send"),
dbeam8b52edff2017-06-16 22:36:18508 gin::CreateFunctionTemplate(
509 isolate, base::Bind(&WebUIExtension::Send))->GetFunction());
Dan Beam079d5c12017-06-16 19:23:30510```
511
512The `chrome.send()` method takes a message name and argument list.
513
514```js
515chrome.send('messageName', [arg1, arg2, ...]);
516```
517
518The message name and argument list are serialized to JSON and sent via the
519`ViewHostMsg_WebUISend` IPC message from the renderer to the browser.
520
521```c++
522// In the renderer (WebUIExtension::Send()):
523render_view->Send(new ViewHostMsg_WebUISend(render_view->GetRoutingID(),
dbeam8b52edff2017-06-16 22:36:18524 frame->GetDocument().Url(),
525 message, *content));
Dan Beam079d5c12017-06-16 19:23:30526```
527```c++
528// In the browser (WebUIImpl::OnMessageReceived()):
529IPC_MESSAGE_HANDLER(ViewHostMsg_WebUISend, OnWebUISend)
530```
531
532The browser-side code does a map lookup for the message name and calls the found
533callback with the deserialized arguments:
534
535```c++
536// WebUIImpl::ProcessWebUIMessage():
537message_callbacks_.find(message)->second.Run(&args);
538```
539
540<a name="cr_addWebUIListener">
541### cr.addWebUIListener()
542
543WebUI listeners are a convenient way for C++ to inform JavaScript of events.
544
545Older WebUI code exposed public methods for event notification, similar to how
546responses to [chrome.send()](#chrome_send) used to work. They both
547resulted in global namespace polution, but it was additionally hard to stop
548listening for events in some cases. **cr.addWebUIListener** is preferred in new
549code.
550
551Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript,
552just like [cr.sendWithPromise()](#cr_sendWithPromise).
553
554```js
555// addWebUIListener():
556webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
557webUIListenerMap[eventName][createUid()] = callback;
558```
559
560The C++ responds to a globally exposed function (`cr.webUIListenerCallback`)
561with an event name and a variable number of arguments.
562
563```c++
564// WebUIMessageHandler:
565template <typename... Values>
566void FireWebUIListener(const std::string& event_name, const Values&... values) {
567 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name),
568 values...);
569}
570```
571
572C++ handlers call this `FireWebUIListener` method when an event occurs that
573should be communicated to the JavaScript running in a tab.
574
575```c++
576void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) {
577 FireWebUIListener("donuts-baked", base::FundamentalValue(num_donuts));
578}
579```
580
581JavaScript can listen for WebUI events via:
582
583```js
584var donutsReady = 0;
585cr.addWebUIListener('donuts-baked', function(numFreshlyBakedDonuts) {
586 donutsReady += numFreshlyBakedDonuts;
587});
588```
589
590<a name="cr_sendWithPromise"></a>
591### cr.sendWithPromise()
592
593`cr.sendWithPromise()` is a wrapper around `chrome.send()`. It's used when
594triggering a message requires a response:
595
596```js
597chrome.send('getNumberOfDonuts'); // No easy way to get response!
598```
599
600In older WebUI pages, global methods were exposed simply so responses could be
601sent. **This is discouraged** as it pollutes the global namespace and is harder
602to make request specific or do from deeply nested code.
603
604In newer WebUI pages, you see code like this:
605
606```js
607cr.sendWithPromise('getNumberOfDonuts').then(function(numDonuts) {
608 alert('Yay, there are ' + numDonuts + ' delicious donuts left!');
609});
610```
611
612On the C++ side, the message registration is similar to
613[`chrome.send()`](#chrome_send) except that the first argument in the
614message handler's list is a callback ID. That ID is passed to
615`ResolveJavascriptCallback()`, which ends up resolving the `Promise` in
616JavaScript and calling the `then()` function.
617
618```c++
619void DonutHandler::HandleGetNumberOfDonuts(const base::ListValue* args) {
620 base::Value* callback_id;
621 args->Get(0, &callback_id);
622 size_t num_donuts = GetOven()->GetNumberOfDonuts();
623 ResolveJavascriptCallback(*callback_id, base::FundamentalValue(num_donuts));
624}
625```
626
627Under the covers, a map of `Promise`s are kept in JavaScript.
628
629The callback ID is just a namespaced, ever-increasing number. It's used to
630insert a `Promise` into the JS-side map when created.
631
632```js
633// cr.sendWithPromise():
634var id = methodName + '_' + uidCounter++;
635chromeSendResolverMap[id] = new PromiseResolver;
636chrome.send(methodName, [id].concat(args));
637```
638
639The corresponding number is used to look up a `Promise` and reject or resolve it
640when the outcome is known.
641
642```js
643// cr.webUIResponse():
644var resolver = chromeSendResolverMap[id];
645if (success)
646 resolver.resolve(response);
647else
648 resolver.reject(response);
649```
650
651This approach still relies on the C++ calling a globally exposed method, but
652reduces the surface to only a single global (`cr.webUIResponse`) instead of
653many. It also makes per-request responses easier, which is helpful when multiple
654are in flight.
655
656## See also
657
658* WebUI's C++ code follows the [Chromium C++ styleguide](../c++/c++.md).
659* WebUI's HTML/CSS/JS code follows the [Chromium Web
660 Development Style Guide](../styleguide/web/web.md)
661
662
663<script>
664let nameEls = Array.from(document.querySelectorAll('[id], a[name]'));
665let names = nameEls.map(nameEl => nameEl.name || nameEl.id);
666
667let localLinks = Array.from(document.querySelectorAll('a[href^="#"]'));
668let hrefs = localLinks.map(a => a.href.split('#')[1]);
669
670hrefs.forEach(href => {
671 if (names.includes(href))
672 console.info('found: ' + href);
673 else
674 console.error('broken href: ' + href);
675})
676</script>