V3官方文档
https://blog.csdn.net/lxm1353723767/article/details/127706101
功能总结
Manifest V3 从 Chrome 88 版本开始可用。 使用 Manifest V3 的扩展有许多新特性和功能变化:
- Service Worker替换背景页面。
- 现在使用新的declarativeNetRequest API处理网络请求修改。
- 相比于之前拦截一个请求和程序式地修改它,新 API 委托 Chrome 浏览器自身来计算和修改请求
- 你需要声明一系列的规则: 匹配的请求模式和匹配时要执行的操作。然后,浏览器按照这些定义的规则修改网络请求
- 在V3 中 webRequest API的阻塞方式被限制为强制安装的扩展使用。
- 不再允许远程托管代码;扩展只能执行包含在其包中的 JavaScript。
Promise支持已添加到许多方法中
,但仍支持回调作为替代方法。(我们最终将支持对所有适当方法的承诺。)- Manifest V3 中还引入了许多其他相对较小的功能更改。
- Action API 统一(browser_action 和 page_action 统一到一个单独的 action API:)
远程托管代码
不再支持加载远程托管的代码主要出于两个原因:
安全因素,远程代码总是有不安全因素存在 Chrome 在审核提交的插件时更可靠,更高效,不需要再去关注远程代码,只需要审核包内的代码即可。
下面两种都被认为是远程托管代码:
- 从服务器获取的 javaScript 文件
- 在运行时阶段传入 eval() 的代码字符串
如果真的有需要,我们仍然有其他可选方案可用:
- 配置驱动的功能和逻辑—在运行时加载远程配置(例如 JSON 文件),并在本地缓存。扩展然后使用这个缓存的配置来决定开启哪些功能和逻辑。
- 使用远程服务器,逻辑外部化—将某些程序逻辑迁移到远程web服务器。
- 自己写一个eval()方法改个名字,或者找第三方包
executeScript() 变化:不再执行任意的字符串,仅支持脚本文件和函数
还有一些马上即将加入的新特性:
- 动态 content Scripts
- 新的 favicon API
- 内存存储(in-memory storage)
网络请求修改
V3 提供了一个新的 API declarativeNetRequest 来用于修改和阻塞网络请求。这种方式隐私保护更好,性能更加。该 API 的本质特征是:
相比于之前拦截一个请求和程序式地修改它,新 API 委托 Chrome 浏览器自身来计算和修改请求 你需要声明一系列的规则: 匹配的请求模式和匹配时要执行的操作。然后,浏览器按照这些定义的规则修改网络请求
在V3 中 webRequest API的阻塞方式被限制为强制安装的扩展使用。 网络请求的修改我们这里不详细展开讨论,如果开发中有需要可以查看相应的 API 文档。
Promises
V3 现在原生支持 Promise。许多常用 API 现在都支出,最终所有合适的 API 都会支持 Promise。
如果使用 callback,就不会返回 Promise,优先执行 callback。
更新 manifest.json 文件
1.清单版本
更改"manifest_version"元素的值是升级扩展的关键。这决定了您使用的是 Manifest V2 还是 Manifest V3 功能集:
// Manifest V2
{
"manifest_version": 2
}
// Manifest V3
{
"manifest_version": 3
}
background.js
Manifest V3,用单个扩展服务工作者替换后台页面。"background"在字段下注册 service worker ,它使用"service_worker"指定单个 JavaScript 文件的键。
Manifest V3 不支持多个后台脚本,只能service_worker指定一个
文档,但您可以选择通过指定将服务工作者声明为ES 模块" type": "module",这允许您导入更多代码。 使用importScripts
background.js 必须放在项目根目录下,不允许嵌套在文件夹内(这是一个bug) (在 Chrome 93 之前,service worker 文件必须 在 manifest.json 所在的根路径下。)
这是 Service Worker 规范的限制,自 Chrome 93 以后的版本放宽了这个限制。放在文件夹内也没有问题
如果想把background放入文件夹内,则必须添加
"minimum_chrome_version": "93",
指定浏览器最低版本为93
//v3
"background": {
"service_worker":"background/background.js", //错误路径
"service_worker":"background.js", //正确路径
"type": "module"
},
// Manifest V2
{
"background": {
"scripts": [
"backgroundContextMenus.js",
"backgroundOauth.js"
],
"persistent": false
},
}
// Manifest V3
{
"background": {
"service_worker": "background.js",
"type": "module" //optional
}
}
Manifest V2 中的后台页面被 Manifest V3 中的service workers替换;这是一个影响大多数扩展的基础性变化。以下是一些显着差异:
MV2 - 背景页面 | MV3 - 服务工作者 |
---|---|
可以使用持久页面。 | 不使用时终止。 |
可以访问 DOM。 | 无权访问 DOM。 |
可以用XMLHttpRequest()。 | 必须fetch()用来提出请求。 |
使用Service worker编写代码。只能注册事件侦听器,不能持续运行。 举个例子:
// 错误用法
const count = 1
chrome.runtime.onMessage.addListener(({ type, name }) => {
if (type === "add-count") {
count++;
}
});
// 迁移方案
chrome.runtime.onMessage.addListener(({ type, name }) => {
const count = await chrome.storage.local.get(['count']);
if (type === "add-count") {
count++;
chrome.storage.local.set({'count': count})
}
});
权限
在 Manifest V3 中,您需要将主机权限和可选的主机权限与其他权限分开指定。
// 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/",
],
"optional_host_permissions": [
"*://*/*",
]
}
background.js 正确引用
那么您的 background.js 文件中可能有错误。然而,在这篇文章发布之时,除了 generic 之外,没有足够的错误消息Service worker registration failed。尝试按照此处描述的 Simeon 解决方法进行操作 例如,您可以将 v2background.js脚本包装到一个错误捕捉服务工作者中,并在其中导入您的旧脚本:
// manifest.json
{
"name": "Throw on Register!",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background-wrapper.js"
}
}
// background-wrapper.js file
try {
importScripts("background.js");
} catch (e) {
console.error(e);
}
// background.js
console.log("start");
throw new Error("lol");
console.log("end");
JS种类 | 可访问的API | DOM访问情况 | JS访问情况 | 直接跨域 |
---|---|---|---|---|
injected script | 和普通JS无任何差别,不能访问任何扩展API | 可以访问 | 可以访问 | 不可以 |
content script | 只能访问 extension、runtime等部分API | 可以访问 | 不可以 | 不可以 |
popup js | 可访问绝大部分API,除了devtools系列 | 不可直接访问 | 不可以 | 可以 |
background js | 可访问绝大部分API,除了devtools系列 | 不可直接访问 | 不可以 | 可以 |
devtools js | 只能访问 devtools、extension、runtime等部分API | 可以访问devtools | 可以访问devtools | 不可以 |
manifest.json 所有配置项
{
// Required - 通俗易懂
"manifest_version": 3,
"name": "配置项例子",
"version": "你的项目版本",
// 『重点』action配置项主要用于点击图标弹出框,对于弹出框接受的是html文件
"action": {
"default_title": "Click to view a popup",
"default_popup": "popup.html"
}
// 通俗易懂
"default_locale": "en",
"description": "A plain text description",
"icons": {
...
},
"author": ...,
// 『重点』下面将出现的background.js 配置service work
"background": {
// Required
"service_worker": "service-worker.js"
},
// 『重点』下面将出现content_script.js 应用于所有页面上下文的js
"content_scripts": [
{
"matches": [
"https://*.nytimes.com/*"
],
"css": [
"my-styles.css"
],
"js": [
"content-script.js"
],
"run_at": "document_idle"
//"document_idle" | "document_start" | "document_end" 三种可选
}
],
// 使用/添加devtools中的功能
"devtools_page": "devtools.html",
/**
* 三个permission
* host_permissions - 允许使用扩展的域名
* permissions - 包含已知字符串列表中的项目 【只需一次弹框要求允许】
* optional_permissions - 与常规类似permissions,但由扩展的用户在运行时授予,而不是提前授予【安全】
* 列出常见选项
* {
* activeTab: 当扩展卡选项被改变需要重新获取新的权限
* tabs: 操作选项卡api(改变位置等)
* downloads: 访问chrome.downloads API 的权限 便于下载但还是会受到跨域影响
* history: history api权限
* storage: 访问localstorage/sessionStorage权限
* }
*/
"host_permissions": [
"http://*/*",
"https://*/*"
],
"permissions": [
"tabs"
],
"optional_permissions": [
"downloads"
],
// 内部弹出可选页面 - 见fehelper操作页
"options_page": "options.html",
"options_ui": {
"chrome_style": true,
"page": "options.html"
}
}
插件之间的通讯
content script和service worker
和 content script 有关的通信
使用 chrome.runtime.sendMessage 发送信息 使用 官网的~~chrome.runtime.onMessage.addListener ~~ 方法会出现问题,使用 chrome.tabs.sendMessage接收监听信息
content script
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
// 可写成switch形式 监听所有
if (sender === "") {
// do something
}
if (request.from === "cc") {
// from 不是固定词,可使用其他自定义词汇
// do something
}
console.log(request.number);
sendResponse({ number: request.number });
// 修改dom
document.querySelector("#s-usersetting-top").innerText = request.number;
// 发送信息
chrome.runtime.sendMessage({ number: request.number + 1 }, (response) => {
console.log(
`content script -> background infos have been received. number: ${response.number}`
);
});
});
chrome.runtime.sendMessage({ number: 1 }, (response) => {
console.log(
`content script -> background infos have been received. number: ${response.number}`
);
});
service-worker.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
console.log(request);
// 可写成switch形式 监听所有
// if (sender === "") {
// do something
// }
if (request.from === "cc") {
// from 不是固定词,可使用其他自定义词汇
// do something
console.log("haha");
}
// 不能使用这种方式 使用下面tabs的方式,详见最下面常见问题
// chrome.runtime.sendMessage({number: request.number + 1}, (response) => {
// console.log(
// `background -> content script infos have been received. number: ${response.number}`
// );
// });
// 通过 tabs 发送消息改变
// tabs api,必须被注册在 manifest 的 permissions 字段中给插件使用,这里不然获取不到 url。
//"permissions": ["tabs"]
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.sendMessage(
tabs[0].id,
{ number: request.number + 1 },
function(response) {
console.log(
`background -> content script infos have been received. number: ${response.number}`
);
}
);
});
// 消息回传
sendResponse({ number: request.number });
});
popup和service worker
当只有一对一关系时,可使用万能的chrome.runtime.sendMessage & chrome.runtime.sendMessage popup
// 无用,https://stackoverflow.com/questions/74224782/background-page-runs-but-popup-says-you-do-not-have-a-background-page-when-u
//chrome.extension.getBackgroundPage() 因背景页面无法访问到DOM,此API返回未定义
// const background = chrome.extension.getBackgroundPage();
// console.log(background);
document.querySelector("#button").addEventListener("click", () => {
const val1 = document.querySelector("#input1").value || "0";
const val2 = document.querySelector("#input2").value || "0";
chrome.runtime.sendMessage({ val1, val2 }, (response) => {
document.querySelector("#ans").innerHTML = response.res;
});
});
service worker
const dealwithBigNumber = (val1, val2) => BigInt(val1) * BigInt(val2) + "";
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const { val1, val2 } = request;
sendResponse({ res: dealwithBigNumber(val1, val2) });
});
// 如果manifest.json未配置 action.default_popup,点击扩展按钮会触发此事件
chrome.action.onClicked.addListener(() => {
console.log('点击右上角插件图标');
});
popup&content script
// 获取当前tab标签
const getCurrentTab = async() => {
let queryOptions = { active: true, currentWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
};
$("#background").paigusu({ color: "#1926dc" }, async(event, obj) => {
$("#info").innerHTML = "修改中";
$("#show").css("background", "#" + obj.hex);
const tab = await getCurrentTab();
await chrome.tabs.sendMessage(tab.id, { color: "#" + obj.hex });
$("#info").innerHTML = "修改成功";
});
content script
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log(request)
$('body').css('background', request.color)
});
devtools与content script通讯
同1 content script与service worker/popup的通信
1对多通信 使用chrome.tabs去找对应页面
devtools与popup通讯
同2 content script与service worker/popup的通信
1对1通信 使用chrome.runtime 一把嗦
操作storage储存
// 初始化设置
//chrome.storage.local.get 获取
//chrome.storage.local.set 设置
// storage api,必须被注册在 manifest 的 permissions 字段中给插件使用
// "permissions": ["storage"]
chrome.storage.local.get(['theme'], res => {
let { theme } = res;
console.log("初始化设置 theme--->", theme);
switchThemes(theme);
});
插件监听被安装事件
// 在 onInstalled 监听器内部,扩展使用 storage API 设置一个值。这将允许多个扩展组件访问该值并进行更新。
// 大部分 API,包括 storage api,必须被注册在 manifest 的 permissions 字段中给插件使用。
chrome.runtime.onInstalled.addListener(() => {
console.log("插件已安装");
// 设置主题类型
// setThemeType("light");
});
设置插件的本地数据
chrome.storage.local.set({
"demo": "demo 数据",
"env": "dev"
}, function() {
console.log("chrome extension is install.");
});
清除插件所有的本地数据
chrome.storage.local.clear();
添加图标点击事件监听
chrome.action.onClicked.addListener(() => {
console.log('1、点击了流沙插件图标');
// 获取主题类型
chrome.storage.local.get(["theme"], res => {
console.log("2、缓存的theme", res);
let { theme } = res;
// 修改初始值
theme = theme === "light" ? "dark" : "light";
console.log("3、切换 theme 为:", theme);
// 设置图标
chrome.action.setIcon({
path: "icons/popup_" + theme + "_32.png"
});
// 设置title
chrome.action.setTitle({
title: theme === "light" ? "流沙:明亮模式" : "流沙:暗黑模式"
});
const html = `popups/abc.html`
chrome.action.setPopup({
popup: html,
tabId: tabs[0].id
})
});
})
设置Popup页面
const html = `popups/abc.html`
chrome.action.setPopup({
popup: html,
tabId: tabs[0].id
})
网络请求
必须fetch()用来提出请求。 应为服务工作者无权访问 DOM。则无法访问XMLHttpRequest,而是支持更现代化fetch()。URL.createObjectURL服务人员也不支持,因为它可能会造成内存泄漏。
修改Icon时候报错
使用修改Icon时候报错 Uncaught (in promise) Error: Failed to set icon 'assets/images/icon_default.png': Failed to fetch
//代码如下
chrome.action.setIcon({
path: 'assets/images/icon_default.png', //错误案例
path: '/assets/images/icon_default.png', //您可以尝试添加前导/,例如"/icon16.png"
tabId: tabs[0].id
})
web可访问资源
此更改将扩展资源的访问限制在特定的站点/扩展。 现在提供的不再是文件列表,而是对象列表,每个对象都可以映射到一组资源到一组url或扩展id:
// Manifest V2
"web_accessible_resources": [RESOURCE_PATHS]
// Manifest V3
//web_accessible_resources的值是个数组,说明可以有多个规则
"web_accessible_resources": [
{
"resources": ["assets/*"], //你插件项目的那些资源可以被访问
"matches": ["https://*.aliexpress.com/*"], //那些域名可以访问
"extension_ids": [EXTENSION_IDS], //通过其他插件的ID,决定是否可以访问
"use_dynamic_url": boolean //如果为 true,则只允许通过动态 ID 访问资源。每个会话都会生成一个动态 ID。这意味着它会在浏览器重新启动或扩展程序重新加载时重新生成。
}
]
//matches 字段提供的模式字符串,后面必须包含*号,不指定星号会报错,无法加载 manifest。
对于获取了权限的content_script通过代码执行注入
executeScript() V3变化:不再执行任意的字符串,仅支持脚本文件和函数
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['content-script.js']
});
});
DevTools
DevTools 扩展的结构与任何其他扩展一样:它可以有一个背景页面、内容脚本和其他项目。此外,每个 DevTools 扩展都有一个 DevTools 页面,可以访问 DevTools API。
每次打开 DevTools 窗口时,都会创建一个扩展的 DevTools 页面实例。DevTools 页面在 DevTools 窗口的整个生命周期内都存在。DevTools 页面可以访问 DevTools API 和一组有限的扩展 API。
- 能嵌入我们自定义的页面(html/表达式)到devtools中的 elements & sources两个面板中
- 依靠的是 chrome.devtools.panels 下的sources.createSidebarPane / elements.createSidebarPane。能分别在 sources和elements标签页下新建siderbar
有两种方式嵌入siderbar内容,需要注意的是 两者只会生效一个
入参 | 注意点 | |
---|---|---|
setExpression | string -> 会将表达式以js方式嵌入 | 需要将方法写成字符串形式,window对象指向当前页面 |
setPage | string -> url专门写一个html给siderbar显示 | window对象指向特定html,无法获取页面window与dom |
// [devtools].js
chrome.devtools.panels.elements.createSidebarPane(
"element pannel",
(sidebar) => {
sidebar.setExpression("(() => {return {a:1}})()");
}
);
chrome.devtools.panels.sources.createSidebarPane(
"sources pannel",
(sidebar) => {
sidebar.setPage("sources.html");
}
);
Devtools 也是插件的一种形式,不同于 Popup,它有一组 chrome.devtools 独有的 API,当我们调用 chrome.devtools.panels.create 即可创建一个自定义的面板
chrome.devtools.panels.create(
// 扩展面板显示名称
"DevPanel",
// 扩展面板icon,并不展示
"panel.png",
// 扩展面板页面
"index.html",
function (panel) {
console.log("自定义面板创建成功!");
}
);
像 Vue Devtools 和 React Devtools 都是这种形式,其视图本质上就是一个 Web 页面
背景页面使用存储 API 持久化状态
- 背景页面将不会存任何数据
- 使用chrome.storage.local.set({ name }); 来存数据
- setTimeout,setInterval定时器 可能会失败
- 使用alarms警报侦听器,它应在脚本的顶层注册。
// background.js
chrome.alarms.create({ delayInMinutes: 3 });
chrome.alarms.onAlarm.addListener(() => {
chrome.action.setIcon({
path: getRandomIconPath(),
});
});
弃用的 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
以及那些没明说的:
- chrome.extension.sendMessage()
- chrome.extension.connect()
- chrome.extension.onConnect
- chrome.extension.onMessage
查阅官方文档时,那些标签也能帮助到我们。
Promise 标签:支持 Promise
<=MV2 标签:该API仅在V2前支持
>=MV3标签:该API在V3后支持
Deprecated标签:已废弃的 API