Skip to content

Instantly share code, notes, and snippets.

@Wxh16144
Created July 10, 2025 03:32
Show Gist options
  • Select an option

  • Save Wxh16144/23a7fdeef32074f5fa769043b741ae32 to your computer and use it in GitHub Desktop.

Select an option

Save Wxh16144/23a7fdeef32074f5fa769043b741ae32 to your computer and use it in GitHub Desktop.
Chrome 扩展二分诊断工具
const fs = require('fs');
const path = require('path');
const os = require('os');
const BACKUP_DIR = path.join(os.homedir(), 'chrome-extensions-backup');
// 获取Chrome扩展目录路径
function getChromeExtensionsPath() {
const platform = os.platform();
const homedir = os.homedir();
switch (platform) {
case 'win32':
return path.join(
process.env.LOCALAPPDATA ||
path.join(homedir, 'AppData', 'Local'),
'Google', 'Chrome', 'User Data', 'Default', 'Extensions'
);
case 'darwin': // macOS
return path.join(
homedir,
'Library', 'Application Support', 'Google', 'Chrome', 'Default', 'Extensions'
);
case 'linux':
return path.join(
homedir,
'.config', 'google-chrome', 'Default', 'Extensions'
);
default:
throw new Error(`Unsupported platform: ${platform}`);
}
}
// 获取所有扩展信息
async function getChromeExtensions() {
const extensionsPath = getChromeExtensionsPath();
const extensions = [];
console.log(`Looking for Chrome extensions in: ${extensionsPath}`);
try {
// 检查目录是否存在
await fs.promises.access(extensionsPath);
} catch (error) {
throw new Error(`Chrome extensions directory not found: ${extensionsPath}`);
}
// 读取所有扩展ID目录
const extensionIds = await fs.promises.readdir(extensionsPath);
console.log(`Found ${extensionIds.length} extensions`);
for (const extId of extensionIds) {
const extPath = path.join(extensionsPath, extId);
const stat = await fs.promises.stat(extPath);
if (!stat.isDirectory()) continue;
try {
// 获取第一个版本目录
const versions = await fs.promises.readdir(extPath);
if (versions.length === 0) continue;
// 获取最新版本目录
const latestVersion = versions.sort().reverse()[0]; // 假设版本名是可排序的
// 读取manifest.json
const manifestPath = path.join(extPath, latestVersion, 'manifest.json');
const manifestData = await fs.promises.readFile(manifestPath, 'utf8');
const manifest = JSON.parse(manifestData);
// 处理本地化名称(可能包含多语言支持)
let name = manifest.name;
if (typeof name === 'object' && name !== null) {
name = name.en || name.en_US || Object.values(name)[0] || '';
}
// 处理本地化描述
let description = manifest.description || '';
if (typeof description === 'object' && description !== null) {
description = description.en || description.en_US || Object.values(description)[0] || '';
}
extensions.push({
id: extId,
name: name,
version: manifest.version,
manifest_version: manifest.manifest_version,
description: description,
// homepage_url: manifest.homepage_url || '',
// permissions: manifest.permissions || [],
// icons: manifest.icons ? Object.values(manifest.icons) : [],
path: path.join(extPath, latestVersion),
});
} catch (error) {
console.error(`Error processing extension ${extId}: ${error.message}`);
}
}
return extensions;
}
// 递归复制目录
async function copyDir(src, dest) {
await fs.promises.mkdir(dest, { recursive: true });
const entries = await fs.promises.readdir(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
await copyDir(srcPath, destPath);
} else {
await fs.promises.copyFile(srcPath, destPath);
}
}
}
// 备份所有扩展到安全目录
async function backupAllExtensions(targetDir = BACKUP_DIR) {
const extensionsPath = getChromeExtensionsPath();
await fs.promises.mkdir(targetDir, { recursive: true });
const extensionIds = await fs.promises.readdir(extensionsPath);
for (const extId of extensionIds) {
const extPath = path.join(extensionsPath, extId);
const stat = await fs.promises.stat(extPath).catch(() => null);
if (!stat || !stat.isDirectory()) continue;
const destPath = path.join(targetDir, extId);
await fs.promises.rm(destPath, { recursive: true, force: true }).catch(() => {});
await copyDir(extPath, destPath);
}
}
// 写入所有扩展ID到本地文件
async function saveAllExtensionIds() {
const extensionsPath = getChromeExtensionsPath();
const extensionIds = await fs.promises.readdir(extensionsPath);
const outputPath = path.join(process.cwd(), 'all_extension_ids.json');
await fs.promises.writeFile(outputPath, JSON.stringify(extensionIds, null, 2));
console.log(`All extension IDs saved to: ${outputPath}`);
}
// 二分指定ID文件,生成两个新文件
async function splitIds(inputFile) {
const ids = JSON.parse(await fs.promises.readFile(inputFile, 'utf8'));
const mid = Math.floor(ids.length / 2);
const first = ids.slice(0, mid);
const second = ids.slice(mid);
const base = path.basename(inputFile, '.json');
const dir = path.dirname(inputFile);
const out1 = path.join(dir, `${base}_1.json`);
const out2 = path.join(dir, `${base}_2.json`);
await fs.promises.writeFile(out1, JSON.stringify(first, null, 2));
await fs.promises.writeFile(out2, JSON.stringify(second, null, 2));
console.log(`Split ${inputFile} into:`);
console.log(` ${out1} (${first.length} ids)`);
console.log(` ${out2} (${second.length} ids)`);
}
// 清空并恢复指定ID的扩展到Chrome目录
async function restoreExtensionsFromIdsFile(idsFile) {
const ids = JSON.parse(await fs.promises.readFile(idsFile, 'utf8'));
const chromeDir = getChromeExtensionsPath();
// 清空Chrome扩展目录
const oldIds = await fs.promises.readdir(chromeDir);
for (const extId of oldIds) {
const extPath = path.join(chromeDir, extId);
await fs.promises.rm(extPath, { recursive: true, force: true }).catch(() => {});
}
// 复制所需扩展
for (const extId of ids) {
const src = path.join(BACKUP_DIR, extId);
const stat = await fs.promises.stat(src).catch(() => null);
if (!stat || !stat.isDirectory()) continue;
const dest = path.join(chromeDir, extId);
await copyDir(src, dest);
}
console.log(`Restored ${ids.length} extensions from ${idsFile} to Chrome extensions directory.`);
}
// 主函数
async function main() {
try {
const args = process.argv.slice(2);
if (args.includes('--backup')) {
await backupAllExtensions();
console.log(`All Chrome extensions have been backed up to: ${BACKUP_DIR}`);
return;
}
if (args.includes('--save-ids')) {
await saveAllExtensionIds();
return;
}
if (args.includes('--split-ids')) {
const idx = args.indexOf('--split-ids');
const inputFile = args[idx + 1];
if (!inputFile) {
console.error('Please provide the input file, e.g. --split-ids all_extension_ids.json');
return;
}
await splitIds(inputFile);
return;
}
if (args.includes('--restore-ids')) {
const idx = args.indexOf('--restore-ids');
const idsFile = args[idx + 1];
if (!idsFile) {
console.error('Please provide the ids file, e.g. --restore-ids all_extension_ids_2.json');
return;
}
await restoreExtensionsFromIdsFile(idsFile);
return;
}
console.log('Scanning Chrome extensions...');
const extensions = await getChromeExtensions();
const result = {
timestamp: new Date().toISOString(),
browser: 'Google Chrome',
profile: 'Default',
extensions_count: extensions.length,
extensions: extensions
};
// 保存为JSON文件
const outputPath = path.join(process.cwd(), 'chrome_extensions.json');
await fs.promises.writeFile(outputPath, JSON.stringify(result, null, 2));
console.log(`\n✅ Successfully found ${extensions.length} extensions`);
console.log(`📄 Results saved to: ${outputPath}`);
} catch (error) {
console.error(`❌ Error: ${error.message}`);
console.log('Possible solutions:');
console.log('1. Ensure Chrome is installed on your system');
console.log('2. If using a non-default profile, modify the script');
console.log('3. Run the script with appropriate permissions');
process.exit(1);
}
}
// 执行主函数
main();
@Wxh16144
Copy link
Author

