附加组件开发者容器概述

容器允许用户在同一站点上同时登录多个帐户,并为用户提供隔离网站数据以增强隐私和安全的功能。在 Firefox 中,我们一直在研究 容器 很长一段时间了。

我们从浏览器本身的平台工作开始,添加了一个基本的用户界面,然后继续进行 测试飞行员实验,我们在扩展程序中扩展了该功能。现在我们正在从测试飞行员毕业,并将 我们的扩展程序,Firefox 多帐户容器 移动到addons.mozilla.org.

此外,我们已经更新了 Firefox 平台,以便扩展程序可以管理容器。这意味着开发者可以访问必要的 API 来创建新的容器扩展程序。您可以在容器 API 之上构建新的扩展程序以满足您的需求和用例!这已经发生了, addons.mozilla.org 上出现了许多新的扩展程序

这篇文章介绍了 contextualIdentities API,并以开发者为中心,逐步介绍了一个容器附加组件示例。

什么是容器?

容器通过允许用户在网站间的数据流中设置障碍来工作,方法是 隔离每个独立的浏览上下文中的 cookie、indexedDB、localStorage 和缓存。例如,与用户个人容器关联的浏览器存储与用户的“工作”容器分离。通过这种方式,用户可以根据所处的上下文采用不同的身份——我们称之为上下文身份

Cookie 存储 是一个关键的 WebExtension API 概念,它表示浏览器中的存储隔离。在其他浏览器中,Cookie 存储用于区分私人窗口和普通窗口。在 Firefox 中,Cookie 存储现在还将区分容器。

容器是 Firefox 独有的。这些新的 API 使所有开发人员能够创建新的隐私、安全和选项卡管理体验,这些体验是目前其他浏览器中没有的。

设置您的清单

要使用 contextualIdentities API,请将 “contextualIdentities”“cookies” 权限添加到您的 manifest.json 中,以便您的扩展程序可以创建与 Cookie 存储关联的容器选项卡。

"permissions": [
  "cookies",
  "contextualIdentities"
]

管理容器选项卡

cookie 权限提供对 cookieStoreId 属性的访问,该属性是容器选项卡管理所需的。contexualIdentities API 方法返回 cookieStoreId,可用于诸如 tab.create 之类的 方法。

const containers = await browser.contextualIdentities.query({});
browser.tabs.create({
  cookieStoreId: container[0].cookieStoreId,
  url: "https://example.com"
});

此代码创建一个新的选项卡,它请求https://example.com使用与用户第一个容器关联的 cookieStoreId

我们还可以使用 cookieStoreId 来查找与之关联的所有打开的容器选项卡

browser.tabs.query({cookieStoreId});

监控更改

Firefox 和容器附加组件能够创建、更新和删除容器。扩展程序现在可以监控这些更改,在容器列表发生更改时提醒它们更新其布局或管理界面。

要监控更改,扩展程序可以使用 onCreatedonUpdatedonRemoved 监听器,这些监听器会传递修改后的容器对象。

const rebuildEvent = () => {
  this.rebuildMenu();
};
browser.contextualIdentities.onRemoved.addListener((container) => {
  this.removeContainer(container.cookieStoreId);
  rebuildEvent();
});
browser.contextualIdentities.onUpdated.addListener(rebuildEvent);
browser.contextualIdentities.onCreated.addListener(rebuildEvent);
this.rebuildMenu();

确保使用 onRemoved 监听器监控用户或其他扩展程序删除的容器。如果您扩展程序以编程方式创建容器选项卡,这一点尤其重要。例如,扩展程序可能配置为每周五下午 5 点打开一个“购物”选项卡,以便从当地酒吧订购啤酒。但是,如果用户先前删除了“购物”选项卡,那么如果扩展程序没有监控删除事件,则该扩展程序很可能会出现故障。这将是一个令人沮丧的结果。

使用颜色和图标保持一致的 UI

我们注意到上传到 addons.mozilla.org 的一些容器附加组件正在使用不同的图标,并且看起来与 Firefox 非常不同。为了让扩展程序开发人员的生活更轻松,并让用户体验更和谐,我们现在公开了颜色代码和图标 URL,您可以使用它们来保持您选择的颜色与我们的调色板一致。我们认为这是一个双赢的局面。

在 Firefox 和我们的容器扩展程序中,我们优化了我们的调色板。Firefox 设计师努力选择了在深色和浅色主题中都可见的颜色,以符合可访问性要求。当开发人员使用诸如“粉红色”和“蓝色”之类的 CSS 颜色关键字时,结果看起来与 Firefox 提供的本机颜色非常不同。

我们更新的 API 提供容器颜色的公共图标 URL 和十六进制颜色代码。请在您的扩展程序中使用 “colorCode” 属性,因为将来我们可能会更新颜色代码以匹配 Firefox 中的更改。

Firefox 和容器扩展程序之间的一致性对于防止错误和提供愉悦的用户体验至关重要。因此,与调色板一样,我们还在 iconUrl 属性中提供图标的 URL。我们鼓励您使用我们的资产来提供易于导航的用户界面,因为我们与容器一起构建。我们提前感谢您。

在扩展程序中,您可以像这样查询当前活动的容器

const containers = await browser.contextualIdentities.query({});
containers.forEach((container) => {
  console.log(container);
  /* Object {
       name: "Personal",
       icon: "fingerprint", 
       iconUrl: "resource://usercontext-content/fingerprint.svg",
       color: "blue",
       colorCode: "#37adff",
       cookieStoreId: "firefox-container-1" 
     } */
});

构建容器附加组件示例

为了演示如何使用容器的 Web 扩展 API,我将带您逐步了解我制作的 容器扩展程序。此附加组件允许用户根据每个容器的偏好自动将 HTTP 流量重定向到 HTTPS。

用户可能决定为“银行”容器打开 HTTPS,如上所示。然后,当用户在“银行”选项卡中访问 http://example-bank.com 时,他们会看到他们的选项卡实际上最终会访问 HTTPS 页面,而不是:https://example-bank.com

在我编写的扩展程序中,我具有以下功能

createIcon(container) {
  const icon = document.createElement("div");
  icon.classList.add("icon");
  const iconUrl = container.iconUrl || "img/blank-tab.svg";
  icon.style.mask = `url(${iconUrl}) top left / contain`;
  icon.style.background = container.colorCode || "#000";
  return icon;
}

async createRow(container) {
  const li = document.createElement("li");
  li.appendChild(this.createIcon(container));
  ...
}

async rebuildMenu() {
  const containers = await browser.contextualIdentities.query({});
  ...
  containers.unshift({
    cookieStoreId: "firefox-default",
    name: "Default"
  });
  const rowPromises = [];
  containers.forEach((container) => {
    rowPromises.push(this.createRow(container));
  });
  ...
}

在我的 rebuildMenu 函数中,我查询用户拥有的所有容器。然后,我为默认的 Firefox 选项卡添加一个项目。当代码使用容器对象调用 createIcon 时,可以使用 iconUrlcolorCode 属性来获取关联的图标。我将图标用作 CSS 中 div 的 SVG 遮罩,这会导致背景颜色用作图标颜色,就像它在原生 Firefox 菜单中一样。

使容器 API 可靠

容器是 Firefox Beta 版和正式版中默认禁用的平台功能。到目前为止,扩展程序开发人员必须通知用户在about:preferences中启用容器,以便使用容器 API。这种情况随着 Firefox Quantum 的发布而改变(现在在 开发者版 中)。在 Firefox Quantum 中,如果您是一位创建容器扩展程序的开发人员,那么您的扩展程序会启用容器。因此,现在,当用户安装您的扩展程序时,他们不需要执行其他步骤。如果他们尝试禁用容器,则需要先禁用您的扩展程序。

这为扩展程序开发人员提供了保证,即 容器 API 将在安装扩展程序时工作。过去,用户可以在任何时候禁用容器,并破坏所有依赖容器的扩展程序。现在,他们必须先禁用扩展程序本身,才能禁用容器选项卡。

我们还对现有的“query”、“get”、“update”和“remove”方法进行了更改,使其更“promise 友好”。我们现在不再使用 null 或 false 值来解析 promise,而是在出现错误时拒绝 promise。在无法找到容器或出现内部错误的情况下,我们会拒绝 API 的 promise,因此,将 API 调用包装在 try...catch 块中允许您的代码处理这些错误

async getContainer(cookieStoreId) {
  let container;
  try {
    container = await browser.contextualIdentities.get(cookieStoreId);
  } catch (e) {
    /* Containers may be disabled, the API might have failed
       or the container has been deleted. */
    this.warnUser(e);
  }
  return container;
}

将容器添加到现有扩展程序

扩展程序通常为用户实现选项,这些选项并不适合所有浏览活动。特定的扩展程序可以提供隐私、安全或其他用户界面优势和增强功能。也许该扩展程序是一个简单的计时器,用于跟踪您在工作日中在社交媒体中观看猫咪 GIF 的频率。在完成“工作”容器后,您可能不再需要它。大多数扩展程序都需要用户发起交互,并且您的扩展程序“始终处于开启状态”可能没有必要或有益。

相反,将容器用作“上下文”的指示器会简化您扩展程序的用户体验。在打开特定容器时添加新功能的扩展程序更有可能被积极使用,因为它们会挂接到现有容器。例如,HTTPS Everywhere 的“始终使用 https”选项会破坏许多网站,但如果在您使用“银行”容器时默认实现,它始终是相关的,并且处于上下文中。

虽然扩展程序已经可以根据 URL 更改其行为,但我们认为容器的安全和隐私优势为用户创建了新的激励来配置设置。

新的容器扩展程序的想法

我们对容器扩展程序提供基于上下文的浏览增强功能的可能性感到兴奋。当用户想要使用“工作”选项卡时,扩展程序可以配置为阻止不适合工作的页面。当用户不想在家里被工作提醒时,扩展程序可以配置为自动删除用户的“工作”历史记录,但记住“个人”历史记录。

例如,扩展程序可以

  • 将社交页面自动加载到“社交”选项卡中
  • 在“工作”选项卡中关闭选项卡时删除 cookie
  • 在“购物”选项卡中阻止按键记录脚本
  • 为固定选项卡创建唯一的容器
  • 加载网站的多个版本以进行 QA 测试,同时仍然提供嵌入在浏览器中的历史记录和开发工具(而不是无头浏览器测试)。

例如,我们已经看到创建了许多容器扩展程序

  • Containers on the go - 为用户提供一个临时容器,该容器在选项卡的生命周期内有效。临时容器模拟私人选项卡,因为容器彼此隔离。一旦选项卡被丢弃,容器就会被删除,这将删除与之关联的 cookie 和其他存储。
  • Cookie AutoDelete - 已被修改为在启用容器时逐渐增强,允许用户更改每个容器的 cookie 删除设置。
  • Conex - panorama 扩展程序的容器实现
  • 还有更多

容器 WebExtension API 允许开发者重写容器本身。开发者可以分叉 我们的扩展,并在此基础上构建改进。如果你正在寻找想法,我们有一份关于 开放增强请求 的长列表,扩展开发者可以使用提供的 API 在他们自己的扩展中解决这些请求。

正如你从所有这些更改和更新中看到的那样,我们确实 拥抱了容器在标签管理中的使用

容器扩展的下一步

我们还需要对 API 进行一些改进。以下是队列中的一些内容:

发布你的扩展

术语

在创建容器扩展时,我们建议使用“容器标签”来解释附加组件,而不是使用 API 名称 contextualIdentities

隐私最佳实践

如果你构建了一个使用容器的扩展,但它以损害用户隐私的方式使用容器,请公开此信息。让用户知道你的扩展不符合 为容器设计的隔离标准。例如,在容器之间移动标签会带来风险,可能会让用户接触到额外的跟踪器。

修复现有扩展的问题

为了使你的浏览器扩展在 Firefox 中正常工作,请记住在创建和查询标签时检查 cookieStoreId。我们看到的一些扩展损坏报告是由于扩展复制标签网址并在稍后重新打开它们,而没有考虑标签所属的 cookieStoreId。这是一个关于 GitHub 上报告的问题 的示例。

我要感谢无数为容器做出贡献的用户、测试者、编码人员和工作人员。

欢迎反馈:containers@mozilla.com 或访问 Discourse

关于 Jonathan Kingston

Firefox 的前端安全工程师,致力于 HTTPS 采用、容器和内容安全。

Jonathan Kingston 的更多文章…


12 条评论

  1. greg

    除非容器能自动在与新标签生成相同的容器中创建新标签(Ctrl+T),否则它们只是一个新奇的想法,在实际应用中并不实用,在日常使用中几乎不可行。

    2017 年 10 月 3 日 下午 12:03

    1. Jonathan Kingston

      鉴于“Conex”已经做出了这种选择,你可以下载它并获得这种体验。这篇文章主要是在庆祝附加组件可以为用户提供他们想要的调整。

      我们不将其设为默认的原因是,这会让匿名变得太容易。

      文章中还提到了,我们正在探索像 Conex 这样的扩展如何能够设置默认标签,这将改善用户体验。

      2017 年 10 月 3 日 下午 12:17

      1. Sam Adams

        你能否提供一个选项,让用户选择 Cntrl + T 快捷键的行为方式?

        默认行为可以保持不变,这样不会破坏向后兼容性或新用户的预期,但对于想要更改的用户,它提供了灵活性,让他们可以选择快捷键是在同一个容器中打开还是在默认容器中打开。我不希望为了做到这一点而安装一个扩展,因为我已经安装了不少扩展了。安装的扩展越多,你增加的开销就越多。

        2017 年 10 月 4 日 下午 11:55

        1. Jonathan Kingston

          我们将在平台更改发生后讨论我们是否以及何时会采用这一功能,以便使这一功能成为可能。如“容器扩展的下一步”部分中所述,“扩展能够指定默认标签 cookieStoreId”将解决这个问题,并带来更多激动人心的隐私选项,例如默认情况下使用私有容器,我认为这实际上可以解决许多问题,比如数据迁移。

          WebExtensions 的开销远小于之前的附加组件,而且鉴于我们仍在朝着这个方向进行探索,我个人不会担心安装一个非常精简的扩展,它只做这件事。

          也就是说,我们也不希望将其设为容器用户的默认行为,因为它会导致与默认标签类似的弊端。

          2017 年 10 月 4 日 下午 2:18

  2. WalterK

    感谢 Firefox 团队在容器方面进行的创新!

    虽然容器已经可以使用了,但完成最后 20% 的工作似乎进展缓慢。

    在错误 1191418 中追踪的待处理问题越来越多。对我来说,主要的隐私相关问题是缺少按容器管理浏览历史记录和 Cookie 的功能。历史记录和 Cookie 视图都应该增强,以便按容器进行排序和过滤。

    此外,新的 Photon 设计使得在多个标签打开时更难看到哪个标签属于哪个容器。颜色线条太细,需要在底部添加边距,以免与标签底部边框混淆。

    2017 年 10 月 3 日 下午 6:18

    1. Jonathan Kingston

      鉴于容器在 Gecko 中触及的领域数量,待处理问题可能会继续增加。
      由于我们将工作量分散到多个领域,这些领域没有按该错误分类,比如 GitHub,因此你可能根本没有看到完整的画面。例如,我们在 GitHub 上有大约 400 个已关闭的错误。
      我们根据以下因素优先考虑工作:项目重点、用户反馈和复杂性。从 57 版本开始,历史记录发生了变化,我们正在跟踪这些变化的进展。
      开发这些容器 API 将允许开发者快速创建原型,以针对 Firefox 现有的这一全新的功能提供反馈,这是一个全新的功能。

      感谢你提醒我提出有关 Photon 中突出显示的错误,自实现以来,我一直对此感到困扰:https://bugzilla.mozilla.org/show_bug.cgi?id=1405542

      2017 年 10 月 3 日 下午 8:09

  3. Graham Perrin

    很棒。这将是一个非常有用的参考点。

    你是否可以为标题和副标题添加更多 #content-... 锚点?

    我预见经常会引用 _隐私最佳实践_...

    非常感谢

    2017 年 10 月 3 日 下午 9:39

    1. Jonathan Kingston

      感谢提醒,鉴于我创建的这长篇大论,这样做很有意义。
      对于这篇文章的长度,我深感歉意。

      2017 年 10 月 4 日 上午 2:47

  4. glandium

    我前几天查看了 API,想实现一个附加组件,将网站迁移到容器中,这似乎是可行的,但对于本地存储,似乎没有对容器/cookie store id 的识别。

    (我想要实现的功能是:我希望 $site1 和 $site2 始终使用 $container,但是由于我在容器存在之前就一直在使用它们,因此与它们相关的所有内容(Cookie、本地存储等)都在默认容器中,在 $container 中打开它们将意味着在那里登录,并且可能还需要其他操作,而我仅仅希望从默认容器“继续”会话(并删除默认容器中的 Cookie/本地存储))

    2017 年 10 月 3 日 下午 10:02

    1. Jonathan Kingston

      我们还没有完整的 API 图像来实现这一点。具体来说,实现这一点的主要问题是 IndexedDb 和发现页面的数据库名称。
      忽略 IndexedDb,这可以通过内容脚本实现,这将允许扩展将数据从一个容器迁移到后台脚本,然后迁移回另一个容器标签内容脚本。

      但是,我建议不要使用这种方法,因为它会导致 数据迁移隐私问题。如果你要这样做,请警告用户风险,即使是初始迁移。

      总体来说,简单概括
      网络很混乱,合并存储将允许跟踪器跨越容器边界。管理所有边缘情况可能会导致网站出现故障。

      2017 年 10 月 4 日 上午 3:00

      1. glandium

        然而,并非所有使用容器都是为了隐私原因。我自己的用例之一是隔离使用 auth0 但需要使用*不同*帐户进行身份验证的 Mozilla 网站,当你没有使用容器时,这是一件很麻烦的事情(在我们使用 auth0 之前,情况甚至更糟)。如果能够自动迁移相应的 Cookie,而不必关心它,那就更方便了。

        2017 年 10 月 4 日 上午 3:48

        1. Jonathan Kingston

          没错,这就是为什么我们要求那些不关心我们的隐私和安全优势的附加组件提供某种形式的警告。这不是一项强制性要求,但这样做是一个好习惯,就像警告用户可能会破坏跨域安全一样。

          试图将存储从默认标签迁移到容器,就像试图从吐司上刮掉边缘, чтобы вернуть оригинальный хлеб.
          一旦用户访问了多个网站,扩展就无法保证哪些存储项是必要的,不会破坏网站。因此,在你的示例中,迁移数据的問題基本上是一个全有或全无的问题,你不能安全地只获取 Google Cookie 并假设 oAuth 会正常工作,同样,你不能删除 Google Analytics Cookie 并修复隐私问题,而不会冒着破坏网站的风险。

          但是,我们并没有阻止你这样做,就像 Ad Block Plus 或 HTTPS Everywhere 一样,它们在某种程度上是一个不完美的解决方案,但效果很好,但对于任何类似的扩展,我担心的是,它会演变成维护大型的边缘情况列表,就像那些扩展一样。这在很大程度上是 Firefox 无法原生提供此类解决方案的原因。

          2017 年 10 月 4 日 下午 2:12

本文的评论已关闭。