一步步构建 Firefox 扩展

2025-06-10

一步步构建 Firefox 扩展

最近我一直在使用dev.to上的阅读列表。它是个不错的工具,但我习惯于将文章保存到Pocket中以便以后阅读。
在本文中,我们将创建一个 Firefox 扩展,以便自动将文章同时添加到您的 Dev.to 阅读列表和 Pocket 帐户中。

它看起来是这样的(扩展文件可在本文末尾找到):

该扩展程序要求您已经在浏览器中连接到 Pocket 帐户(因此我们不必处理 API 身份验证)。

什么是浏览器扩展?

浏览器扩展是 Firefox 浏览特定页面时执行的一组脚本。这些脚本可以修改页面的 HTML、CSS 和 JavaScript,并可以访问特定的JavaScript API(例如书签、身份等)。

脚本分为两种类型:内容脚本后台脚本。内容脚本在页面内执行,而后台脚本执行长期操作并维护长期状态。后台脚本也可以访问所有 WebExtension API。

以下是该项目的最终文件结构:

  • manifest.json (配置文件)
  • background.js (我们的背景脚本)
  • devtopocket.js (在 dev.to 页面上执行的内容脚本)
  • 图片/

内容和背景脚本

我们的项目中有两个脚本:一个处理后台工作(发送 Ajax 请求),另一个(内容脚本)在“阅读列表”Dev.to 按钮上注册点击事件:

内容脚本

内容脚本(devtopocket.js)注册点击并将请求发送到我们的后台脚本。

devtopocket.js

document.getElementById("reaction-butt-readinglist").addEventListener("click", function() {
    if(window.confirm("Do you want to save this article in Pocket?")) {
        sendBackgroundToPocket();
    }
});
Enter fullscreen mode Exit fullscreen mode

sendBackgroundToPocket方法需要与后台脚本进行通信并要求其发送Ajax请求。

browser.runtime为我们的扩展脚本之间提供了一个双向通信通道。browser.runtime.sendMessage在该通道上发送一条消息,并返回一个等待另一端响应的 Promise。一旦我们收到响应(意味着 Ajax 请求已完成),就会向用户显示一条消息(参见上面的 gif):

devtopocket.js

function sendBackgroundToPocket(){
    browser.runtime.sendMessage({"url": window.location.href}).then(function(){
        document.getElementById("article-reaction-actions").insertAdjacentHTML("afterend", "<div id='devtopocket_notification' style='text-align: center;padding: 10px 0px 28px;'>This article has been saved to Pocket!</div>")
        setTimeout(function(){
            document.getElementById("devtopocket_notification").remove()
        }, 2000)
    });  
}
Enter fullscreen mode Exit fullscreen mode

背景脚本

后台脚本用于编写不依赖于打开特定网页的耗时操作。

这些脚本随扩展程序一起加载,并一直执行,直到扩展程序被禁用或卸载。

我们的后台脚本(background.js)有两个作用:

  • 发送 Ajax 请求
  • 通过 History API 对 URL 更改做出反应

在扩展配置(下面的manifest.json)中,我们将说“在与url模式匹配的页面上加载devtopocket.js”,当我们直接浏览到文章页面时它就会起作用。

dev.to 网站的“问题”在于它使用 HTML5 History API 来浏览页面(每个单页 Web 应用都如此)。如果页面未完全重新加载,Firefox 不会监听 URL 变化,因此不会执行我们的内容脚本。因此,我们需要一个后台脚本通过 History API 监听 URL 变化,并在需要时手动执行前端脚本。

我们通过使用webNavigation API来监听 url 的变化

background.js

browser.webNavigation.onHistoryStateUpdated.addListener(function(details) {
    browser.tabs.executeScript(null,{file:"devtopocket.js"});
}, {
    url: [{originAndPathMatches: "^.+://dev.to/.+/.+$"}]
});
Enter fullscreen mode Exit fullscreen mode

{originAndPathMatches: "^.+://dev.to/.+/.+$"}将监听器限制为特定的目标 URL 模式(与我们将在 中定义的模式相同manifest.json)。

browser.tabs.executeScript方法在当前选项卡中加载内容脚本。

后台脚本需要从我们的内容脚本中获取一条消息(当单击“阅读列表”按钮时):

background.js

function handleMessage(message, sender, sendResponse) {
    if(message.url) {
        sendToPocket(message.url, sendResponse)
        return true;
    }
}
browser.runtime.onMessage.addListener(handleMessage)
Enter fullscreen mode Exit fullscreen mode

