OLD | NEW |
(Empty) | |
| 1 <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 |
| 21 implemented with web technologies** (i.e. HTML, CSS, JavaScript). |
| 22 |
| 23 Examples of WebUI in Chromium: |
| 24 |
| 25 * Settings (chrome://settings) |
| 26 * History (chrome://history) |
| 27 * Downloads (chrome://downloads) |
| 28 |
| 29 <div class="note"> |
| 30 Not all web-based UIs in Chrome have chrome:// URLs. |
| 31 </div> |
| 32 |
| 33 This document explains how WebUI works. |
| 34 |
| 35 <a name="bindings"></a> |
| 36 ## What's different from a web page? |
| 37 |
| 38 WebUIs are granted super powers so that they can manage Chrome itself. For |
| 39 example, it'd be very hard to implement the Settings UI without access to many |
| 40 different privacy and security sensitive services. Access to these services are |
| 41 not granted by default. |
| 42 |
| 43 Only special URLs are granted WebUI "bindings" via the child security process. |
| 44 |
| 45 Specifically, 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"> |
| 59 A URL is of the format <protocol>://<host>/<path>. |
| 60 </div> |
| 61 |
| 62 A `chrome:` URL loads a file from disk, memory, or can respond dynamically. |
| 63 |
| 64 Because Chrome UIs generally need access to the browser (not just the current |
| 65 tab), much of the C++ that handles requests or takes actions lives in the |
| 66 browser process. The browser has many more privileges than a renderer (which is |
| 67 sandboxed and doesn't have file access), so access is only granted for certain |
| 68 URLs. |
| 69 |
| 70 ### `chrome:` protocol |
| 71 |
| 72 Chrome recognizes a list of special protocols, which it registers while starting |
| 73 up. |
| 74 |
| 75 Examples: |
| 76 |
| 77 * chrome-devtools: |
| 78 * chrome-extensions: |
| 79 * chrome: |
| 80 * file: |
| 81 * view-source: |
| 82 |
| 83 This document mainly cares about the **chrome:** protocol, but others can also |
| 84 be granted [WebUI bindings](#bindings) or have special |
| 85 properties. |
| 86 |
| 87 ### `chrome:` hosts |
| 88 |
| 89 After registering the `chrome:` protocol, a set of factories are created. These |
| 90 factories contain a list of valid host names. A valid hostname generates a |
| 91 controller. |
| 92 |
| 93 In the case of `chrome:` URLs, these factories are registered early in the |
| 94 browser process lifecycle. |
| 95 |
| 96 ```c++ |
| 97 // ChromeBrowserMainParts::PreMainMessageLoopRunImpl(): |
| 98 content::WebUIControllerFactory::RegisterFactory( |
| 99 ChromeWebUIControllerFactory::GetInstance()); |
| 100 ``` |
| 101 |
| 102 When a URL is requested, a new renderer is created to load the URL, and a |
| 103 corresponding class in the browser is set up to handle messages from the |
| 104 renderer to the browser (a `RenderFrameHost`). |
| 105 |
| 106 The URL of the request is inspected: |
| 107 |
| 108 ```c++ |
| 109 if (url.SchemeIs("chrome") && url.host_piece() == "donuts") // chrome://donuts |
| 110 return &NewWebUI<DonutsUI>; |
| 111 return nullptr; // Not a known host; no special access. |
| 112 ``` |
| 113 |
| 114 and if a factory knows how to handle a host (returns a `WebUIFactoryFunction`), |
| 115 the navigation machinery [grants the renderer process WebUI |
| 116 bindings](#bindings) via the child security policy. |
| 117 |
| 118 ```c++ |
| 119 // RenderFrameHostImpl::AllowBindings(): |
| 120 if (bindings_flags & BINDINGS_POLICY_WEB_UI) { |
| 121 ChildProcessSecurityPolicyImpl::GetInstance()->GrantWebUIBindings( |
| 122 GetProcess()->GetID()); |
| 123 } |
| 124 ``` |
| 125 |
| 126 The factory creates a [`WebUIController`](#WebUIController) for a tab. |
| 127 Here's an example: |
| 128 |
| 129 ```c++ |
| 130 // Controller for chrome://donuts. |
| 131 class 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 |
| 146 If we assume the contents of `IDR_DONUTS_HTML` yields: |
| 147 |
| 148 ```html |
| 149 <h1>$i18n{mmmDonuts}</h1> |
| 150 ``` |
| 151 |
| 152 Visiting `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 |
| 158 Delicious 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 |
| 165 one. `WebUI` lives in the browser process, and is owned by a `RenderFrameHost`. |
| 166 `WebUI`s have a concrete implementation (`WebUIImpl`) in `content/` and are |
| 167 created in response to navigation events. |
| 168 |
| 169 A `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 |
| 171 hostname of a requested URL. |
| 172 |
| 173 A `WebUI` *can* handle messages itself, but often defers these duties to |
| 174 separate [`WebUIMessageHandler`](#WebUIMessageHandler)s, which are generally |
| 175 designed for handling messages on certain topics. |
| 176 |
| 177 A `WebUI` can be created speculatively, and are generally fairly lightweight. |
| 178 Heavier duty stuff like hard initialization logic or accessing services that may |
| 179 have 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, |
| 184 and are re-used where possible between navigations (i.e. refreshing a page). |
| 185 Because they run in a separate process and can exist before a corresponding |
| 186 renderer process has been created, special care is required to communicate with |
| 187 the renderer if reliable message passing is required. |
| 188 |
| 189 <a name="WebUIController"></a> |
| 190 ### WebUIController |
| 191 |
| 192 A `WebUIController` is the brains of the operation, and is responsible for |
| 193 application-specific logic, setting up translations and resources, creating |
| 194 message handlers, and potentially responding to requests dynamically. In complex |
| 195 pages, logic is often split across multiple |
| 196 [`WebUIMessageHandler`s](#WebUIMessageHandler) instead of solely in the |
| 197 controller for organizational benefits. |
| 198 |
| 199 A `WebUIController` is owned by a [`WebUI`](#WebUI), and is created and set on |
| 200 an existing [`WebUI`](#WebUI) when the correct one is determined via URL |
| 201 inspection (i.e. chrome://settings creates a generic [`WebUI`](#WebUI) with a |
| 202 settings-specific `WebUIController`). |
| 203 |
| 204 ### WebUIDataSource |
| 205 |
| 206 <a name="WebUIMessageHandler"></a> |
| 207 ### WebUIMessageHandler |
| 208 |
| 209 Because some pages have many messages or share code that sends messages, message |
| 210 handling is often split into discrete classes called `WebUIMessageHandler`s. |
| 211 These handlers respond to specific invocations from JavaScript. |
| 212 |
| 213 So, the given C++ code: |
| 214 |
| 215 ```c++ |
| 216 void OvenHandler::RegisterMessages() { |
| 217 web_ui()->RegisterMessageHandler("bakeDonuts", |
| 218 base::Bind(&OvenHandler::HandleBakeDonuts, base::Unretained(this))); |
| 219 } |
| 220 |
| 221 void 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 |
| 228 Can 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++) → Renderer (JS) |
| 237 |
| 238 <a name="AllowJavascript"></a> |
| 239 ### WebUIMessageHandler::AllowJavascript() |
| 240 |
| 241 This method determines whether browser → renderer communication is allowed. |
| 242 It is called in response to a signal from JavaScript that the page is ready to |
| 243 communicate. |
| 244 |
| 245 In the JS: |
| 246 |
| 247 ```js |
| 248 window.onload = function() { |
| 249 app.initialize(); |
| 250 chrome.send('startPilotLight'); |
| 251 }; |
| 252 ``` |
| 253 |
| 254 In the C++: |
| 255 |
| 256 ```c++ |
| 257 void 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"> |
| 265 Relying on the <code>'load'</code> event or browser-side navigation callbacks to |
| 266 detect page readiness omits <i>application-specific</i> initialization, and a |
| 267 custom <code>'initialized'</code> message is often necessary. |
| 268 </div> |
| 269 |
| 270 <a name="CallJavascriptFunction"></a> |
| 271 ### WebUIMessageHandler::CallJavascriptFunction() |
| 272 |
| 273 When the browser process needs to tell the renderer/JS of an event or otherwise |
| 274 execute code, it can use `CallJavascriptFunction()`. |
| 275 |
| 276 <div class="note"> |
| 277 Javascript must be <a href="#AllowJavascript">allowed</a> to use |
| 278 <code>CallJavscriptFunction()</code>. |
| 279 </div> |
| 280 |
| 281 ```c++ |
| 282 void OvenHandler::OnPilotLightExtinguished() { |
| 283 CallJavascriptFunction("app.pilotLightExtinguished"); |
| 284 } |
| 285 ``` |
| 286 |
| 287 This works by crafting a string to be evaluated in the renderer. Any arguments |
| 288 to 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 |
| 295 and sent to the renderer via a `FrameMsg_JavaScriptExecuteRequest` IPC message. |
| 296 |
| 297 While 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 |
| 304 webui codebase. This functionality can easily be accomplished with the following |
| 305 alternatives: |
| 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 |
| 320 event has occurred. This is generally used for events that are not guaranteed to |
| 321 happen in timely manner, or may be caused to happen by unpredictable events |
| 322 (i.e. user actions). |
| 323 |
| 324 Here's some example to detect a change to Chrome's theme: |
| 325 |
| 326 ```js |
| 327 cr.addWebUIListener("theme-changed", refreshThemeStyles); |
| 328 ``` |
| 329 |
| 330 This Javascript event listener can be triggered in C++ via: |
| 331 |
| 332 ```c++ |
| 333 void MyHandler::OnThemeChanged() { |
| 334 FireWebUIListener("theme-changed"); |
| 335 } |
| 336 ``` |
| 337 |
| 338 Because it's not clear when a user might want to change their theme nor what |
| 339 theme they'll choose, this is a good candidate for an event listener. |
| 340 |
| 341 If 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 |
| 350 observers of global services or other callbacks that might call at unpredictable |
| 351 times. |
| 352 |
| 353 For example: |
| 354 |
| 355 ```c++ |
| 356 class 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 |
| 366 Because browser-side C++ handlers are created before a renderer is ready, the |
| 367 above code may result in calling [`FireWebUIListener`](#FireWebUIListener) |
| 368 before the renderer is ready, which may result in dropped updates or |
| 369 accidentally running Javascript in a renderer that has navigated to a new URL. |
| 370 |
| 371 A safer way to set up communication is: |
| 372 |
| 373 ```c++ |
| 374 class 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 ``` |
| 385 when a renderer has been created and the |
| 386 document has loaded enough to signal to the C++ that it's ready to respond to |
| 387 messages. |
| 388 |
| 389 <a name="OnJavascriptDisallowed"></a> |
| 390 ### WebUIMessageHandler::OnJavascriptDisallowed() |
| 391 |
| 392 `OnJavascriptDisallowed` is a lifecycle method called when it's unclear whether |
| 393 it's safe to send JavaScript messsages to the renderer. |
| 394 |
| 395 There'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 |
| 403 Though it's possible to programmatically disable Javascript, it's uncommon to |
| 404 need to do so. |
| 405 |
| 406 Because there's no single strategy that works for all cases of a renderer's |
| 407 state (i.e. queueing vs dropping messages), these lifecycle methods were |
| 408 introduced so a WebUI application can implement these decisions itself. |
| 409 |
| 410 Often, it makes sense to disconnect from observers in |
| 411 `OnJavascriptDisallowed()`: |
| 412 |
| 413 ```c++ |
| 414 void OvenHandler::OnJavascriptDisallowed() { |
| 415 scoped_oven_observer_.RemoveAll() |
| 416 } |
| 417 ``` |
| 418 |
| 419 Because `OnJavascriptDisallowed()` is not guaranteed to be called before a |
| 420 `WebUIMessageHandler`'s destructor, it is often advisable to use some form of |
| 421 scoped observer that automatically unsubscribes on destruction but can also |
| 422 imperatively unsubscribe in `OnJavascriptDisallowed()`. |
| 423 |
| 424 <a name="RejectJavascriptCallback"></a> |
| 425 ### WebUIMessageHandler::RejectJavascriptCallback() |
| 426 |
| 427 This method is called in response to |
| 428 [`cr.sendWithPromise()`](#cr_sendWithPromise) to reject the issued Promise. This |
| 429 runs the rejection (second) callback in the [Promise's |
| 430 executor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Glob
al_Objects/Promise) |
| 431 and any |
| 432 [`catch()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Gl
obal_Objects/Promise/catch) |
| 433 callbacks in the chain. |
| 434 |
| 435 ```c++ |
| 436 void OvenHandler::HandleBakeDonuts(const base::ListValue* args) { |
| 437 base::Value* callback_id; |
| 438 args->Get(0, &callback_id); |
| 439 if (!GetOven()->HasGas()) { |
| 440 RejectJavascriptCallback(callback_id, |
| 441 base::StringValue("need gas to cook the donuts!")); |
| 442 } |
| 443 ``` |
| 444 |
| 445 This method is basically just a |
| 446 [`CallJavascriptFunction()`](#CallJavascriptFunction) wrapper that calls a |
| 447 global "cr.webUIResponse" method with a success value of false. |
| 448 |
| 449 ```c++ |
| 450 // WebUIMessageHandler::RejectJavascriptCallback(): |
| 451 CallJavascriptFunction("cr.webUIResponse", callback_id, base::Value(false), |
| 452
response); |
| 453 ``` |
| 454 |
| 455 See also: [`ResolveJavascriptCallback`](#ResolveJavascriptCallback) |
| 456 |
| 457 <a name="ResolveJavascriptCallback"></a> |
| 458 ### WebUIMessageHandler::ResolveJavascriptCallback() |
| 459 |
| 460 This method is called in response to |
| 461 [`cr.sendWithPromise()`](#cr_sendWithPromise) to fulfill an issued Promise, |
| 462 often with a value. This results in runnings any fulfillment (first) callbacks |
| 463 in the associate Promise executor and any registered |
| 464 [`then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Glo
bal_Objects/Promise/then) |
| 465 callbacks. |
| 466 |
| 467 So, given this JS code: |
| 468 |
| 469 ```js |
| 470 cr.sendWithPromise('bakeDonuts').then(function(numDonutsBaked) { |
| 471 shop.donuts += numDonutsBaked; |
| 472 }); |
| 473 ``` |
| 474 |
| 475 Some handling C++ might do this: |
| 476 |
| 477 ```c++ |
| 478 void 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) → Browser (C++) |
| 487 |
| 488 <a name="chrome_send"></a> |
| 489 ### chrome.send() |
| 490 |
| 491 When the JavaScript `window` object is created, a renderer is checked for [WebUI |
| 492 bindings](#bindings). |
| 493 |
| 494 ```c++ |
| 495 // RenderFrameImpl::DidClearWindowObject(): |
| 496 if (enabled_bindings_ & BINDINGS_POLICY_WEB_UI) |
| 497 WebUIExtension::Install(frame_); |
| 498 ``` |
| 499 |
| 500 If the bindings exist, a global `chrome.send()` function is exposed to the |
| 501 renderer: |
| 502 |
| 503 ```c++ |
| 504 // WebUIExtension::Install(): |
| 505 v8::Local<v8::Object> chrome = |
| 506 GetOrCreateChromeObject(isolate, context->Global()); |
| 507 chrome->Set(gin::StringToSymbol(isolate, "send"), |
| 508 gin::CreateFunctionTemplate( |
| 509 isolate, base::B
ind(&WebUIExtension::Send))->GetFunction()); |
| 510 ``` |
| 511 |
| 512 The `chrome.send()` method takes a message name and argument list. |
| 513 |
| 514 ```js |
| 515 chrome.send('messageName', [arg1, arg2, ...]); |
| 516 ``` |
| 517 |
| 518 The 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()): |
| 523 render_view->Send(new ViewHostMsg_WebUISend(render_view->GetRoutingID(), |
| 524
frame->GetDocument().Url(), |
| 525
message, *content)); |
| 526 ``` |
| 527 ```c++ |
| 528 // In the browser (WebUIImpl::OnMessageReceived()): |
| 529 IPC_MESSAGE_HANDLER(ViewHostMsg_WebUISend, OnWebUISend) |
| 530 ``` |
| 531 |
| 532 The browser-side code does a map lookup for the message name and calls the found |
| 533 callback with the deserialized arguments: |
| 534 |
| 535 ```c++ |
| 536 // WebUIImpl::ProcessWebUIMessage(): |
| 537 message_callbacks_.find(message)->second.Run(&args); |
| 538 ``` |
| 539 |
| 540 <a name="cr_addWebUIListener"> |
| 541 ### cr.addWebUIListener() |
| 542 |
| 543 WebUI listeners are a convenient way for C++ to inform JavaScript of events. |
| 544 |
| 545 Older WebUI code exposed public methods for event notification, similar to how |
| 546 responses to [chrome.send()](#chrome_send) used to work. They both |
| 547 resulted in global namespace polution, but it was additionally hard to stop |
| 548 listening for events in some cases. **cr.addWebUIListener** is preferred in new |
| 549 code. |
| 550 |
| 551 Adding WebUI listeners creates and inserts a unique ID into a map in JavaScript, |
| 552 just like [cr.sendWithPromise()](#cr_sendWithPromise). |
| 553 |
| 554 ```js |
| 555 // addWebUIListener(): |
| 556 webUIListenerMap[eventName] = webUIListenerMap[eventName] || {}; |
| 557 webUIListenerMap[eventName][createUid()] = callback; |
| 558 ``` |
| 559 |
| 560 The C++ responds to a globally exposed function (`cr.webUIListenerCallback`) |
| 561 with an event name and a variable number of arguments. |
| 562 |
| 563 ```c++ |
| 564 // WebUIMessageHandler: |
| 565 template <typename... Values> |
| 566 void FireWebUIListener(const std::string& event_name, const Values&... values) { |
| 567 CallJavascriptFunction("cr.webUIListenerCallback", base::Value(event_name), |
| 568 values...); |
| 569 } |
| 570 ``` |
| 571 |
| 572 C++ handlers call this `FireWebUIListener` method when an event occurs that |
| 573 should be communicated to the JavaScript running in a tab. |
| 574 |
| 575 ```c++ |
| 576 void OvenHandler::OnBakingDonutsFinished(size_t num_donuts) { |
| 577 FireWebUIListener("donuts-baked", base::FundamentalValue(num_donuts)); |
| 578 } |
| 579 ``` |
| 580 |
| 581 JavaScript can listen for WebUI events via: |
| 582 |
| 583 ```js |
| 584 var donutsReady = 0; |
| 585 cr.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 |
| 594 triggering a message requires a response: |
| 595 |
| 596 ```js |
| 597 chrome.send('getNumberOfDonuts'); // No easy way to get response! |
| 598 ``` |
| 599 |
| 600 In older WebUI pages, global methods were exposed simply so responses could be |
| 601 sent. **This is discouraged** as it pollutes the global namespace and is harder |
| 602 to make request specific or do from deeply nested code. |
| 603 |
| 604 In newer WebUI pages, you see code like this: |
| 605 |
| 606 ```js |
| 607 cr.sendWithPromise('getNumberOfDonuts').then(function(numDonuts) { |
| 608 alert('Yay, there are ' + numDonuts + ' delicious donuts left!'); |
| 609 }); |
| 610 ``` |
| 611 |
| 612 On the C++ side, the message registration is similar to |
| 613 [`chrome.send()`](#chrome_send) except that the first argument in the |
| 614 message handler's list is a callback ID. That ID is passed to |
| 615 `ResolveJavascriptCallback()`, which ends up resolving the `Promise` in |
| 616 JavaScript and calling the `then()` function. |
| 617 |
| 618 ```c++ |
| 619 void 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 |
| 627 Under the covers, a map of `Promise`s are kept in JavaScript. |
| 628 |
| 629 The callback ID is just a namespaced, ever-increasing number. It's used to |
| 630 insert a `Promise` into the JS-side map when created. |
| 631 |
| 632 ```js |
| 633 // cr.sendWithPromise(): |
| 634 var id = methodName + '_' + uidCounter++; |
| 635 chromeSendResolverMap[id] = new PromiseResolver; |
| 636 chrome.send(methodName, [id].concat(args)); |
| 637 ``` |
| 638 |
| 639 The corresponding number is used to look up a `Promise` and reject or resolve it |
| 640 when the outcome is known. |
| 641 |
| 642 ```js |
| 643 // cr.webUIResponse(): |
| 644 var resolver = chromeSendResolverMap[id]; |
| 645 if (success) |
| 646 resolver.resolve(response); |
| 647 else |
| 648 resolver.reject(response); |
| 649 ``` |
| 650 |
| 651 This approach still relies on the C++ calling a globally exposed method, but |
| 652 reduces the surface to only a single global (`cr.webUIResponse`) instead of |
| 653 many. It also makes per-request responses easier, which is helpful when multiple |
| 654 are 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> |
| 664 let nameEls = Array.from(document.querySelectorAll('[id], a[name]')); |
| 665 let names = nameEls.map(nameEl => nameEl.name || nameEl.id); |
| 666 |
| 667 let localLinks = Array.from(document.querySelectorAll('a[href^="#"]')); |
| 668 let hrefs = localLinks.map(a => a.href.split('#')[1]); |
| 669 |
| 670 hrefs.forEach(href => { |
| 671 if (names.includes(href)) |
| 672 console.info('found: ' + href); |
| 673 else |
| 674 console.error('broken href: ' + href); |
| 675 }) |
| 676 </script> |
OLD | NEW |