GeckoView 扩展管理 API

Agi Sferro <agi@sferro.dev>

2019 年 11 月 19 日

简介

本文档描述了使用 GeckoView 安装、卸载和更新扩展的 API。

安装扩展可以让扩展在启动时运行,这对于拦截网络请求的扩展(例如广告拦截器或代理扩展)特别有用。它还提供了来自第三方扩展的其他安全措施,例如签名检查和提示用户授予权限。

对于此版本的 API,我们将假设扩展商店由 addons.mozilla.org 支持,签名也是如此。在将来,我们可能会考虑运行第三方扩展商店,但本文档明确不包括在范围之内。

API

嵌入器将能够使用类似名称的 API 来安装、卸载、启用、禁用和更新扩展。

安装

Gecko 将下载 install 中提供的 URI 指向的扩展,解析清单和签名,并提供一个 onInstallPrompt 回调,其中包含扩展请求的权限列表以及有关扩展的一些信息。

嵌入器将能够使用 installBuiltIn 安装捆绑的一方扩展。此方法仅接受以 resource:// 开头的 URI,并将提供其他权限,例如能够使用应用程序消息传递,并且不需要签名。

每个权限都将具有一个机器可读名称,嵌入器将使用该名称生成面向用户的国际化字符串。例如,“bookmarks” 允许访问书签,“sessions” 允许访问最近关闭的会话。当前在 Firefox 桌面版 UI 中显示的完整权限列表位于:toolkit/global/extensionPermissions.ftl

WebExtension.MetaData 属性预计将设置为绝对 moz-extension url(例如 baseUrloptionsPageUrl),但在安装新扩展后无法立即使用。一旦扩展完全启动,委托方法 WebExtensionController.AddonManagerDelegate.onReady 将向嵌入器应用程序提供一个新的 MetaData 对象实例,其中 baseUrl 预计将设置为 "moz-extension://..." url(如果扩展清单文件 manifest.json 中声明了选项页面,则 optionsPageUrl 也是如此)。

更新

要更新扩展,嵌入器可以调用 update,该方法将检查是否有任何更新可用(使用扩展提供的 update_url,或者如果未提供 update_url 则使用 addons.mozilla.org)。嵌入器将收到一个 GeckoResult,其中包含更新后的扩展对象。此结果还可用于了解更新过程何时完成,例如,嵌入器可以使用它向用户显示持续通知,以避免在更新过程中应用程序被终止。

如果更新后的扩展需要其他权限,GeckoView 将调用 onUpdatePrompt

在该回调解决之前(即嵌入器返回的 GeckoResult 完成),旧的插件将继续运行。只有在提示解决并应用更新后,插件的新版本才会开始运行,并且 update 返回的 GeckoResult 才会解决。

此回调将提供当前 WebExtension 对象和更新后的 WebExtension 对象,以便嵌入器向用户显示适当的信息,例如,应用程序可能会决定记住用户是否拒绝了特定版本的请求,并且仅在版本字符串更改时提示用户。

作为更新的副作用,Gecko 将检查其内部黑名单,并可能禁用与当前 Gecko 版本不兼容或被认为不安全的扩展。生成的 WebExtension 对象将通过将 isEnabled 设置为 false 来反映这一点。嵌入器可以使用 metaData.blockedReason 检查扩展被禁用的原因。

在没有嵌入器输入的情况下,Gecko 不会更新任何扩展或黑名单状态。

启用和禁用

嵌入器将能够使用同名 API 来启用和禁用扩展。如果扩展已添加到 Gecko 黑名单中,则调用扩展上的 enable 实际上可能无法启用它。嵌入器可以检查 metaData.blockedReason 的值,以向用户显示扩展是否可以实际启用。返回的 WebExtension 对象将在 isEnabled 中反映更新的启用状态。

列出

