从零到一:开发一个Chrome浏览器使用时长统计扩展

70 阅读6分钟

前言

作为一个重度浏览器用户,你是否想过自己每天在各个网站上花费了多少时间?今天我将分享如何从零开始开发一个Chrome扩展,用于统计网站使用时长。这个项目涵盖了现代Chrome扩展开发的核心技术栈:Manifest V3Service WorkerChrome APIs以及现代前端技术

项目地址

Github 地址

项目概述

我们要开发的扩展具备以下功能:

  • ✅ 自动统计各网站的使用时长
  • ✅ 智能检测用户活跃状态(空闲时暂停计时)
  • ✅ 本地数据存储与历史记录管理
  • ✅ 美观的弹窗界面展示统计数据
  • ✅ 数据重置与实时刷新功能

技术亮点:

  • 使用Manifest V3最新标准
  • Service Worker架构设计
  • 多脚本协同工作(Background + Content Script)
  • 现代CSS设计与响应式布局

1. 项目架构设计

1.1 文件结构规划

website-timer-extension/
├── manifest.json          # 扩展配置文件
├── background.js           # 后台服务脚本
├── content.js             # 内容脚本
├── popup/                 # 弹窗界面
│   ├── popup.html         # 弹窗HTML
│   ├── popup.js           # 弹窗逻辑
│   └── popup.css          # 弹窗样式
└── icons/                 # 扩展图标
    ├── icon16.png
    ├── icon32.png
    ├── icon48.png
    └── icon128.png

1.2 技术架构图

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Content       │    │   Background    │    │   Popup         │
│   Script        │────│   Service       │────│   Interface     │
│   (页面监听)      │    │   Worker        │    │   (数据展示)      │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                        │                        │
         │                        │                        │
         ▼                        ▼                        ▼
┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   页面活动       │    │   时间统计       │    │   数据可视化     │
│   状态检测       │    │   数据存储       │    │   用户交互       │
└─────────────────┘    └─────────────────┘    └─────────────────┘

2. Manifest V3配置详解

2.1 核心配置解析

首先创建manifest.json,这是扩展的"身份证":

{
  "manifest_version": 3,
  "name": "网站使用时长统计器",
  "version": "1.0",
  "description": "统计并显示每日各网站的使用时长",
  
  "permissions": [
    "tabs",        // 访问标签页信息
    "storage",     // 本地存储
    "idle",        // 检测用户空闲状态
    "activeTab"    // 访问当前活跃标签页
  ],
  
  "host_permissions": [
    "<all_urls>"   // 访问所有网站(用于注入content script)
  ],
  
  "background": {
    "service_worker": "background.js"  // Manifest V3使用Service Worker
  },
  
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content.js"],
    "run_at": "document_start"  // 页面开始加载时立即运行
  }],
  
  "action": {
    "default_popup": "popup/popup.html",
    "default_title": "网站使用时长统计"
  }
}

2.2 Manifest V2 vs V3 关键差异

特性Manifest V2Manifest V3
后台脚本Background PageService Worker
权限系统宽泛权限精细化权限控制
API调用同步调用Promise-based异步
安全性较低显著提升

3. Service Worker核心逻辑

3.1 设计思路

Service Worker是整个扩展的"大脑",负责:

  1. 监听标签页切换事件
  2. 计算并记录使用时长
  3. 管理数据存储和清理
  4. 检测用户活跃状态

3.2 WebsiteTimer类设计

class WebsiteTimer {
  constructor() {
    this.activeTabInfo = null;      // 当前活跃标签页信息
    this.lastActiveTime = null;     // 最后活跃时间戳
    this.isUserActive = true;       // 用户活跃状态
    this.currentDay = this.getTodayKey();
    this.initializeEventListeners();
  }
  
  // 核心方法概览
  handleTabChange()      // 处理标签页切换
  saveTimeSpent()        // 保存时间统计
  recordVisit()          // 记录访问次数
  handleIdleState()      // 处理空闲状态
}

3.3 关键技术实现

标签页监听

// 监听标签页激活
chrome.tabs.onActivated.addListener((activeInfo) => {
  this.handleTabChange(activeInfo.tabId);
});

// 监听URL变化
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === 'complete' && tab.active) {
    this.handleTabChange(tabId);
  }
});

智能时间统计

async saveTimeSpent() {
  if (!this.activeTabInfo || !this.lastActiveTime) return;
  
  const timeSpent = Date.now() - this.lastActiveTime;
  
  // 过滤过短访问(<10秒不统计)
  if (timeSpent < 10000) {
    console.log(`访问时间过短,跳过统计`);
    return;
  }
  
  // 更新存储数据
  const result = await chrome.storage.local.get([this.currentDay]);
  const todayData = result[this.currentDay] || {};
  
  if (!todayData[domain]) {
    todayData[domain] = {
      timeSpent: 0,
      visits: 0,
      lastTitle: this.activeTabInfo.title
    };
  }
  
  todayData[domain].timeSpent += timeSpent;
  await chrome.storage.local.set({ [this.currentDay]: todayData });
}

4. Content Script页面监听

4.1 为什么需要Content Script?

