快速学会Chrome扩展开发
早在18年有开源过这样一个项目,Chrome扩展项目,但目前Chrome扩展更新了一些新的语法这些项目实际已经过时。
庆幸的是Chrome扩展的开发并不复杂,里面一些内容依然存在一定的参考价值,那么如何用最快速的方法学习Chrome开发呢?
分享下我是如何在少量的时间学习更多的技术的,成品->源码->博客,是的,这个顺序和大多数人是相反的,也稍有难度。各个语言之间的相似性很高,是可以触类旁通的,且很多技术博客存在一定的滞后性,前沿的技术往往没有资料可以参考,这时候可以利用一些手段获取并进行学习,遇到不能揣摩的再去查阅资料。编程语言的区别就好比,韩语,日语,英语,中文的区别一样,语法类似,写法不同。
获取Google/Edge商店插件源码
Google商店有非常多优秀的扩展插件,但插件本身大多数是不开源的,不过在安装插件的时候,本地都有会一份缓存的源码。
- 浏览器访问:
edge://extensions/
edge://extensions/
- 复制某个插件的ID
比如:dbjbempljhcmhlfpfacalomonjpalpko
- 终端执行
find . -name 'dbjbempljhcmhlfpfacalomonjpalpko'
find . -name 'dbjbempljhcmhlfpfacalomonjpalpko'
- 找到路径
/Users/alien/Library/Application Support/Microsoft Edge Dev/Default/Extensions/dbjbempljhcmhlfpfacalomonjpalpko
同样的方法,可以找到其他浏览器扩展的位置:
/Users/alien/Library/Application Support/Microsoft Edge/Default/Extensions/bkdgflcldnnnapblkhphbgpggdiikppg
/Users/alien/Library/Application Support/360Chrome/Default/Extensions/ppobicepmejenmhimdajjhelpomokpof
/Users/alien/Library/Application Support/Microsoft Edge/Default/Extensions/bkdgflcldnnnapblkhphbgpggdiikppg
/Users/alien/Library/Application Support/360Chrome/Default/Extensions/ppobicepmejenmhimdajjhelpomokpof
M2和M3版本变化
manifest_version: 3
(MV3)和 manifest_version: 2
(MV2)是Chrome扩展的两个主要版本,它们在多个方面有显著的差异。特别是在背景脚本(background scripts)的处理、权限模型和某些API的使用上有重要变化。以下是MV3和MV2之间的主要区别,尤其关注背景部分的语法变化:
1. 背景脚本(Background Scripts)
MV3:
使用**服务工作流(Service Workers)**代替传统的长时间运行的背景页面。
服务工作流是事件驱动的,只在需要时运行,这有助于提高性能和效率。
服务工作流的声明在
manifest.json
中是这样的:json"background": { "service_worker": "background.js" }
"background": { "service_worker": "background.js" }
MV2:
使用长时间运行的背景页面(Background Pages)。
背景页面可以持续运行,即使没有打开的浏览器窗口。
背景页面在
manifest.json
中的声明通常如下所示:json"background": { "scripts": ["background.js"], "persistent": true }
"background": { "scripts": ["background.js"], "persistent": true }
2. 权限模型
MV3:
- 引入了更细粒度的权限控制,包括在运行时请求权限。
host_permissions
字段用于声明对特定网站的访问权限。- 重点放在最小必要权限原则上,以增强用户隐私保护。
MV2:
- 权限通常在安装时全部请求,包括对网站的访问权限。
- 对网站的权限通常在
"permissions"
字段中声明。
3. 网络请求处理
MV3:
- 使用
declarativeNetRequest
API 替代webRequest
API 中的某些功能。 declarativeNetRequest
API 允许扩展声明性地修改或阻止网络请求,无需访问请求的实际数据。
- 使用
MV2:
- 广泛使用
webRequest
API 来监听和修改网络请求。 - 允许直接访问和修改请求数据,但可能对性能和隐私产生影响。
- 广泛使用
4. 其他变化
Content Security Policy(CSP):
- MV3对CSP实施了更严格的规则,以增强安全性。
扩展页面的隔离级别:
- MV3提高了扩展页面(如弹出页面和选项页面)与网页内容之间的隔离。
弹出和选项页:
- 在MV3中,弹出和选项页的处理方式与MV2相似,但受CSP规则的影响更大。
存储API:
- MV3推荐使用
chrome.storage.local
或chrome.storage.sync
,而不是localStorage
。
- MV3推荐使用
结论
manifest_version: 3
带来了许多改进,特别是在性能、安全性和隐私方面。然而,这些变化也意味着开发者需要适应新的API和架构。对于新的扩展项目,建议使用MV3,而现有的MV2扩展可能需要经过重大修改才能迁移到MV3。
declarativeNetRequestWithHostAccess权限说明
declarativeNetRequestWithHostAccess
是一个在Chrome扩展的 manifest.json
文件中使用的权限声明,它与Manifest V3中引入的 declarativeNetRequest
API相关。这个权限和API用于创建和管理网络请求规则,从而允许扩展以声明性的方式修改、重定向或阻止网络请求。
declarativeNetRequest
API 的主要用途:
创建网络请求规则: 允许你定义一组规则,这些规则可以在网络请求发生时自动应用,以修改或阻止这些请求。这是一种比传统
webRequest
API 更安全和高效的方式来处理网络请求。不需要读取请求数据: 与
webRequest
API不同,declarativeNetRequest
API不需要处理或修改请求的实际数据,这提高了性能并减少了对用户数据隐私的影响。支持动态和静态规则: 你可以定义静态规则(在
manifest.json
中声明)和动态规则(通过扩展代码创建)。
declarativeNetRequestWithHostAccess
权限的作用:
- 当在
manifest.json
文件中声明declarativeNetRequestWithHostAccess
权限时,它允许扩展的declarativeNetRequest
规则访问那些在"host_permissions"
字段中声明的网站。这意味着如果你想要让你的网络请求规则应用于特定的网站,你需要在"host_permissions"
中声明这些网站的URL模式,并在权限中声明declarativeNetRequestWithHostAccess
。
示例 manifest.json
配置:
{
"manifest_version": 3,
"name": "示例扩展",
"version": "1.0",
"permissions": ["declarativeNetRequestWithHostAccess"],
"host_permissions": ["https://*.example.com/*"],
// 其他配置...
}
{
"manifest_version": 3,
"name": "示例扩展",
"version": "1.0",
"permissions": ["declarativeNetRequestWithHostAccess"],
"host_permissions": ["https://*.example.com/*"],
// 其他配置...
}
在这个示例中,扩展有权利使用 declarativeNetRequest
规则来影响在 host_permissions
中声明的域(如 https://*.example.com/*
)的网络请求。
总的来说,declarativeNetRequestWithHostAccess
是Manifest V3中提供更安全、更高效处理网络请求的机制的一部分,它允许扩展在不直接访问请求数据的情况下管理网络请求。
使用 declarativeNetRequest
API 来管理网络请求规则主要涉及到创建和注册这些规则,以及(可选地)在运行时动态修改这些规则。以下是使用这个API的基本步骤:
1. 在 manifest.json
中声明权限和规则文件
首先,需要在扩展的 manifest.json
文件中声明 declarativeNetRequest
权限和规则文件的路径:
{
"manifest_version": 3,
"name": "网络请求管理扩展",
"version": "1.0",
"permissions": ["declarativeNetRequest"],
"host_permissions": ["*://*.example.com/*"],
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset1",
"enabled": true,
"path": "rules.json"
}
]
}
// 其他配置...
}
{
"manifest_version": 3,
"name": "网络请求管理扩展",
"version": "1.0",
"permissions": ["declarativeNetRequest"],
"host_permissions": ["*://*.example.com/*"],
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset1",
"enabled": true,
"path": "rules.json"
}
]
}
// 其他配置...
}
在这个例子中,rules.json
是一个包含你的网络请求规则的文件。
2. 创建规则文件
创建一个包含你想要应用的规则的JSON文件(在这个例子中是 rules.json
)。规则可以用来重定向、阻止、修改请求头等。例如:
[
{
"id": 1,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://*.example.com/*",
"resourceTypes": ["image"]
}
}
]
[
{
"id": 1,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"urlFilter": "*://*.example.com/*",
"resourceTypes": ["image"]
}
}
]
这个规则会阻止所有指向 example.com
域的图片请求。
3. 在扩展中使用规则
一旦定义了规则并在 manifest.json
中声明,这些规则将自动被Chrome扩展应用。你无需在扩展的JavaScript代码中显式地处理这些规则。
4. 动态修改规则(可选)
如果需要,你可以在扩展的JavaScript代码中动态地添加、删除或修改规则。这可以通过调用 chrome.declarativeNetRequest.updateDynamicRules
方法实现。例如:
let addRules = [{
"id": 2,
"priority": 1,
"action": {
"type": "redirect",
"redirect": { "url": "https://example.com/redirected.png" }
},
"condition": {
"urlFilter": "example.com/*.png",
"resourceTypes": ["image"]
}
}];
let removeRuleIds = [1];
chrome.declarativeNetRequest.updateDynamicRules({
addRules: addRules,
removeRuleIds: removeRuleIds
});
let addRules = [{
"id": 2,
"priority": 1,
"action": {
"type": "redirect",
"redirect": { "url": "https://example.com/redirected.png" }
},
"condition": {
"urlFilter": "example.com/*.png",
"resourceTypes": ["image"]
}
}];
let removeRuleIds = [1];
chrome.declarativeNetRequest.updateDynamicRules({
addRules: addRules,
removeRuleIds: removeRuleIds
});
这段代码会动态地添加一个新规则来重定向特定图片请求,并移除之前的一个规则。
5. 测试和调试
加载你的扩展到Chrome并测试你的规则是否按预期工作。调试可以通过检查网络请求在开发者工具的Network面板中的表现来进行。
使用 declarativeNetRequest
API 是一种更安全和高效的方式来处理网络请求,因为它不要求扩展具有对用户数据的直接访问权限。这使得扩展的开发和维护更简单,同时也提高了用户隐私和安全性。
配置web_accessible_resources属性并inject到目标页面中
省流结论:web_accessible_resources实际就是用于注入第三方网站的资源,也就是inject.js,默认情况下,浏览器因为安全问题是不允许跨域访问的,那么我们如果希望在第三方网站引用插件中的图片或者js,比如
<script src=
<img src=
就需要使用这个属性,然后我们就可以在content-script.js中把这个资源给inject注入进去,通过chrome.extension.getURL(jsPath);
可以获取类似于https://www.xx.com/xx.png
这样的虚拟路径,这个js就像某个网站自己开发的一样,可以访问里面的任何js对象或者DOM元素。如果只是插件自己用,比如popup.html,那么就不需要定义这个属性了。可能有人疑问,content_scripts不是也是这样的作用吗?注入js是不是多次一举?实际不是的,content_scripts虽然是直接注入到目标网页中,可以与目标网页互动,但实际上它在隔离环境(isolated world))中运行,与网页自身的 JavaScript 隔离,所以它无法直接访问网页自身的 JavaScript 环境(比如网页中的全局变量或函数)。同样,网页也无法访问 content script 中的函数或变量。它们都只能访问自己各自的js。content-script虽不能访问目标网页的js,但可以访问目标网页的DOM。
这是我的manifest.json
中的一段配置:
"web_accessible_resources": [{
"matches": [ "<all_urls>" ],
"resources": [ "assets/*.jpg", "assets/*.png", "assets/*.js", "assets/*.css" ]
},
{
"matches": [ "https://chat.openai.com/*", "https://claude.ai/*", "https://bard.google.com/*" ],
"resources": [ "mainUI.js" ]
},
{
"matches": [ "*://*.com/*" ],
"resources": [ "mainUI.js" ]
}
],
"web_accessible_resources": [{
"matches": [ "<all_urls>" ],
"resources": [ "assets/*.jpg", "assets/*.png", "assets/*.js", "assets/*.css" ]
},
{
"matches": [ "https://chat.openai.com/*", "https://claude.ai/*", "https://bard.google.com/*" ],
"resources": [ "mainUI.js" ]
},
{
"matches": [ "*://*.com/*" ],
"resources": [ "mainUI.js" ]
}
],
web_accessible_resources
在Chrome扩展的 manifest_version: 3
中扮演着重要角色。它用于指定哪些扩展资源(如脚本、图片、样式表等)可以被网页内容直接访问。这通常用于以下几种情况:
内容脚本与网页交互:如果你的扩展需要在网页中注入脚本或其他资源,并且这些资源需要被网页直接访问或执行,那么你需要在
web_accessible_resources
中声明这些资源。跨域访问限制:为了安全起见,Chrome扩展通常受到同源策略(same-origin policy)的限制。
web_accessible_resources
允许你定义哪些资源可以被跨源访问,这对于需要在多个不同域上运行的扩展特别有用。资源共享:如果你希望允许其他扩展或网页应用访问你的扩展中的特定资源,可以通过
web_accessible_resources
实现。
基本上,web_accessible_resources
提供了一种方式,让开发者能够控制和管理扩展资源的可访问性,从而确保资源的安全使用和有效共享。
一旦你在 manifest_version: 3
的 Chrome 扩展中通过 web_accessible_resources
声明了某个 JavaScript 文件,第三方网页就可以直接访问这个文件。这样做的步骤和注意事项如下:
声明资源:在扩展的
manifest.json
文件中的web_accessible_resources
部分,列出你想要公开访问的资源(如 JavaScript 文件)。例如:json"web_accessible_resources": [ { "resources": ["myscript.js"], "matches": ["<all_urls>"] } ]
"web_accessible_resources": [ { "resources": ["myscript.js"], "matches": ["<all_urls>"] } ]
这里
"resources"
指定了要公开的资源文件名,而"matches"
指定了允许访问这些资源的网页 URL 模式。访问资源:一旦声明,任何网页都可以通过特定的 URL 访问这些资源。这个 URL 的格式通常是:
chrome-extension://[EXTENSION_ID]/[RESOURCE_PATH]
chrome-extension://[EXTENSION_ID]/[RESOURCE_PATH]
其中
[EXTENSION_ID]
是你的扩展的唯一标识符,[RESOURCE_PATH]
是相对于扩展根目录的资源路径。安全性:虽然
web_accessible_resources
提供了资源的跨域访问能力,但你需要仔细考虑安全性问题。确保只公开那些真正需要被外部访问的资源,避免潜在的安全风险。使用案例:这种机制通常用于内容脚本需要与第三方网页共享某些脚本或资源的情况,比如动态注入的脚本或者特定的工具库。
记得测试你的扩展以确保一切按预期工作,同时保持对安全的高度警觉。
当您使用 content_scripts
在 Chrome 扩展中注入 JavaScript 文件到网页时,这个脚本实际上是在所谓的 "隔离环境"(isolated world)中运行的。这意味着虽然脚本可以与网页的 DOM 进行交互,但它无法直接访问网页自身的 JavaScript 环境(比如网页中的全局变量或函数)。同样,网页也无法访问 content script 中的函数或变量。
这种隔离机制是出于安全考虑,以防止脚本间的潜在冲突和数据泄露。然而,有时候,你可能确实需要让网页直接访问或执行扩展中的某些 JavaScript 代码。这就是 web_accessible_resources
发挥作用的地方:
当你想要网页直接访问扩展中的资源(如 JavaScript 文件)时,你需要使用
web_accessible_resources
声明这些资源。这样,网页可以通过特定的 URL 访问并加载这些资源,就像它们是网页自身的一部分一样。如果你只是想在网页中运行一些脚本,而不需要网页直接访问这些脚本文件,那么仅使用
content_scripts
就足够了。
总结一下,如果你的目的是让网页能够直接访问或执行扩展中的 JavaScript 文件,你需要使用 web_accessible_resources
进行声明。如果你只需要在隔离环境中运行脚本与网页的 DOM 进行交互,那么只用 content_scripts
就可以了。
如果插件自身的各个组件(如背景脚本、内容脚本、弹出页面等)之间需要使用某些资源,而不需要这些资源被外部网页直接访问,那么就没有必要在 manifest.json
中定义 web_accessible_resources
。
在这种情况下,插件的不同部分可以直接使用在扩展包内的资源。例如:
内容脚本:通过
content_scripts
在manifest.json
中声明的脚本可以直接注入到目标网页中,与网页的 DOM 互动,但在隔离环境中运行,与网页自身的 JavaScript 隔离。背景脚本:负责管理扩展的生命周期和核心功能,可以使用扩展包中的所有资源。
弹出页面和选项页面:作为扩展的用户界面部分,可以直接引用扩展包中的资源,如 HTML、CSS、JavaScript 文件等。
只有在你希望网页内容(非扩展的一部分)能够直接访问你的扩展中的某些资源时(例如,你想要网页通过 <script>
标签加载扩展中的 JavaScript 文件),你才需要使用 web_accessible_resources
。如果不是这种情况,就不需要声明它。
在content_scripts中动态导入模块或脚本(content_scripts环境)
注:content_scripts脚本中是支持ES6语法的,所以可以动态导入,不过运行环境依然是在content_scripts的隔离环境中。
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js", "import_test.js"],
"run_at": "document_start"
}
],
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js", "import_test.js"],
"run_at": "document_start"
}
],
导入另外一个模块,如: inject/test.js
:
(function() {
const importPath = "inject/test.js";
import(chrome.runtime.getURL(importPath));
})();
(function() {
const importPath = "inject/test.js";
import(chrome.runtime.getURL(importPath));
})();
这段代码是一个自执行的 JavaScript 函数,旨在动态导入一个由 Chrome 扩展提供的 JavaScript 文件。让我们逐步分析这段代码:
自执行函数:
javascript(function() { // 代码 })();
(function() { // 代码 })();
这是一个立即执行的函数表达式(IIFE),它定义了一个匿名函数并立即执行它。这种模式通常用来创建一个封闭的作用域,防止变量污染全局作用域。
定义
importPath
变量:javascriptimport(chrome.runtime.getURL(importPath));
import(chrome.runtime.getURL(importPath));
这里,
importPath
被设置为"inject/test.js"
。这个路径是相对于 Chrome 扩展根目录的。动态导入脚本:
javascriptimport(chrome.runtime.getURL(importPath));
import(chrome.runtime.getURL(importPath));
这里使用了 JavaScript 的动态
import()
语法,用于异步加载模块。chrome.runtime.getURL(importPath)
会生成一个完整的 URL,指向扩展中的inject/test.js
文件。因为这是一个扩展内的资源,所以 URL 会以chrome-extension://
开头。
这种代码模式通常用于以下情况:
- 当您想要动态地(而非在页面加载时)导入脚本。
- 当您想要利用模块化的 JavaScript 代码,例如使用 ES6 模块。
关于直接注入的问题,这段代码本身并不直接将脚本注入到网页的全局环境中;它仅仅加载一个模块到当前执行它的环境(例如,一个内容脚本的环境)。如果您需要将代码注入到网页的全局环境中,您需要使用前面提到的创建 <script>
标签并将其添加到 DOM 的方法。这种动态导入的方式更适用于模块化的代码,且是在扩展的隔离环境中运行。
socket-io的使用
注意,在浏览器中,只能使用socket.io的客户端,服务端需要部署到node或者python中。