动态结果类型

本文档讨论了地址栏结果中一个特殊的类别,称为动态结果类型。动态结果类型允许您轻松地向地址栏添加新的结果类型。

本文档的目标读者是需要添加新的地址栏结果类型的开发者。

动机

在正常的 Firefox 使用过程中,地址栏会提供许多不同类型的结果。例如,当您键入搜索词时,地址栏可能会显示来自您当前搜索引擎的搜索建议结果。它也可能会显示与您的搜索匹配的浏览历史记录结果。如果您键入了某些短语,例如“更新 Firefox”,它会显示一个提示结果,让您知道 Firefox 是否已更新。

这些结果类型中的每一个都内置在地址栏的实现中。如果您想添加一种新的结果类型——例如,当用户键入“天气”时显示天气预报的卡片——一种方法是添加新的结果类型。您需要更新地址栏中与结果类型相关的所有代码路径。例如,您需要更新处理结果点击的代码路径,以便在点击您的天气卡片时打开相应的预报 URL;您需要更新地址栏视图(面板),以便正确绘制您的卡片;您可能需要更新键盘选择行为,如果您的卡片包含可以独立选择的元素,例如一周中的不同日期;等等。

动态结果类型

**动态结果类型**是实现新结果类型的另一种方法。与其添加一种新的内置类型以及随之而来的一切,不如添加一个新的提供程序子类并注册一个模板,该模板描述视图应该如何绘制您的结果类型并指示哪些元素是可选择的。地址栏会处理其他所有事情。

动态结果类型本质上是一个抽象层:作为结果的一般类别的支持内置在地址栏中,并且特定动态结果类型的每个实现都填充了细节。

此外,动态结果类型可以在运行时添加。

入门

要了解动态结果类型的实现方式,您可以查看 UrlbarProviderCalculator

下一节将描述添加新的动态结果类型需要采取的具体步骤。

实现步骤

本节介绍如何添加新的动态结果类型。

1. 注册动态结果类型

首先,注册新的动态结果类型

UrlbarResult.addDynamicResultType(name);

name 是新类型的字符串标识符。它必须是唯一的;也就是说,它必须与所有其他动态结果类型名称不同。它还将用于 DOM ID、DOM 类名和 CSS 选择器,因此它不应包含任何空格或其他在 CSS 中无效的字符。

2. 注册视图模板

接下来,为新类型添加视图模板

UrlbarView.addDynamicViewTemplate(name, viewTemplate);

name 是步骤 1 中描述的新类型的名称。

viewTemplate 是一个称为视图模板的对象。它以声明的方式描述了应该为新类型的所有结果在视图中创建的 DOM。

3. 添加提供程序

与任何类型的结果一样,动态结果类型的结果必须由一个或多个提供程序创建。为新的提供程序创建一个 UrlbarProvider 子类,并像往常一样实现所有常用的提供程序方法

class MyDynamicResultTypeProvider extends UrlbarProvider {
  // ...
}

startQuery 方法应该创建具有以下两个要求的 UrlbarResult 对象

  • 结果类型必须为 UrlbarUtils.RESULT_TYPE.DYNAMIC

  • 结果负载必须具有一个 dynamicType 属性,其值为步骤 1 中使用的动态结果类型的名称。

结果的源、其他负载属性和其他结果属性与动态结果类型无关,您应该选择适合您用例的值。

如果为您的结果在视图中创建的任何元素都可以使用键盘或鼠标选择,那么请确保实现提供程序的 onEngagement 方法。

有关一般提供程序实现的帮助,请参阅地址栏的 架构概述

如果您在 mozilla-central 的内部地址栏实现中创建提供程序,那么不要忘记在 UrlbarProvidersManager 中注册它。

4. 实现提供程序的 getViewUpdate 方法

getViewUpdate 是动态结果类型提供程序特有的提供程序方法。它的作用是更新特定结果的视图 DOM。它由视图为提供程序创建的视图中的每个结果调用。它返回一个称为视图更新对象的对象。

回想一下,视图模板是在前面步骤 2 中添加的。视图模板描述了如何构建所有动态结果类型的 DOM 结构。此步骤中的视图更新对象描述了如何为特定结果填充该结构。

向提供程序添加 getViewUpdate 方法

