如何创建一个火狐浏览器附加组件

473 阅读10分钟

How to Create a Firefox Add-on

在本教程中,我们将介绍如何创建一个Firefox插件,允许用户在指定时间创建带有自定义文本的警报。我们将介绍如何创建一个基本的附加组件,添加一个弹出窗口和一个选项页面,添加权限,存储信息,发送通知,以及创建一个用于发送通知的后台脚本。

本教程不需要任何关于创建Firefox附加组件或任何浏览器扩展的经验。你只需要知道一些JavaScript基础知识。你可以在这个GitHub仓库中找到本教程的代码,你也可以在这里找到发布的创建的附加组件。

设置我们的Firefox附加组件

创建Firefox附加组件的第一步是创建manifest.json 文件。这个文件是Firefox附加组件唯一需要的文件。manifest.json 文件的基本格式应包括以下关键。

  • name: 附加组件的名称,以slug格式表示,如my-extension
  • version: 附加组件的当前版本。当更新扩展中的任何东西时,你将需要更新这个版本,所以建议从低处开始。
  • manifest_version: 在写这篇文章的时候,Firefox只支持Manifest V2,所以这个值应该是2 。但是,如果将来增加了对V3的支持,这个值也可以是3

这些是任何附加组件的必选字段。下面两个是可选的,但建议使用。

  • description: 你的附加组件的简短描述,解释其目的。
  • icons: 一个不同大小的图标列表。这些图标将在设置、浏览器的工具栏以及其他地方使用。建议添加的尺寸为:16px,32px,48px, 和128px

对于我们的附加组件,让我们先创建一个名为firefox-alarms-addon 的文件夹。然后添加一个manifest.json ,内容如下。

{
  "name": "personalized-alarms",
  "version": "0.0.1",
  "description": "Create personalized alarms",
  "manifest_version": 2,
  "icons": {
    "16": "assets/images/icon16.png",
    "32": "assets/images/icon32.png",
    "48": "assets/images/icon48.png",
    "128": "assets/images/icon128.png"
  }
}

正如你所看到的,icons 的键是一个对象,其键是文件大小和它的路径。路径是相对于插件的根,也就是manifest.json 的位置。在本教程中,我使用的是Twitter Emojiiconscout下载的一个图标,在这里我也可以下载所需的不同尺寸。

如果你跟着学,从我们的repo中抓取这些文件,并把它们放在适当的目录中(assets/images/)。

这就是创建Firefox附加组件所需要的全部内容!

在Firefox中加载附加组件

为了测试我们的Firefox附加组件,并在以后将其上传到Mozilla的开发者中心之前能够对其进行调试,请打开Firefox,然后从右侧菜单中选择附加组件和主题,或者使用快捷键ctrl+shift+A,然后点击管理你的扩展程序旁边的 "设置 "图标,选择调试附加组件

Manage Your Extension

一个新的页面将为临时扩展打开。

Temporary Extensions

点击加载临时附加组件按钮,选择你刚刚创建的manifest.json 文件。如果一切操作正确,你会看到新创建的附加组件的一些信息和我们在manifest.json 中指定的图标。

Personalized Alarms add-on

添加一个弹出窗口

火狐浏览器附加组件可以通过不同的方法进行访问,其中之一就是添加一个弹出式页面。当添加一个弹出页面时,你的扩展的图标将显示在工具栏上,一旦用户点击它,你指定的弹出页面就会出现。

我们将使用弹出页面向用户显示即将到来的警报列表,以及一个添加新警报的链接,将用户带到选项页面(我们将在下一节中讨论)。

在项目根部创建一个popup.html 文件,内容如下。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Personalized Alarms</title>
    <link href="assets/css/bootstrap.min.css" rel="stylesheet" />
  </head>
  <body class="p-3">
    <h1>Upcoming Alarms</h1>
    <ul class="list-group" id="alarmsList">
    </ul>
    <div class="mt-3">
      <a href="#" class="link-primary" id="optionsLink">Add an Alarm</a>
    </div>
    <script src="assets/js/jquery.min.js"></script>
  </body>
