使用 Tabs API

选项卡允许用户在其浏览器窗口中打开多个网页,然后切换这些网页。借助 Tabs API,您可以操作这些选项卡,创建实用程序,为用户提供使用选项卡的新方式,或实现扩展程序的功能。

在这篇操作指南文章中,我们将探讨:

  • 使用 Tabs API 所需的权限。
  • 使用 tabs.query 发现有关选项卡及其属性的更多信息。
  • 创建、复制、移动、更新、重新加载和移除选项卡。
  • 操作选项卡的缩放级别。
  • 操作选项卡的 CSS。

最后,我们将介绍 API 提供的其他一些杂项功能。

注意: 有些 Tab API 功能在其他地方有介绍。这些是可用于通过脚本操作选项卡内容的方法(tabs.connecttabs.sendMessagetabs.executeScript)。如果您想了解这些方法的更多信息,请参阅概念文章 内容脚本 和操作指南 修改网页

权限与 Tabs API

对于大多数 Tabs API 功能,您不需要任何权限;但是,也有一些例外情况:

以下是您可能在扩展程序的 manifest.json 文件中请求 "tabs" 权限的方式:

json
"permissions": [
  "<all_urls>",
  "tabs"
],

此请求允许您在用户访问的所有网站上使用所有 Tabs API 功能。还有另一种请求权限以使用 tabs.executeScript()tabs.insertCSS() 的替代方法,即不需要主机权限的 "activeTab"。此权限提供与具有 <all_urls>"tabs" 相同的权限,但有两个限制:

  • 用户必须通过扩展程序的浏览器或页面操作、上下文菜单或快捷键与扩展程序进行交互。
  • 它只在活动选项卡中授予权限。

这种方法的好处是用户不会收到权限警告,提示您的扩展程序可以“访问所有网站的数据”。这是因为 <all_urls> 权限允许扩展程序随时在任何选项卡中执行脚本,而 "activeTab" 仅限于允许扩展程序在当前选项卡中执行用户请求的操作。

发现有关选项卡及其属性的更多信息

有时您可能想获取所有浏览器窗口中所有选项卡的列表。其他时候,您可能想查找与某些特定条件匹配的选项卡子集,例如从特定选项卡打开的选项卡或显示特定域页面的选项卡。一旦您有了选项卡列表,您可能想了解更多有关其属性的信息。

这就是 tabs.query() 的作用。单独使用它来获取所有选项卡,或者使用 queryInfo 对象来指定查询条件(例如选项卡是否处于活动状态、是否在当前窗口中,或者 17 个条件中的一个或多个),tabs.query() 返回一个包含有关选项卡信息的 tabs.Tab 对象数组。

如果您只想获取当前选项卡的信息,可以使用 tabs.getCurrent() 为该选项卡获取一个 tabs.Tab 对象。如果您有选项卡的 ID,可以使用 tabs.get() 获取其 tabs.Tab 对象。

操作方法示例

要了解 tabs.query()tabs.Tab 的用法,让我们看看 tabs-tabs-tabs 示例如何将“切换到选项卡”列表添加到其工具栏按钮弹出窗口中。

The tabs toolbar menu showing the switch to tap area

manifest.json

这是 manifest.json

json
{
  "browser_action": {
    "default_title": "Tabs, tabs, tabs",
    "default_popup": "tabs.html"
  },
  "description": "A list of methods you can perform on a tab.",
  "homepage_url": "https://github.com/mdn/webextensions-examples/tree/main/tabs-tabs-tabs",
  "manifest_version": 2,
  "name": "Tabs, tabs, tabs",
  "permissions": ["tabs"],
  "version": "1.0"
}

备注

  • tabs.htmlbrowser_action 中定义为 default_popup 每当用户点击扩展程序的工具栏图标时,它就会显示。
  • 权限包括 tabs。 这是支持选项卡列表功能所必需的,因为扩展程序读取选项卡的标题以显示在弹出窗口中。