/**
 * Returns a view update object that describes how to update the view DOM
 * for a given result.
 *
 * @param {UrlbarResult} result
 *   The view update object describes how to update the view DOM for this
 *   particular result.
 * @param {Map} idsByName
 *   A map from names in the view template to the IDs of their corresponding
 *   elements in the DOM.
 */
getViewUpdate(result, idsByName) {
  let viewUpdate = {
    // ...
  };
  return viewUpdate;
}

result 是请求视图更新的提供程序的结果。

idsByName 是从视图模板中的名称到 DOM 中其对应元素的 ID 的映射。如果视图更新的部分依赖于元素 ID,例如某些 ARIA 属性,这将很有用。

返回值是一个视图更新对象。它以声明的方式描述了应该对视图 DOM 执行的更新。有关此对象的说明,请参阅 视图更新对象

5. 样式化结果

如果您在 mozilla-central 的内部地址栏实现中创建提供程序,则添加样式 urlbar-dynamic-results.css

本节的其余部分将讨论您需要使用的 CSS 规则来样式化您的结果。

有两个 DOM 注解可用于样式化。第一个是在结果行上设置的 dynamicType 属性,第二个是在从视图模板创建的子元素上设置的类。

dynamicType 行属性

对应于结果的视图中的最顶层元素称为**行**。行具有 urlbarView-row 类,并且对应于动态结果类型结果的行具有一个名为 dynamicType 的属性。此属性的值是在前面步骤 1 中选择的动态结果类型的名称。

因此,可以使用以下 CSS 选择器选择特定动态结果类型的行,其中 TYPE_NAME 是类型的名称

.urlbarView-row[dynamicType=TYPE_NAME]

子元素类

视图模板 中所述,视图模板中的每个对象都可以具有 name 属性。对应于视图模板中对象的视图中的元素接收一个名为 urlbarView-dynamic-TYPE_NAME-ELEMENT_NAME 的类,其中 TYPE_NAME 是动态结果类型的名称,而 ELEMENT_NAME 是视图模板中对象的名称。

因此,可以使用以下方法选择动态结果类型行中的元素

.urlbarView-dynamic-TYPE_NAME-ELEMENT_NAME

如果视图模板中的对象没有 name 属性,则它不会接收该类,因此无法使用此选择器进行选择。

视图模板

**视图模板**是一个普通的 JS 对象,它以声明的方式描述了如何构建动态结果类型的 DOM。当特定动态结果类型的结果显示在视图中时,该类型的视图模板用于构建表示该类型的一般视图部分。

属性

视图模板对象是一个树状嵌套结构,其中嵌套中的每个对象都表示要创建的 DOM 元素。此树状结构是使用下面描述的 children 属性实现的。结构中的每个对象都可以包含以下属性

{string} name

对象的名称。这是结构中除根对象之外的所有对象都需要的,它具有两个重要的功能

  1. 为对象创建的元素将自动具有名为 urlbarView-dynamic-${dynamicType}-${name} 的类,其中 dynamicType 是动态结果类型的名称。该元素还将自动具有一个属性 name,其值为该名称。该类和属性允许在 CSS 中设置元素的样式。

  2. 在更新视图时使用名称,如 视图更新对象 中所述。

名称在视图模板中必须是唯一的,但不需要是全局唯一的。换句话说,两个不同的视图模板可以使用相同的名称,其他无关的 DOM 元素可以在其 ID 和类中使用相同的名称。

{string} tag

对象的元素标签名称。这是结构中除根对象之外的所有对象都需要的,它声明了将为对象创建的元素类型:spandivimg 等。

{object} [attributes]

从属性名称到值的可选映射。对于每个名称-值对,都会在为对象创建的元素上设置一个属性。

一个特殊的 selectable 属性告诉视图该元素可以使用键盘选择。该元素将自动参与视图的键盘选择行为。

类似地,role=button ARIA 属性也会自动允许元素参与键盘选择。selectable 属性在指定 role=button 时不是必需的。

{数组} [children]

一个可选的子元素列表。数组中的每个项目都必须是本节中描述的对象。对于每个项目,都会根据该项目创建子元素并将其添加到为父对象创建的元素中。

{数组} [classList]

一个可选的类列表。每个类都将通过调用element.classList.add()添加到为对象创建的元素中。

示例

