Chrome Extension 扩展程序迁移至 Manifest V3

8,607 阅读6分钟

Manifest V3 从 Chrome 88 版本开始可用。

V3 新特性

  • Service workers 替换 background pages
  • 网络请求的修改现在使用新的API declarativeNetRequest 处理
  • 不再支持加载远程托管的代码(Javascript or Wasm)。扩展程序现在只能执行包内部的 Javascript。
  • 许多 API 方法现在原生支持 Promise,可选的 callback 回调方式依然支持

主要特性

Service workers

从 background pages 到 Service workders 是一种底层机制的改变。

同web service worker 一样,它监听并响应事件,也负责管理缓存、预加载资源和启用离线网页这些事情。但 extension service workders 更专注于对扩展程序 API 暴露的浏览器事件做出相应的响应。也就是说,现在对于浏览器事件响应更快更高效,用户体验更好。主要是在于性能上的提升。

网络请求修改

V3 提供了一个新的 API declarativeNetRequest 来用于修改和阻塞网络请求。这种方式隐私保护更好,性能更加。该 API 的本质特征是:

  • 相比于之前拦截一个请求和程序式地修改它,新 API 委托 Chrome 浏览器自身来计算和修改请求
  • 你需要声明一系列的规则: 匹配的请求模式和匹配时要执行的操作。然后,浏览器按照这些定义的规则修改网络请求

在V3 中 webRequest API的阻塞方式被限制为强制安装的扩展使用。

网络请求的修改我们这里不详细展开讨论,如果开发中有需要可以查看相应的 API 文档。

远程托管代码

不再支持加载远程托管的代码主要出于两个原因:

  • 安全因素,远程代码总是有不安全因素存在
  • Chrome 在审核提交的插件时更可靠,更高效,不需要再去关注远程代码,只需要审核包内的代码即可。

Promises

V3 现在原生支持 Promise。许多常用 API 现在都支出,最终所有合适的 API 都会支持 Promise。

如果使用 callback,就不会返回 Promise,优先执行 callback。

不管我们在开发中是否使用了 webextension-polyfill 库,callback 的方式仍然支持,但现在推荐使用 Promise 或 async/await 的方式。

其他特性

  • Action API 整合:Browser Action 和 page Action 被统一整合到一个单独的 Action API
  • web可访问资源:现在必须指定站点和插件
  • 内容安全策略(CSP)
  • executeScript() 变化:不再执行任意的字符串,仅支持脚本文件和函数

还有一些马上即将加入的新特性:

  • 动态 content Scripts
  • 新的 favicon API
  • 内存存储(in-memory storage)

更详细讲解请参考: Overview of Manifest V3 - Chrome Developers

迁移到 V3

更新 manifest.json 文件

首先就是更改 manifest_version 的值为 "3"

manifest 版本

// Manifest V2
"manifest_version": 2
// Manifest V3
"manifest_version": 3

更改 manifest_version 后,只要不符合 V3 要求的都会报错提示,注意查看。只要不报错,就符合 V3 规范。 extension-error.png

Host 权限

V3 将 permissions 拆分开来,host 相关的权限添加到 host_permissions 下:

// Manifest V2
"permissions": [
  "tabs",
  "bookmarks",
  "http://www.blogger.com/",
],
"optional_permissions": [
  "*://*/*",
  "unlimitedStorage"
]
// Manifest V3
"permissions": [
  "tabs",
  "bookmarks"
],
"optional_permissions": [
  "unlimitedStorage"
],
"host_permissions": [
  "http://www.blogger.com/",
  "*://*/*"
],

内容安全策略

V2 需要指定一个字符串,V3 是一个对象:

// Manifest V2

"content_security_policy": "..."
// Manifest V3

"content_security_policy": {
  "extension_pages": "...",
  "sandbox": "..."
}

Action API 统一

browser_actionpage_action 统一到一个单独的 action API:

// Manifest V2

// manifest.json
{
  "browser_action": { … },
  "page_action": { … }
}

// background.js
chrome.browserAction.onClicked.addListener(tab => { … });
chrome.pageAction.onClicked.addListener(tab => { … });
// Manifest V3

// manifest.json
{
  "action": { … }
}


// background.js
chrome.action.onClicked.addListener(tab => { … });

web可访问资源

 此更改将扩展资源的访问限制在特定的站点/扩展。 现在提供的不再是文件列表,而是对象列表,每个对象都可以映射到一组资源到一组url或扩展id:

// Manifest V2
"web_accessible_resources": [RESOURCE_PATHS]
// Manifest V3

"web_accessible_resources": [{
  "resources": [RESOURCE_PATHS],
  "matches": [MATCH_PATTERNS],
  "extension_ids": [EXTENSION_IDS],
  optional "use_dynamic_url": boolean
}]

