构建一个Chrome扩展 | 原创,AI翻译

Home PDF

你是否曾经打开过太多浏览器标签页,并希望有一个工具来自动管理它们?在本博客文章中,我们将详细介绍如何创建一个名为“Tabs Killer”的Chrome扩展程序,它会在标签页数量超过用户定义的限制时自动关闭最旧的标签页。我将逐步分解代码,解释其工作原理,并提供见解,帮助你构建自己的Chrome扩展程序。

在本文结束时,你将了解Chrome扩展的结构、如何使用Chrome API以及如何创建一个带有设置的弹出界面。


“Tabs Killer” 干什么?

“Tabs Killer”是一个Chrome扩展程序,它:

该扩展程序包括一个用于配置设置的弹出界面和一个用于处理标签页管理的后台脚本。


项目结构

这是“Tabs Killer”扩展程序的文件结构:

tabs-killer/
├── manifest.json         # 扩展配置
├── popup.html            # 弹出界面
├── popup.js             # 弹出逻辑
├── background.html       # 后台页面
├── app.build.js          # 主应用逻辑(假设)
├── js/
│   ├── lib/              # 外部库(jQuery、Underscore、Bootstrap、RequireJS)
│   ├── tabmanager.js     # 标签页管理逻辑(假设)
│   └── settings.js       # 设置管理(假设)
├── css/
│   └── popup.css         # 弹出样式
└── img/
    ├── icon16.png        # 16x16 图标
    ├── icon48.png        # 48x48 图标
    └── icon128.png       # 128x128 图标

