一、概念
Extensions are software programs, built on web technologies (such as HTML, CSS, and JavaScript) that enable users to customize the Chrome browsing experience.
扩展程序是基于 Web 技术(例如 HTML、CSS 和 JavaScript)构建的软件程序,可让用户自定义 Chrome 浏览体验。
我们所说的chrome插件一般都是指chrome扩展程序(Chrome Extension),它通过向 Chrome 浏览器添加特性和功能来增强浏览体验。它其实就是一个由HTML、CSS、JS、图片等资源组成的一个.crx后缀的文件。它可以向页面中注入 javascript 脚本,对页面进行处理,比如屏蔽页面中可能的广告元素,改变某些元素的样式,增加一些 UI等。
chrome插件除了Chrome浏览器之外,还可以运行在所有webkit内核的浏览器,比如360极速浏览器、360安全浏览器、搜狗浏览器、QQ浏览器等等。丰富的 chrome 插件可以扩展chorme的能力,极大的提升我们的工作效率。
二、插件构成
chrome 插件通常由以下几部分组成:
-
manifest.json
:Manifest文件是一个插件的元数据,它告诉Chrome插件的名称、描述、版本、权限以及其他插件需要的属性(类似package.json
的存在)。 -
background script
:是扩展的事件处理程序; 它包含对扩展很重要的浏览器事件的侦听器。它处于休眠状态,直到触发事件,然后执行指示的逻辑。有效的后台脚本仅在需要时加载,并在空闲时卸载。可以调用全部的 chrome API,实现跨域请求、网页截屏、弹出 chrome 通知消息等功能。相当于在一个隐藏的浏览器页面内默默运行。 -
content script
:也被称为 injected script,是插件注入到页面的脚本。content script 可以操作 DOM,但是它和页面其他的脚本是隔离的,访问不到其他脚本定义的变量、函数等,相当于运行在单独的沙盒里。content script 可以调用有限的 chrome 插件 API,网络请求受到同源策略限制,所以一般网络请求都交给 background script 处理。 -
action
:包括点击插件图标弹出的页面(简称 popup)、运行于弹窗的html显示 & js脚本。 -
options_page
: 插件的配置页面(简称 options), 正如扩展程序允许用户自定义 Chrome 浏览器一样,选项页面支持扩展程序的自定义。选项可用于启用功能并允许用户选择与其需求相关的功能。
三、插件生命周期与API
扩展可以使用浏览器提供的所有JavaScript API 。扩展程序比网络应用程序更强大的原因在于它们可以访问Chrome API
,从而可以拥有更多的功能,比如更改网站的功能或行为、允许用户跨网站收集和组织信息 、向 Chrome 开发者工具添加功能等。
生命周期
插件的生命周期是指从用户安装或更新插件,到用户卸载插件的过程。在这个过程中,插件可以响应各种浏览器或用户事件,执行相应的操作。
// background.js
// 安装或更新:用户第一次安装插件,或者插件有新的版本可供更新时,浏览器会加载并初始化插件
chrome.runtime.onInstalled.addListener(function () {});
// 启动:用户打开浏览器时,插件会被启动。插件可以在这个阶段初始化数据,设置默认状态等
chrome.runtime.onStartup.addListener(function() {});
// 运行:插件被启动后,就进入了运行阶段。在这个阶段,插件可以响应用户操作,监听和处理浏览器事件,提供各种功能。
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {});
// 停止:用户关闭浏览器时,插件会被停止。插件可以监听 onSuspend 事件,保存数据,清理资源等。
chrome.runtime.onSuspend.addListener(function() {});
// 卸载:用户从浏览器中卸载插件时,插件的生命周期就结束了。插件可以监听 onInstalled 事件的uninstall原因,执行卸载操作。
chrome.runtime.setUninstallURL("https://your_website.com/uninstall", function() {});
- content脚本会运行多份,每新开一个tab都会运行一份content脚本,生命周期也和当前的web站点一致,也就是说,如果刷新当前站点,那么content也会重新执行一次。
-
background脚本是全局唯一的,每次安装时或者重新加载时都会执行一次。
-
popup脚本,会在每次点击时弹出悬浮框时执行一次。
Chrome API
-
chrome.tabs
API 允许插件操作浏览器的标签页,例如创建新的标签页,关闭标签页,切换标签页,修改标签页的URL等。以下是一个创建新标签页的示例:
chrome.tabs.create({url: "http://www.example.com"});
chrome.bookmarks
API 允许插件操作用户的书签,例如创建书签,删除书签,搜索书签等。以下是一个创建书签的示例:
chrome.bookmarks.create({
'parentId': '1',
'title': 'Extension bookmarks',
'url': 'http://www.example.com'
});
chrome.storage
用于在插件中存储和读取数据。chrome.storage.sync
来保存和读取数据。该 API 提供类似 localStorage 的功能,但也有一些不同:
// 保存数据
chrome.storage.sync.set({ key: value }, function () {
console.log("Data saved.");
});
// 读取数据
chrome.storage.sync.get("key", function (result) {
console.log("Data retrieved: ", result.key);
});
- 数据会与用户的Chrome账号关联,可在不同设备间同步。
-
批量的读取和写入数据操作是异步执行的,因此与 localStorage 引起的阻塞和串行相比操作更快
-
存储的数据类型可以是对象,而 localStorage 只允许存储字符串
插件权限声明
插件需要的权限需要在 manifest.json
文件中的 "permissions"
部分进行声明。例如,如果一个插件需要访问用户的浏览历史,那么它需要添加 "history"
权限:
host_permissions
是一个用于声明扩展程序所需访问的主机权限的配置项。通过使用 host_permissions,扩展程序可以向浏览器声明它需要访问特定网站的权限。
{
"name": "My extension",
...
"permissions": [
"history",
"tabs",
"storage",
"alarms",
"webRequest",
],
"host_permissions": ["https://*"]
...
}
权限的种类很多,不同的权限对应插件可以访问的API和资源。更多权限可以在 Chrome 扩展官方文档 中查询。
四、通信
通信的本质其实就是不同模块之间如何传递消息,从而更好的进行协作和沟通。由于内容脚本在网页而不是扩展的上下文中运行,因此它们通常需要某种方式与扩展的其余部分进行通信。所以只有知道popup、content、background之间如何传递消息,我们才能更快速的开发插件。
简单的一次性请求(Simple one-time requests)
在插件中,我们通常需要在不同的脚本之间进行通信,例如在background脚本和content脚本之间,或者在popup脚本和background脚本之间。Chrome提供了chrome.runtime.sendMessage
和chrome.runtime.onMessage
API,用于在插件的不同组件之间发送和接收消息。
chrome.runtime.onMessage 的回调函数可以接收三个参数:message
、sender
和 sendResponse
。
-
message
参数是发送的消息对象,它包含了发送方传递的数据。可以是任何 JavaScript 对象,可以是简单的字符串或更复杂的数据结构。 -
sender
参数是消息的发送方信息,它是一个包含以下属性的对象:-
id
:发送方的扩展程序或脚本的唯一标识符。 -
url
(在 Chrome MV3 中已弃用):发送方的扩展程序或脚本的 URL。 -
tab
:发送方的扩展程序或脚本所在的标签页(Tab)对象。可以通过sender.tab.id
获取标签页的唯一标识符。
-
-
sendResponse
通过检查 sender
参数,您可以确定消息的来源,例如,如果扩展程序与内容脚本之间进行通信,您可以检查 sender.tab
属性来获取消息是来自哪个标签页。
// 发送消息
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
// 接收端
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(sender.tab "from a content script:" + sender.tab.url :"from the extension");
if (request.greeting === "hello")
sendResponse({farewell: "goodbye"});
// return true // Will respond asynchronously.
}
);
In the above example,
sendResponse()
was called synchronously. If you want to asynchronously usesendResponse()
, addreturn true;
to theonMessage
event handler.在上面的例子中,
sendResponse()
被同步调用。如果要异步使用sendResponse()
,请添加return true;
到onMessage
事件处理程序中。
长期连接(Long-lived connections)
有时,持续时间比单个请求和响应更长的对话更有用。在这种情况下,可以使用方法chrome.runtime.connect()
或方法 chrome.tabs.connect() 为内容脚本 content script 和扩展程序之间建立一个长连接(可以为信息通道 channel 设置名称,以区别多个通道)。
// 在页面脚本 content script 建立长连接的信息通道
let port = chrome.runtime.connect({ name: "knockknock" });
// 通过该端口发送信息
port.postMessage({ joke: "Knock knock" });
// 设置事件监听器,通过该端口接收信息,将接收到的信息作为入参
port.onMessage.addListener(function (msg) {
if (msg.question === "Who's there?") {
console.log("SZ")
};
});
信息通道是双向的,因此除了发起端创建端口,还需要在接收端使用方法 chrome.runtime.onConnect() 响应通道连接请求。当通道发起端口调用 connect 方法时,接收端的监听器就会调用回调函数,它将相应的 runtime.Port
端口对象作为入参,然后可以使用该端口在通道中发送和接收消息,这样通道两端的接口就可以相互接收和发送信息了。
chrome.runtime.onConnect.addListener(function (port) {
console.assert(port.name === "knockknock");
port.onMessage.addListener(function (msg) {
if (msg.joke === "Knock knock")
port.postMessage({ question: "Who's there?" });
});
});
跨扩展程序的通讯(Cross-extension messaging)
除了在扩展程序内进行信息传递,还可以使用类似的 messaging API 在不同扩展程序间进行通讯。
发送请求信息时,必须提供扩展程序的 ID,以便其他扩展程序判断是否作出响应。
侦听传入请求和连接与内部情况类似,只不过您使用runtime.onMessageExternal
或runtime.onConnectExternal
方法。
五、迁移到Manifest V3(Migrate to Manifest V3)
- 自2022年1月17日起,Chrome 应用商店已经停止接受新的 Manifest V2扩展。
- 2023年6月 Chrome 115开始,关闭对 Manifest V2扩展的支持。
- 2024年1月,Chrome 应用商店将删除所有的 Manifest V2扩展。
新增特性
-
使用 Service worker 替代了后台页面 background pages
后台脚本运行在 Service workers 环境中,其工作方式是基于监听-响应的模式的,它不会常驻后台可以提升性能,就是说MV2中的background scrips被成功注册后是一直运行着的,而MV3中的Service Worker 在不用时会被中止,并在下次有需要时重启;
但由于运行环境没有后台页面,因此无法使用与 DOM 相关的 API。无法使用方法
window.setTimeout()
和window.setInterval()
,可以使用 Alarms API 代替;不支持使用XMLHttpRequest,建议使用fetch()。
-
使用
chrome.declarativeNetRequest
代替chrome.webRequest
来拦截和修改web请求;由于WebRequest API的机制是当网络请求发起进,就会拦截,然后就可以进行各种分析处理,但是很容易被开发者滥用,导致性能受影响,并且给开发者的权限太大,很可能导致安全问题。
chrome.declarativeNetRequest
需要通过指定rules来实现请求的修改,浏览器会在匹配到符合规则的请求和操作时,按照rules中定义好的规则进行修改,程序中不再能直接查看请求的实际内容。 -
远程托管的代码(Remotely hosted code):MV3中不再支持非扩展包内的js代码的执行,包括从远程服务器获取的js文件,和程序运行时传给
eval
的代码字符串,因此所有代码都需要打包在程序包中。 -
MV3对权限声明进行了拆分,新增
host_permissions
字段单独声明host访问权限,其他权限的声明仍然在permissions
字段下。 -
部分API的删除和修改