第05章 Background Scripts 后台脚本
12/3/25About 7 min
第5章:Background Scripts 后台脚本
学习目标
- 理解Background Scripts的概念和作用机制
- 掌握Service Worker在Chrome扩展中的应用
- 学会事件监听和生命周期管理
- 熟练运用后台脚本进行数据处理和API调用
5.1 Background Scripts概述
Background Scripts是Chrome扩展中的核心组件,它在浏览器后台持续运行,不依赖于特定网页或用户界面。
5.1.1 基本概念
核心特点
- 独立于网页运行
- 可以监听浏览器事件
- 处理长时间运行的任务
- 管理扩展状态
5.1.2 Manifest配置
在Manifest V3中,使用Service Worker替代传统的Background Pages:
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0",
"background": {
"service_worker": "background.js"
},
"permissions": [
"storage",
"tabs"
]
}5.2 Service Worker基础
5.2.1 Service Worker生命周期
5.2.2 基础事件监听
// background.js
// 扩展安装或更新时触发
chrome.runtime.onInstalled.addListener((details) => {
console.log('Extension installed:', details);
if (details.reason === 'install') {
// 首次安装
initializeExtension();
} else if (details.reason === 'update') {
// 扩展更新
handleUpdate();
}
});
// 扩展启动时触发
chrome.runtime.onStartup.addListener(() => {
console.log('Extension started');
});
function initializeExtension() {
// 设置默认配置
chrome.storage.sync.set({
'isEnabled': true,
'theme': 'light'
});
}
function handleUpdate() {
// 处理更新逻辑
console.log('Extension updated');
}5.2.3 Python模拟示例
# background_worker.py - 模拟Chrome扩展背景脚本的Python实现
import asyncio
import json
from datetime import datetime
from typing import Dict, Any
class BackgroundWorker:
"""模拟Chrome扩展Background Script的Python类"""
def __init__(self):
self.is_running = False
self.event_listeners = {}
self.storage = {}
async def start(self):
"""启动后台工作器"""
self.is_running = True
print(f"[{datetime.now()}] Background worker started")
# 模拟onInstalled事件
await self.trigger_event('onInstalled', {'reason': 'startup'})
# 开始事件循环
while self.is_running:
await asyncio.sleep(1)
await self.process_events()
def add_listener(self, event_name: str, callback):
"""添加事件监听器"""
if event_name not in self.event_listeners:
self.event_listeners[event_name] = []
self.event_listeners[event_name].append(callback)
async def trigger_event(self, event_name: str, data: Dict[str, Any]):
"""触发事件"""
if event_name in self.event_listeners:
for callback in self.event_listeners[event_name]:
await callback(data)
async def process_events(self):
"""处理周期性事件"""
# 模拟定期检查任务
current_time = datetime.now()
if current_time.second % 30 == 0: # 每30秒触发一次
await self.trigger_event('periodic_check', {'time': current_time})
# 使用示例
async def on_installed_handler(details):
print(f"Extension installed: {details}")
async def periodic_check_handler(details):
print(f"Periodic check at: {details['time']}")
# 创建后台工作器实例
worker = BackgroundWorker()
worker.add_listener('onInstalled', on_installed_handler)
worker.add_listener('periodic_check', periodic_check_handler)
# 启动工作器(注释掉实际运行代码)
# asyncio.run(worker.start())5.3 事件监听与处理
5.3.1 浏览器事件监听
// 标签页事件监听
chrome.tabs.onCreated.addListener((tab) => {
console.log('新标签页创建:', tab.url);
processNewTab(tab);
});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete') {
console.log('页面加载完成:', tab.url);
analyzePageContent(tab);
}
});
chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
console.log('标签页关闭:', tabId);
cleanupTabData(tabId);
});
// 处理新标签页
function processNewTab(tab) {
// 检查URL是否需要特殊处理
if (tab.url.includes('github.com')) {
// 为GitHub页面添加特殊功能
chrome.tabs.sendMessage(tab.id, {
action: 'initGithubFeatures'
});
}
}
function analyzePageContent(tab) {
// 分析页面内容
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: extractPageInfo
});
}
function extractPageInfo() {
// 在页面中执行的函数
const title = document.title;
const description = document.querySelector('meta[name="description"]')?.content;
chrome.runtime.sendMessage({
action: 'pageAnalyzed',
data: { title, description, url: location.href }
});
}
function cleanupTabData(tabId) {
// 清理与标签页相关的数据
chrome.storage.local.remove([`tab_${tabId}_data`]);
}5.3.2 消息监听
// 监听来自content script或popup的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('收到消息:', message);
switch (message.action) {
case 'getData':
handleGetData(message, sendResponse);
return true; // 异步响应
case 'saveData':
handleSaveData(message.data, sendResponse);
return true;
case 'processUrl':
processUrl(message.url, sendResponse);
return true;
default:
sendResponse({ error: '未知操作' });
}
});
async function handleGetData(message, sendResponse) {
try {
const data = await chrome.storage.sync.get(message.key);
sendResponse({ success: true, data });
} catch (error) {
sendResponse({ success: false, error: error.message });
}
}
async function handleSaveData(data, sendResponse) {
try {
await chrome.storage.sync.set(data);
sendResponse({ success: true });
} catch (error) {
sendResponse({ success: false, error: error.message });
}
}5.4 定时任务和周期性操作
5.4.1 使用Alarms API
// 设置定时任务
chrome.alarms.create('dataSync', {
delayInMinutes: 1,
periodInMinutes: 30
});
chrome.alarms.create('dailyCleanup', {
when: Date.now() + 24 * 60 * 60 * 1000 // 24小时后
});
// 监听定时器事件
chrome.alarms.onAlarm.addListener((alarm) => {
console.log('定时器触发:', alarm.name);
switch (alarm.name) {
case 'dataSync':
performDataSync();
break;
case 'dailyCleanup':
performDailyCleanup();
break;
}
});
async function performDataSync() {
try {
console.log('开始数据同步...');
// 获取本地数据
const localData = await chrome.storage.local.get(null);
// 同步到服务器
const response = await fetch('https://api.example.com/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(localData)
});
if (response.ok) {
console.log('数据同步成功');
// 更新最后同步时间
chrome.storage.sync.set({
'lastSyncTime': Date.now()
});
}
} catch (error) {
console.error('数据同步失败:', error);
}
}
function performDailyCleanup() {
console.log('执行日常清理...');
// 清理过期数据
chrome.storage.local.get(null, (items) => {
const keysToRemove = [];
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
for (const [key, value] of Object.entries(items)) {
if (value.timestamp && value.timestamp < oneWeekAgo) {
keysToRemove.push(key);
}
}
if (keysToRemove.length > 0) {
chrome.storage.local.remove(keysToRemove);
console.log(`清理了 ${keysToRemove.length} 个过期项目`);
}
});
// 设置下一次清理
chrome.alarms.create('dailyCleanup', {
when: Date.now() + 24 * 60 * 60 * 1000
});
}5.4.2 Python模拟定时任务
# alarm_system.py - 模拟Chrome扩展Alarm系统
import asyncio
import time
from datetime import datetime, timedelta
from typing import Dict, Callable
class AlarmSystem:
"""模拟Chrome扩展的Alarm系统"""
def __init__(self):
self.alarms = {}
self.running = False
self.listeners = []
def create_alarm(self, name: str, delay_minutes: int = None,
period_minutes: int = None, when_timestamp: float = None):
"""创建定时器"""
now = time.time()
if when_timestamp:
next_run = when_timestamp
elif delay_minutes:
next_run = now + (delay_minutes * 60)
else:
next_run = now
self.alarms[name] = {
'name': name,
'next_run': next_run,
'period_minutes': period_minutes
}
print(f"创建定时器: {name}, 下次执行: {datetime.fromtimestamp(next_run)}")
def add_listener(self, callback: Callable):
"""添加定时器监听器"""
self.listeners.append(callback)
async def start(self):
"""启动定时器系统"""
self.running = True
print("定时器系统启动")
while self.running:
current_time = time.time()
for alarm_name, alarm_info in list(self.alarms.items()):
if current_time >= alarm_info['next_run']:
# 触发定时器
await self.trigger_alarm(alarm_info)
# 如果是周期性定时器,设置下次执行时间
if alarm_info['period_minutes']:
alarm_info['next_run'] = current_time + (alarm_info['period_minutes'] * 60)
else:
# 一次性定时器,删除
del self.alarms[alarm_name]
await asyncio.sleep(1) # 每秒检查一次
async def trigger_alarm(self, alarm_info):
"""触发定时器事件"""
print(f"定时器触发: {alarm_info['name']}")
for listener in self.listeners:
await listener(alarm_info)
def stop(self):
"""停止定时器系统"""
self.running = False
print("定时器系统停止")
# 使用示例
async def alarm_handler(alarm_info):
alarm_name = alarm_info['name']
if alarm_name == 'dataSync':
await perform_data_sync()
elif alarm_name == 'dailyCleanup':
await perform_daily_cleanup()
async def perform_data_sync():
print("执行数据同步...")
# 模拟数据同步逻辑
await asyncio.sleep(2)
print("数据同步完成")
async def perform_daily_cleanup():
print("执行日常清理...")
# 模拟清理逻辑
await asyncio.sleep(1)
print("清理完成")
# 创建并使用定时器系统
alarm_system = AlarmSystem()
alarm_system.add_listener(alarm_handler)
# 设置定时器
alarm_system.create_alarm('dataSync', delay_minutes=1, period_minutes=30)
alarm_system.create_alarm('dailyCleanup', when_timestamp=time.time() + 5)
# 启动系统(注释掉实际运行)
# asyncio.run(alarm_system.start())5.5 数据处理和API调用
5.5.1 网络请求处理
// API调用管理
class APIManager {
constructor() {
this.baseUrl = 'https://api.example.com';
this.apiKey = null;
this.requestQueue = [];
this.rateLimitDelay = 1000; // 1秒
}
async initialize() {
// 从存储中获取API密钥
const result = await chrome.storage.sync.get(['apiKey']);
this.apiKey = result.apiKey;
}
async makeRequest(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const defaultOptions = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
}
};
const requestOptions = { ...defaultOptions, ...options };
try {
const response = await fetch(url, requestOptions);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API请求失败:', error);
throw error;
}
}
async fetchUserData(userId) {
return await this.makeRequest(`/users/${userId}`);
}
async updateUserPreferences(userId, preferences) {
return await this.makeRequest(`/users/${userId}/preferences`, {
method: 'PUT',
body: JSON.stringify(preferences)
});
}
// 批量处理请求
async batchProcess(requests) {
const results = [];
for (const request of requests) {
try {
const result = await this.makeRequest(request.endpoint, request.options);
results.push({ success: true, data: result });
// 添加延迟以避免率限
await new Promise(resolve => setTimeout(resolve, this.rateLimitDelay));
} catch (error) {
results.push({ success: false, error: error.message });
}
}
return results;
}
}
// 初始化API管理器
const apiManager = new APIManager();
apiManager.initialize();5.5.2 数据缓存和优化
// 数据缓存管理
class DataCache {
constructor() {
this.cachePrefix = 'cache_';
this.defaultTTL = 5 * 60 * 1000; // 5分钟
}
async set(key, data, ttl = this.defaultTTL) {
const cacheItem = {
data: data,
timestamp: Date.now(),
ttl: ttl
};
await chrome.storage.local.set({
[`${this.cachePrefix}${key}`]: cacheItem
});
}
async get(key) {
const result = await chrome.storage.local.get([`${this.cachePrefix}${key}`]);
const cacheItem = result[`${this.cachePrefix}${key}`];
if (!cacheItem) {
return null;
}
// 检查是否过期
if (Date.now() - cacheItem.timestamp > cacheItem.ttl) {
await this.delete(key);
return null;
}
return cacheItem.data;
}
async delete(key) {
await chrome.storage.local.remove([`${this.cachePrefix}${key}`]);
}
async clear() {
const allData = await chrome.storage.local.get(null);
const cacheKeys = Object.keys(allData).filter(key =>
key.startsWith(this.cachePrefix)
);
if (cacheKeys.length > 0) {
await chrome.storage.local.remove(cacheKeys);
}
}
// 获取带缓存的数据
async getWithCache(key, fetchFunction, ttl) {
let data = await this.get(key);
if (data === null) {
// 缓存未命中,执行获取函数
data = await fetchFunction();
await this.set(key, data, ttl);
}
return data;
}
}
// 使用示例
const dataCache = new DataCache();
// 带缓存的API调用
async function getCachedUserProfile(userId) {
return await dataCache.getWithCache(
`user_profile_${userId}`,
() => apiManager.fetchUserData(userId),
10 * 60 * 1000 // 10分钟缓存
);
}注意事项
在Manifest V3中,Service Worker有以下限制:
- 不支持DOM操作
- 无法使用同步存储API
- 生命周期受限,可能会被浏览器终止
- 需要使用Promise-based API
5.6 错误处理和日志
5.6.1 统一错误处理
// 错误处理管理器
class ErrorHandler {
constructor() {
this.errorLog = [];
this.maxLogSize = 100;
}
logError(error, context = '') {
const errorInfo = {
message: error.message,
stack: error.stack,
context: context,
timestamp: new Date().toISOString(),
url: location.href
};
this.errorLog.push(errorInfo);
// 保持日志大小限制
if (this.errorLog.length > this.maxLogSize) {
this.errorLog = this.errorLog.slice(-this.maxLogSize);
}
// 存储到本地
chrome.storage.local.set({ 'errorLog': this.errorLog });
console.error('捕获错误:', errorInfo);
}
async getErrorLog() {
const result = await chrome.storage.local.get(['errorLog']);
return result.errorLog || [];
}
clearErrorLog() {
this.errorLog = [];
chrome.storage.local.remove(['errorLog']);
}
}
const errorHandler = new ErrorHandler();
// 全局错误捕获
self.addEventListener('error', (event) => {
errorHandler.logError(event.error, '全局错误');
});
self.addEventListener('unhandledrejection', (event) => {
errorHandler.logError(new Error(event.reason), 'Promise拒绝');
});实践建议
- 性能监控: 定期监控后台脚本的性能
- 资源管理: 及时清理不需要的数据和监听器
- 错误恢复: 实现优雅的错误处理和恢复机制
- 调试工具: 使用Chrome开发者工具调试Service Worker
总结
本章介绍了Chrome扩展Background Scripts的核心概念和实践应用。掌握Service Worker的生命周期管理、事件监听机制、定时任务处理和数据管理是开发高效Chrome扩展的基础。在下一章中,我们将学习如何开发用户交互的Popup弹窗界面。