让我们回到前面提到的天气预报示例。对于我们的天气预报动态结果类型的每个结果,我们可能希望显示城市名称的标签以及用于今天和明天预报的高温和低温的两个按钮。视图模板可能如下所示

{
  stylesheet: "style.css",
  children: [
    {
      name: "cityLabel",
      tag: "span",
    },
    {
      name: "today",
      tag: "div",
      classList: ["day"],
      attributes: {
        selectable: true,
      },
      children: [
        {
          name: "todayLabel",
          tag: "span",
          classList: ["dayLabel"],
        },
        {
          name: "todayLow",
          tag: "span",
          classList: ["temperature", "temperatureLow"],
        },
        {
          name: "todayHigh",
          tag: "span",
          classList: ["temperature", "temperatureHigh"],
        },
      },
    },
    {
      name: "tomorrow",
      tag: "div",
      classList: ["day"],
      attributes: {
        selectable: true,
      },
      children: [
        {
          name: "tomorrowLabel",
          tag: "span",
          classList: ["dayLabel"],
        },
        {
          name: "tomorrowLow",
          tag: "span",
          classList: ["temperature", "temperatureLow"],
        },
        {
          name: "tomorrowHigh",
          tag: "span",
          classList: ["temperature", "temperatureHigh"],
        },
      },
    },
  ],
}

请注意,我们在todaytomorrow元素上设置了特殊的selectable属性,以便可以使用键盘选择它们。

视图更新对象

**视图更新对象**是一个简单的 JS 对象,它声明性地描述了如何更新特定动态结果类型的结果的 DOM。当动态结果类型的结果显示在视图中时,会从结果的提供者请求视图更新对象,并用于更新该结果的 DOM。

请注意本节中描述的视图更新对象与上一节中描述的视图模板之间的区别。视图模板用于构建适合特定动态结果类型的所有结果的通用 DOM 结构。视图更新对象用于填充特定结果的结构。

当结果显示在视图中时,视图首先查找结果的动态结果类型的视图模板。它使用视图模板构建 DOM 子树。接下来,视图从其提供者请求该结果的视图更新对象。视图更新对象告诉视图在哪些元素上设置哪些结果特定的属性,在元素上设置哪些结果特定的文本内容,等等。视图更新对象不能创建新元素或以其他方式修改结果的 DOM 子树的结构。

通常,视图更新对象基于结果的有效负载。

属性

视图更新对象是一个具有两级的嵌套结构。它看起来像这样

{
  name1: {
    // individual update object for name1
  },
  name2: {
    // individual update object for name2
  },
  name3: {
    // individual update object for name3
  },
  // ...
}

顶层将视图模板中的对象名称映射到各个更新对象。各个更新对象告诉视图如何更新具有指定名称的元素。如果特定元素不需要更新,则它不需要在视图更新对象中包含条目。

每个单独的更新对象可以具有以下属性

{object} [attributes]

属性名称到值的映射。每个名称-值对都会导致在元素上设置一个属性。

{对象} [style]

一个简单的对象,可用于向元素添加内联样式,例如display: noneelement.style针对此对象中的每个名称-值对进行更新。

{对象} [l10n]

一个{ id, args }对象,它将传递给document.l10n.setAttributes()

{字符串} [textContent]

一个将设置为element.textContent的字符串。

示例

继续我们的天气预报示例,视图更新对象需要更新我们在视图模板中声明的一些内容

  • 城市标签

  • “今天”标签

  • 今天的低温和高温

  • “明天”标签

  • 明天的低温和高温

通常,其中每个(除了“今天”和“明天”标签之外)都来自我们的结果有效负载。视图中的内容和有效负载中的内容之间存在重要联系:有效负载中的数据提供视图中显示的信息。

然后我们的视图更新对象将如下所示

{
  cityLabel: {
    textContent: result.payload.city,
  },
  todayLabel: {
    textContent: "Today",
  },
  todayLow: {
    textContent: result.payload.todayLow,
  },
  todayHigh: {
    textContent: result.payload.todayHigh,
  },
  tomorrowLabel: {
    textContent: "Tomorrow",
  },
  tomorrowLow: {
    textContent: result.payload.tomorrowLow,
  },
  tomorrowHigh: {
    textContent: result.payload.tomorrowHigh,
  },
}

可访问性

与内置类型一样,动态结果类型也支持视图中的 a11y,您应该确保您的视图实现是完全可访问的。

