Skip to content

弹出页面和选项页面 (Popup and Options Pages)

浏览器扩展几乎总是需要通过用户界面允许用户控制其某些方面的行为。弹出页面和选项页面是满足这一需求的基本扩展组件。内容脚本也可以用于提供页面内用户界面,但这样做会存在一些重要的权衡和复杂性。

: 有关这些权衡的内容,请参阅内容脚本章节。

弹出页面

顾名思义,弹出页面是一个网页,它渲染在一个“弹出”在浏览器网页上方的容器中。它完全由浏览器扩展控制,并且保证会覆盖浏览器当前的网页(图8-1)。 8-1

弹出页面属性

弹出页面容器可以被视为一个没有任何浏览器边框的简单浏览器标签页:没有URL栏,没有浏览器控件等。在几乎所有其他方面,弹出页面都与其他网页一样。以下演示扩展展示了弹出页面的一些基本属性(图8-2): 示例8-1a. manifest.json

json
{
  "name": "MVX",
  "version": "0.0.1",
  "manifest_version": 3,
  "action": {
    "default_popup": "popup.html"
  }
}

示例8-1b. popup.html

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link href="popup.css" rel="stylesheet" />
  </head>
  <body>
    <h1>这是一个弹出页面!</h1>
    <div id="url"></div>
    <div id="xid"></div>
    <a href="popup.js">popup.js</a>
    <script src="popup.js"></script>
  </body>
</html>

示例8-1c. popup.js

javascript
document.querySelector("#url").innerHTML = 
`<pre>页面URL: ${window.location.href}</pre>`;
document.querySelector("#xid").innerHTML = 
`<pre>扩展ID: ${chrome.runtime.id}</pre>`;

示例8-1d. popup.css

css
body {
    text-align: center;
    min-width: 20rem;
    padding: 4rem;
}

8-2

manifest.json中,弹出页面通过action.default_popup值指定弹出HTML文件的路径来声明。正如扩展清单章节中所述,action字典定义了工具栏图标按钮的外观和行为:

  • 如果action字典定义了default_popup值,点击该按钮将打开弹出页面。
  • 如果action字典未定义default_popup值,点击工具栏图标按钮将调用在任何地方定义的chrome.action.onClicked()处理程序。

弹出容器的大小将根据弹出页面的内容进行调整,但不同浏览器对大小规则的处理会略有不同。例如,Google Chrome对弹出页面的最大尺寸没有上限,允许其比浏览器窗口本身还要大。相反,Mozilla Firefox将弹出页面的大小限制为不超过800px x 600px。

提示:为了处理这些可变的弹出页面大小限制,您应该使弹出页面具有响应性,或者限制弹出页面内显示的内容量。