sendToPocket接收消息时会调用该方法。
为了将 URL 保存到 Pocket,我们将调用 Pocket 提供的现有保存页面 ( https://getpocket.com/save )。一个经典的 Ajax 请求即可完成此操作:

function sendToPocket(url, sendResponse) {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            sendResponse();
        }
    };
    xhr.open("GET", "https://getpocket.com/save?url="+url, true);
    xhr.send();
}
Enter fullscreen mode Exit fullscreen mode

您可能会看到跨域请求问题,我们稍后将使用扩展权限解决该问题。

清单

manifest.json是我们的扩展配置文件。它类似于package.jsonJavaScript Web 应用中的配置文件或 Android 应用中的 AndroidManifest.xml 文件。您可以定义项目的版本和名称、所需的权限以及组成扩展程序的 JavaScript 源文件。

首先我们编写应用程序定义:

{
    "manifest_version": 2,
    "name": "DevToPocket",
    "version": "1.0.0",

    "description": "Send your DEV.to reading list to Pocket",

    "icons": {
        "48": "icons/devtopocket-48.png"
    },
    ...
}
Enter fullscreen mode Exit fullscreen mode

至少提供一个 48x48 的图标,如果您提供更多尺寸,Firefox 会根据您的屏幕分辨率尝试使用最佳的图标尺寸。我们将使用这个图标:

图标/devtopocket-48.png

然后我们定义我们的权限:

{
    ...
    "permissions": [
        "storage",
        "cookies",
        "webNavigation",
        "tabs",
        "*://dev.to/*/*",
        "*://getpocket.com/*"
    ]
}
Enter fullscreen mode Exit fullscreen mode

您可以在Mozilla 文档中找到权限列表

权限中的 URL 赋予了我们的扩展程序扩展权限。在我们的例子中,它允许我们从 dev.to 访问 getpocket.com,且不受跨域限制。我们可以通过 dev.to 注入脚本,tabs.executeScript并且能够访问 getpocket.com 的 Cookie,从而对 Ajax 请求进行身份验证。完整的主机权限列表可在此处查看。

完整manifest.json文件:

{
    "manifest_version": 2,
    "name": "DevToPocket",
    "version": "1.0.0",

    "description": "Send your DEV.to reading list to Pocket",

    "icons": {
        "48": "icons/devtopocket-48.png"
    },

    "content_scripts": [
        {
            "matches": ["*://dev.to/*/*"],
            "js": ["devtopocket.js"]
        }
    ],
    "background": {
        "scripts": ["background.js"]
    },

    "permissions": [
        "storage",
        "cookies",
        "webNavigation",
        "tabs",
        "*://dev.to/*/*",
        "*://getpocket.com/*"
    ]
}
Enter fullscreen mode Exit fullscreen mode

运行扩展

为了运行您的扩展,我们将使用web-ext命令行:https://github.com/mozilla/web-ext

这是一个帮助构建、运行和测试 WebExtensions 的命令行工具。

npm install --global web-ext
Enter fullscreen mode Exit fullscreen mode

然后在终端中,在项目文件夹中运行以下命令:

web-ext run
Enter fullscreen mode Exit fullscreen mode

它将启动一个浏览器,并临时加载你的扩展程序。当你进行一些更改时,扩展程序会自动重新加载。

签署扩展

要在其他人的浏览器中安装您的扩展程序,您需要打包并签署该扩展程序。

首先在Mozilla 开发者中心创建一个开发者帐户,然后在此处检索您的 API 凭据:https://addons.mozilla.org/en-US/developers/addon/api/key/

运行web-ext sign 命令:

web-ext sign --api-key=user:XXX --api-secret=YYY
Enter fullscreen mode Exit fullscreen mode

您的扩展文件随后将在web-ext-artifacts/devtopocket-XXX-an+fx.xpi中可用。在 Firefox 中打开该文件进行安装。


完整的源代码可以在 GitHub 上找到:https://github.com/scleriot/devtopocket
您可以下载并安装最新版本:https://github.com/scleriot/devtopocket/releases/latest

此扩展程序也适用于 Android 版 Firefox!

鏂囩珷鏉ユ簮锛�https://dev.to/scleriot/build-a-firefox-extension-step-by-step-5dbl
PREV
2024 年最适合远程工作的地点
NEXT
架构是一种负担 简介 与整个公司共享数据库 围绕业务软件构建系统 数十个应用程序之间紧密耦合 在别人的项目上构建自己的项目 所有业务逻辑都在规则管理引擎中 结论