matches 字段提供的模式字符串,后面必须包含*号,不指定星号会报错,无法加载 manifest。

更详细讲解请参考:Manifest - Web Accessible Resources - Chrome Developers

执行代码

如果在 V2 中执行了远程托管代码,注入代码字符串到页面或者在运行时阶段 eval() 代码字符串。你需要在V3中做相应更新。

// Manifest V2: 

chrome.tabs.executeScript()
chrome.tabs.insertCSS()
chrome.tabs.removeCSS()
// Manifest V3: 

chrome.scripting.executeScript()
chrome.scripting.insertCSS()
chrome.scripting.removeCSS()
远程托管代码

下面两种都被认为是远程托管代码:

  • 从服务器获取的 javaScript 文件
  • 在运行时阶段传入 eval() 的代码字符串

如果真的有需要,我们仍然有其他可选方案可用:

  • 配置驱动的功能和逻辑—在运行时加载远程配置(例如 JSON 文件),并在本地缓存。扩展然后使用这个缓存的配置来决定开启哪些功能和逻辑。
  • 使用远程服务器,逻辑外部化—将某些程序逻辑迁移到远程web服务器。
执行任意字符串

V2 允许执行任意的代码字符串在 tabs.executeScript() 选项对象的 code 属性上。V3 不再允许。

注入静态文件用法的更改:

// Manifest V2

// background.js
chrome.tabs.executeScript({
  file: 'content-script.js'
});

// content-script.js
alert('File test alert');
// Manifest V3

// background.js
async function getCurrentTab() {/* ... */}
let tab = await getCurrentTab();

chrome.scripting.executeScript({
  target: {tabId: tab.id},
  files: ['content-script.js']
});

// content-script.js
alert('File test alert');

func 属性和 args 属性用于注入一个函数。这个函数并不是在content script中运行的,它被发送到目标 tab 并在那里运行。

// Manifest V2

// background.js
let name = 'World!';
chrome.tabs.executeScript({
  code: `alert('Hello, ${name}!')`
});
// Manifest V3

// background.js
async function getCurrentTab() {/* ... */}
let tab = await getCurrentTab();

function showAlert(givenName) {
  alert(`Hello, ${givenName}`);
}

let name = 'World';
chrome.scripting.executeScript({
  target: {tabId: tab.id},
  func: showAlert,
  args: [name],
});

这里是其用法的一个官方例子:chrome-extensions-samples/popup.js at main · GoogleChrome/chrome-extensions-samples · GitHub

Background service workers

什么是 Service Worker?

一句话解释就是:service worker is a script that your browser runs in the background, separate from a web page, opening the door to features that don't need a web page or user interaction.

Service Worker 延展介绍: Service Worker

// MV2
"background": {
  "scripts": ["background.js"],
  "persistent": false
}
// MV3
"background": {
  "service_worker": "background.js"
}

迁移到新的 background 环境,有两个主要点需要记住:

  • service worker在不使用时被终止,在需要时重新启动(类似于event page,即 "persistent": false )。
  • service worker没有访问DOM的权限。

Service worker也带来了 background 使用上的一些变化,这里不详细展开,请查看官网文档:Migrating from background pages to service workers - Chrome Developers

修改网络请求

这里不展开介绍,有需要大家查阅官方文档:Migrating to Manifest V3 - Chrome Developers

弃用的 API

V3 将会移除这些弃用的 API,如果在 V2 项目中有使用这些 API,你需要做合适的更改。这些 API 包括:

  • chrome.extension.sendRequest()
  • chrome.extension.onRequest
  • chrome.extension.onRequestExternal
  • chrome.extension.lastError
  • chrome.extension.getURL()
  • chrome.extension.getExtensionTabs()
  • chrome.tabs.Tab.selected
  • chrome.tabs.sendRequest()
  • chrome.tabs.getSelected()
  • chrome.tabs.getAllInWindow()
  • chrome.tabs.onSelectionChanged
  • chrome.tabs.onActiveChanged
  • chrome.tabs.onHighlightChanged

As well as the undocumented:

  • chrome.extension.sendMessage()
  • chrome.extension.connect()
  • chrome.extension.onConnect
  • chrome.extension.onMessage

如何迁移V3时,替换这些废弃API?官网在 API 文档中会明确提示的。以 chrome.tabs.sendRequest() 为例:

api-example.png

可以看到已经提示你去使用 runtime.sendMessage()

查阅官方文档时,那些标签也能帮助到我们。

Promise 标签:支持 Promise

<=MV2 标签:该API仅在V2前支持

>=MV3标签:该API在V3后支持

Deprecated标签:已废弃的 API