</html>

正如你所看到的,这只是一个HTML文件。我们还把bootstrap.min.css 添加到assets/css ,并在此链接,把jquery.min.js 添加到assets/js/jquery.min.js 下面,并同样链接。这两个库只是为了使事情变得更容易,但你不一定要真正使用它们。你可以从我们的 repo这里这里抓取它们。

在页面的内容中,我们将显示即将发生的警报列表和一个指向选项页面的链接。

要使弹出式窗口工作,下一步是在manifest.json 中添加以下内容。

"browser_action": {
  "default_popup": "popup.html",
  "browser_style": true
}

browser_action 是一个有许多选项的对象,但唯一强制性的是 ,这是弹出式窗口从附加组件根目录的相对路径。 不是强制性的,但建议设置为 。这意味着Firefox将注入浏览器的默认样式,以确保附加组件的弹出式窗口样式与浏览器的其他部分类似。default_popup browser_style true

这就是添加弹出窗口所需的全部内容。进入我们之前去的临时附加组件页面,点击附加组件的重新加载按钮。这将使火狐浏览器检查manifest.json ,以了解任何变化,并应用它们。

一旦你这样做,你就能在工具栏菜单中看到你的扩展程序的图标。

Add-on in toolbar

如果你点击它,你可以看到我们刚刚创建的弹出式页面。

Add-on Popup

在我们的弹出式页面中还剩下两件事,以使其完全发挥作用:使用存储来获得即将到来的警报,以及使 "添加警报 "链接将用户带到选项页面。

使用存储

浏览器扩展中的存储允许我们存储与扩展或用户相关的数据,可以是机器上的本地数据,也可以是基于用户账户的同步数据。本地存储将信息存储在浏览器本地,这意味着,如果用户从另一台机器上用相同的电子邮件登录到火狐,这些存储的信息将不会出现在那里。同步存储为当前登录的用户存储信息,这使得这些信息在用户登录的任何地方都可以使用。

同步存储应该用于用户希望在任何地方都可用的某些设置,而本地存储应该用于仅与当前浏览器安装有关的信息或选项。

在我们的例子中,我们将使警报在用户登录的任何地方都可用,所以我们将它们存储在同步存储中。但是,假设我们想添加一个 "临时禁用 "选项,使警报器暂时静音。在这种情况下,使用本地存储可能会更合适。

存储可以通过Storage APIgetset方法轻松访问,但首先,我们需要申请权限在我们的附加组件中使用storage 。这可以在manifest.json 里面完成。

"permissions": [
  "storage"
],

当用户安装你的附加组件时,他们会看到你需要哪些权限,需要他们接受才能安装你的附加组件。

为了能够在本地测试该附加组件,我们还需要添加一件事:一个明确的附加组件ID,以便能够使用存储。要做到这一点,也要在manifest.json

"browser_specific_settings": {
  "gecko": {
    "id": "addon@example.com",
    "strict_min_version": "42.0"
  }
}

这只是为了能够在本地进行测试。一旦我们发布了它,我们将从清单中删除这个。

我们要做的下一件事是创建一个新的assets/js/popup.js 文件,它将从存储中获取警报并显示它们。

要从存储中获取项目,您可以使用browser.storage.sync.getbrowser.storage.local.get。这取决于你是将信息存储在同步存储还是本地存储。在我们的例子中,我们将警报存储在同步存储中,所以我们将使用browser.storage.sync.get 。需要注意的是,browser.storage.sync.*browser.storage.local.* 下的所有方法都有相同的签名,接受/返回相同的类型。

browser.storage.sync.get 需要一个参数:一个字符串数组,这是我们要检索的数据的键。这些键是在我们设置存储时定义的(我们将在下一节讨论)。这个函数返回一个承诺,该承诺解析为一个 对象,包含我们在第一个参数中指定的键和它们的值(如果存在的话)。results