tabs.html

tabs.html 定义了扩展程序弹出窗口的内容:

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="tabs.css" />
  </head>

  <body>
    <div class="panel">
      <div class="panel-section panel-section-header">
        <div class="text-section-header">Tabs-tabs-tabs</div>
      </div>

      <a href="#" id="tabs-move-beginning">
        Move active tab to the beginning of the window
      </a>
      <br />

      <!-- Define the other menu items -->

      <div class="switch-tabs">
        <p>Switch to tab</p>
        <div id="tabs-list"></div>
      </div>
    </div>

    <script src="tabs.js"></script>
  </body>
</html>

它执行以下操作:

  1. 声明菜单项。
  2. 声明一个 ID 为 tabs-list 的空 div,用于包含选项卡列表。
  3. 调用 tabs.js
tabs.js

tabs.js 中,我们将看到如何构建选项卡列表并将其添加到弹出窗口中。

创建弹出窗口

首先,添加一个事件处理程序,在加载 tabs.html 时执行 listTabs()

js
document.addEventListener("DOMContentLoaded", listTabs);

listTabs() 所做的第一件事是调用 getCurrentWindowTabs()。在这里,tabs.query() 用于获取当前窗口中选项卡的 tabs.Tab 对象:

js
function getCurrentWindowTabs() {
  return browser.tabs.query({ currentWindow: true });
}

现在,listTabs() 已准备好为弹出窗口创建内容。

首先:

  1. 获取 <div id="tabs-list"> 元素。
  2. 创建一个文档片段(用于构建列表)。
  3. 设置计数器。
  4. 清除 <div id="tabs-list"> 元素的内容。
js
function listTabs() {
  getCurrentWindowTabs().then((tabs) => {
    const tabsList = document.getElementById("tabs-list");
    const currentTabs = document.createDocumentFragment();
    const limit = 5;
    let counter = 0;

    tabsList.textContent = "";
    // ...
  });
}

接下来,我们将为每个选项卡创建链接:

  1. 遍历 tabs.Tab 对象中的前 5 个项目。
  2. 对于每个项目,向文档片段添加一个超链接。
    • 链接的标签(即其文本)使用选项卡的 title(或 id,如果它没有 title)设置。
    • 链接的地址使用选项卡的 id 设置。
js
function listTabs() {
  getCurrentWindowTabs().then((tabs) => {
    // ...
    for (const tab of tabs) {
      if (!tab.active && counter <= limit) {
        const tabLink = document.createElement("a");

        tabLink.textContent = tab.title || tab.id;

        tabLink.setAttribute("href", tab.id);
        tabLink.classList.add("switch-tabs");
        currentTabs.appendChild(tabLink);
      }

      counter += 1;
    }
    // ...
  });
}

最后,将文档片段写入 <div id="tabs-list"> 元素:

js
function listTabs() {
  getCurrentWindowTabs().then((tabs) => {
    // ...
    tabsList.appendChild(currentTabs);
  });
}

使用活动选项卡

另一个相关的示例功能是“提醒活动选项卡”信息选项,它将活动选项卡的所有 tabs.Tab 对象属性转储到警报中:

js
// Other if conditions...
if (e.target.id === "tabs-alert-info") {
  callOnActiveTab((tab) => {
    let props = "";
    for (const item in tab) {
      props += `${item} = ${tab[item]} \n`;
    }
    alert(props);
  });
}

其中 callOnActiveTab() 通过遍历 tabs.Tab 对象查找已设置活动的项来查找活动选项卡对象:

js
document.addEventListener("click", (e) => {
  function callOnActiveTab(callback) {
    getCurrentWindowTabs().then((tabs) => {
      for (const tab of tabs) {
        if (tab.active) {
          callback(tab, tabs);
        }
      }
    });
  }
});

创建、复制、移动、更新、重新加载和移除选项卡

收集到有关选项卡的信息后,您很可能会对它们做一些事情——要么为用户提供操作和管理选项卡的功能,要么在您的扩展程序中实现功能。

