当前位置: 首页 > news >正文

Chrome插件学习笔记(二)

Chrome插件学习笔记(二)

参考文章:

  • https://developer.chrome.com/docs/extensions/reference/api/sidePanel?hl=zh-cn
  • https://developer.chrome.com/docs/extensions/reference/api/webRequest?hl=zh-cn
  • https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest?hl=zh-cn

1、什么是sidePanel

上一篇文章Chrome插件学习笔记(一)认识了Chrome插件中的Popup,虽然Popup很轻便并且资源占用很低,但是有时候不能满足诉求,比如不能失去焦点后就会消失,无法长期保持打开状态

如下图浏览器页面右侧即为sidePanel,会占据页面一部分空间,可长期保持打开状态,适合需要持续交互的功能

在这里插入图片描述

2、Chrome插件开发

在浏览器使用过程中有时候打开页面的时候并没有及时打开控制台导致无法及时看到请求信息,这次插件功能是做一个快速复制请求Cookie/Curl,同时可以为修改/阻止请求的插件

2.1、第一个sidePanel插件

1、manifest.json

注意这里不能设置default_popup

{"name": "NetWorker","description": "Listen for network requests","version": "0.0.1","manifest_version": 3,"permissions": ["sidePanel"],"host_permissions": ["*://*/*"],"action": {"default_title": "Click to switch side panel"},"background": {"service_worker": "background.js","type": "module"},"side_panel": {"default_path": "sidepanel/sidepanel.html"}
}
2、background.js

这里可以设置openPanelOnActionClick控制sidePanel的展开和折叠,同时也可以通过其他的交互手动调用open方法展开sidePanel(注意这里没有close方法!!!)

chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true })
3、sidespanel.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><div id="data-container">sidespanel!</div><script type="module" src="sidepanel.js"></script>
</body>
</html>
4、页面效果

在这里插入图片描述

2.2、修改请求功能

1、manifest.json

ManifestV3中已经无法使用webRequest修改请求,需要使用declarativeNetRequest