注意:如果您要使附加组件与Chrome浏览器兼容,请务必查看"使附加组件与Chrome浏览器兼容 "部分。

用以下内容创建assets/js/popup.js

$(document).ready(() => {
  const listElement = $('#alarmsList');

  browser.storage.sync.get(['alarms'])
    .then((result) => {
      if (result.alarms && result.alarms.length) {
        //loop over the alarms and display them
        result.alarms.forEach((alarm) => {
          appendItem(alarm.content, alarm.time);
        });
      } else {
        //show no items available
        appendItem('No alarms are available');
      }
    });

  function appendItem(content, badgeContent = null) {
    listElement.append(`
      <li class="list-group-item d-flex justify-content-between align-items-center">
        ${content}
        ${badgeContent ? `<span class="badge bg-primary rounded-pill">${badgeContent}</span>` : ''}
      </li>
    `);
  }
});

你还需要在popup.html 中包含这个文件。

  ...
  <script src="assets/js/popup.js"></script>
</body>
</html>

当文件准备好后,我们要使用browser.storage.sync.get ,以获得用户创建的警报器。然后我们要检查是否有任何警报。如果有,我们将循环显示它们,并使用appendItem 辅助函数,它只是将HTML列表元素li 附加到#alarmsList 。如果没有可用的警报,我们只是显示 "没有可用的项目"。

如果我们现在重新加载附加组件,你会发现一个新的附加组件的安装已经被添加。这是因为我们在manifest.json 中明确指定了ID。你可以删除旧的ID以避免冲突。

你会注意到我们的弹出窗口中没有任何变化,因为我们还没有添加任何警报器。我们将在下一节做这个。

添加一个选项页

为了让你的用户能够自定义或编辑附加组件中的选项或设置,你可以创建一个HTML页面来保存这些选项以及设置或改变它们背后的逻辑。然后你在manifest.json 文件中链接到它。

在我们的附加组件中,我们将使用选项页面来允许用户创建警报。让我们首先创建文件options.html 。你可以在add-on项目目录的任何地方创建它。我们将在项目的根目录下创建它,内容如下。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Options</title>
  <link href="assets/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body class="p-3">
  <h1>Add Alarm</h1>
  <form>
    <div class="form-group">
      <label for="name">Alarm Name</label>
      <input type="text" class="form-control" name="name" id="name" placeholder="Wake up" />
    </div>
    <div class="form-group">
      <label for="time">Time</label>
      <input type="time" class="form-control" name="time" id="time" />
    </div>
    <button type="submit" class="btn btn-primary mt-3">
      Add a New Alarm
    </button>
  </form>
  <script src="assets/js/jquery.min.js"></script>
  <script src="assets/js/options.js"></script>
</body>
</html>

这里,我们只是显示一个有两个输入字段的表单。"警报名称",这将是发送通知时在警报中显示的文本,以及 "时间",这是要设置警报的时间。

我们需要创建assets/js/options.js ,它将监听submit 事件的form ,并在同步存储中设置alarms ,为数组添加一个新的警报。

与我们使用get 方法类似,要设置存储,我们可以使用browser.storage.sync.setbrowser.storage.local.set,这取决于我们是只在本地存储数据还是在所有登录的实例之间同步存储。由于我们要将警报存储在sync ,我们将使用browser.storage.sync.set

set 方法需要一个参数,是一个由键和值组成的对象。键是我们以后用来检索值的东西,就像我们之前用get

创建assets/js/options.js ,内容如下。