以下函数可用:

注意: 这些函数都要求提供它们所操作的选项卡的 ID(或 ID 列表)。

而以下函数将作用于活动选项卡(如果未提供选项卡 id):

操作方法示例

tabs-tabs-tabs 示例使用了所有这些功能,除了更新选项卡的 URL。这些 API 的使用方式类似,因此我们将查看其中一个更复杂的实现,即“将活动选项卡移动到窗口列表的开头”选项。

但首先,这是该功能实际运行的演示:

manifest.json

所有这些功能都不需要权限即可操作,因此 manifest.json 文件中没有需要特别强调的功能。

tabs.html

tabs.html 定义了弹出窗口中显示的“菜单”,其中包括“将活动选项卡移动到窗口列表的开头”选项,并包含一系列由视觉分隔符分组的 <a> 标签。每个菜单项都带有一个 id,在 tabs.js 中用于确定请求哪个菜单项。

html
<a href="#" id="tabs-move-beginning">
  Move active tab to the beginning of the window
</a>
<br />
<a href="#" id="tabs-move-end">Move active tab to the end of the window</a>
<br />

<div class="panel-section-separator"></div>

<a href="#" id="tabs-duplicate">Duplicate active tab</a><br />
<a href="#" id="tabs-reload">Reload active tab</a><br />
<a href="#" id="tabs-alert-info">Alert active tab info</a><br />
tabs.js

为了实现 tabs.html 中定义的“菜单”,tabs.js 包含一个用于监听 tabs.html 中点击事件的监听器:

js
document.addEventListener("click", (e) => {
  function callOnActiveTab(callback) {
    getCurrentWindowTabs().then((tabs) => {
      for (const tab of tabs) {
        if (tab.active) {
          callback(tab, tabs);
        }
      }
    });
  }
});

然后,一系列 if 语句将查找匹配被点击项的 id

此代码片段用于“将活动选项卡移动到窗口列表的开头”选项:

js
if (e.target.id === "tabs-move-beginning") {
  callOnActiveTab((tab, tabs) => {
    let index = 0;
    if (!tab.pinned) {
      index = firstUnpinnedTab(tabs);
    }
    console.log(`moving ${tab.id} to ${index}`);
    browser.tabs.move([tab.id], { index });
  });
}

值得注意的是 console.log() 的使用。这使您能够将信息输出到调试器控制台,这在解决开发过程中发现的问题时很有用。

Example of the console.log output, from the move tabs feature, in the debugging console

移动代码首先调用 callOnActiveTab(),后者又调用 getCurrentWindowTabs() 以获取包含活动窗口选项卡的 tabs.Tab 对象。然后它遍历该对象以查找并返回活动选项卡对象:

js
function callOnActiveTab(callback) {
  getCurrentWindowTabs().then((tabs) => {
    for (const tab of tabs) {
      if (tab.active) {
        callback(tab, tabs);
      }
    }
  });
}

固定选项卡

选项卡的一个功能是用户可以在窗口中“固定”选项卡。固定选项卡放置在选项卡列表的开头,并且无法移动。这意味着选项卡可以移动到的最早位置是任何固定选项卡之后的第一个位置。因此,调用 firstUnpinnedTab() 通过遍历 tabs 对象来查找第一个未固定选项卡的位置:

js
function firstUnpinnedTab(tabs) {
  for (const tab of tabs) {
    if (!tab.pinned) {
      return tab.index;
    }
  }
}

我们现在拥有移动选项卡所需的一切:活动选项卡对象(从中可以获取选项卡 id)以及要将选项卡移动到的位置。因此,我们可以实现移动:

js
browser.tabs.move([tab.id], { index });

复制、重新加载、创建和移除选项卡的其余功能也以类似方式实现。

操作选项卡的缩放级别

下一组功能允许您获取 (tabs.getZoom) 和设置 (tabs.setZoom) 选项卡内的缩放级别。您还可以检索缩放设置 (tabs.getZoomSettings),但在撰写本文时,在 Firefox 中尚无法设置这些设置 (tabs.setZoomSettings)。

缩放级别可以在 30% 到 500% 之间(表示为小数 0.35)。

在 Firefox 中,默认缩放设置为:

  • 默认缩放级别 100%.
  • 缩放模式: 自动(因此浏览器管理缩放级别的设置方式)。
  • 缩放更改的范围: "per-origin",这意味着当您再次访问某个站点时,它会采用您上次访问时设置的缩放级别。

操作方法示例

tabs-tabs-tabs 示例包含缩放功能的三个演示:放大、缩小和重置缩放。这是该功能的实际操作:

让我们来看看放大的实现方式。

manifest.json

所有缩放功能都不需要权限,因此 manifest.json 文件中没有需要特别强调的功能。

tabs.html

我们已经讨论了 tabs.html 如何定义此扩展程序的选项,提供缩放选项没有做任何新的或独特的事情。

tabs.js

tabs.js 首先定义了缩放代码中使用的几个常量:

js
const ZOOM_INCREMENT = 0.2;
const MAX_ZOOM = 5;
const MIN_ZOOM = 0.3;
const DEFAULT_ZOOM = 1;

然后它使用我们之前讨论的相同监听器,以便它可以在 tabs.html 中处理点击事件。

对于放大功能,它运行:

js
// Other if conditions...
if (e.target.id === "tabs-add-zoom") {
  callOnActiveTab((tab) => {
    browser.tabs.getZoom(tab.id).then((zoomFactor) => {
      // The maximum zoomFactor is 5, it can't go higher
      if (zoomFactor >= MAX_ZOOM) {
        alert("Tab zoom factor is already at max!");
      } else {
        let newZoomFactor = zoomFactor + ZOOM_INCREMENT;
        // If the newZoomFactor is set to higher than the max accepted
        // it won't change, and does not alert that it's at maximum
        newZoomFactor = newZoomFactor > MAX_ZOOM ? MAX_ZOOM : newZoomFactor;
        browser.tabs.setZoom(tab.id, newZoomFactor);
      }
    });
  });
}

此代码使用 callOnActiveTab() 获取活动选项卡的详细信息,然后 tabs.getZoom 获取选项卡的当前缩放因子。当前缩放与定义的最小值 (MAX_ZOOM) 进行比较,如果选项卡已达到最大缩放,则发出警报。否则,缩放级别会增加,但限制在最大缩放范围内,然后使用 tabs.getZoom 设置缩放。

操作选项卡的 CSS

Tabs API 提供的另一个重要功能是操作选项卡内 CSS 的能力——向选项卡添加新 CSS (tabs.insertCSS()) 或从选项卡中移除 CSS (tabs.removeCSS())。

例如,如果您想突出显示某些页面元素或更改页面的默认布局,这会很有用。

操作方法示例

apply-css 示例使用这些功能为活动选项卡中的网页添加红色边框。这是该功能的实际操作:

让我们看看它是如何设置的。

manifest.json

manifest.json 请求使用 CSS 功能所需的权限。您需要以下两者之一:

  • "tabs" 权限和主机权限;或者,
  • "activeTab" 权限。

后者最有用,因为它允许扩展程序在从扩展程序的浏览器或页面操作、上下文菜单或快捷方式运行时,在活动选项卡中使用 tabs.insertCSS()tabs.removeCSS()

json
{
  "description": "Adds a page action to toggle applying CSS to pages.",

  "manifest_version": 2,
  "name": "apply-css",
  "version": "1.0",
  "homepage_url": "https://github.com/mdn/webextensions-examples/tree/main/apply-css",

  "background": {
    "scripts": ["background.js"]
  },

  "page_action": {
    "default_icon": "icons/off.svg"
  },

  "permissions": ["activeTab", "tabs"]
}

