学习目标
- 了解 Chrome Extension 的基本概念和工作原理
- 理解扩展程序的组成结构和核心文件
- 掌握 Chrome Web Store 的发布流程
知识点总结
Chrome Extension 基本概念
Chrome Extension(Chrome扩展程序)是一种小型软件程序,可以修改和增强 Google Chrome 浏览器的功能。扩展程序使用标准的 Web 技术构建:HTML、CSS 和 JavaScript。
12/3/25About 8 min
学习目标
Chrome Extension(Chrome扩展程序)是一种小型软件程序,可以修改和增强 Google Chrome 浏览器的功能。扩展程序使用标准的 Web 技术构建:HTML、CSS 和 JavaScript。
学习目标
Chrome Extension 开发环境主要包含以下几个部分:
学习目标
Manifest 文件是 Chrome Extension 的配置文件,定义了扩展的元数据、权限、资源和行为。它是每个扩展程序的必需文件,位于扩展根目录。
学习目标
Content Scripts 是在网页上下文中运行的 JavaScript 文件,可以读取和修改网页的 DOM。它们在隔离的环境中执行,拥有对网页 DOM 的完全访问权限,但与网页的 JavaScript 环境相互隔离。
Background Scripts是Chrome扩展中的核心组件,它在浏览器后台持续运行,不依赖于特定网页或用户界面。
核心特点
Popup是用户点击扩展图标时显示的小窗口界面,是用户与扩展交互的主要入口。
核心特性
Options页面是Chrome扩展的设置中心,提供比Popup更丰富的配置功能和更大的操作空间。
核心特点
Chrome扩展的消息传递机制是连接各个组件的桥梁,实现background script、content script、popup和options页面之间的通信。
通信特点
Chrome扩展提供了多种存储选项,每种都有其特定的用途和限制。
// 存储配额信息
const StorageQuotas = {
sync: {
totalBytes: 102400, // 100KB
maxItems: 512, // 最大项目数
quotaBytesPerItem: 8192, // 单项8KB
maxWriteOperationsPerHour: 1800,
maxWriteOperationsPerMinute: 120
},
local: {
totalBytes: 5242880, // 5MB
maxItems: Infinity, // 无限制
quotaBytesPerItem: Infinity // 无限制
},
session: {
totalBytes: 10485760, // 10MB
maxItems: Infinity, // 无限制
quotaBytesPerItem: Infinity // 无限制
}
};
// 检查存储使用情况
async function checkStorageUsage() {
const syncUsage = await chrome.storage.sync.getBytesInUse();
const localUsage = await chrome.storage.local.getBytesInUse();
console.log('存储使用情况:');
console.log(`Sync: ${syncUsage}/${StorageQuotas.sync.totalBytes} bytes`);
console.log(`Local: ${localUsage}/${StorageQuotas.local.totalBytes} bytes`);
return {
sync: {
used: syncUsage,
total: StorageQuotas.sync.totalBytes,
percentage: (syncUsage / StorageQuotas.sync.totalBytes * 100).toFixed(2)
},
local: {
used: localUsage,
total: StorageQuotas.local.totalBytes,
percentage: (localUsage / StorageQuotas.local.totalBytes * 100).toFixed(2)
}
};
}
// tabs-manager.js - 标签页管理器
class TabsManager {
constructor() {
this.activeTabsCache = new Map();
this.tabGroups = new Map();
this.setupEventListeners();
}
setupEventListeners() {
// 监听标签页变化
chrome.tabs.onCreated.addListener((tab) => {
this.onTabCreated(tab);
});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
this.onTabUpdated(tabId, changeInfo, tab);
});
chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
this.onTabRemoved(tabId, removeInfo);
});
chrome.tabs.onActivated.addListener((activeInfo) => {
this.onTabActivated(activeInfo);
});
chrome.tabs.onMoved.addListener((tabId, moveInfo) => {
this.onTabMoved(tabId, moveInfo);
});
}
// 获取当前活动标签页
async getCurrentTab() {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
return tabs[0];
}
// 获取所有标签页
async getAllTabs() {
return await chrome.tabs.query({});
}
// 根据URL模式查找标签页
async findTabsByUrl(urlPattern) {
const allTabs = await this.getAllTabs();
return allTabs.filter(tab => {
if (typeof urlPattern === 'string') {
return tab.url.includes(urlPattern);
} else if (urlPattern instanceof RegExp) {
return urlPattern.test(tab.url);
}
return false;
});
}
// 创建新标签页
async createTab(options = {}) {
const defaultOptions = {
active: true,
pinned: false
};
const tabOptions = { ...defaultOptions, ...options };
try {
const tab = await chrome.tabs.create(tabOptions);
console.log('标签页已创建:', tab.id);
return tab;
} catch (error) {
console.error('创建标签页失败:', error);
throw error;
}
}
// 复制标签页
async duplicateTab(tabId) {
try {
const tab = await chrome.tabs.duplicate(tabId);
console.log('标签页已复制:', tab.id);
return tab;
} catch (error) {
console.error('复制标签页失败:', error);
throw error;
}
}
// 更新标签页
async updateTab(tabId, updateProperties) {
try {
const tab = await chrome.tabs.update(tabId, updateProperties);
console.log('标签页已更新:', tabId);
return tab;
} catch (error) {
console.error('更新标签页失败:', error);
throw error;
}
}
// 关闭标签页
async closeTab(tabId) {
try {
await chrome.tabs.remove(tabId);
console.log('标签页已关闭:', tabId);
return true;
} catch (error) {
console.error('关闭标签页失败:', error);
return false;
}
}
// 批量关闭标签页
async closeTabs(tabIds) {
try {
await chrome.tabs.remove(tabIds);
console.log('批量关闭标签页:', tabIds.length);
return true;
} catch (error) {
console.error('批量关闭失败:', error);
return false;
}
}
// 移动标签页
async moveTab(tabId, moveProperties) {
try {
const tabs = await chrome.tabs.move(tabId, moveProperties);
console.log('标签页已移动:', tabId);
return tabs;
} catch (error) {
console.error('移动标签页失败:', error);
throw error;
}
}
// 固定/取消固定标签页
async togglePinTab(tabId) {
try {
const tab = await chrome.tabs.get(tabId);
const updatedTab = await chrome.tabs.update(tabId, {
pinned: !tab.pinned
});
console.log(`标签页 ${tabId} ${updatedTab.pinned ? '已固定' : '已取消固定'}`);
return updatedTab;
} catch (error) {
console.error('切换固定状态失败:', error);
throw error;
}
}
// 静音/取消静音
async toggleMuteTab(tabId) {
try {
const tab = await chrome.tabs.get(tabId);
const updatedTab = await chrome.tabs.update(tabId, {
muted: !tab.mutedInfo.muted
});
console.log(`标签页 ${tabId} ${updatedTab.mutedInfo.muted ? '已静音' : '已取消静音'}`);
return updatedTab;
} catch (error) {
console.error('切换静音状态失败:', error);
throw error;
}
}
// 高级功能:标签页分组管理
async createTabGroup(tabIds, options = {}) {
try {
const groupId = await chrome.tabs.group({ tabIds });
if (options.title) {
await chrome.tabGroups.update(groupId, {
title: options.title,
color: options.color || 'blue'
});
}
this.tabGroups.set(groupId, {
id: groupId,
title: options.title || `Group ${groupId}`,
color: options.color || 'blue',
tabIds: tabIds,
createdAt: Date.now()
});
console.log('标签页组已创建:', groupId);
return groupId;
} catch (error) {
console.error('创建标签页组失败:', error);
throw error;
}
}
// 事件处理器
onTabCreated(tab) {
console.log('标签页已创建:', tab.id, tab.url);
this.activeTabsCache.set(tab.id, tab);
// 发送创建事件通知
chrome.runtime.sendMessage({
action: 'tabCreated',
tab: tab
});
}
onTabUpdated(tabId, changeInfo, tab) {
console.log('标签页已更新:', tabId, changeInfo);
// 更新缓存
this.activeTabsCache.set(tabId, tab);
// 检查URL变化
if (changeInfo.url) {
this.handleUrlChange(tab, changeInfo.url);
}
// 检查状态变化
if (changeInfo.status === 'complete') {
this.handlePageLoadComplete(tab);
}
// 发送更新事件通知
chrome.runtime.sendMessage({
action: 'tabUpdated',
tabId: tabId,
changeInfo: changeInfo,
tab: tab
});
}
onTabRemoved(tabId, removeInfo) {
console.log('标签页已删除:', tabId);
// 清理缓存
this.activeTabsCache.delete(tabId);
// 发送删除事件通知
chrome.runtime.sendMessage({
action: 'tabRemoved',
tabId: tabId,
removeInfo: removeInfo
});
}
onTabActivated(activeInfo) {
console.log('标签页已激活:', activeInfo.tabId);
// 发送激活事件通知
chrome.runtime.sendMessage({
action: 'tabActivated',
activeInfo: activeInfo
});
}
onTabMoved(tabId, moveInfo) {
console.log('标签页已移动:', tabId, moveInfo);
}
handleUrlChange(tab, newUrl) {
// 处理URL变化的逻辑
console.log(`标签页 ${tab.id} URL变化: ${newUrl}`);
// 可以在这里添加URL过滤、重定向等功能
if (this.shouldBlockUrl(newUrl)) {
this.redirectTab(tab.id, 'chrome://newtab/');
}
}
handlePageLoadComplete(tab) {
// 页面加载完成的处理
console.log(`标签页 ${tab.id} 加载完成: ${tab.title}`);
// 可以在这里执行content script注入等操作
this.injectContentScript(tab.id);
}
shouldBlockUrl(url) {
// URL过滤逻辑
const blockedPatterns = [
/.*\.malicious-site\.com/,
/.*dangerous-content.*/
];
return blockedPatterns.some(pattern => pattern.test(url));
}
async redirectTab(tabId, newUrl) {
try {
await chrome.tabs.update(tabId, { url: newUrl });
console.log(`标签页 ${tabId} 已重定向到: ${newUrl}`);
} catch (error) {
console.error('重定向失败:', error);
}
}
async injectContentScript(tabId) {
try {
await chrome.scripting.executeScript({
target: { tabId: tabId },
files: ['content-script.js']
});
console.log(`Content script已注入到标签页 ${tabId}`);
} catch (error) {
console.error('注入content script失败:', error);
}
}
// 获取标签页统计信息
async getTabStats() {
const allTabs = await this.getAllTabs();
const stats = {
total: allTabs.length,
active: 0,
pinned: 0,
muted: 0,
loading: 0,
byDomain: new Map(),
byWindow: new Map()
};
allTabs.forEach(tab => {
if (tab.active) stats.active++;
if (tab.pinned) stats.pinned++;
if (tab.mutedInfo && tab.mutedInfo.muted) stats.muted++;
if (tab.status === 'loading') stats.loading++;
// 按域名统计
try {
const domain = new URL(tab.url).hostname;
stats.byDomain.set(domain, (stats.byDomain.get(domain) || 0) + 1);
} catch (e) {
// 忽略无效URL
}
// 按窗口统计
const windowId = tab.windowId;
stats.byWindow.set(windowId, (stats.byWindow.get(windowId) || 0) + 1);
});
return {
...stats,
byDomain: Object.fromEntries(stats.byDomain),
byWindow: Object.fromEntries(stats.byWindow)
};
}
// 清理重复标签页
async removeDuplicateTabs() {
const allTabs = await this.getAllTabs();
const urlMap = new Map();
const duplicateTabs = [];
// 找出重复的标签页
allTabs.forEach(tab => {
if (urlMap.has(tab.url)) {
duplicateTabs.push(tab.id);
} else {
urlMap.set(tab.url, tab.id);
}
});
if (duplicateTabs.length > 0) {
await this.closeTabs(duplicateTabs);
console.log(`已关闭 ${duplicateTabs.length} 个重复标签页`);
}
return duplicateTabs.length;
}
}
// 全局标签页管理器实例
const tabsManager = new TabsManager();
// 导出供其他模块使用
window.tabsManager = tabsManager;