由于动态结果类型的视图是使用视图模板和视图更新对象实现的,因此在实践中,支持动态结果类型的 a11y 意味着在视图模板和视图更新对象中包含适当的ARIA 属性,并使用attributes属性。

许多 ARIA 属性依赖于元素 ID,这就是为什么getViewUpdate提供程序方法的idsByName参数很有用的原因。

通常,可访问的地址栏结果需要在其顶级 DOM 元素上使用 ARIA 属性role=group来指示结果的 DOM 子树中的所有子元素都形成一个逻辑组。可以在视图模板中的根对象上设置此属性。

示例

继续天气预报示例,我们希望屏幕阅读器知道我们的结果由城市标签标记,以便在选择结果时宣布城市。

相关的 ARIA 属性是aria-labelledby,其值为具有标签的元素的 ID。在我们的getViewUpdate实现中,我们可以使用idsByName映射获取视图为我们的城市标签创建的元素 ID,如下所示

getViewUpdate(result, idsByName) {
  return {
    root: {
      attributes: {
        "aria-labelledby": idsByName.get("cityLabel"),
      },
    },
    // *snipping the view update object example from earlier*
  };
}

在这里,我们使用名称“root”来引用视图模板中的根对象,因此我们还需要通过向顶级对象添加name属性来更新我们的视图模板,如下所示

{
  stylesheet: "style.css",
  name: "root",
  attributes: {
    role: "group",
  },
  children: [
    {
      name: "cityLabel",
      tag: "span",
    },
    // *snipping the view template example from earlier*
  ],
}

请注意,我们还在根上包含了role=group ARIA 属性,如上所述。我们可以在视图更新对象而不是视图模板中包含它,但由于它不依赖于idsByName映射中的特定结果或元素 ID,因此视图模板更有意义。

模仿内置地址栏结果

有时需要创建一个看起来和行为都像通常的内置地址栏结果的新结果类型。在这种情况下,可以使用两个便利的功能。

URL 导航

如果结果的有效负载包含字符串url属性,则选择结果将导航到该 URL。在导航之前,将调用结果提供程序的onEngagement方法。

文本高亮

大多数内置地址栏结果通过加粗匹配的子字符串来强调其文本中用户搜索字符串的出现。搜索建议结果则相反,它们强调用户尚未键入的建议部分。此强调功能称为**高亮显示**,它也适用于动态结果类型的结果。

动态结果类型的高亮显示是一个相当自动化的过程。您要高亮显示的文本必须作为结果有效负载中的属性存在。不要像通常那样将属性设置为字符串值,而是将其设置为包含两个元素的数组,其中第一个元素是文本,第二个元素是UrlbarUtils.HIGHLIGHT值,例如以下示例中的title有效负载属性

let result = new UrlbarResult(
  UrlbarUtils.RESULT_TYPE.DYNAMIC,
  UrlbarUtils.RESULT_SOURCE.OTHER_NETWORK,
  {
    title: [
      "Some result title",
      UrlbarUtils.HIGHLIGHT.TYPED,
    ],
    // *more payload properties*
  }
);

您的视图模板必须创建与有效负载属性相对应的元素。也就是说,它必须包含一个对象,其中name属性的值是有效负载属性的名称,如下所示

{
  children: [
    {
      name: "title",
      tag: "span",
    },
    // ...
  ],
}

相反,您的视图更新对象不能包含对元素的更新。也就是说,它们不能包含名称为有效负载属性名称的属性。

相反,当视图准备好更新结果的 DOM 时,它将自动查找与有效负载属性相对应的元素,将其textContent设置为数组中的文本值,并应用适当的高亮显示,如下所述。

有两个可能的UrlbarUtils.HIGHLIGHT值。每个都控制如何执行高亮显示

UrlbarUtils.HIGHLIGHT.TYPED

有效负载文本中与用户搜索字符串匹配的子字符串将被强调。

UrlbarUtils.HIGHLIGHT.SUGGESTED

如果用户搜索字符串出现在有效负载文本中,则匹配的子字符串之后的文本其余部分将被强调。

附录 A:示例

本节列出了一些动态结果类型的示例和实际消费者。

Tab-to-Search 提供程序

这是 mozilla-central 中一个使用动态结果类型的内置提供程序。