$(document).ready(() => {
  const nameElm = $('#name');
  const timeElm = $('#time');
  const formElm = $('form');
  formElm.on('submit', () => {
    $('.alert').remove(); //remove previous success alerts, if any
    //get existing alarms
    browser.storage.sync.get(['alarms'])
      .then((result) => {
        let alarms = result.alarms;
        const alarmName = nameElm.val().trim() + '_' + (Math.random() * 100);
        if (!alarms) {
          alarms = [];
        }
        alarms.push({
          content: nameElm.val().trim(),
          time: timeElm.val(),
          alarmName
        });

        //set alarms in the storage
        browser.storage.sync.set({alarms})
          .then(() => {
            //TODO schedule notification
            formElm.prepend('<div class="alert alert-success">Alarm added successfully</div>');
            nameElm.val('');
            timeElm.val('');
          });
      });
    return false; //disable default form submit action
  });
});

在表单提交时,我们首先检索存储的警报,如果有的话。然后,我们通过表单将我们创建的新警报推送到alarms 数组中。请注意我们也在创建一个alarmName 变量。我们将使用这个变量来创建一个独特的警报,然后在用户删除它的时候取消它。最后,我们用browser.storage.sync.set 来设置新的alarms 数组。

你可能还注意到,我们添加了一个TODO 注释,这就是我们在下一节中安排通知的地方。

我们的选项页现在已经准备好了。为了使其可用,我们首先需要在manifest.json 中添加以下内容。

"options_ui": {
  "page": "options.html",
  "browser_style": false
}

这告诉火狐在哪里可以找到我们的选项页。false 我们还将browser_style ,因为我们不希望Firefox的风格设计覆盖Bootstrap的风格设计。

第二,我们现在要让弹出窗口中的链接将用户带到选项页面。要做到这一点,我们在附加到#optionsLink 的新事件监听器中使用browser.runtime.openOptionsPage()方法。我们将在assets/js/popup.js 中加入以下内容。

$(document).ready(() => {
  ...

  // New code here
  $('#optionsLink').on('click', () => {
    browser.runtime.openOptionsPage();
  });

  function appendItem(content, badgeContent = null) { ... }
});

现在,当用户点击 "添加报警器 "链接时,会将他们带到选项页面。

进入临时附加组件页面,并点击重新加载按钮。现在,我们的选项页将被注册。

让我们来测试一下。打开弹出窗口,点击 "添加一个警报"。它应该会把你带到附加组件页面的偏好标签,内容将是我们在options.html 页面中添加的内容。

Options Page

现在,尝试添加一个具有任何名称和时间的测试警报,并点击 "添加警报"。之后你应该能在弹出的窗口中看到它。

Add-on Popup

我们仍然需要对assets/js/popups.js ,也就是显示时间晚于当前时间的警报。将对browser.storage.sync.get 的调用改为如下。

browser.storage.sync.get(['alarms'])
  .then((result) => {
    if (result.hasOwnProperty('alarms') && result.alarms) {
      //get current time
      const minutes = (new Date).getMinutes().toString().padStart(2, '0');
      const hours = (new Date).getHours().toString().padStart(2, '0');
      const now = new Date('1970-01-01T' + hours + ':' + minutes + 'Z').getTime();

      //loop over the alarms and display them
      result.alarms.forEach((alarm) => {
        const alarmTime = new Date('1970-01-01T' + alarm.time + 'Z').getTime();
        if (alarmTime > now) {
          appendItem(alarm.content, alarm.time);
        }
      });
    } else {
      //show no items available
      appendItem('No alarms are available');
    }
  });

这将检查每个警报的时间是否大于当前时间,然后显示它。我们之所以将时间格式化为'1970-01-01T' + alarm.time + 'Z' ,是因为我们创建的警报与日期无关。这只是为了使教程更简单。在计算当前时间时,当hoursminutes 是一位数时,我们也用零来填充,因为new Date 的要求格式应该是两位数,这两个数字都是。

如果你现在检查,你会发现我们之前添加的闹钟是否显示取决于其时间。你也可以测试在另一个时间添加一个新的闹钟,看看它是否出现在弹出窗口。