dynamic-result-types.rst (22638B)
1 Dynamic Result Types 2 ==================== 3 4 This document discusses a special category of address bar results called dynamic 5 result types. Dynamic result types allow you to easily add new types of results 6 to the address bar. 7 8 The intended audience for this document is developers who need to add new kinds 9 of address bar results. 10 11 .. contents:: 12 :depth: 2 13 14 15 Motivation 16 ---------- 17 18 The address bar provides many different types of results in normal Firefox 19 usage. For example, when you type a search term, the address bar may show you 20 search suggestion results from your current search engine. It may also show you 21 results from your browsing history that match your search. If you typed a 22 certain phrase like "update Firefox," it will show you a tip result that lets 23 you know whether Firefox is up to date. 24 25 Each of these types of results is built into the address bar implementation. If 26 you wanted to add a new type of result -- say, a card that shows the weather 27 forecast when the user types "weather" -- one way to do so would be to add a new 28 result type. You would need to update all the code paths in the address bar that 29 relate to result types. For instance, you'd need to update the code path that 30 handles clicks on results so that your weather card opens an appropriate 31 forecast URL when clicked; you'd need to update the address bar view (the panel) 32 so that your card is drawn correctly; you may need to update the keyboard 33 selection behavior if your card contains elements that can be independently 34 selected such as different days of the week; and so on. 35 36 Dynamic Result Types 37 -------------------- 38 39 **Dynamic result types** are an alternative way of implementing new result 40 types. Instead of adding a new built-in type along with all that entails, you 41 add a new provider subclass and register a template that describes how the view 42 should draw your result type and indicates which elements are selectable. The 43 address bar takes care of everything else. 44 45 Dynamic result types are essentially an abstraction layer: Support for them as a 46 general category of results is built into the address bar, and each 47 implementation of a specific dynamic result type fills in the details. 48 49 In addition, dynamic result types can be added at runtime. 50 51 Getting Started 52 --------------- 53 54 To get a feel for how dynamic result types are implemented, you can look at the 55 :searchfox:`UrlbarProviderCalculator <browser/components/urlbar/UrlbarProviderCalculator.sys.mjs>`. 56 57 The next section describes the specific steps you need to take to add a new 58 dynamic result type. 59 60 Implementation Steps 61 -------------------- 62 63 This section describes how to add a new dynamic result type. 64 65 1. Register the dynamic result type 66 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 67 68 First, register the new dynamic result type: 69 70 .. code-block:: javascript 71 72 UrlbarResult.addDynamicResultType(name); 73 74 ``name`` is a string identifier for the new type. It must be unique; that is, it 75 must be different from all other dynamic result type names. It will also be used 76 in DOM IDs, DOM class names, and CSS selectors, so it should not contain any 77 spaces or other characters that are invalid in CSS. 78 79 2. Register the view template 80 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 81 82 Next, add the view template for the new type: 83 84 .. code-block:: javascript 85 86 UrlbarView.addDynamicViewTemplate(name, viewTemplate); 87 88 ``name`` is the new type's name as described in step 1. 89 90 ``viewTemplate`` is an object called a view template. It describes in a 91 declarative manner the DOM that should be created in the view for all results of 92 the new type. 93 94 3. Add the provider 95 ~~~~~~~~~~~~~~~~~~~ 96 97 As with any type of result, results for dynamic result types must be created by 98 one or more providers. Make a ``UrlbarProvider`` subclass for the new provider 99 and implement all the usual provider methods as you normally would: 100 101 .. code-block:: javascript 102 103 class MyDynamicResultTypeProvider extends UrlbarProvider { 104 // ... 105 } 106 107 The ``startQuery`` method should create ``UrlbarResult`` objects with the 108 following two requirements: 109 110 * Result types must be ``UrlbarUtils.RESULT_TYPE.DYNAMIC``. 111 * Result payloads must have a ``dynamicType`` property whose value is the name 112 of the dynamic result type used in step 1. 113 114 The results' sources, other payload properties, and other result properties 115 aren't relevant to dynamic result types, and you should choose values 116 appropriate to your use case. 117 118 If any elements created in the view for your results can be picked with the 119 keyboard or mouse, then be sure to implement your provider's 120 ``onEngagement`` method. 121 122 For help on implementing providers in general, see the address bar's 123 `Architecture Overview`__. 124 125 If you are creating the provider in the internal address bar implementation in 126 mozilla-central, then don't forget to register it in ``UrlbarProvidersManager``. 127 128 __ https://firefox-source-docs.mozilla.org/browser/urlbar/overview.html#urlbarprovider 129 130 4. Implement the provider's getViewUpdate method 131 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 132 133 ``getViewUpdate`` is a provider method particular to dynamic result type 134 providers. Its job is to update the view DOM for a specific result. It's called 135 by the view for each result in the view that was created by the provider. It 136 returns an object called a view update object. 137 138 Recall that the view template was added earlier, in step 2. The view template 139 describes how to build the DOM structure for all results of the dynamic result 140 type. The view update object, in this step, describes how to fill in that 141 structure for a specific result. 142 143 Add the ``getViewUpdate`` method to the provider: 144 145 .. code-block:: javascript 146 147 /** 148 * Returns a view update object that describes how to update the view DOM 149 * for a given result. 150 * 151 * @param {UrlbarResult} result 152 * The view update object describes how to update the view DOM for this 153 * particular result. 154 * @param {Map} idsByName 155 * A map from names in the view template to the IDs of their corresponding 156 * elements in the DOM. 157 */ 158 getViewUpdate(result, idsByName) { 159 let viewUpdate = { 160 // ... 161 }; 162 return viewUpdate; 163 } 164 165 ``result`` is the result from the provider for which the view update is being 166 requested. 167 168 ``idsByName`` is a map from names in the view template to the IDs of their 169 corresponding elements in the DOM. This is useful if parts of the view update 170 depend on element IDs, as some ARIA attributes do. 171 172 The return value is a view update object. It describes in a declarative manner 173 the updates that should be performed on the view DOM. See `View Update Objects`_ 174 for a description of this object. 175 176 5. Style the results 177 ~~~~~~~~~~~~~~~~~~~~ 178 179 If you are creating the provider in the internal address bar implementation in 180 mozilla-central, then add styling `urlbar-dynamic-results.css`_. 181 182 .. _urlbar-dynamic-results.css: https://searchfox.org/mozilla-central/source/browser/themes/shared/urlbar-dynamic-results.css 183 184 The rest of this section will discuss the CSS rules you need to use to style 185 your results. 186 187 There are two DOM annotations that are useful for styling. The first is the 188 ``dynamicType`` attribute that is set on result rows, and the second is a class 189 that is set on child elements created from the view template. 190 191 dynamicType Row Attribute 192 ......................... 193 194 The topmost element in the view corresponding to a result is called a 195 **row**. Rows have a class of ``urlbarView-row``, and rows corresponding to 196 results of a dynamic result type have an attributed called ``dynamicType``. The 197 value of this attribute is the name of the dynamic result type that was chosen 198 in step 1 earlier. 199 200 Rows of a specific dynamic result type can therefore be selected with the 201 following CSS selector, where ``TYPE_NAME`` is the name of the type: 202 203 .. code-block:: css 204 205 .urlbarView-row[dynamicType=TYPE_NAME] 206 207 Child Element Class 208 ................... 209 210 As discussed in `View Templates`_, each object in the view template can have a 211 ``name`` property. The elements in the view corresponding to the objects in the 212 view template receive a class named 213 ``urlbarView-dynamic-TYPE_NAME-ELEMENT_NAME``, where ``TYPE_NAME`` is the name 214 of the dynamic result type, and ``ELEMENT_NAME`` is the name of the object in 215 the view template. 216 217 Elements in dynamic result type rows can therefore be selected with the 218 following: 219 220 .. code-block:: css 221 222 .urlbarView-dynamic-TYPE_NAME-ELEMENT_NAME 223 224 If an object in the view template does not have a ``name`` property, then it 225 won't receive the class and it therefore can't be selected using this selector. 226 227 View Templates 228 -------------- 229 230 A **view template** is a plain JS object that declaratively describes how to 231 build the DOM for a dynamic result type. When a result of a particular dynamic 232 result type is shown in the view, the type's view template is used to construct 233 the part of the view that represents the type in general. 234 235 Properties 236 ~~~~~~~~~~ 237 238 A view template object is a tree-like nested structure where each object in the 239 nesting represents a DOM element to be created. This tree-like structure is 240 achieved using the ``children`` property described below. Each object in the 241 structure may include the following properties: 242 243 ``{string} name`` 244 The name of the object. This is required for all objects in the structure 245 except the root object and serves two important functions: 246 247 1. The element created for the object will automatically have a class named 248 ``urlbarView-dynamic-${dynamicType}-${name}``, where ``dynamicType`` is the 249 name of the dynamic result type. The element will also automatically have 250 an attribute ``name`` whose value is this name. The class and attribute 251 allow the element to be styled in CSS. 252 253 2. The name is used when updating the view, as described in `View Update 254 Objects`_. 255 256 Names must be unique within a view template, but they don't need to be 257 globally unique. In other words, two different view templates can use the same 258 names, and other unrelated DOM elements can use the same names in their IDs 259 and classes. 260 261 ``{string} tag`` 262 The element tag name of the object. This is required for all objects in the 263 structure except the root object and declares the kind of element that will be 264 created for the object: ``span``, ``div``, ``img``, etc. 265 266 ``{object} [attributes]`` 267 An optional mapping from attribute names to values. For each name-value pair, 268 an attribute is set on the element created for the object. 269 270 A special ``selectable`` attribute tells the view that the element is 271 selectable with the keyboard. The element will automatically participate in 272 the view's keyboard selection behavior. 273 274 Similarly, the ``role=button`` ARIA attribute will also automatically allow 275 the element to participate in keyboard selection. The ``selectable`` attribute 276 is not necessary when ``role=button`` is specified. 277 278 ``{array} [children]`` 279 An optional list of children. Each item in the array must be an object as 280 described in this section. For each item, a child element as described by the 281 item is created and added to the element created for the parent object. 282 283 ``{array} [classList]`` 284 An optional list of classes. Each class will be added to the element created 285 for the object by calling ``element.classList.add()``. 286 287 Example 288 ~~~~~~~ 289 290 Let's return to the weather forecast example from `earlier <Motivation_>`__. For 291 each result of our weather forecast dynamic result type, we might want to 292 display a label for a city name along with two buttons for today's and 293 tomorrow's forecasted high and low temperatures. The view template might look 294 like this: 295 296 .. code-block:: javascript 297 298 { 299 stylesheet: "style.css", 300 children: [ 301 { 302 name: "cityLabel", 303 tag: "span", 304 }, 305 { 306 name: "today", 307 tag: "div", 308 classList: ["day"], 309 attributes: { 310 selectable: true, 311 }, 312 children: [ 313 { 314 name: "todayLabel", 315 tag: "span", 316 classList: ["dayLabel"], 317 }, 318 { 319 name: "todayLow", 320 tag: "span", 321 classList: ["temperature", "temperatureLow"], 322 }, 323 { 324 name: "todayHigh", 325 tag: "span", 326 classList: ["temperature", "temperatureHigh"], 327 }, 328 }, 329 }, 330 { 331 name: "tomorrow", 332 tag: "div", 333 classList: ["day"], 334 attributes: { 335 selectable: true, 336 }, 337 children: [ 338 { 339 name: "tomorrowLabel", 340 tag: "span", 341 classList: ["dayLabel"], 342 }, 343 { 344 name: "tomorrowLow", 345 tag: "span", 346 classList: ["temperature", "temperatureLow"], 347 }, 348 { 349 name: "tomorrowHigh", 350 tag: "span", 351 classList: ["temperature", "temperatureHigh"], 352 }, 353 }, 354 }, 355 ], 356 } 357 358 Observe that we set the special ``selectable`` attribute on the ``today`` and 359 ``tomorrow`` elements so they can be selected with the keyboard. 360 361 View Update Objects 362 ------------------- 363 364 A **view update object** is a plain JS object that declaratively describes how 365 to update the DOM for a specific result of a dynamic result type. When a result 366 of a dynamic result type is shown in the view, a view update object is requested 367 from the result's provider and is used to update the DOM for that result. 368 369 Note the difference between view update objects, described in this section, and 370 view templates, described in the previous section. View templates are used to 371 build a general DOM structure appropriate for all results of a particular 372 dynamic result type. View update objects are used to fill in that structure for 373 a specific result. 374 375 When a result is shown in the view, first the view looks up the view template of 376 the result's dynamic result type. It uses the view template to build a DOM 377 subtree. Next, the view requests a view update object for the result from its 378 provider. The view update object tells the view which result-specific attributes 379 to set on which elements, result-specific text content to set on elements, and 380 so on. View update objects cannot create new elements or otherwise modify the 381 structure of the result's DOM subtree. 382 383 Typically the view update object is based on the result's payload. 384 385 Properties 386 ~~~~~~~~~~ 387 388 The view update object is a nested structure with two levels. It looks like 389 this: 390 391 .. code-block:: javascript 392 393 { 394 name1: { 395 // individual update object for name1 396 }, 397 name2: { 398 // individual update object for name2 399 }, 400 name3: { 401 // individual update object for name3 402 }, 403 // ... 404 } 405 406 The top level maps object names from the view template to individual update 407 objects. The individual update objects tell the view how to update the elements 408 with the specified names. If a particular element doesn't need to be updated, 409 then it doesn't need an entry in the view update object. 410 411 Each individual update object can have the following properties: 412 413 ``{object} [attributes]`` 414 A mapping from attribute names to values. Each name-value pair results in an 415 attribute being set on the element. 416 417 ``{object} [style]`` 418 A plain object that can be used to add inline styles to the element, like 419 ``display: none``. ``element.style`` is updated for each name-value pair in 420 this object. 421 422 ``{object} [l10n]`` 423 An ``{ id, args }`` object that will be passed to 424 ``document.l10n.setAttributes()``. 425 426 ``{string} [textContent]`` 427 A string that will be set as ``element.textContent``. 428 429 Example 430 ~~~~~~~ 431 432 Continuing our weather forecast example, the view update object needs to update 433 several things that we declared in our view template: 434 435 * The city label 436 * The "today" label 437 * Today's low and high temperatures 438 * The "tomorrow" label 439 * Tomorrow's low and high temperatures 440 441 Typically, each of these, with the possible exceptions of the "today" and 442 "tomorrow" labels, would come from our results' payloads. There's an important 443 connection between what's in the view and what's in the payloads: The data in 444 the payloads serves the information shown in the view. 445 446 Our view update object would then look something like this: 447 448 .. code-block:: javascript 449 450 { 451 cityLabel: { 452 textContent: result.payload.city, 453 }, 454 todayLabel: { 455 textContent: "Today", 456 }, 457 todayLow: { 458 textContent: result.payload.todayLow, 459 }, 460 todayHigh: { 461 textContent: result.payload.todayHigh, 462 }, 463 tomorrowLabel: { 464 textContent: "Tomorrow", 465 }, 466 tomorrowLow: { 467 textContent: result.payload.tomorrowLow, 468 }, 469 tomorrowHigh: { 470 textContent: result.payload.tomorrowHigh, 471 }, 472 } 473 474 Accessibility 475 ------------- 476 477 Just like built-in types, dynamic result types support a11y in the view, and you 478 should make sure your view implementation is fully accessible. 479 480 Since the views for dynamic result types are implemented using view templates 481 and view update objects, in practice supporting a11y for dynamic result types 482 means including appropriate `ARIA attributes <aria_>`_ in the view template and 483 view update objects, using the ``attributes`` property. 484 485 Many ARIA attributes depend on element IDs, and that's why the ``idsByName`` 486 parameter to the ``getViewUpdate`` provider method is useful. 487 488 Usually, accessible address bar results require the ARIA attribute 489 ``role=group`` on their top-level DOM element to indicate that all the child 490 elements in the result's DOM subtree form a logical group. This attribute can be 491 set on the root object in the view template. 492 493 .. _aria: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA 494 495 Example 496 ~~~~~~~ 497 498 Continuing the weather forecast example, we'd like for screen readers to know 499 that our result is labeled by the city label so that they announce the city when 500 the result is selected. 501 502 The relevant ARIA attribute is ``aria-labelledby``, and its value is the ID of 503 the element with the label. In our ``getViewUpdate`` implementation, we can use 504 the ``idsByName`` map to get the element ID that the view created for our city 505 label, like this: 506 507 .. code-block:: javascript 508 509 getViewUpdate(result, idsByName) { 510 return { 511 root: { 512 attributes: { 513 "aria-labelledby": idsByName.get("cityLabel"), 514 }, 515 }, 516 // *snipping the view update object example from earlier* 517 }; 518 } 519 520 Here we're using the name "root" to refer to the root object in the view 521 template, so we also need to update our view template by adding the ``name`` 522 property to the top-level object, like this: 523 524 .. code-block:: javascript 525 526 { 527 stylesheet: "style.css", 528 name: "root", 529 attributes: { 530 role: "group", 531 }, 532 children: [ 533 { 534 name: "cityLabel", 535 tag: "span", 536 }, 537 // *snipping the view template example from earlier* 538 ], 539 } 540 541 Note that we've also included the ``role=group`` ARIA attribute on the root, as 542 discussed above. We could have included it in the view update object instead of 543 the view template, but since it doesn't depend on a specific result or element 544 ID in the ``idsByName`` map, the view template makes more sense. 545 546 Mimicking Built-in Address Bar Results 547 -------------------------------------- 548 549 Sometimes it's desirable to create a new result type that looks and behaves like 550 the usual built-in address bar results. Two conveniences are available that are 551 useful in this case. 552 553 URL Navigation 554 ~~~~~~~~~~~~~~ 555 556 If a result's payload includes a string ``url`` property, picking the result 557 will navigate to the URL. The ``onEngagement`` method of the result's provider 558 will be called before navigation. 559 560 Text Highlighting 561 ~~~~~~~~~~~~~~~~~ 562 563 Most built-in address bar results emphasize occurrences of the user's search 564 string in their text by boldfacing matching substrings. Search suggestion 565 results do the opposite by emphasizing the portion of the suggestion that the 566 user has not yet typed. This emphasis feature is called **highlighting**, and 567 it's also available to the results of dynamic result types. 568 569 Highlighting for dynamic result types is a fairly automated process. The text 570 that you want to highlight must be present as a property in your result 571 payload. Instead of setting the property to a string value as you normally 572 would, set it to an array with two elements, where the first element is the text 573 and the second element is a ``UrlbarUtils.HIGHLIGHT`` value, like the ``title`` 574 payload property in the following example: 575 576 .. code-block:: javascript 577 578 let result = new UrlbarResult({ 579 type: UrlbarUtils.RESULT_TYPE.DYNAMIC, 580 source: UrlbarUtils.RESULT_SOURCE.OTHER_NETWORK, 581 payload: { 582 title: [ 583 "Some result title", 584 UrlbarUtils.HIGHLIGHT.TYPED, 585 ], 586 // *more payload properties* 587 } 588 }); 589 590 Your view template must create an element corresponding to the payload 591 property. That is, it must include an object where the value of the ``name`` 592 property is the name of the payload property, like this: 593 594 .. code-block:: javascript 595 596 { 597 children: [ 598 { 599 name: "title", 600 tag: "span", 601 }, 602 // ... 603 ], 604 } 605 606 In contrast, your view update objects must *not* include an update for the 607 element. That is, they must not include a property whose name is the name of the 608 payload property. 609 610 Instead, when the view is ready to update the DOM of your results, it will 611 automatically find the elements corresponding to the payload property, set their 612 ``textContent`` to the text value in the array, and apply the appropriate 613 highlighting, as described next. 614 615 There are two possible ``UrlbarUtils.HIGHLIGHT`` values. Each controls how 616 highlighting is performed: 617 618 ``UrlbarUtils.HIGHLIGHT.TYPED`` 619 Substrings in the payload text that match the user's search string will be 620 emphasized. 621 622 ``UrlbarUtils.HIGHLIGHT.SUGGESTED`` 623 If the user's search string appears in the payload text, then the remainder of 624 the text following the matching substring will be emphasized. 625 626 Appendix A: Examples 627 -------------------- 628 629 This section lists some example and real-world consumers of dynamic result 630 types. 631 632 `Tab-to-Search Provider`__ 633 This is a built-in provider in mozilla-central that uses dynamic result types. 634 635 __ https://searchfox.org/mozilla-central/source/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs