Chrome插件学习笔记
参考文章:
- https://www.bookstack.cn/read/chrome-plugin-develop/spilt.2.8bdb1aac68bbdc44.md
- https://juejin.cn/post/7498336041410052132
- https://juejin.cn/post/7159443319938875428
1、认识Chrome插件
1.1、Chrome插件是什么
注意,浏览器插件权限非常高,一定不要安装来路不明的浏览器插件!!!
严格来讲,Chrome插件东西应该叫Chrome扩展(Chrome Extension),是一个用Web技术开发、用来增强浏览器功能的软件。
1.2、Chrome插件基本结构
my-extension/
├── manifest.json # 核心配置文件(必需)
├── background.js # 后台逻辑(Manifest V3 必需)
├── popup/ # 弹出窗口相关文件
│ ├── popup.html
│ ├── popup.js
│ └── popup.css
├── content/ # 注入网页的脚本
│ └── content-scripts.js
├── icons/ # 插件图标
│ ├── icon16.png
│ ├── icon48.png
│ └── icon128.png
└── options/ # 设置页面
├── options.html
├── options.js
└── options.css
1.3、Chrome插件如何使用
这里如果不打开开发者模式是没办法以文件夹的形式直接加载插件,只能安装crx格式的文件
这里以modheader插件为例,将其固定到导航栏的位置后,可以点击插件快速为所有的网络请求加指定的header头
2、Chrome插件开发
Chrome插件开发不需要专门的IDE,普通的web开发工具即可,这里使用的是Vscode
开发插件名称为Credential Manager,主要用于快速复制cookie、storage,同时监控网页的内存使用情况
2.1、开始造轮子
我们首先
1、manifest.json
Chrome插件最重要的文件就是manifest.json,用来说明管理插件的信息,需要什么权限,什么icon显示等等
{
"manifest_version": 3,
"name": "Credential Manager",
"version": "0.0.1",
"description": "Manage credentials easily",
"icons": {
"16": "assets/icon16.png",
"48": "assets/icon48.png",
"128": "assets/icon128.png"
},
"action": {
"default_icon": "assets/icon16.png",
"default_popup": "popup/popup.html"
},
"background": {
"service_worker": "background.js",
"type": "module"
}
}
2、popup.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<h1>popup.html</h1>
</body>
</html>
3、background.js
4、加载Chrome插件
注意代码变动一定要重新加载浏览器插件
5、页面效果
到这里我们完成了一个最简陋的浏览器插件,接下来我们来完善cookie相关功能
2.2、调试技巧
Chrome插件主要分为三部分:background、popup、content(暂未涉及)
1、background
所有在background.js中打印的内容都在service worker这个控制台里面,包括后续在background中存储的内容也都在service worker这个控制台里面
2、popup
注意,popup的控制台会在插件弹窗消失的时候消失!!!
popup需要在点开插件的情况下右键打开控制台
3、content
与网页共享控制台,可以f12直接打开
2.3、Cookie复制功能
1、manifest.json
访问Cookie需要在manifest.json声明cookie权限,同时需要声明访问哪些域名
{
"manifest_version": 3,
"name": "Credential Manager",
"version": "0.0.1",
"description": "Manage credentials easily",
"icons": {
"16": "assets/icon16.png",
"48": "assets/icon48.png",
"128": "assets/icon128.png"
},
"permissions": [
"cookies"
],
"host_permissions": [
"*://*/*"
],
"action": {
"default_icon": "assets/icon16.png",
"default_popup": "popup/popup.html"
},
"background": {
"service_worker": "background.js",
"type": "module"
}
}
2、popup.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="tab-nav">
<button class="tab-nav-item active" data-tab="cookies">Cookies</button>
</div>
<!-- Cookies Tab -->
<div class="tab-content active" data-tab="cookies">
<div class="toolbar">
<input type="text" id="cookieSearch" class="search-input" placeholder="Filter by domain">
<button class="button" id="getCookies">Get Cookies</button>
</div>
<div class="data-container" id="cookieContainer">
<span class="init">try to checking cookies...</span>
</div>
</div>
<script type="module" src="popup.js"></script>
</body>
</html>
3、popup.css
body {
width: 500px;
padding: 10px;
}
.tab-nav {
display: flex;
border-bottom: 1px solid #ddd;
margin-bottom: 10px;
}
.tab-nav-item {
font-size: 16px;
font-weight: bold;
padding: 10px 20px;
background: none;
border: none;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.tab-nav-item.active {
border-bottom-color: #0078d4;
color: #0078d4;
}
.tab-content {
display: none;
padding: 10px 0;
}
.tab-content.active {
display: block;
}
.button {
padding: 10px 15px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
position: relative;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
background: linear-gradient(135deg, #0078d4, #006cbd);
color: white;
}
.button:hover {
background: linear-gradient(135deg, #006cbd, #005ea6);
box-shadow: 0 4px 8px rgba(0, 120, 212, 0.2);
transform: translateY(-1px);
}
.toolbar {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.search-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: #0078d4;
box-shadow: 0 0 0 2px rgba(0, 120, 212, 0.1);
}
.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;
}
.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;
word-wrap: break-word;
overflow-wrap: anywhere;
}
.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;
}
4、cookie-manager.js
// Cookie 管理器(静态方法直接调用)
export class CookieManager {
// 获取所有cookie
static async cookies(domainFilter = '') {
// domainFilter支持""、"example.com"、".example.com"
return new Promise(resolve => {
chrome.cookies.getAll({ domain: domainFilter == '' ? null : domainFilter }, resolve);
});
}
// 添加cookie
static async add(cookieData) {
//{
// url: string, Cookie 所属的完整 URL
// name: string, Cookie 名称
// value: string, Cookie 值
// domain?: string, 域名(可选)
// path?: string, 路径(默认 "/")
// secure?: boolean, 是否仅 HTTPS(默认当前页面协议)
// expirationDate?: number 过期时间戳
// }
return new Promise(resolve => {
chrome.cookies.set(cookieData, resolve);
});
}
// 移除cookie
static async remove(cookie) {
// cookie = { domain: ".example.com", path: "/api", secure: true }
const url = `http${cookie.secure ? 's' : ''}://${cookie.domain}${cookie.path}`;
return new Promise(resolve => {
chrome.cookies.remove({ url, name: cookie.name }, resolve);
});
}
}
5、popup.js
import { CookieManager } from '../manager/cookie-manager.js';
document.addEventListener('DOMContentLoaded', () => {
const cookieContainer = document.getElementById('cookieContainer');
// Tab页切换
const tabs = document.querySelectorAll('.tab-nav-item');
const tabContents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// 移除所有Tab的激活状态
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
// 设置当前激活Tab的状态
tab.classList.add('active');
const targetTab = document.querySelector(`.tab-content[data-tab="${tab.dataset.tab}"]`);
targetTab.classList.add('active');
});
});
// Cookie 操作
async function showCookies() {
try {
const filter = cookieSearch.value.trim();
const cookies = await CookieManager.cookies(filter);
cookieContainer.innerHTML = cookies.map(c => `<div class="data-item" data-value="${c.value}">
<div class="copy-badge">Click to Copy</div>
<div class="data-metas">
<div class="meta">
<strong>Domain: </strong>
${c.domain}
</div>
<div class="meta">
<strong>Name: </strong>
${c.name}
</div>
<div class="meta">
<strong>Value: </strong>
${c.value}
</div>
</div>
</div>`).join('')
} catch (error) {
cookieContainer.textContent = `错误: ${error.message}`;
}
}
document.getElementById('getCookies').addEventListener('click', showCookies);
// 粘贴data-item值
document.addEventListener('click', async (e) => {
const item = e.target.closest('.data-item');
if (item) {
try {
const value = item.dataset.value;
await navigator.clipboard.writeText(value);
// 添加视觉反馈
item.style.backgroundColor = '#93b5cf';
setTimeout(() => {
item.style.backgroundColor = '#f8f9fa';
}, 500);
} catch (err) {
console.error('复制失败:', err);
}
}
});
});
6、background.js
console.log("welcome credential manager!")
// Cookie变化监听
chrome.cookies.onChanged.addListener(changeInfo => {
chrome.storage.local.get(['cookieLogs'], ({ cookieLogs = [] }) => {
const newLog = {
timestamp: Date.now(),
...changeInfo
};
chrome.storage.local.set({
cookieLogs: [...cookieLogs.slice(-99), newLog]
});
});
});
7、页面效果
此时就拿到了浏览器所有的cookie,并且可以根据域名去筛选cookie,点击即可复制
2.4、Storage复制功能
这里只针对localstorage
1、manifest.json
访问storage需要在manifest.json声明storage权限;同时storage是基于页面的,需要激活页面的权限;获取storage需要获取网页的storage,所以需要执行脚本的权限
{
"manifest_version": 3,
"name": "Credential Manager",
"version": "0.0.1",
"description": "Manage credentials easily",
"icons": {
"16": "assets/icon16.png",
"48": "assets/icon48.png",
"128": "assets/icon128.png"
},
"permissions": [
"cookies",
"storage",
"activeTab",
"scripting"
],
"host_permissions": [
"*://*/*"
],
"action": {
"default_icon": "assets/icon16.png",
"default_popup": "popup/popup.html"
},
"background": {
"service_worker": "background.js",
"type": "module"
}
}
2、popup.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="tab-nav">
<button class="tab-nav-item active" data-tab="cookies">Cookies</button>
<button class="tab-nav-item" data-tab="storage">Storage</button>
</div>
<!-- Cookies Tab -->
<div class="tab-content active" data-tab="cookies">
<div class="toolbar">
<input type="text" id="cookieSearch" class="search-input" placeholder="Filter by domain">
<button class="button" id="getCookies">Get Cookies</button>
</div>
<div class="data-container" id="cookieContainer">
<span class="init">try to checking cookies...</span>
</div>
</div>
<!-- Storage Tab -->
<div class="tab-content" data-tab="storage">
<div class="toolbar">
<input type="text" id="storageSearch" class="search-input" placeholder="Filter by item key">
<button class="button" id="getStorage">Get storage</button>
</div>
<div class="data-container" id="storageContainer">
<span class="init">try to checking storages...</span>
</div>
</div>
<script type="module" src="popup.js"></script>
</body>
</html>
3、storage-manager.js
// Storage 管理器(静态方法直接调用)
export class StorageManager {
static async currentTab() {
return new Promise(resolve => {
chrome.tabs.query({ active: true, currentWindow: true }, resolve);
});
}
// 获取当前页的LocalStorage数据
static async localStorage() {
const [tab] = await this.currentTab()
return new Promise(resolve => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
return Object.keys(localStorage).map(key => ([key, localStorage.getItem(key)]));
}
}, (results) => {
resolve(results[0]?.result || {});
});
});
}
// 添加localStorage项
static async addLocalStorageItem(key, value, options = {}) {
const [tab] = await this.currentTab()
return new Promise((resolve, reject) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
args: [key, value, options],
func: (k, v, opts) => {
// 安全写入机制
try {
localStorage.setItem(k, v);
return true;
} catch (error) {
console.error('Storage item add failed:', error);
return false;
}
}
}, (results) => {
if (results?.[0]?.result) {
resolve({ success: true });
} else {
reject(new Error('Storage item add failed'));
}
});
});
}
// 移除localStorage项
static async removeLocalStorageItem(key) {
const [tab] = await this.currentTab()
return new Promise((resolve, reject) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
args: [key],
func: (k) => {
try {
localStorage.removeItem(k, v);
return true;
} catch (error) {
console.error('Storage item remove failed:', error);
return false;
}
}
}, (results) => {
if (results?.[0]?.result) {
resolve({ success: true });
} else {
reject(new Error('Storage item remove failed'));
}
});
});
}
}
4、popop.js
import { CookieManager } from '../manager/cookie-manager.js';
import { StorageManager } from '../manager/storage-manager.js';
document.addEventListener('DOMContentLoaded', () => {
const cookieContainer = document.getElementById('cookieContainer');
const storageContainer = document.getElementById('storageContainer');
// Tab页切换
const tabs = document.querySelectorAll('.tab-nav-item');
const tabContents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// 移除所有Tab的激活状态
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
// 设置当前激活Tab的状态
tab.classList.add('active');
const targetTab = document.querySelector(`.tab-content[data-tab="${tab.dataset.tab}"]`);
targetTab.classList.add('active');
});
});
// Cookie 操作
async function showCookies() {
try {
const filter = cookieSearch.value.trim();
const cookies = await CookieManager.cookies(filter);
cookieContainer.innerHTML = cookies.map(c => `<div class="data-item" data-value="${c.value}">
<div class="copy-badge">Click to Copy</div>
<div class="data-metas">
<div class="meta">
<strong>Domain: </strong>
${c.domain}
</div>
<div class="meta">
<strong>Name: </strong>
${c.name}
</div>
<div class="meta">
<strong>Value: </strong>
${c.value}
</div>
</div>
</div>`).join('')
} catch (error) {
cookieContainer.textContent = `错误: ${error.message}`;
}
}
document.getElementById('getCookies').addEventListener('click', showCookies);
// Storage 操作
async function showStorages() {
try {
const filter = storageSearch.value.trim().toLowerCase();
const storages = await StorageManager.localStorage();
const filtered = storages.filter((item) => {
if (item[0].toLowerCase().includes(filter)) {
return item
}
})
storageContainer.innerHTML = filtered.map(f => `<div class="data-item" data-value="${f[1]}">
<div class="copy-badge">Click to Copy</div>
<div class="data-metas">
<div class="meta">
<strong>Key: </strong>
${f[0]}
</div>
<div class="meta">
<strong>Value: </strong>
${f[1]}
</div>
</div>
</div>`).join('')
} catch (error) {
storageContainer.textContent = `获取失败: ${error.message}`;
}
}
document.getElementById('getStorage').addEventListener('click', showStorages);
// 粘贴data-item值
document.addEventListener('click', async (e) => {
const item = e.target.closest('.data-item');
if (item) {
try {
const value = item.dataset.value;
await navigator.clipboard.writeText(value);
// 添加视觉反馈
item.style.backgroundColor = '#93b5cf';
setTimeout(() => {
item.style.backgroundColor = '#f8f9fa';
}, 500);
} catch (err) {
console.error('复制失败:', err);
}
}
});
});
5、background.js
这里会tab页切换历史,同时获取最新的激活tab页,为后续获取storage作准备
console.log("welcome credential manager!")
let lastTabId = 0
// 激活Tab页监听
async function handleTabActivated(tabId, windowId, type = "active") {
console.log("lastTabId", lastTabId, "tabId", tabId);
if (lastTabId == tabId && type == 'active') {
return
}
try {
const tab = await chrome.tabs.get(tabId);
console.log("当前激活页面", tab);
chrome.storage.local.get(['tabLogs'], ({ tabLogs = [] }) => {
const newLog = {
timestamp: Date.now(),
windowId,
...tab
};
chrome.storage.local.set({
tabLogs: [...tabLogs.slice(-99), newLog]
});
});
} catch (error) {
console.warn('获取标签页失败:', error);
}
}
// Tab页切换监听
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
console.log('tabId:', tabId, "changeInfo", changeInfo);
handleTabActivated(tabId, tab?.windowId, "update");
});
chrome.tabs.onActivated.addListener(activeInfo => {
handleTabActivated(activeInfo.tabId, activeInfo.windowId);
});
// 窗口切换监听(切换窗口导致的tab页切换不会触发chrome.tabs.onActivated事件)
chrome.windows.onFocusChanged.addListener(windowId => {
if (windowId !== chrome.windows.WINDOW_ID_NONE) {
chrome.tabs.query({ active: true, windowId }, ([tab]) => {
handleTabActivated(tab.id, windowId);
});
}
});
// Cookie变化监听
chrome.cookies.onChanged.addListener(changeInfo => {
chrome.storage.local.get(['cookieLogs'], ({ cookieLogs = [] }) => {
const newLog = {
timestamp: Date.now(),
...changeInfo
};
chrome.storage.local.set({
cookieLogs: [...cookieLogs.slice(-99), newLog]
});
});
});
6、页面效果
2.5、页面内存监控
1、manifest.json
访问网页需要声明content_scripts相关内容
{
"manifest_version": 3,
"name": "Credential Manager",
"version": "0.0.1",
"description": "Manage credentials easily",
"icons": {
"16": "assets/icon16.png",
"48": "assets/icon48.png",
"128": "assets/icon128.png"
},
"permissions": [
"cookies",
"storage",
"activeTab",
"scripting"
],
"host_permissions": [
"*://*/*"
],
"action": {
"default_icon": "assets/icon16.png",
"default_popup": "popup/popup.html"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": [
"*://*/*"
],
"js": [
"content/content-script.js"
],
"css": [
"content/content-script.css"
],
"run_at": "document_end"
}
]
}
2、popup.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="tab-nav">
<button class="tab-nav-item active" data-tab="cookies">Cookies</button>
<button class="tab-nav-item" data-tab="storage">Storage</button>
<button class="tab-nav-item" data-tab="monitor">Monitor</button>
</div>
<!-- Cookies Tab -->
<div class="tab-content active" data-tab="cookies">
<div class="toolbar">
<input type="text" id="cookieSearch" class="search-input" placeholder="Filter by domain">
<button class="button" id="getCookies">Get Cookies</button>
</div>
<div class="data-container" id="cookieContainer">
<span class="init">try to checking cookies...</span>
</div>
</div>
<!-- Storage Tab -->
<div class="tab-content" data-tab="storage">
<div class="toolbar">
<input type="text" id="storageSearch" class="search-input" placeholder="Filter by item key">
<button class="button" id="getStorage">Get storage</button>
</div>
<div class="data-container" id="storageContainer">
<span class="init">try to checking storages...</span>
</div>
</div>
<!-- Monitor Tab -->
<div class="tab-content" data-tab="monitor">
<div class="data-container" id="monitorContainer">
<span class="init">try to checking storages...</span>
</div>
</div>
<script type="module" src="popup.js"></script>
</body>
</html>
3、popop.js
popop中会每五秒向background发送消息获取网页的内存情况
import { CookieManager } from '../manager/cookie-manager.js';
import { StorageManager } from '../manager/storage-manager.js';
document.addEventListener('DOMContentLoaded', () => {
const cookieContainer = document.getElementById('cookieContainer');
const storageContainer = document.getElementById('storageContainer');
const monitorContainer = document.getElementById('monitorContainer');
// Tab页切换
const tabs = document.querySelectorAll('.tab-nav-item');
const tabContents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
// 移除所有Tab的激活状态
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
// 设置当前激活Tab的状态
tab.classList.add('active');
const targetTab = document.querySelector(`.tab-content[data-tab="${tab.dataset.tab}"]`);
targetTab.classList.add('active');
});
});
// Cookie 操作
async function showCookies() {
try {
const filter = cookieSearch.value.trim();
const cookies = await CookieManager.cookies(filter);
cookieContainer.innerHTML = cookies.map(c => `<div class="data-item" data-value="${c.value}">
<div class="copy-badge">Click to Copy</div>
<div class="data-metas">
<div class="meta">
<strong>Domain: </strong>
${c.domain}
</div>
<div class="meta">
<strong>Name: </strong>
${c.name}
</div>
<div class="meta">
<strong>Value: </strong>
${c.value}
</div>
</div>
</div>`).join('')
} catch (error) {
cookieContainer.textContent = `错误: ${error.message}`;
}
}
document.getElementById('getCookies').addEventListener('click', showCookies);
// Storage 操作
async function showStorages() {
try {
const filter = storageSearch.value.trim().toLowerCase();
const storages = await StorageManager.localStorage();
const filtered = storages.filter((item) => {
if (item[0].toLowerCase().includes(filter)) {
return item
}
})
storageContainer.innerHTML = filtered.map(f => `<div class="data-item" data-value="${f[1]}">
<div class="copy-badge">Click to Copy</div>
<div class="data-metas">
<div class="meta">
<strong>Key: </strong>
${f[0]}
</div>
<div class="meta">
<strong>Value: </strong>
${f[1]}
</div>
</div>
</div>`).join('')
} catch (error) {
storageContainer.textContent = `获取失败: ${error.message}`;
}
}
document.getElementById('getStorage').addEventListener('click', showStorages);
// 粘贴data-item值
document.addEventListener('click', async (e) => {
const item = e.target.closest('.data-item');
if (item) {
try {
const value = item.dataset.value;
await navigator.clipboard.writeText(value);
// 添加视觉反馈
item.style.backgroundColor = '#93b5cf';
setTimeout(() => {
item.style.backgroundColor = '#f8f9fa';
}, 500);
} catch (err) {
console.error('复制失败:', err);
}
}
});
//
async function getPerformanceMetrics() {
const { memory, timestamp } = await chrome.runtime.sendMessage({
type: 'getPerformanceMetrics',
});
monitorContainer.innerHTML = `<div>Memory:${memory}</div>
<div>Datetime:${timestamp}</div>`
}
setInterval(getPerformanceMetrics, 5000)
getPerformanceMetrics()
});
4、content.js
content中会每秒向background发送消息,同步最新的内存占用情况;同时会在页面加入悬浮窗,显示页面实时内存占用情况
// 创建悬浮窗容器
const monitor = document.createElement('div');
monitor.id = 'performance-monitor';
document.body.appendChild(monitor);
let start = false
setInterval(update, 1000);
console.log("当前页面已注入content");
function update() {
usedJSHeapSize = performance.memory.usedJSHeapSize;
if (usedJSHeapSize >= 1024 * 1024 * 100) {
monitor.innerHTML = `${(usedJSHeapSize / 1024 / 1024 / 1024).toFixed(1)}G`
} else {
monitor.innerHTML = `${(usedJSHeapSize / 1024 / 1024).toFixed(0)}M`
}
if (start) {
chrome.runtime.sendMessage({
type: 'setPerformanceMetrics',
data: {
memory: monitor.innerHTML,
timestamp: Date.now()
}
});
}
}
chrome.runtime.onMessage.addListener((message) => {
if (message.type == "startCollect") {
console.log("开始收集tab【" + message.data.title + "】的数据",formatDate(new Date()))
start = true
}
if (message.type == "stopCollect") {
console.log("因切换到tab【" + message.data.title + "】停止收集数据", formatDate(new Date()))
start = false
}
return true
});
let isDragging = false;
let startX, startY, initialX, initialY, initialW, initialH, dx, dy;
monitor.addEventListener('mousedown', (e) => {
isDragging = true;
const computedStyle = getComputedStyle(monitor);
initialX = parseFloat(computedStyle.right);
initialY = parseFloat(computedStyle.top);
initialW = parseFloat(computedStyle.width);
initialH = parseFloat(computedStyle.height);
startX = e.clientX;
startY = e.clientY;
monitor.style.transition = 'none';
});
document.addEventListener('mousemove', e => {
if (!isDragging) return;
requestAnimationFrame(() => {
dx = e.clientX - startX;
dy = e.clientY - startY;
monitor.style.right = `${Math.min(Math.max(initialX - dx, 0), document.documentElement.clientWidth - initialW)}px`;
monitor.style.top = `${Math.min(Math.max(initialY + dy, 0), document.documentElement.clientHeight - initialH)}px`;
});
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
monitor.style.transition = 'transition: all 0.3s';
// 清理状态
isDragging = false;
});
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 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}`;
}
5、background.js
在每次激活tab页变更时的时候通知旧的tab页无需发送内存数据,通知新的tab页开始发送内存数据,收到页面发送的数据后将其存在background的localstorage中(注意是background的localstorage中)
console.log("welcome credential manager!")
let lastTabId = 0
// 激活Tab页监听
async function handleTabActivated(tabId, windowId, type = "active") {
console.log("lastTabId", lastTabId, "tabId", tabId);
if (lastTabId == tabId && type == 'active') {
return
}
try {
const tab = await chrome.tabs.get(tabId);
console.log("当前激活页面", tab);
chrome.tabs.sendMessage(lastTabId, {
type: 'stopCollect',
data: tab
}).catch(() => { });
lastTabId = tabId
chrome.tabs.sendMessage(tabId, {
type: 'startCollect',
data: tab
}).catch(() => { });
chrome.storage.local.get(['tabLogs'], ({ tabLogs = [] }) => {
const newLog = {
timestamp: Date.now(),
windowId,
...tab
};
chrome.storage.local.set({
tabLogs: [...tabLogs.slice(-99), newLog]
});
});
} catch (error) {
console.warn('获取标签页失败:', error);
}
}
// Tab页切换监听
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
console.log('tabId:', tabId, "changeInfo", changeInfo);
handleTabActivated(tabId, tab?.windowId, "update");
});
chrome.tabs.onActivated.addListener(activeInfo => {
handleTabActivated(activeInfo.tabId, activeInfo.windowId);
});
// 窗口切换监听(切换窗口导致的tab页切换不会触发chrome.tabs.onActivated事件)
chrome.windows.onFocusChanged.addListener(windowId => {
if (windowId !== chrome.windows.WINDOW_ID_NONE) {
chrome.tabs.query({ active: true, windowId }, ([tab]) => {
handleTabActivated(tab.id, windowId);
});
}
});
// Cookie变化监听
chrome.cookies.onChanged.addListener(changeInfo => {
chrome.storage.local.get(['cookieLogs'], ({ cookieLogs = [] }) => {
const newLog = {
timestamp: Date.now(),
...changeInfo
};
chrome.storage.local.set({
cookieLogs: [...cookieLogs.slice(-99), newLog]
});
});
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log("接收到消息", message);
if (message.type == "setPerformanceMetrics") {
chrome.storage.local.set({
performanceMetrics: message.data
});
}
if (message.type == "getPerformanceMetrics") {
chrome.storage.local.get("performanceMetrics", (result) => {
sendResponse(result.performanceMetrics); // 通过 sendResponse 返回结果
});
}
return true
});
6、页面效果
3、结语
相信通过文中代码就可以感受到chrome插件强大的功能,再次提醒不要安装来路不明的chrome插件!!!