{"name": "NetWorker","description": "Listen for network requests","version": "0.0.1","manifest_version": 3,"permissions": ["sidePanel","declarativeNetRequest"],"host_permissions": ["*://*/*"],"action": {"default_title": "Click to switch side panel"},"background": {"service_worker": "background.js","type": "module"},"side_panel": {"default_path": "sidepanel/sidepanel.html"}
}
2、background.js
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true })chrome.declarativeNetRequest.getDynamicRules(rules => {let ruleIds = rules.map(rule => rule.id);chrome.declarativeNetRequest.updateDynamicRules({removeRuleIds: ruleIds,addRules: [{id: 10001,priority: 10001,action: {type: "modifyHeaders",requestHeaders: [{header: "X-My-Custom-Header",operation: "set",value: "HelloWorld"},{header: "Accept-Language",operation: "set",value: "en-US,en;q=0.9"}]},condition: {urlFilter: "*",resourceTypes: ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", "other"]}}]});
});
3、页面效果

在这里插入图片描述

2.3、阻止请求功能

1、background.js
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true })chrome.declarativeNetRequest.getDynamicRules(rules => {let ruleIds = rules.map(rule => rule.id);chrome.declarativeNetRequest.updateDynamicRules({removeRuleIds: ruleIds,addRules: [{id: 10001,priority: 10001,action: {type: "modifyHeaders",requestHeaders: [{header: "X-My-Custom-Header",operation: "set",value: "HelloWorld"},{header: "Accept-Language",operation: "set",value: "en-US,en;q=0.9"}]},condition: {urlFilter: "*",resourceTypes: ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", "other"]}},{id: 10002,priority: 1,action: {type: "block"},condition: {urlFilter: "wwads.cn",resourceTypes: ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", "other"]}}]});
});
2、页面效果

未屏蔽广告相关请求

在这里插入图片描述

屏蔽广告相关请求

在这里插入图片描述

2.4、监听请求功能

1、manifest.json
{"name": "NetWorker","description": "Listen for network requests","version": "0.0.1","manifest_version": 3,"permissions": ["sidePanel","declarativeNetRequest","webRequest","storage"],"host_permissions": ["*://*/*"],"action": {"default_title": "Click to switch side panel"},"background": {"service_worker": "background.js","type": "module"},"side_panel": {"default_path": "sidepanel/sidepanel.html"}
}
2、background.js

注意onBeforeRequest中只能获取requestBody,onSendHeaders中只能获取requestHeaders、extraHeaders,因此需要进行数据拼接

chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true })chrome.declarativeNetRequest.getDynamicRules(rules => {let ruleIds = rules.map(rule => rule.id);chrome.declarativeNetRequest.updateDynamicRules({removeRuleIds: ruleIds,addRules: [{id: 10001,priority: 10001,action: {type: "modifyHeaders",requestHeaders: [{header: "X-My-Custom-Header",operation: "set",value: "HelloWorld"},{header: "Accept-Language",operation: "set",value: "en-US,en;q=0.9"}]},condition: {urlFilter: "*",resourceTypes: ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", "other"]}},{id: 10002,priority: 1,action: {type: "block"},condition: {urlFilter: "wwads.cn",resourceTypes: ["main_frame", "sub_frame", "stylesheet", "script", "image", "font", "object", "xmlhttprequest", "ping", "csp_report", "media", "websocket", "other"]}}]});
});const requestBodys = {}chrome.webRequest.onBeforeRequest.addListener(details => {if ('requestBody' in details) {if ('raw' in details['requestBody']) {const requestBodyRaw = details['requestBody']['raw']const parseRequestBodyRaw = requestBodyRaw.map(r => {if ('bytes' in r) {return { bytes: new TextDecoder().decode(r.bytes) }}return r})const newDetails = JSON.parse(JSON.stringify(details))newDetails.requestBody.raw = parseRequestBodyRawrequestBodys[details.requestId] = newDetails}} else {requestBodys[details.requestId] = details}},{ urls: ["<all_urls>"] },["requestBody", "extraHeaders"]
);chrome.webRequest.onSendHeaders.addListener(details => {if (!(details.requestId in requestBodys)) {return}chrome.storage.local.get(['networklogs'], ({ networklogs = [] }) => {const newLog = {requestHeaders: details['requestHeaders'],...requestBodys[details.requestId],};chrome.storage.local.set({networklogs: [newLog, ...networklogs.slice(-99)]});delete requestBodys[details.requestId];});},{ urls: ["<all_urls>"] },["requestHeaders", "extraHeaders"]
);// chrome.webRequest.onCompleted.addListener(
//     details => {
//         console.log('onCompleted:', details);
//     },
//     { urls: ["<all_urls>"] },
//     ["responseHeaders"]
// );chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {if (message.action === 'networklogs') {chrome.storage.local.get(['networklogs'], ({ networklogs }) => {sendResponse(networklogs); // 通过 sendResponse 返回结果});return true;}
});
3、sidepanel.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="sidespanel.css">
</head>
<body><div id="data-container"></div><script type="module" src="sidepanel.js"></script>
</body>
</html>
4、sidepanel.js
import { timestampToYmshms } from '../util/datetime.js';const container = document.getElementById("data-container")document.addEventListener('click', async (e) => {const item = e.target.closest('.data-item');if (item) {try {const value = item.dataset.requestid;await navigator.clipboard.writeText(cookies[value]);item.style.backgroundColor = '#93b5cf';setTimeout(() => {item.style.backgroundColor = '#f8f9fa';}, 500);} catch (err) {console.error('复制失败:', err);}}
});document.addEventListener('dblclick', async (e) => {const item = e.target.closest('.data-item');if (item) {try {const value = item.dataset.requestid;await navigator.clipboard.writeText(curls[value]);item.style.backgroundColor = '#ff9300';setTimeout(() => {item.style.backgroundColor = '#f8f9fa';}, 500);} catch (err) {console.error('复制失败:', err);}}
});let cookies
let curlssetInterval(async () => {const networklogs = await chrome.runtime.sendMessage({action: 'networklogs',});cookies = {}curls = {}container.innerHTML = networklogs.map(n => {const requestId = parseInt(n['requestId'])const url = n['url']const method = n['method']const requestHeaders = n['requestHeaders']const requestBody = n?.['requestBody']const cookieHeader = requestHeaders?.filter(h => {return h['name'] === 'Cookie'})let cookieif (cookieHeader?.length > 1) {cookie = 'Multiple Cookie'} else if (cookieHeader?.length < 1) {cookie = 'No Cookie'} else if (cookieHeader?.length == 1) {cookie = cookieHeader[0]['value']}cookies[requestId] = cookielet curl = `curl -X ${method} '${url}'`;requestHeaders.forEach(header => {curl += ` -H '${header.name.toLowerCase()}: ${header.value}'`;});if (requestBody && requestBody.raw) {curl += ' --data-raw $\''requestBody.raw.forEach(element => {curl += element.bytes || '';});curl += '\''}curls[requestId] = curl.replaceAll('\"', '"').replaceAll('\r\n', '\\r\\n')return `<div class="data-item" data-requestid="${requestId}"><div class="copy-badge">Click to Copy</div><div class="data-metas"><div class="meta"><strong>url: </strong>${url}</div><div class="meta"><strong>method: </strong>${method}</div><div class="meta"><strong>date: </strong>${timestampToYmshms(n['timeStamp'])}</div></div></div>`}).join('')
}, 2000)
5、sidepanel.css
.data-container {background: white;border-radius: 8px;border: 1px solid #eee;padding: 10px;max-height: 400px;overflow-y: auto;overflow-x: hidden;
}.data-container .init{font-size: 14px;color: #cccccc;
}.data-item {padding: 12px;margin: 8px 0;background: #f8f9fa;border-radius: 6px;border-left: 4px solid #0078d4;transition: all 0.2s;cursor: pointer;position: relative;user-select: none;
}.data-item:hover {transform: translateX(2px);box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.1);
}.data-metas .meta {padding: 1px 0;font-size: 12px;max-height: 50px;overflow: hidden;          /* 隐藏溢出的内容 */white-space: nowrap;       /* 不自动换行 */text-overflow: ellipsis;   /* 用省略号替代超出的文本 */
}.copy-badge {position: absolute;right: 10px;top: 12px;transform: translateY(-50%);background: rgba(0, 120, 212, 0.1);color: #0078d4;padding: 4px 8px;border-radius: 4px;font-size: 12px;opacity: 0;transition: opacity 0.3s;z-index: 999;
}.data-item:hover .copy-badge {opacity: 1;
}
6、datetime.js
 export function timestampToYmshms(timestamp) {const date = new Date(timestamp);const year = date.getFullYear();const month = String(date.getMonth() + 1).padStart(2, '0');const day = String(date.getDate()).padStart(2, '0');const hours = String(date.getHours()).padStart(2, '0');const minutes = String(date.getMinutes()).padStart(2, '0');const seconds = String(date.getSeconds()).padStart(2, '0');return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
7、页面效果

点击card复制请求cookie,双击card复制请求的curl格式代码

在这里插入图片描述

http://www.xdnf.cn/news/737785.html

相关文章:

  • C++核心编程_赋值运算符重载
  • 2025最新Nginx安装配置保姆级教程(Windows)
  • 《JavaScript高级程序设计》读书笔记 34 - 代理基础
  • 【术语扫盲】BSP与MSP
  • FreeRTOS多任务系统①
  • Vector - VT System - 板卡_VT板卡使用介绍目录
  • 【Redis】hash
  • LevelDB、BoltDB 和 RocksDB区块链应用比较
  • 前端基础之《Vue(17)—路由集成》
  • 【C/C++】无限长有序数组中查找特定元素
  • 语音通信接通率、应答率和转化率有什么区别?
  • (20)Java 在 AI ML 领域应用
  • Spring AI开发跃迁指南(第二章:急速上手5——Spring AI 结构化输出源码级原理详解及使用实例)
  • 电动飞行器(eVTOL)动力测试实验室系统方案
  • JavaScript正则表达式
  • 精通 Kubernetes:从故障排除到化繁为简
  • MySql--定义表存储引擎、字符集和排序规则
  • 前端面试题目-高频问题集合
  • 用OLEDB读取EXCEL时,单元格内容长度超过255被截断
  • 痉挛性斜颈相关内容说明
  • 换行符在markdown格式时异常2
  • 智能化能源管理系统在“双碳”背景下的新价值
  • 本地部署Ollama DeepSeek-R1:8B,接入Cherry Studio
  • 优先队列用法
  • [正点原子]ESP32S3 RGB屏幕移植LVGL
  • 基本数据指针的解读-C++
  • 数据即资产:GEO如何重塑企业的信息价值链
  • 电子电路:D触发器的工作原理及应用详解
  • 在Mathematica中使用WhenEvent求解微分方程
  • java代码性能优化