您会注意到除了 "activeTab" 之外还请求了 "tabs" 权限。这个额外的权限是为了使扩展程序的脚本能够访问选项卡的 URL,其重要性我们稍后会看到。

manifest.json 文件中的其他主要功能是:

  • 后台脚本,它在扩展程序加载后立即开始运行。
  • “页面操作”,它定义了一个要添加到浏览器地址栏的图标。
background.js

在启动时,background.js 设置了一些常量来定义要应用的 CSS、"页面操作"的标题以及扩展程序将使用的协议列表:

js
const CSS = "body { border: 20px solid red; }";
const TITLE_APPLY = "Apply CSS";
const TITLE_REMOVE = "Remove CSS";
const APPLICABLE_PROTOCOLS = ["http:", "https:"];

首次加载时,扩展程序使用 tabs.query() 获取当前浏览器窗口中所有选项卡的列表。然后它遍历这些选项卡并调用 initializePageAction()

js
browser.tabs.query({}).then((tabs) => {
  for (const tab of tabs) {
    initializePageAction(tab);
  }
});

initializePageAction 使用 protocolIsApplicable() 来确定活动选项卡的 URL 是否是 CSS 可以应用到的协议之一:

js
function protocolIsApplicable(url) {
  const anchor = document.createElement("a");
  anchor.href = url;
  return APPLICABLE_PROTOCOLS.includes(anchor.protocol);
}

然后,如果示例可以作用于该选项卡,initializePageAction() 会将选项卡的 pageAction(导航栏)图标和标题设置为“关闭”版本,然后使 pageAction 可见:

js
function initializePageAction(tab) {
  if (protocolIsApplicable(tab.url)) {
    browser.pageAction.setIcon({ tabId: tab.id, path: "icons/off.svg" });
    browser.pageAction.setTitle({ tabId: tab.id, title: TITLE_APPLY });
    browser.pageAction.show(tab.id);
  }
}

接下来,pageAction.onClicked 上的监听器等待 pageAction 图标被点击,并在点击时调用 toggleCSS

js
browser.pageAction.onClicked.addListener(toggleCSS);

toggleCSS() 获取 pageAction 的标题,然后执行所描述的操作:

  • 对于“应用 CSS”:

    • pageAction 图标和标题切换到“移除”版本。
    • 使用 tabs.insertCSS() 应用 CSS。
  • 对于“移除 CSS”:

    • pageAction 图标和标题切换到“应用”版本。
    • 使用 tabs.removeCSS() 移除 CSS。
js
function toggleCSS(tab) {
  function gotTitle(title) {
    if (title === TITLE_APPLY) {
      browser.pageAction.setIcon({ tabId: tab.id, path: "icons/on.svg" });
      browser.pageAction.setTitle({ tabId: tab.id, title: TITLE_REMOVE });
      browser.tabs.insertCSS({ code: CSS });
    } else {
      browser.pageAction.setIcon({ tabId: tab.id, path: "icons/off.svg" });
      browser.pageAction.setTitle({ tabId: tab.id, title: TITLE_APPLY });
      browser.tabs.removeCSS({ code: CSS });
    }
  }

  browser.pageAction.getTitle({ tabId: tab.id }).then(gotTitle);
}

最后,为确保每次选项卡更新后 pageAction 都是有效的,tabs.onUpdated 上的监听器会在每次选项卡更新时调用 initializePageAction(),以检查选项卡是否仍在使用 CSS 可以应用的协议。

js
browser.tabs.onUpdated.addListener((id, changeInfo, tab) => {
  initializePageAction(tab);
});

其他一些有趣的功能

还有一些其他的 Tabs API 功能不属于前面的任何部分:

  • 使用 tabs.captureVisibleTab 捕获可见的选项卡内容。
  • 使用 tabs.detectLanguage 检测选项卡中内容的主要语言。例如,这可以用于将扩展程序的 UI 语言与其运行的页面语言匹配。

了解更多

如果您想了解有关 Tabs API 的更多信息,请查看: