Chrome 请求过滤扩展实现
目录
引子
接着 Chrome 扩展 : 入门,接下来开始实现一开始自己的想法:网络请求过滤。简单的说就是监听某个网站的所有请求,把想要的请求在扩展插件中展示出来。扩展名为 Capture Request 。
需求具体化
上面的想法比较模糊,为了达到这个目的,结合文档的示例,要做的有:
- 扩展要有对应的图标及提示。
- 点击工具栏扩展图标,打开一个新的 Tab 页面,用来展示请求的相关信息。
- 扩展监听处于激活 Tab 的网站请求,可以配置过滤监听的网址。
- 对监听的请求,支持根据 url 筛选并导出。
有些功能不方便直接在文档找到,这个时候,建议在 Chrome 商店找一个开源扩展,根据效果看里面用的一些 API ,然后找到对应文档。这里参考了 FeHelper 里面的一些实现。需要注意到是 FeHelper 开发基于 manifest_version
版本为 2 ,以下开发扩展基于的版本是推荐的版本为 3 ,完整代码见 Capture Request 。
实现
图标相关信息配置
按照入门里面介绍的信息,图标可能出现的地方有工具栏、扩展管理页、权限警告和 favicon 上,在 manifest.json
中配置的下面相关字段:
{
"name": "Capture Request",
"description": "Capture Request",
"version": "1.0",
"manifest_version": 3,
"action": {
"default_icon": {
"16": "xxx.png",
"32": "xxx.png",
}
},
"icons":{
"16": "xxx.png",
"32": "xxx.png",
}
}
什么尺寸图标用在什么地方的详细说明在这里,文档上推荐用 PNG 的图片格式。按照这个来,发现有的图标太小了会看起来明显模糊,也可以用比较大的尺寸,Chrome 会自己把图片压缩到需要的尺寸。
点击扩展打开新 Tab 页面
在入门里面点击扩展的展现形式是打开了一个弹窗,在文档 Design the user interface 中介绍的形式有 Popup 、Tooltip、Omnibox、Context menu、Override pages ,最有可能就是 Override pages ,但试了一下发现没有效果,于是去看别人的实现,发现可以通过监听点击图标事件打开新 Tab 。
但看似可以直接用的 API ,实际上还有下面几点要考虑:
- 在哪里监听这事件?
- 什么时候监听这个事件?
- 怎么打开新 Tab ?
- 是否需要权限,如果需要,涉及那些权限?
在入门里面添加功能是通过后台脚本,文档开头说的一句个人觉得很重要:
扩展是通过基于事件编程来改变或增强 Chrome 浏览体验。
从文档中可以发现,在后台脚本中可以解决上面提的第 1、2 两个问题,需要的权限是 scripting
。
打开 Tab 使用的 API 是 chrome.tabs ,需要的权限是 tabs
。
主要做法是在 manifest.json
中添加下面配置:
{
...
+ "permissions": [
+ "scripting",
+ "tabs",
+ ],
+ "background": {
+ "service_worker": "background.js"
+ },
...
}
然后新建后台脚本文件 background.js
,并添加下面主要逻辑代码:
chrome.action.onClicked.addListener(() => {
chrome.tabs.create({
url: 'page.html'
});
});
监听请求及配置
要把处于激活 Tab 网站上的请求显示到打开的扩展页面上,主要需要考虑的点有:
- 怎么找到激活的 Tab ?
- 怎么截获网页请求?
- 截获的请求怎么同步到扩展自定义页面上?
通过上面打开 Tab 的效果实现,可以联想到相关的 API 应该也在 chrome.tabs 中,发现提供了 query
方法可以解决第 1 个问题。
截获请求的方法通过网上搜索,发现文档 chrome.webRequest ,里面详细的介绍了扩展中请求的生命周期及触发的事件,经过对比思考,个人最后决定监听 onResponseStarted
事件,需要的权限是 webRequest
。这样就解决了第 2 个问题。
参照入门里面改变颜色的方式,类似的可以把请求缓存到 chrome.storage ,然后在扩展页面获取,需要的权限是 storage
。关于数据同步,可以通过监听 chrome.storage.onChanged
事件拿到变动的最新数据。这样就解决了第 3 个问题。
在调试的过程中,发现存在本地的数据使用 chrome.storage.sync
时,请求达到一定量后,会报错。看了文档发现这种方式的最大值有一定的限制,不太适合存储大量请求数据的场景,使用 chrome.storage.local
更加合适。
配置过滤请求的方式可直接按照入门里面的配置方式处理,但有一点需要注意的是,每当配置更新的时候,需要重新监听 onResponseStarted
事件。
主要做法是在 manifest.json
的 permissions
字段中添加 webRequest
、storage
。
在 background.js
中添加主要代码:
// 储存请求数据默认值
let requestList = []
// 网址过滤的默认值
let urlPattern = '<all_urls>'
// 监听请求事件的处理程序
const handlerResponseStarted = (details) => {
// 找到处于激活状态的 Tab
chrome.tabs.query({ active: true }, (tab) => {
requestList.unshift(details)
chrome.storage.local.set({ requestList });
return { cancel: true };
})
}
// 监听 storage 改变事件
chrome.storage.onChanged.addListener((changeObj, areaName) => {
const { urlPattern } = changeObj
// 由于在 page.html 里面也监听了,所以要判断是不是 urlPattern 变动了
if (areaName !== 'local' || !urlPattern) {
console.warn('urlPattern does not change')
return;
}
const { newValue } = urlPattern
const hasAddListen = chrome.webRequest.onResponseStarted.hasListener(handlerResponseStarted)
if (hasAddListen) {
chrome.webRequest.onResponseStarted.removeListener(handlerResponseStarted);
}
chrome.webRequest.onResponseStarted.addListener(
handlerResponseStarted,
{ urls: [newValue] },
);
})
为扩展页面 page.html
添加脚本文件 pages.js
,添加关键逻辑:
chrome.storage.onChanged.addListener((changeObj, areaName) => {
const { requestList } = changeObj
// 由于在 background.js 里面也监听了,所以要判断是不是 requestList 变动了
if (areaName !== 'local' || !requestList) {
console.warn('requestList does not change')
return;
}
const { newValue } = requestList || { newValue: [] }
const newItem = newValue[0] || null
if (!newItem) {
console.warn('no data')
return;
}
// 显示数据的逻辑
showData(newItem)
})
导出数据
截获了想要的数据,有需要导出到本地使用的场景,参考 FeHelper 里面的实现,找到了文档 chrome.downloads ,需要的权限是 downloads
。
主要做法是在 manifest.json
的 permissions
字段中添加 downloads
。
在 pages.js
添加关键逻辑:
let localFilterList = []; // 页面筛选后的数据
// 点击导出的按钮
const exportEle = document.querySelector('#operate-export')
exportEle.addEventListener('click', () => {
if (!localFilterList.length) {
alert('无有效数据')
return;
}
const txt = JSON.stringify(localFilterList)
let blob = new Blob([txt], { type: 'application/octet-stream' });
// 文件名称获取时间的秒数,可按照自己喜好定义
let dt = (new Date()).getSeconds();
chrome.downloads.download({
url: URL.createObjectURL(blob),
saveAs: true,
conflictAction: 'overwrite',
filename: dt + '.json'
});
})