嵌入器应使用 install 和 update 的结果来保存所有可用扩展的集合。要检索已安装的扩展,嵌入器可以使用 listInstalled,它将异步检索扩展的完整列表。我们建议在每次向用户显示扩展管理器 UI 时都调用 listInstalled,以确保所有信息都是最新的。

概述

public class WebExtensionController {
  // Start the process of installing an extension,
  // the embedder will either get the installed extension
  // or an error
  GeckoResult<WebExtension> install(String uri);

  // Install a built-in WebExtension with privileged
  // permissions, uri must be resource://
  // Privileged WebExtensions have access to experiments
  // (i.e. they can run chrome code), don’t need signatures
  // and have access to native messaging to the app
  GeckoResult<WebExtension> installBuiltIn(String uri)

  GeckoResult<Void> uninstall(WebExtension extension);

  GeckoResult<WebExtension> enable(WebExtension extension);

  GeckoResult<WebExtension> disable(WebExtension extension);

  GeckoResult<List<WebExtension>> listInstalled();

  // Checks for updates. This method returns a GeckoResult that is
  // resolved either with the updated WebExtension object or null
  // if the extension does not have pending updates.
  GeckoResult<WebExtension> update(WebExtension extension);

  public interface PromptDelegate {
      GeckoResult<AllowOrDeny> onInstallPrompt(WebExtension extension);

      GeckoResult<AllowOrDeny> onUpdatePrompt(
          WebExtension currentlyInstalled,
          WebExtension updatedExtension,
          List<String> newPermissions);

      // Called when the extension calls browser.permission.request
      GeckoResult<AllowOrDeny> onOptionalPrompt(
          WebExtension extension,
          List<String> optionalPermissions);
  }

  void setPromptDelegate(PromptDelegate promptDelegate);
}

作为本文档的一部分,我们将向 WebExtension 添加一个 MetaData 字段,其中包含有关扩展的所有已知信息。注意:我们将 ActionIcon 重命名为 Icon 以表示其作为 WebExtension 图标类的通用用途。

public class WebExtension {
  // Renamed from ActionIcon
  static class Icon {}

  final MetaData metadata;
  final boolean isBuiltIn;

  final boolean isEnabled;

  public static class SignedStateFlags {
    final static int UNKNOWN;
    final static int PRELIMINARY;
    final static int SIGNED;
    final static int SYSTEM;
    final static int PRIVILEGED;
  }

  // See nsIBlocklistService.idl
  public static class BlockedReason {
    final static int NOT_BLOCKED;
    final static int SOFTBLOCKED;
    final static int BLOCKED;
    final static int OUTDATED;
    final static int VULNERABLE_UPDATE_AVAILABLE;
    final static int VULNERABLE_NO_UPDATE;
  }

  public class MetaData {
    final Icon icon;
    final String[] permissions;
    final String[] origins;
    final String name;
    final String description;
    final String version;
    final String creatorName;
    final String creatorUrl;
    final String homepageUrl;
    final String baseUrl;
    final String optionsPageUrl;
    final boolean openOptionsPageInTab;
    final boolean isRecommended;
    final @BlockedReason int blockedReason;
    final @SignedState int signedState;
    // more if needed
  }
}

实现细节

我们将使用 AddonManager 作为 WebExtensionController 的后端,并使用 PromptDelegate 将提示委托给应用程序。为了便于实现,我们还将合并 WebExtensionControllerWebExtensionEventDispatcher

现有 API

某些 API 今天返回的 WebExtension 对象可能尚未通过 listInstalled 提取。在这些情况下,GeckoView 将返回一个存根 WebExtension 对象,其中元数据字段为 null,以避免等待插件列表调用。为了确保元数据字段已填充,嵌入器需要在应用程序启动期间至少调用一次 listInstalled

弃用路径

现有的 registerWebExtensionunregisterWebExtension API 将被 installBuiltInuninstall 弃用。在 installBuiltIn 实现完成并将其标记为 API 中的弃用后,我们将删除上述 API 6 个版本。