chrome-ext-bisect

Chrome 扩展二分诊断工具,帮助你快速定位导致 Chrome 卡顿或异常的扩展。

安装

chrome-ext-bisect.js 文件放到任意目录下, 随后用 node 执行即可

Important

首次使用前,务必先执行备份命令,防止数据丢失!


命令说明

1. 备份所有扩展(强烈推荐第一步)

node chrome-ext-bisect.js --backup
  • 作用:将当前 Chrome 所有扩展完整备份到 ~/chrome-extensions-backup 目录。
  • 说明:后续所有操作都基于此备份目录,避免误删或丢失扩展。

2. 导出所有扩展 ID

node chrome-ext-bisect.js --save-ids
  • 作用:扫描当前 Chrome 扩展目录,导出所有扩展 ID 到 all_extension_ids.json

3. 二分扩展 ID 列表

node chrome-ext-bisect.js --split-ids all_extension_ids.json
  • 作用:将指定的 ID 列表文件二分,生成两个新文件(如 all_extension_ids_1.jsonall_extension_ids_2.json)。
  • 说明:可对任意分割出来的文件继续执行本命令,递归二分,直到定位到问题扩展。

4. 恢复指定扩展集合到 Chrome 目录

node chrome-ext-bisect.js --restore-ids all_extension_ids_1.json
  • 作用:
    1. 自动清空当前 Chrome 扩展目录
    2. 从备份目录中恢复指定 ID 的扩展到 Chrome 扩展目录
  • 说明:用于二分测试时快速切换扩展集合。

推荐诊断流程

  1. 先备份node chrome-ext-bisect.js --backup
  2. 导出所有扩展 ID:node chrome-ext-bisect.js --save-ids
  3. 二分扩展 ID 文件:node chrome-ext-bisect.js --split-ids all_extension_ids.json
  4. --restore-ids 恢复某一半扩展,重启 Chrome 观察问题
  5. 继续对有问题的那一半二分,重复步骤 3-4,直到定位到具体扩展

注意事项

  • 备份目录默认为 ~/chrome-extensions-backup,如需更改请修改源码常量。
  • 恢复扩展时会清空当前 Chrome 扩展目录,请确保已备份。
  • 建议在关闭 Chrome 的情况下操作扩展目录。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment