Bug 1944416: Restore individual tabs from closed groups in closed windows r=dao,sessi...
[gecko.git] / browser / components / urlbar / docs / dynamic-result-types.rst
blob80bc8473f0b855e0a02ac9bb7f8016cb399259fa
1 Dynamic Result Types
2 ====================
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.
8 The intended audience for this document is developers who need to add new kinds
9 of address bar results.
11 .. contents::
12    :depth: 2
15 Motivation
16 ----------
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.
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.
36 Dynamic Result Types
37 --------------------
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.
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.
49 In addition, dynamic result types can be added at runtime.
51 Getting Started
52 ---------------
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>`.
57 The next section describes the specific steps you need to take to add a new
58 dynamic result type.
60 Implementation Steps
61 --------------------
63 This section describes how to add a new dynamic result type.
65 1. Register the dynamic result type
66 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68 First, register the new dynamic result type:
70 .. code-block:: javascript
72     UrlbarResult.addDynamicResultType(name);
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.
79 2. Register the view template
80 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
82 Next, add the view template for the new type:
84 .. code-block:: javascript
86     UrlbarView.addDynamicViewTemplate(name, viewTemplate);
88 ``name`` is the new type's name as described in step 1.
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.
94 3. Add the provider
95 ~~~~~~~~~~~~~~~~~~~
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:
101 .. code-block:: javascript
103     class MyDynamicResultTypeProvider extends UrlbarProvider {
104       // ...
105     }
107 The ``startQuery`` method should create ``UrlbarResult`` objects with the
108 following two requirements:
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.
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.
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.
122 For help on implementing providers in general, see the address bar's
123 `Architecture Overview`__.
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``.
128 __ https://firefox-source-docs.mozilla.org/browser/urlbar/overview.html#urlbarprovider
130 4. Implement the provider's getViewUpdate method
131 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
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.
143 Add the ``getViewUpdate`` method to the provider:
145 .. code-block:: javascript
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     }
165 ``result`` is the result from the provider for which the view update is being
166 requested.
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.
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.
176 5. Style the results
177 ~~~~~~~~~~~~~~~~~~~~
179 If you are creating the provider in the internal address bar implementation in
180 mozilla-central, then add styling `urlbar-dynamic-results.css`_.
182 .. _urlbar-dynamic-results.css: https://searchfox.org/mozilla-central/source/browser/themes/shared/urlbar-dynamic-results.css
184 The rest of this section will discuss the CSS rules you need to use to style
185 your results.
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.
191 dynamicType Row Attribute
192 .........................
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.
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:
203 .. code-block:: css
205     .urlbarView-row[dynamicType=TYPE_NAME]
207 Child Element Class
208 ...................
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.
217 Elements in dynamic result type rows can therefore be selected with the
218 following:
220 .. code-block:: css
222     .urlbarView-dynamic-TYPE_NAME-ELEMENT_NAME
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.
227 View Templates
228 --------------
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.
235 Properties
236 ~~~~~~~~~~
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:
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:
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.
253   2. The name is used when updating the view, as described in `View Update
254      Objects`_.
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.
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.
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.
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.
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.
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.
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()``.
287 Example
288 ~~~~~~~
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:
296 .. code-block:: javascript
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     }
358 Observe that we set the special ``selectable`` attribute on the ``today`` and
359 ``tomorrow`` elements so they can be selected with the keyboard.
361 View Update Objects
362 -------------------
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.
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.
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.
383 Typically the view update object is based on the result's payload.
385 Properties
386 ~~~~~~~~~~
388 The view update object is a nested structure with two levels. It looks like
389 this:
391 .. code-block:: javascript
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     }
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.
411 Each individual update object can have the following properties:
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.
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.
422 ``{object} [l10n]``
423   An ``{ id, args }`` object that will be passed to
424   ``document.l10n.setAttributes()``.
426 ``{string} [textContent]``
427   A string that will be set as ``element.textContent``.
429 Example
430 ~~~~~~~
432 Continuing our weather forecast example, the view update object needs to update
433 several things that we declared in our view template:
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
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.
446 Our view update object would then look something like this:
448 .. code-block:: javascript
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     }
474 Accessibility
475 -------------
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.
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.
485 Many ARIA attributes depend on element IDs, and that's why the ``idsByName``
486 parameter to the ``getViewUpdate`` provider method is useful.
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.
493 .. _aria: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA
495 Example
496 ~~~~~~~
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.
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:
507 .. code-block:: javascript
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     }
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:
524 .. code-block:: javascript
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     }
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.
546 Mimicking Built-in Address Bar Results
547 --------------------------------------
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.
553 URL Navigation
554 ~~~~~~~~~~~~~~
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.
560 Text Highlighting
561 ~~~~~~~~~~~~~~~~~
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.
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:
576 .. code-block:: javascript
578     let result = new UrlbarResult(
579       UrlbarUtils.RESULT_TYPE.DYNAMIC,
580       UrlbarUtils.RESULT_SOURCE.OTHER_NETWORK,
581       {
582         title: [
583           "Some result title",
584           UrlbarUtils.HIGHLIGHT.TYPED,
585         ],
586         // *more payload properties*
587       }
588     );
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:
594 .. code-block:: javascript
596     {
597       children: [
598         {
599           name: "title",
600           tag: "span",
601         },
602         // ...
603       ],
604     }
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.
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.
615 There are two possible ``UrlbarUtils.HIGHLIGHT`` values. Each controls how
616 highlighting is performed:
618 ``UrlbarUtils.HIGHLIGHT.TYPED``
619   Substrings in the payload text that match the user's search string will be
620   emphasized.
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.
626 Appendix A: Examples
627 --------------------
629 This section lists some example and real-world consumers of dynamic result
630 types.
632 `Tab-to-Search Provider`__
633   This is a built-in provider in mozilla-central that uses dynamic result types.
635 __ https://searchfox.org/mozilla-central/source/browser/components/urlbar/UrlbarProviderTabToSearch.sys.mjs