一、Chrome Extension 简介
Chrome Extension,本质上是一个由 HTML、CSS、JavaScript 等前端技术开发的程序,就像我们平时开发的前端项目一样,它只是一个有各种资源组成的程序,被安装到浏览器后,能极大地扩展浏览器的功能。Chrome Extension 可以理解为一个独立运行在 Chrome 浏览器下的 APP,能够与打开的网页、Chrome 控制面板、第三方插件等进行通信。比如,它可以实现屏蔽广告(如 Adblock Plus)、帮助开发者进行调试开发(如 React Developers Tools)、自动更换壁纸(如 Momentum)、解决跨域问题(如 Allow CORS)、翻译网页内容等诸多功能。2009 年,Google Chrome Web Store 推出,标志着 Chrome Extension 正式进入开发者社区。此后,随着 Chrome 浏览器用户基数的增加,Chrome Extension 也在不断发展。2010 年开始稳步增长,发布了许多实用的拓展;2013 年,Chrome App 和扩展合并;2014 年,采用 Material Design 风格并增加更多 API;2016 年,Google 宣布推出 Manifest V3 计划;2021 年,Manifest V3 正式发布;2022 年持续发展,到 2024 年 Manifest V2 将会被逐步弃用。使用 Chrome Extension 可以根据个人需求自定义浏览器功能,提高工作效率,改善隐私和安全,同时也为开发者创造了创新和实用的工具。它不仅是技术的体现,还能调整用户使用浏览器的心态,让用户更加舒适、高效地浏览网页。
二、开发基础
1.基本组成
Chrome Extension 主要由以下几个部分组成:
manifest.json:这是扩展的核心配置文件,就像项目的 “说明书” 一样,详细列出了扩展的名称、版本、描述、权限等重要信息。它位于扩展的根目录,是 Chrome 浏览器识别和运行扩展的关键。例如,通过 “manifest_version” 指定清单文件的版本,目前逐渐向 Manifest V3 过渡,到 2024 年 Manifest V2 将被逐步弃用。“name” 定义扩展的名称,方便用户识别;“version” 明确扩展的版本号,便于开发者进行版本管理;“description” 提供扩展的描述,帮助用户了解其功能。此外,“icons” 属性可以设置不同尺寸的图标,适应不同的显示场景,如在扩展管理页面、安装过程以及浏览器工具栏上的显示。“browser_action” 或 “page_action” 配置项可以定义扩展在浏览器工具栏上的表现行为,包括图标、标题和点击图标时弹出的页面等。“permissions” 则用于声明扩展所需的权限,确保扩展能够正常运行并访问必要的资源。总之,manifest.json 是 Chrome Extension 不可或缺的重要组成部分。
background script:可以理解为插件运行在浏览器中的一个后台 “网站” 或脚本,与当前浏览页面无关。它通常包含对扩展很重要的浏览器事件的侦听器,处于休眠状态,直到触发事件才执行相应的逻辑。有效的后台脚本仅在需要时加载,并在空闲时卸载。例如,可以调用全部的 Chrome API,实现跨域请求、网页截屏、弹出 Chrome 通知消息等功能。在 manifest.json 文件中,通过 “background” 配置项来指定后台脚本的相关信息,如 “scripts” 属性可以指定要执行的脚本文件。
content script:是在网页上下文中运行的文件,能够读取浏览器访问的网页的详细信息,对其进行更改,并将信息传递给父级扩展。它可以操作 DOM,但是和页面其他的脚本是隔离的,访问不到其他脚本定义的变量、函数等,相当于运行在单独的沙盒里。在 manifest.json 中,通过 “content_scripts” 属性来配置内容脚本,包括匹配的域名、要执行的脚本文件以及脚本运行的时刻等。
popup:当用户点击插件图标时弹出的页面,包含 HTML、CSS 和 JavaScript 文件。它会在每次点击插件图标时重新载入,可以实现与用户的交互功能。在 manifest.json 中,通过 “browser_action” 或 “page_action” 的 “default_popup” 属性来指定弹出页面的路径。
这些组成部分相互协作,共同构成了功能强大的 Chrome Extension。
2.开发准备
开发 Chrome Extension 非常简单,仅需 Chrome 浏览器和一个带语法高亮的文本编辑器即可。首先,打开 Chrome 浏览器,在地址栏中输入 “chrome://extensions/”,进入扩展程序管理页面,然后开启开发者模式。这样就可以通过加载已解压的扩展程序来进行本地开发和调试。对于文本编辑器,可以选择 Visual Studio Code、Sublime Text 等,它们提供了丰富的插件和语法高亮功能,方便开发者编写和编辑 Chrome Extension 的代码。在开发过程中,可以利用 Chrome 浏览器提供的开发者工具来调试扩展程序,查看日志输出、检查元素等,提高开发效率。总之,开发 Chrome Extension 所需的工具简单易获取,使得开发者能够快速上手并实现各种创意功能。
三、开发步骤
1.创建 manifest
manifest.json 文件是 Chrome Extension 的核心配置文件,它定义了插件的基本属性信息和运行路径等。在创建这个文件时,我们需要明确以下几个关键部分:
manifest_version:指定清单文件的版本。目前,逐渐向 Manifest V3 过渡,到 2024 年 Manifest V2 将被逐步弃用。这个版本号的选择会影响到插件能够使用的 API 和功能。name:插件的名称,应简洁明了,方便用户识别。例如,可以根据插件的功能来命名,如 “广告拦截器”、“语法检查助手” 等。
version:插件的版本号,便于开发者进行版本管理。每次对插件进行更新时,应该相应地增加版本号,以便用户了解插件的更新情况。
description:对插件功能的简短描述,帮助用户在安装之前了解插件的用途。描述应该清晰、准确,突出插件的主要特点和优势。
icons:定义一系列图标,用于在不同的场景下显示插件的图标。可以根据需要提供不同尺寸的图标,以适应不同的显示环境。例如,可以提供 16x16、32x32、48x48 等尺寸的图标。
browser_action或page_action:配置插件在浏览器工具栏上的表现行为。可以定义图标、标题和点击图标时弹出的页面等。如果插件对在浏览器中加载的所有网页都生效,可以选择browser_action;如果只针对特定的网页生效,则可以选择page_action。
permissions:声明插件所需的权限,确保插件能够正常运行并访问必要的资源。例如,如果插件需要访问存储功能,就需要在permissions中添加 “storage” 权限。
总之,创建 manifest.json 文件是开发 Chrome Extension 的重要一步,它为插件的运行提供了基本的配置信息和权限声明。
2.加载插件
加载插件的方法非常简单。首先,在地址栏中输入 “chrome://extensions/”,进入扩展程序管理页面。然后,开启开发者模式,这将允许我们加载未经过 Chrome Web Store 审核的本地插件。接着,点击左上角的 “加载已解压的扩展程序”,并选择插件所在的目录。这样,插件就会被成功载入到 Chrome 浏览器中。
需要注意的是,插件不会热更新,每次修改代码后,需要点击扩展程序管理页面中的刷新按钮,才能载入最新的代码。此外,为了方便使用,可以将插件固定到标签栏里,这样可以快速访问插件的功能。
3.添加功能
注册 background.js,作为后台脚本初始化事件监听并设置存储初始值。
background.js 是一种后台脚本,它在插件安装或重新加载时被扫描并初始化。在这个脚本中,我们可以添加事件监听器,以响应各种浏览器事件。例如,可以在插件安装完毕后,设置一个初始值为空数组的存储字段,以便后续存储用户的访问历史。
以下是一个示例代码:
chrome.runtime.onInstalled.addListener(() => { console.log('后台脚本运行成功!') chrome.storage.sync.set({ history: [] }); });
这段代码在插件安装后,打印一段日志信息,并通过 storage API 设置一个初始值为空数组的存储字段。覆盖默认 popup 界面,展示用户访问历史,包括从 storage 读取历史内容并组装成 html 插入文档。为了展示用户的访问历史,我们需要覆盖默认的 popup 界面。在 manifest.json 文件中,可以通过 “action” 配置项的 “default_popup” 属性来指定弹出页面的路径。例如:
{ "action": { "default_popup": "popup.html" } }
在 popup.html 文件中,可以使用 HTML、CSS 和 JavaScript 来构建用户界面。以下是一个示例代码:
html> "stylesheet" href="popup.css" > "container">暂无浏览记录~
在 popup.js 文件中,可以从 storage 中读取历史内容,并将其组装成 html 插入到文档中。以下是一个示例代码:
chrome.storage.sync.get("history" , ({ history }) => { const contentHTML = history.length === 0? "暂无浏览记录~" : history.map((record) => { return ``; }).join("" ); document.querySelector('#container' ).innerHTML = contentHTML });
通过内容脚本记录浏览历史,将访问页面的标题、url 和时间存储到 storage。为了记录用户的浏览历史,我们需要在内容脚本中编写记录的逻辑。在 manifest.json 文件中,可以通过 “content_scripts” 配置项来指定要注入到网页中的脚本。例如:
{ "content_scripts" : [{ "matches" : ["*://*/*" ], "js" : ["content/index.js" ] }] }
在 content/index.js 文件中,可以获取访问的页面的标题、url 和时间,并将其存储到 storage 中。以下是一个示例代码:
chrome.storage.sync.get("history" , ({ history }) => { console.log("history--->" , history ); history.unshift({ title: document.title, url: location.href, time: new Date().toLocaleString(), }); chrome.storage.sync.set({ history }); });
4.添加自定义 icon
为了让插件更加个性化,可以使用熊猫图片作为插件的 icon。首先,在插件目录中新增一个 assets 目录,并将熊猫图片命名为 icon.png 放入该目录。然后,在 manifest.json 文件中,通过 “action” 配置项的 “default_icon” 属性来指定 icon 的路径。例如:
{ "action" : { "default_icon" : { "16" : "/assets/icon.png" } } }
这样,插件就会使用熊猫图片作为 icon 显示在浏览器工具栏上。
四、开发注意事项
1.chrome 传递消息
chrome 传递消息的 API 在不同版本有变化,需进行兼容处理。在开发 Chrome Extension 的过程中,需要注意 chrome 传递消息的 API 在不同版本有变化。为了确保插件在不同版本的 Chrome 浏览器上都能正常运行,可以进行兼容处理。以下是一段兼容处理的代码示例:
function compatibleChrome () { if (!chrome.runtime) { // Chrome 20 - 21 chrome.runtime = chrome.extension; } else if (!chrome.runtime.onMessage) { // Chrome 22 - 25 chrome.runtime.onMessage = chrome.extension.onMessage; chrome.runtime.sendMessage = chrome.extension.sendMessage; chrome.runtime.onConnect = chrome.extension.onConnect; chrome.runtime.connect = chrome.extension.connect; } }
通过这段代码,可以在不同版本的 Chrome 浏览器中实现对传递消息 API 的兼容,确保插件能够正常接收和发送消息。
2.本地存储
本地存储 Localstorage 只能设置字符串,json 需转成字符串形式,配置 background.js 可随时读取本地数据。在使用 Chrome Extension 的本地存储 Localstorage 时,需要注意它只能设置字符串类型的值。如果要存储 JSON 对象,需要将其转换为字符串形式。例如:
// 设置 JSON 对象到 Localstorage const jsonObject = { key: 'value' }; window.localStorage.setItem('domain:key' , JSON.stringify(jsonObject)); // 从 Localstorage 获取 JSON 对象 const storedValue = window.localStorage.getItem('domain:key' ); const parsedObject = JSON.parse(storedValue);
此外,如果要随时读取本地数据,可以在 manifest.json 中配置 background.js。例如:
{ "background" : { "scripts" : ["js/background.js" ] } }
这样,content_script 里面的代码就可以随时读取 Localstorage 里面的数据,避免了需要 Browser_action 的 popup.html 一直打开的情况。通过这种方式,可以更加方便地管理和使用插件的本地存储数据。
五、开发案例展示
1.git 提交的 emoji 速查工具
此工具由一个manifest.json文件、html文件和一个png图片文件组成。功能非常简单,点击弹出一个html页面,以达到速查的目的,html里可以替换成任何想要的内容。以下是manifest.json的基本配置:
{"manifest_version" :2,"name" :"git commit emoji速查" ,"description" :"git commit emoji对照表" ,"version" :"1.0.0" ,"browser_action" :{ "default_icon" :"icon.png" , "default_title" :"这是一个 git commit emoji 速查的Chrome插件" , "default_popup" :"popup.html" } }
2.自定义右键菜单
鼠标右键的菜单使用chrome.contextMenus实现。例如:
chrome.contextMenus.create({id: 'page' ,title: '测试右键菜单' }); chrome.contextMenus.create({id: 'baidu-search' ,title: '使用百度搜索:%s' ,contexts: ['selection' ]}); chrome.contextMenus.onClicked.addListener(function (info, tab) { switch(info.menuItemId){ case 'baidu-search' : chrome.tabs.create({url: 'https://www.baidu.com/s?ie=utf-8&wd=' + encodeURI(info.selectionText)}); break ; } });
3.覆盖特定页面
在manifest.json里添加chrome_url_overrides,可以被替换的只有新标签页newtab、历史记录页history、书签页bookmarks这三个选项,但是一个插件只能重写一个默认页。例如:
"chrome_url_overrides" :{ "newtab" :"newtab.html" }
4.开发者工具自定义面板
通过chrome.devtools API来实现自定义开发者工具面板,大家熟悉的 vue 插件就是通过这种方式实现。例如:
// 几个参数依次为:panel标题、图标(其实设置了也没地方显示)、要加载的页面、加载成功后的回调 chrome.devtools.panels.create("TestPanel" , "" , "devtools.html" , function (panel) { console.log("自定义面板创建成功!" , panel); }); // 创建自定义侧边栏 chrome.devtools.panels.elements.createSidebarPane("Images" , function (sidebar) { sidebar.setExpression('document.querySelectorAll("img")' , 'All Images' ); });
5.选项页
选项页实际上是指插件的详细信息介绍,配置在manifest.json里的options_ui。例如:
"options_ui" :{ "page" :"options.html" , "browser_style" :true }
6.搜索建议
在manifest.json里配置触发关键词,配置后再地址栏输入关键词后按空格键触发插件搜索建议。例如:
"omnibox" :{ "keyword" :"go" } chrome.omnibox.onInputChanged.addListener((text, suggest) => { console.log('inputChanged: ' + text); if (!text) return ; if (text == 'c' ) { suggest([{ content: 'extension' + text, description: 'chrome://extension' }, { content: 'bookmarks' + text, description: 'chrome://bookmarks' }, { content: 'history' + text, description: 'chrome://history' }]); } }); chrome.omnibox.onInputEntered.addListener((text) => { console.log('inputEntered: ' + text); if