因为弹出页面是通过浏览器的扩展协议提供的(在Google Chrome中为chrome-extension://),所以页面的JavaScript可以访问WebExtensions API。弹出页面没有限制导航到其他URL,因此点击链接到popup.js将成功将弹出页面导航到新URL。

更高级的扩展通常会在弹出页面中构建多视图单页应用程序。这些扩展应使用清单中指定的HTML页面作为弹出页面,相当于网页的index.html入口点。

打开和关闭弹出页面

弹出页面旨在仅在用户明确操作后才显示。浏览器有意阻止您通过编程方式打开弹出页面,以防止扩展通过页面覆盖不断骚扰用户。因此,打开弹出页面主要有两种方式:

  • 当清单中定义了action.default_popup时,点击工具栏图标按钮。
  • 当定义了action.default_popup时,输入清单中commands._execute_action定义的快捷键。

注意:在扩展文档中提到了chrome.action.openPopup()方法,但在撰写本书时,该方法仍不受支持。即使最终支持该功能,也只能在符合条件的用户交互之后直接使用;因此,通过编程方式打开弹出页面实际上仍然是不被允许的。

一旦弹出页面打开,触发关闭的方式有很多种。从弹出页面的角度来看,关闭弹出页面与关闭一个标签页是相同的。以下操作将触发弹出页面关闭:

  • 当弹出页面已经打开时,再次点击工具栏图标按钮。
  • 点击打开的弹出窗口外部且在同一浏览器窗口内部。
  • 从弹出页面的JavaScript中调用window.close()
  • 使用Escape键。

重要的是要注意,点击浏览器窗口外部不会关闭弹出页面。此行为允许您在每个窗口打开一个弹出页面。这也意味着您必须假设用户可以同时打开多个弹出页面。

更改弹出页面

您可以通过WebExtensions API更改弹出页面。所做的更改将在浏览器会话中持续有效。下面的代码片段演示了如何更改弹出页面:

javascript
chrome.action.setPopup({
  popup: chrome.runtime.getURL("new-popup.html")
});

检测弹出页面状态

浏览器扩展会隐式跟踪由扩展控制的活动窗口对象。chrome.extension.getViews()方法提供了对此列表的访问权限,您可以提供一个类型来仅过滤弹出页面视图,如下所示:

javascript
// 返回窗口对象数组
chrome.extension.getViews({ type: "popup" });

如果您希望检测视图是否在弹出页面中呈现,可以对窗口对象执行简单的检查:

javascript
const popups = chrome.extension.getViews({ type: "popup" });
console.log(`Inside a popup: ${popups.includes(window)}`);

建议用法

在开发弹出页面时,应遵循以下策略:

  • 弹出页面最适合用于用户需要快速访问且不会丢失当前页面上下文的用户界面。
  • 由于某些浏览器会限制弹出页面的尺寸,一个经验法则是,界面应包含与手机应用大约相同的内容量。
  • 因为弹出页面会快速关闭并重新打开,所以弹出页面应高效渲染,并且不应依赖可能需要提前终止的长时间运行操作。
  • 当需要弹出页面保持状态或加载数据时,应积极采用缓存策略,以避免任何用户体验上的延迟。
  • 在需要更多屏幕空间的情况下,弹出页面可以提供链接或按钮,以在新的浏览器标签页中打开选项页面或其他视图。

选项页面

选项页面是一种网页,它以两种方式之一呈现:作为独立标签页,或在浏览器扩展管理器页面上的模态容器中(图8-3和8-4)。它完全由浏览器扩展控制。 8-38-4

选项页面属性

当在标签页中打开时,选项页面就像任何其他网页一样。当以模态方式打开时,选项模态容器可以被视为一个没有任何浏览器边框的简单浏览器标签页:没有URL栏,没有浏览器控件等。以下示例扩展演示了选项页面的一些基本属性:

示例8-2a. manifest.json

json
{
  "name": "MVX",
  "version": "0.0.1",
  "manifest_version": 3,
  "action": {
    "default_popup": "popup.html"
  },
  "options_ui": {
    "open_in_tab": false,
    "page": "options.html"
  }
}

示例8-2b. popup.html

html
<!DOCTYPE html>
<html>
   <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <h1>弹出窗口</h1>
    <button id="opts">选项</button>
    <a href="options.html" target="_blank">options.html</a>
    <script src="popup.js"></script>
  </body>
</html>

示例8-2c. popup.js

javascript
document.querySelector("#opts").addEventListener(
  "click",
  () => chrome.runtime.openOptionsPage());

示例8-2d. options.html

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <h1>选项</h1>
    <a href="options.js">options.js</a>
  </body>
</html>

示例8-2e. options.js

javascript
console.log("选项页面加载!");

在manifest.json中,通过options_ui.page值指定选项HTML文件的路径来声明选项页面。选项页面的默认呈现方式有两种:

  • 如果options_ui.open_in_tab为false,选项页面默认将在浏览器标签页中打开。
  • 如果options_ui.open_in_tab为true,选项页面默认将在浏览器扩展管理器页面上的模态中打开。

如果以模态方式打开,选项容器将调整为适合选项页面内容的大小。选项页面可以指定body尺寸以根据需要增大或缩小。

因为选项页面是通过浏览器的扩展协议提供的——在Google Chrome中是chrome-extension://——所以页面的JavaScript可以访问WebExtensions API。在标签页和模态模式下,选项页面都不受限制地可以导航到其他URL,因此点击链接到options.js将成功导航选项页面到新URL。

更高级的扩展通常会在选项页面内构建多视图单页应用程序。这些扩展应使用manifest中指定的HTML页面作为选项,相当于网页的index.html入口点。

打开和关闭选项页面

有几种方法可以打开选项页面,并且与弹出窗口不同,选项页面可以通过编程方式打开。以下操作将打开选项页面,并且始终遵循options_ui.open_in_tab的值:

  • 右键点击工具栏图标按钮并选择“选项”
  • 调用chrome.runtime.openOptionsPage()

当选项页面在标签页中打开时,它像标准网页一样关闭。当以模态方式打开时,以下操作将触发选项页面的关闭:

  • 点击选项页面模态内的X按钮
  • 从选项页面的JavaScript中调用window.close()
  • 在查看选项模态时使用Escape键
  • 关闭扩展管理器标签页

注意,“选项页面”这一称呼主要指的是浏览器通过上下文菜单项或WebExtensions API方法访问页面的能力。一旦作为标签页打开,选项页面与通过扩展协议URL打开的任何其他扩展HTML文件没有区别。

检测选项页面

当选项页面作为标签页打开时,它们被视为任何其他标签页,因此可以通过chrome.extension.getViews()chrome.tabs.query()进行检测。然而,当以模态方式打开时,选项页面会有以下限制:

  • chrome.tabs.query()不会返回选项页面。
  • 当模态窗口打开或发生变化时,chrome.tabs.onCreatedchrome.tabs.onUpdated事件不会触发。
  • 无法通过chrome.tabs.connect()chrome.tabs.sendMessage()向模态选项页面发送消息。
  • chrome.extension.getViews({ type: "tab" })不会返回模态选项窗口。注意,chrome.extension.getViews()仍然会包含模态选项窗口。

建议用法

在开发选项页面时,您应遵循以下策略:

  • 选项页面最适合用于包含控制扩展行为控件的用户界面。
  • 选项页面在内容上没有限制。然而,由于访问它的一个非常常见的路径是右键点击工具栏图标并选择“选项”,因此选项页面可能应该类似于一个用于控制扩展选项的页面。
  • 使用扩展页面的模态版本没有太多优势。用户可能不熟悉扩展管理界面,因此在上面显示模态视图可能会让人困惑。我建议坚持使用标签页版本。

内容脚本限制

需要注意的一个棘手且重要的行为是,禁止以编程方式访问选项页面(以及任何其他扩展URL)。考虑以下示例扩展,它有一个弹出页面,其中包含四个按钮,每个按钮都尝试以不同的方式打开选项页面:

示例8-3a. manifest.json

json
{
  "name": "MVX",
  "version": "0.0.1",
  "manifest_version": 3,
  "action": {
    "default_popup": "popup.html"
  },
  "options_ui": {
    "open_in_tab": true,
    "page": "options.html"
  },
  "permissions": ["scripting"],
  "host_permissions": ["<all_urls>"]
}

示例8-3b. popup.html

html
<!DOCTYPE html>
<html>
  <body>
    <h1>Popup</h1>
    <button id="popup-api">Popup API</button>
    <button id="popup-url">Popup URL</button>
    <button id="cs-api">Content Script API</button>
    <button id="cs-url">Content Script URL</button>
    <script src="popup.js"></script>
  </body>
</html>

示例8-3c. popup.js

javascript
function openOptionsWithUrl() {
  window.open(chrome.runtime.getURL("options.html"));
}

function openOptionsWithApi() {
  chrome.runtime.openOptionsPage();
}

async function sendFnToActiveTab(fn) {
  let [tab] = await chrome.tabs.query({
    active: true,
    currentWindow: true,
  });
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    function: fn,
  });
}

document.querySelector("#popup-api").addEventListener(
  "click",
  () => openOptionsWithApi()
);

document.querySelector("#popup-url").addEventListener(
  "click",
  () => openOptionsWithUrl()
);

document.querySelector("#cs-api").addEventListener(
  "click",
  () => sendFnToActiveTab(openOptionsWithApi)
);

document.querySelector("#cs-url").addEventListener(
  "click",
  () => sendFnToActiveTab(openOptionsWithUrl)
);

示例8-3d. options.html

html
<!DOCTYPE html>
<html>
  <body>
    <h1>Options</h1>
  </body>
</html>

注意,内容脚本按钮必须与有效的活动标签页(如https://blank.org)一起使用。

弹出页面的四个按钮分别提供了弹出源或内容脚本源以及API打开或URL打开的四种组合之一。最初,这里的一切都应该井然有序。当您点击两个弹出按钮时,选项页面将正确打开,没有任何问题。

然而,在点击每个内容脚本按钮后,选项页面将无法正确打开,并且您将看到图8-5和8-6中显示的错误。 8-58-6 浏览器阻止内容脚本以编程方式打开选项页面。在内容脚本中,API 方法被简单地移除,并且阻止扩展URL访问扩展的文件服务器。

这样做的原因可能与安全性有关。内容脚本滥用其能力将用户踢到新标签页的潜在风险太大,因此内容脚本被限制执行此类操作。

总结

在本章中,您了解了浏览器扩展的两个主要用户界面构建模块:弹出页面和选项页面。本章详细介绍了它们在浏览器中的运作方式、如何融入用户流程,以及使用它们的优点和缺点。

在下一章中,您将学习内容脚本如何补充和增强弹出页面和选项页面,以及如何启用一个全新的扩展领域来构建用户界面。。