译:学习Web扩展

187 阅读7分钟

什么是 Web 扩展?

Web 扩展社区小组于 2021 年在 W3C 成立,由苹果、谷歌、微软和 Mozilla 发起,旨在就浏览器扩展的共同愿景达成一致,并努力实现未来的标准化。

扩展的核心是manifest.json文件,这是任何 Web 扩展中唯一必须存在的文件。清单文件包含有关扩展的元数据,以及指向构成扩展的其他文件的指针。

这些其他文件包括后台脚本、侧边栏、弹出窗口和选项页面、扩展页面、内容脚本和 Web 可访问资源。

这个manifest.json文件有特定的语法,截至撰写本文时,最新语法应该是 v3。如果你没有一直关注 Web 扩展的发展,可能不知道 v3 引起了不少争议。

我不会详述所有细节,但总结一下主要批评:v3 将 Web 扩展从被视为拥有自己持久执行环境的一等应用程序,降级为具有有限权限和反应式执行能力的配件。这主要是由于强制使用服务工作者并移除"阻塞式 webRequest"机制的结果。

如果你感兴趣,可以自行谷歌更多信息。

manifest.json

这是一个什么都不做,只展示浏览器识别扩展存在的最低要求的示例:

{
  "manifest_version": 3,
  "name": "AE1",
  "version": "1.0",

  "description": "这个扩展实际上什么都不做",
  "icons": {
    "32": "icons/icon32.png",
    "48": "icons/icon48.png"
  },

  "action": {
    "default_popup": "nothing.html"
  }
}

完整代码见:github.com/huijing/sli…

manifest.json必须有 3 个必填键:manifest_versionnameversion。示例中还包含了descriptioniconsaction键。

虽然descriptionicons是可选的,但如果你想在 Chrome 网上应用店发布扩展,它们是必需的。它们也能提升用户体验,因为给你的扩展一个漂亮的图标和适当的描述。

action决定了扩展在浏览器工具栏中的外观,以及点击它时的行为。popup可以包含任何 HTML 内容,窗口会自动调整大小以适应内容。

<html>
  <body>
    <h1>什么都没有</h1>
  </body>
</html>

要为扩展添加一些样式和交互性,可以通过 link 或 style 和 script 元素包含 CSS 和 JavaScript。

<html>
  <body>
    <h1>什么都没有</h1>
    <button>有点东西</button>
  </body>
  <style>
    body {
      text-align: center;
    }
  </style>
  <script src="nothing.js"></script>
</html>
document.querySelector("button").addEventListener("click", () => {
  document.querySelector("h1").style.color = "tomato";
});

弹出窗口运行的环境与浏览器加载的网页内容是隔离的。在这个例子中,nothing.js针对的是非常通用的buttonh1标签元素,但上面的代码只影响弹出窗口中的元素,而不影响网页上的任何内容。

如果你像我一样是个前端开发者,可能更喜欢看到代码执行后的一些视觉反馈。

要在 Firefox 中加载扩展,你必须将其作为临时附加组件加载。在你修改源代码后重新加载扩展的选项也是可用的。

对于 Chrome,你必须先启用开发者模式,然后才能加载解压的扩展。相同的源代码在两个浏览器中都能工作。

内容脚本

当我们想让扩展实际对网页内容做些事情时,就需要使用内容脚本,它在浏览器加载的网页上下文中运行。这是我们能从扩展访问页面内容的唯一方式。

有 3 种方法可以将扩展的内容脚本加载到网页中:静态声明、动态声明或以编程方式。这些不同的方法允许最广泛的使用场景,无论你是想让扩展默认就修改体验,还是基于特定触发器。

内容脚本是隔离的,意思是它可以对自己的 JavaScript 环境进行更改,而不会与页面或其他扩展的内容脚本冲突。尽管内容脚本可以访问和修改 DOM,但它们只能看到未被任何 JavaScript 修改过的"干净"版本的 DOM。

Firefox 和 Chrome 处理这种隔离行为的方式不同。在 Firefox 中,这个概念被称为Xray Vision,内容脚本可能会遇到来自自己全局作用域的 JavaScript 对象或来自网页的 Xray Vision包装版本。

而在 Chrome 中,存在 3 种世界的概念:主世界、隔离世界和工作世界。每个世界都有自己的上下文、自己的全局变量作用域和原型链。

内容脚本可以直接访问一组特定且有限的扩展 API,但对于其他 API,需要某种形式的消息交换,我们将在下一个示例中简要提及。

这个扩展包含一个按钮,将接管整个网页并用来自互联网的一些像素艺术覆盖它。完整代码见:github.com/huijing/sli…

{
  "manifest_version": 3,
  "name": "AE2",
  "version": "1.0",
  "description": "激活像素艺术",
  "icons": {
    "32": "icons/icon32.png",
    "48": "icons/icon48.png"
  },
  "permissions": ["activeTab", "scripting"],
  "action": {
    "default_popup": "pixel.html"
  },
  "web_accessible_resources": [
    {
      "resources": [
        "images/pixel-adventure-time.png",
        "images/pixel-cat.jpg",
        "images/pixel-city.png",
        "images/pixel-zen-garden.png"
      ],
      "extension_ids": ["*"],
      "matches": ["*://*/*"]
    }
  ]
}

为了让扩展识别和加载图像,它们需要在manifest.json文件中用web_accessible_resources键声明。扩展还需要activeTab权限,这仅在用户交互发生时为活动标签页授予扩展额外权限,以及脚本权限,这是在内容脚本中调用脚本 API 方法所必需的。

let id;
browser.tabs.query({ active: true, currentWindow: true }, (tabs) => {
  id = tabs[0].id;
  browser.scripting.executeScript({
    target: { tabId: tabs[0].id },
    files: ["content.js"],
  });
  browser.scripting.insertCSS({
    target: { tabId: tabs[0].id },
    files: ["content.css"],
  });
});

document.getElementById("pixelate").addEventListener("click", () => {
  browser.tabs.sendMessage(id, { message: "pixelate" });
});

document.getElementById("reset").addEventListener("click", () => {
  browser.tabs.sendMessage(id, { message: "reset" });
});

browser.tabs.query()用于获取我们想要定位的标签页信息,特别是标签页 ID,因为脚本方法需要传入一个标签页 ID,这很合理,因为我们必须指定我们想要注入脚本的目标,对吧?

注入 CSS 也是一样的道理。是否可以通过 JavaScript 来样式化被注入的内容?当然可以。我们应该那样做吗?这完全取决于你。我个人更喜欢把样式放在 CSS 文件中,仅此而已。

那么这个sendMessage()方法又是什么呢?因为内容脚本在网页的上下文中运行,而不是扩展本身,这是内容脚本与扩展通信的方式。扩展和内容脚本将监听彼此的消息,并在同一通道上响应。

要访问扩展提供的图像,需要使用runtime.getURL()方法,它将图像的相对路径转换为浏览器可以正确渲染的完全限定 URL。最后,使用runtime.onMessage()事件来监听消息。

后台脚本

Web 扩展中另一种常见的脚本是后台脚本。它们旨在监视浏览器中的事件并相应地做出反应。例如,如果我们想为扩展实现键盘快捷键,可以利用后台脚本监听特定命令并触发某些操作。

下一个示例将通过使用后台脚本为扩展添加一些键盘快捷键。完整代码见:github.com/huijing/sli…

需要对manifest.json文件进行一些添加才能使功能正常工作。

{
  "background": {
    "service_worker": "background.js", // v3语法
    "scripts": ["background.js"] // v2语法
  },
  "commands": {
    "_execute_action": {
      "suggested_key": {
        "default": "Ctrl+Shift+Y"
      }
    },
    "pixelate": {
      "suggested_key": {
        "default": "Alt+A"
      },
      "description": "向扩展发送'pixelate'事件"
    },
    "reset": {
      "suggested_key": {
        "default": "Ctrl+Shift+E"
      },
      "description": "向扩展发送'reset'事件"
    }
  }
}

Commands API 让我们可以定义命令并将它们绑定到特定的按键组合。这些命令必须首先在manifest.json文件中声明为属性。然后我们使用后台脚本监听 onCommand 事件的触发,并在按下正确的按键组合时运行我们想要运行的任何内容。

有 4 个特殊快捷键具有默认操作,onCommand事件不会触发,_execute_action就是其中之一。这个快捷键的行为就像用户点击了工具栏中的扩展图标一样。

background.js文件中,我们使用onCommand.addListener为清单中列出的每个命令绑定一个处理程序。如前所述,_execute_action命令不会触发事件,所以我们不需要为它准备处理程序。

总结

这涵盖了开始 Web 扩展开发所需的所有基本信息。扩展可以简单到改变页面上文本的颜色,也可以复杂到一个完整的应用程序(是的,如果你愿意,可以使用 React 来构建你的扩展)。

我想如果你对可疑的扩展商店下载心存警惕,你完全可以构建自己的扩展来做你想让它做的任何事情,为什么不呢?

阅读原文