第1步:清单文件(manifest.json

manifest.json 文件是任何Chrome扩展程序的核心。它定义了元数据、权限和关键组件。

{
  "manifest_version": 2,
  "name": "Tabs Killer",
  "description": "当标签页过多时自动关闭最旧的标签页。",
  "version": "1.0",
  "browser_action": {
    "default_icon": "img/icon128.png",
    "default_popup": "popup.html"
  },
  "icons": {
    "128": "img/icon128.png",
    "48": "img/icon48.png",
    "16": "img/icon16.png"
  },
  "background": {
    "page": "background.html"
  },
  "permissions": [
    "tabs",
    "storage"
  ],
  "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}

说明:


第2步:弹出界面(popup.html

当用户点击扩展图标时,弹出界面会出现。它使用 Bootstrap 进行样式设置,并包括一个带有“选项”部分的选项卡界面。

<!doctype html>
<html>
<head>
  <title>Tabs Killer 扩展的弹出界面</title>
  <link rel="stylesheet" href="js/lib/bootstrap/css/bootstrap.css" type="text/css"/>
  <link rel="stylesheet" href="css/popup.css"/>
  <script src="js/lib/jquery.min.js"></script>
  <script src="js/lib/underscore.js"></script>
  <script src="js/lib/bootstrap/js/bootstrap.min.js"></script>
  <script src="js/lib/bootstrap/js/bootstrap-tab.js"></script>
  <script src="js/lib/require.js"></script>
  <script src="app.build.js"></script>
  <script src="popup.js"></script>
</head>
<body>
  <ul class="nav nav-tabs">
    <li><a href="#tabOptions" target="#tabOptions" data-toggle="tab">选项</a></li>
  </ul>
  <div class="tab-content">
    <div class="tab-pane active" id="tabOptions">
      <form class="well">
        <fieldset>
          <legend>设置</legend>
          <p>
            <label for="maxTabs">保持的最大标签页数</label>
            <input type="text" id="maxTabs" class="span1" name="maxTabs"> 个标签页
          </p>
        </fieldset>
        <div id="status" class="alert alert-success invisible"></div>
        <fieldset>
          <legend>自动锁定</legend>
          <label for="white-list-input">包含字符串的标签页 URL:</label>
          <input type="text" id="white-list-input"/>
          <button class="btn-mini add-on" disabled id="white-list-add">添加</button>
          <table class="table table-bordered table-striped" id="white-list">
            <thead>
              <tr>
                <th>URL 模式</th>
                <th></th>
              </tr>
            </thead>
            <tbody></tbody>
          </table>
        </fieldset>
      </form>
    </div>
  </div>
  <script type="text/html" id="url-item-template">
    <tr>
      <td><%=url%></td>
      <td><a class="deleteLink" href="#">移除</a></td>
    </tr>
  </script>
</body>
</html>

说明:


第3步:弹出逻辑(popup.js

此脚本处理弹出界面的交互性,例如保存设置和管理白名单。

require([], function () {
  var GlobalObject = chrome.extension.getBackgroundPage().GlobalObject;

  Popup = {};
  Popup.optionsTab = {};

  Popup.optionsTab.init = function (context) {
    function onBlurInput() {
      var key = this.id;
      Popup.optionsTab.saveOption(key, $(this).val());
    }
    $('#maxTabs').keyup(_.debounce(onBlurInput, 200));
    Popup.optionsTab.loadOptions();
  };

  Popup.optionsTab.loadOptions = function () {
    $('#maxTabs').val(GlobalObject.settings.get('maxTabs'));
    var whiteList = GlobalObject.settings.get('whiteList');
    Popup.optionsTab.buildWhiteListTable(whiteList);

    var $whiteListInput = $('#white-list-input');
    var $whiteListAdd = $('#white-list-add');

    var isValid = function (pattern) {
      return /\S/.test(pattern);
    };

    $whiteListInput.on('input', function () {
      if (isValid($whiteListInput.val())) {
        $whiteListAdd.removeAttr('disabled');
      } else {
        $whiteListAdd.attr('disabled', 'disabled');
      }
    });

    $whiteListAdd.click(function () {
      if (!isValid($whiteListInput.val())) return;
      whiteList.push($whiteListInput.val());
      $whiteListInput.val('').trigger('input').focus();
      Popup.optionsTab.saveOption('whiteList', whiteList);
      Popup.optionsTab.buildWhiteListTable(whiteList);
    });
  };

  Popup.optionsTab.saveOption = function (key, value, hideStatus) {
    if (!hideStatus) $('#status').html('');
    GlobalObject.settings.set(key, value);
    if (!hideStatus) {
      $('#status').removeClass('invisible').css('opacity', '100')
        .html('保存中...').delay(50).animate({opacity: 0});
    }
  };

  Popup.optionsTab.buildWhiteListTable = function (whiteList) {
    var urlItemTemplate = _.template($("#url-item-template").html());
    var $wlTable = $('table#white-list tbody');
    $wlTable.html('');
    for (var i = 0; i < whiteList.length; i++) {
      var $tr = $(urlItemTemplate({url: whiteList[i]}));
      var $deleteLink = $tr.find('a.deleteLink').parent();
      $deleteLink.click(function () {
        whiteList.splice(whiteList.indexOf($(this).data('pattern')), 1);
        Popup.optionsTab.saveOption('whiteList', whiteList, true);
        Popup.optionsTab.buildWhiteListTable(whiteList);
      }).data('pattern', whiteList[i]);
      $wlTable.append($tr);
    }
  };

  $(document).ready(function () {
    $('a[data-toggle="tab"]').on('show', function (e) {
      var tabId = e.target.hash;
      if (tabId === '#tabOptions') {
        Popup.optionsTab.init($('div#tabOptions'));
      }
    });
    $('a[href="#tabOptions"]').click();
  });
});

说明:


第4步:后台逻辑(background.html 和假设脚本)

后台页面(background.html)持续运行并加载核心逻辑。

// background.js(假设,基于提供的片段)
GlobalObject = {};

require(['tabmanager', 'settings'], function (tabmanager, settings) {
  var startup = function () {
    GlobalObject.settings = settings;
    GlobalObject.tabmanager = tabmanager;
    settings.init();
    tabmanager.init();
  };
  startup();
});

假设:

假设的 tabmanager.js 示例:

var tabmanager = {
  init: function () {
    chrome.tabs.onCreated.addListener(this.checkTabCount);
  },
  checkTabCount: function () {
    chrome.tabs.query({}, function (tabs) {
      var maxTabs = GlobalObject.settings.get('maxTabs') || 10;
      var whiteList = GlobalObject.settings.get('whiteList') || [];
      if (tabs.length > maxTabs) {
        var tabsToRemove = tabs.filter(tab => !whiteList.some(pattern => tab.url.includes(pattern)));
        chrome.tabs.remove(tabsToRemove[0].id); // 移除最旧的标签页
      }
    });
  }
};

如何测试扩展程序

  1. 打开 Chrome 并转到 chrome://extensions/
  2. 启用“开发者模式”(右上角切换)。
  3. 点击“加载已解压的扩展程序”并选择 tabs-killer 文件夹。
  4. 点击扩展图标打开弹出界面并测试设置。

编写自己的 Chrome 扩展程序的技巧

  1. 从小开始:从一个简单的清单和一个弹出或后台脚本开始。
  2. 使用 Chrome API:根据需要利用 chrome.tabschrome.storage 等。
  3. 调试:使用 console.log 和 Chrome 的开发者工具(右键点击弹出界面 > 检查)。
  4. 安全性:在生产中避免使用 unsafe-eval;使用更严格的内容安全策略。
  5. UI 库:Bootstrap 和 jQuery 简化了 UI 开发,但保持扩展程序轻量。

结论

“Tabs Killer” 展示了如何结合弹出界面、后台逻辑和 Chrome API 创建一个功能扩展程序。有了这个基础,你可以进一步自定义它——添加通知、改进标签页关闭逻辑或增强 UI。

欢迎实验代码并分享你自己的 Chrome 扩展程序想法!编码愉快!


Back 2025.04.02 Donate