Chrome插件学习笔记

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插件!!!

插件简介】 一款集成本地书签、历史记录与 CSDN搜索(so.csdn.net) 的搜索工具;帮助您的浏览器提升10倍效率,打造更酷的chrome!;CSDN浏览器助手插件 由CSDN官方开发,集成一键呼出搜索、万能快捷工具、个性化标签页和底层免广告四大功能,让您在工作学习场景中,告别繁琐复杂的切换,快速解决问题,打造专属你的效率神器!①一键快捷搜索:在页面任意网页下,点击键盘的 "C",即可弹出 [极简搜索框], 输入关键词即可快速展现 "网页、书签、历史记录"的搜索结果。 ②万能快捷工具:按C呼出搜索框后,输入代码即可快速搜索。③个性化标签页: 新标签页可自定义换肤,自动展现程序员热门网站,并可以自定义常用网站,同时可选择多种搜索引擎,打造专属你的清爽标签页!④底层永久免广告:即日起,安装该插件新用户,可永久去CSDN站内广告。从底层去广告,占用内存极小,让上网体验更清爽。去广告特权版本限时下载! 【插件网站】 https://plugin.csdn.net 【插件更新】 2021-03-01 09:54:18 【插件版本】 2.8.1 【插件标签】 高效开发 工具 Chrome插件插件安装教程】 请下载文件后先解压,然后进入页面: chrome://extensions/ 将文件拖拽到该页面,完成安装。 具体步骤: https://t.csdnimg.cn/NxMv 【热门插件】 ·CSDN 浏览器助手: https://plugin.csdn.net/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

眼眸流转

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值