tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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