虽然Service Worker可以监听标签页切换,但无法检测页面内的用户活跃度。Content Script运行在页面上下文中,能够:

  • 监听用户交互事件(点击、滚动、键盘输入)
  • 检测页面可见性变化
  • 提供更精确的活跃度判断

4.2 PageActivityDetector实现

class PageActivityDetector {
  constructor() {
    this.lastActivityTime = Date.now();
    this.isPageVisible = !document.hidden;
    this.activityEvents = ['mousedown', 'mousemove', 'keypress', 'scroll'];
    this.setupEventListeners();
  }
  
  setupEventListeners() {
    // 页面可见性变化
    document.addEventListener('visibilitychange', () => {
      this.isPageVisible = !document.hidden;
      this.reportActivity();
    });
    
    // 用户活动事件
    this.activityEvents.forEach(eventType => {
      document.addEventListener(eventType, () => {
        this.handleUserActivity();
      }, { passive: true });
    });
  }
}

5. 弹窗界面开发

5.1 现代CSS设计理念

采用渐变背景卡片式布局,提升视觉效果:

.header {
  background: linear-gradient(135deg, #ff7b4c 0%, #fed9c4 100%);
  color: white;
  padding: 20px;
  text-align: center;
}

.website-item:hover {
  background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
  transition: background-color 0.2s ease;
}

5.2 数据渲染优化

renderWebsitesList() {
  // 过滤短时访问(<1分钟)
  const filteredWebsites = websites.filter(([domain, data]) => {
    return data.timeSpent >= 60000;
  });
  
  // 按使用时间排序
  filteredWebsites.sort((a, b) => b[1].timeSpent - a[1].timeSpent);
  
  // 限制显示数量(最多50个)
  const displayWebsites = filteredWebsites.slice(0, 50);
}

5.3 实时数据更新

// 监听存储变化,实时更新界面
chrome.storage.onChanged.addListener((changes, areaName) => {
  if (areaName === 'local') {
    const today = new Date().toISOString().split('T')[0];
    if (changes[today]) {
      popupManager.loadAndDisplayData();
    }
  }
});

6. 数据存储策略

6.1 存储结构设计

// 数据结构示例
{
  "2024-03-15": {
    "github.com": {
      "timeSpent": 3600000,  // 毫秒
      "visits": 12,
      "lastTitle": "GitHub Homepage",
      "lastVisit": 1710504000000
    },
    "stackoverflow.com": {
      "timeSpent": 1800000,
      "visits": 5,
      "lastTitle": "JavaScript Questions"
    }
  }
}

6.2 数据清理策略

async resetDailyData() {
  // 只保留最近7天数据
  const sevenDaysAgo = new Date();
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
  const cutoffDate = sevenDaysAgo.toISOString().split('T')[0];
  
  const keysToRemove = oldKeys.filter(key => key < cutoffDate);
  if (keysToRemove.length > 0) {
    await chrome.storage.local.remove(keysToRemove);
  }
}

7. 性能优化技巧

7.1 防抖处理

// 定期保存数据(避免频繁写入)
setInterval(() => {
  websiteTimer.forceSave();
}, 30000); // 30秒保存一次

7.2 内存管理

// 清理短时访问记录
async cleanupShortVisits() {
  const cleanedData = {};
  Object.entries(todayData).forEach(([domain, data]) => {
    if (data.timeSpent >= 30000) { // 保留>=30秒的记录
      cleanedData[domain] = data;
    }
  });
}

8. 开发调试技巧

8.1 Chrome扩展调试

  1. Service Worker调试

    • 访问 chrome://extensions/
    • 点击"检查视图 service worker"
    • 查看Console日志
  2. 存储数据查看

    • 开发者工具 → Application → Storage → Extension storage
  3. 权限验证

    // 检查权限是否正确授予
    chrome.permissions.contains({
      permissions: ['tabs', 'storage']
    }, (result) => {
      console.log('权限状态:', result);
    });
    

8.2 常见问题排查

问题可能原因解决方案
时间统计不准确事件监听器未正确绑定检查Chrome APIs权限
界面数据不更新存储变化监听失效验证storage.onChanged
Service Worker崩溃内存泄漏或异常添加错误处理和日志

9. 扩展功能建议

基于当前架构,可以轻松扩展以下功能:

9.1 数据可视化

  • 使用Chart.js添加使用趋势图表
  • 周/月统计报告
  • 网站分类标签

9.2 生产力功能

  • 设定使用时长目标和提醒
  • 网站黑名单/白名单
  • 专注模式(屏蔽分散注意力网站)

9.3 数据导出

  • 导出CSV/JSON格式数据
  • 云端数据同步
  • 跨设备数据共享

10. 总结与思考

通过这个项目,我们深入学习了:

  • Manifest V3的核心概念和最佳实践
  • Service Worker在扩展开发中的应用
  • Chrome APIs的高效使用
  • 现代前端技术在扩展UI中的运用

10.1 关键收获

  1. 架构设计的重要性:良好的模块化设计让功能扩展变得简单
  2. 用户体验细节:智能的时间过滤、美观的界面提升了实用性
  3. 性能优化意识:合理的数据清理和存储策略保证了长期稳定性