Skip to content
本页目录

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 功能集:

json
// 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

json
//v3
"background": {
    "service_worker":"background/background.js",  //错误路径
    "service_worker":"background.js",  //正确路径
    "type": "module"
  },
json
// 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编写代码。只能注册事件侦听器,不能持续运行。 举个例子:

text
// 错误用法
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 中,您需要将主机权限和可选的主机权限与其他权限分开指定。

json
// 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脚本包装到一个错误捕捉服务工作者中,并在其中导入您的旧脚本:

json
// 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种类可访问的APIDOM访问情况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 所有配置项

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

javascript
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

javascript
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

javascript
// 无用,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

javascript
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

javascript
// 获取当前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

javascript
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储存

javascript
// 初始化设置
//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);
});

插件监听被安装事件

javascript
// 在 onInstalled 监听器内部,扩展使用 storage API 设置一个值。这将允许多个扩展组件访问该值并进行更新。
// 大部分 API,包括 storage api,必须被注册在 manifest 的 permissions 字段中给插件使用。
chrome.runtime.onInstalled.addListener(() => {
	console.log("插件已安装");
	// 设置主题类型
	// setThemeType("light");
});

设置插件的本地数据

javascript
chrome.storage.local.set({
	"demo": "demo 数据",
	"env": "dev"
}, function() {
	console.log("chrome extension is install.");
});

清除插件所有的本地数据

javascript
 chrome.storage.local.clear();

添加图标点击事件监听

javascript
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页面

javascript
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

javascript
//代码如下
chrome.action.setIcon({
	path: 'assets/images/icon_default.png', //错误案例
	path: '/assets/images/icon_default.png', //您可以尝试添加前导/,例如"/icon16.png"
	tabId: tabs[0].id
})


web可访问资源

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

json
// Manifest V2
"web_accessible_resources": [RESOURCE_PATHS]
json
// 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变化:不再执行任意的字符串,仅支持脚本文件和函数

javascript
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内容,需要注意的是 两者只会生效一个

入参注意点
setExpressionstring -> 会将表达式以js方式嵌入需要将方法写成字符串形式,window对象指向当前页面
setPagestring -> url专门写一个html给siderbar显示window对象指向特定html,无法获取页面window与dom
javascript
// [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 即可创建一个自定义的面板

javascript
chrome.devtools.panels.create(
  // 扩展面板显示名称
  "DevPanel",
  // 扩展面板icon,并不展示
  "panel.png",
  // 扩展面板页面
  "index.html",
  function (panel) {
    console.log("自定义面板创建成功!");
  }
);

像 Vue Devtools 和 React Devtools 都是这种形式,其视图本质上就是一个 Web 页面

背景页面使用存储 API 持久化状态

  1. 背景页面将不会存任何数据
    1. 使用chrome.storage.local.set({ name }); 来存数据
  2. setTimeout,setInterval定时器 可能会失败
    1. 使用alarms警报侦听器,它应在脚本的顶层注册。
javascript
// 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