javascript-Chrome扩展实例(一) Jan 10, 2023 · ecmascript javascript · 分享到: Chrome扩展实例(一) 在Chrome扩展入门中,我们写了一个最简单的形式化Hello插件,这个插件除了点击之后显示写字符外,没有任何实际作用,在这一章节中我们将写一些具备简单功能的扩展。这些扩展需要通过后台、前台或二者配合实现,主要编程语言是JavaScript,使用Chrome浏览器提供的API。我们更希望在开发实例中逐步学习Chrome扩展的内容。 Chrome API文档:https://developer.chrome.com/docs/extensions/reference/ Service Worker后台服务 后台服务实例:将激活的Tab页面移至首位 扩展的权限管理 自定义扩展使用permission的实例 使用扩展修改原网页 分离网页元素和脚本 扩展的上下文 选项设置页面 总结 参考文档 Service Worker后台服务 Service Worker是Chrome扩展在后台运行并监听和响应事件的服务程序,具有相当大的权限,几乎可以使用所有的Chrome API,作为扩展的支柱存在,稍微复杂一些的扩展都会需要service worker。在过去的版本中,service worker原名叫background算是非常贴切的名称了。 Service Worker是事件驱动的服务,平时处于后台静默或休眠状态,当注册在Chrome的API监听到相应事件时才会启动。Service worker注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和截拦作用域范围内所有页面的Web请求。 后台服务实例:将激活的Tab页面移至首位 我们下面用一个例子了解下插件的后台service worker是如何工作的。该例子的作用是将处于激活状态的tab页面移至首位,例如当我们点击某个页面时,该页面的标签页就会被移到首位。为了方便,该例子不会用到popup,conetent等其他页面,只用后台的service worker。 首先必然是写配置清单manifest.json。 1{ 2 "name": "MoveToFisrt", 3 "description": "Demo Extension to move the activated tab to the first place", 4 "version": "1.0", 5 "manifest_version": 3, 6 "background": { 7 "service_worker": "background.js" 8 } 9} 清单中多出的配置项“background”就是用来指定service worker。manifes.json中使用的都是相对路径,因此我们在manifest.json同级目录添加background.js文件。其主要功能就是将激活的标签页移至首位。 1//background.js 2 3//添加tab激活的事件监听器,事件触发后调用回调函数moveToFirstPosition 4//该addListener的回调函数形式:(activeInfo: object) => void 5//activeInfo {tabID:tabID , windowId:windowId} 6chrome.tabs.onActivated.addListener(moveToFirstPosition); 7 8//定义回调函数moveToFirstPosition 9async function moveToFirstPosition(activeInfo) { 10 try { 11 // 将当前的tab移到首位即index:0。 12 await chrome.tabs.move(activeInfo.tabId, {index: 0}); 13 console.log("Success."); 14 } catch (error) { 15 if (error == "Error: Tabs cannot be edited right now (user may be dragging a tab).") { 16 //如果标签无法移动,那么等50ms再尝试 17 setTimeout(() => moveToFirstPosition(activeInfo), 50); 18 } else { 19 console.error(error); 20 } 21 } 22} 这段js代码难度并不大,但是Chrome API有一个默认规则需要注意: 除非特殊说明,chrome.*的API都是异步的。因此调用后会立即返回,而不会等待异步的操作完成。如果需要对异步操作的结果进行处理,需要使用回调函数、Promise或async...await...。 所以,在使用Chrome API开发时,我们会经常见到async ... await..的形式。关于这种语法,可以参考ES 8中新增的规范。 现在我们可以在Chrome浏览器中载入这个扩展并尝试了! 这个例子中我们只是用了后台的service worker(background.js),该js脚本向Chrome的tab接口添加了一个事件监听器,当一个tab标签被激活后,会触发该事件监听器从而执行后台功能(moveToFirstPosition函数)。Chrome的后台脚本基本都是使用事件驱动的模式,这样可以降低扩展对内存的消耗。 扩展的权限管理 在网络安全与数据隐私重要性日益凸显的当下,Chrome在V3版本的manifest.json中,着重考虑了安全与隐私,甚至到了不进行权限授予就不能使用大部分Chrome API的程度。当我们需要操作浏览器或用户的相关数据等Chrome API时,需要给予扩展相对应的权限(Permissions),从而帮助扩展程序或应用程序受到攻击时尽可能减小损失。扩展有四种类型的权限: permissions:包含API文档中特点的权限字段如(storage,geolocation) optional_permissions:类似permissions。但是在运行时由用户决定是否给予权限,而非事先决定。 host_permissions:通过匹配字段来匹配是否有相应权限。 optional_host_permissions:类似host_permissions使用匹配字段。但是在运行时由用户决定是否给予权限,而非事先决定。 扩展程序或应用程序必须在清单文件manifest.json中的 permissions字段中声明所需要的权限,否则Chrome API会拒绝被调用。关于那些API需要什么权限的细节,可以参考Chrome扩展的权限的文档。 自定义扩展使用permission的实例 下面我们要开发一个Chrome扩展,让用户可以改变当前网页的背景色,其中需要使用到存储权限。首先在该扩展的目录下创建manifest.json文件。 1{ 2 "name": "Coloring", 3 "description": "Demo Extension to change background color!", 4 "version": "1.0", 5 "manifest_version": 3, 6 "background": { 7 "service_worker": "background.js" 8 } 9} 注意:此时我们未向其添加权限permission信息。目前为止,这个manifest.json文件和上个例子“将激活的Tab页面移至首位”并无太大区别。接下来,我们编写该扩展的background.js文件。 1const color = "#3aa757"; //绿色 2 3chrome.runtime.onInstalled.addListener(()=>{ 4 //调用存储API,存储color变量。 5 chrome.storage.sync.set({color}); 6 console.log(`[Coloring] default background color is set to: ${color}`); 7}); 我们载入这个扩展,发现这个简单的脚本报错了: 错误原因是chrome.storage.sync是undefined,这是因为Chrome还没将storage接口授权给扩展,因此扩展无法获得chrome.storage.sync.*的API。我们要做就是在manifest.json中添加permissions字段,并在其中添加storage权限。 1{ 2 "name": "Coloring", 3 "description": "Demo Extension to change background color!", 4 "version": "1.0", 5 "manifest_version": 3, 6 "background": { 7 "service_worker": "background.js" 8 }, 9 "permissions":["storage"] 10} 重新加载扩展,可以看到background.js执行无误,并如预期那样在DevTools页面中输出日志。 1[Coloring] default background color is set to: #3aa757 background.js:6 这样我们就能在浏览器中存储了一个颜色数据,不过暂时也只是存储下,并没有实际的用途。 Tips:在重新载入扩展后,chrome://extensions/页面下的扩展依旧会显示有个错误按钮,那是因为扩展会保留之前的错误,而非重载之后依然有错,我们需要手动清除之前的错误信息。 使用扩展修改原网页 现在,我们需要将存储的背景颜色数据拿出来真正利用上。为了降低学习曲线,我们先只是用扩展工具栏上的popup页面来修改网页。结合上一篇文章《javascript-Chrome扩展入门》中popup页面的内容,我们增加popup页面与相关icon图标。首先,在manifest.json文件添加action和icon字段。 1 2{ 3 "name": "Coloring", 4 "description": "Demo Extension to change background color!", 5 "version": "1.0", 6 "manifest_version": 3, 7 "background": { 8 "service_worker": "background.js" 9 }, 10 "permissions": ["storage"], 11 "action": { 12 "default_popup": "popup.html", 13 "default_icon": { 14 "16": "images/get_started16.png", 15 "32": "images/get_started32.png", 16 "48": "images/get_started48.png", 17 "128": "images/get_started128.png" 18 } 19 }, 20 "icons": { 21 "16": "images/get_started16.png", 22 "32": "images/get_started32.png", 23 "48": "images/get_started48.png", 24 "128": "images/get_started128.png" 25 } 26} 其中,正如前一篇文章介绍的,action字段用于代表点击扩展工具栏中扩展图标的响应动作,default_popup是指点击图标后弹出的页面,本例中是popup.html这个页面。default_icon和icon下都是不同尺寸的图标图片,这个例子是借用了谷歌Chromemanifest V2版本的素材,链接:https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/_archive/mv2/tutorials/get_started。(如果链接失效了,自己找一些图片换上去也行) 关于扩展的图标。最好使用PNG格式,兼容性最好,BMP、GIF、ICO和JPEG格式也可以使用,不过写本文时(2023年1月)尚不支持WebP和SVG格式。图标的尺寸最好分别提供16×16像素、32×32像素、48×48像素和128×128像素四种,如果尺寸不合适,Chrome浏览器将会尽力选用合适的尺寸(不保证效果)。 接下来就是编写popup.html页面。我们希望popup.html这个页面能够提供一个按钮,当我们点击按钮时,就能够将网页背景颜色替换成我们之前在background.js存储的绿色。显然,仅仅靠HTML是不可能的,还要借助javascript来实现。基础的HTML代码如下: 1<!DOCTYPE html> 2<html> 3 <head> 4 <style> 5 /*按钮的样式*/ 6 button { 7 height: 30px; 8 width: 30px; 9 outline: none; 10 margin: 10px; 11 border: none; 12 border-radius: 2px; 13 } 14 15 button.current { 16 box-shadow: 0 0 0 2px white, 0 0 0 4px black; 17 } 18 19 body { 20 background: black; 21 } 22 </style> 23 </head> 24 <body> 25 <button id="changeColor">B</button> 26 <script> alert('Add some codes here');// 可以在这里添加JS代码吗??? </script> 27 </body> 28</html> 分离网页元素和脚本 上面的HTML代码很简单,就只有一个button按钮。如果开发者觉得一个简单的js脚本,直接在上面的<script> ... </script>里添加就行,那么这个扩展必然是运行不起来的。因为根据Chrome extension的Content Security Policy(CSP),不允许我们使用内联javascript脚本(inline script)。我们可以试试,跑这个代码会报什么错误。 在网页中,inline script是恶意注入的重灾区,因此处于安全考虑,Chrome extension在开发时禁止使用inline script。即使原生的CSP是可以添加unsafe-inline字段来允许inline script的运行,但是Chrome extension开发组还是完全禁止了unsafe-inline、unsafe-eval这些存在安全隐患方法的使用。如果应要开发者坚持要使用inline script,必须要添加nonce或hash这种额外的安全验证手段,其带来的代码复杂度往往还要超过单独把inline script改成独立的js文件。因此,除非是实在没法改,都不鼓励使用inline script。 因此,我们需要再单独建一个javascript文件popup.js来放脚本程序,同时修改popup.html中inline script为External JavaScript文件链接。 1<!-- no inline script 2<script> alert('Add some codes here');// 可以在这里添加JS代码吗??? </script> 3--> 4<script src="popup.js"></script> 那在popup.js添加什么样的Javascript代码呢?第一,解析popup.html的dom树,得到button表单元素。第二,给button注册监听按钮点击(click)事件。第三,当发生click事件时,需要从浏览器的本地存储中取出background.js存储的颜色数据。第四,解析DOM树修改页面背景的CSS属性。接下来,我们在上面的popup.html中着手添加js代码: 1//popup.js 2//第一,解析dom树,得到button表单元素。 3const button = document.getElementById("changeColor"); 4 5//第二,注册监听按钮点击事件。 6button.addEventListener("click", onClickFunction); 7 8async function onClickFunction(){//注意由于Chrome API都是异步的,因此这里用async函数 9 //调用Chrome接口取出当前标签页 10 const [tab] = await chrome.tabs.query({active: true, currentWindow: true}); 11 // 以当前标签页为上下文,执行setPageBackgroundColor函数 12 chrome.scripting.executeScript({ 13 target: {tabId: tab.id}, 14 function: setPageBackgroundColor, 15 }); 16} 17 18function setPageBackgroundColor(){ 19 //第三,从浏览器的本地存储中取出存储的颜色数据。 20 chrome.storage.sync.get("color", ({ color }) => { 21 //第四,修改页面背景的CSS属性。 22 document.body.style.backgroundColor = color; 23 }); 24} 在上面的js代码中,我们使用到了另外两个Chrome API:chrome.tabs.query和chrome.scripting.executeScript。它们分别来获取当前活动的tab页面和执行脚本命令,需要activeTab, scripting两种权限,因此我们再修改上述manifest.json的permissions字段,添加这两种权限: 1//.... 2 "permissions": ["storage","activeTab", "scripting"], 3//.... 之后重新载入扩展,点击扩展工具栏中的插件,弹出了一个带字母“B”(Button)按钮的小页面,点击该按钮,则会将网页背景颜色改成我们之前存储的绿色#3aa757。我们以常见的百度首页为例: Tips: 请注意,插件通过给body标签设置样式来修改网页背景色。因此,如果网页背景色是由其他标签决定的,便无法生效。 扩展的上下文 说实话,第一次看到例子中popup.js代码有点迷糊,尤其是中间async function onClickFunction()函数,不知道为什么要有这一步操作,还得用chrome.tabs.query先找当前的tab页面,然后在麻烦地用chrome.scripting.executeScript来执行setPageBackgroundColor函数。如此大费周章,为什么不能直接在注册监听事件的时候就将回调函数设置为setPageBackgroundColor呢? 我们可以尝试下看看直接这么做有什么效果,修改popup.js中的时间监听代码: 1//.... 2//第二,注册监听按钮点击事件。 3button.addEventListener("click", setPageBackgroundColor);//直接执行修改背景的函数 4//.... 重新载入后,Chrome没有报错,点击工具栏扩展弹出的按钮,得到的效果如下图所示: 哈,网页页面本身的背景颜色没变,改变的只有扩展popup.html那个小页面的背景颜色!这是因为:要考虑扩展执行时候的上下文啊。 首先,点击按钮"B"所执行的popup.js脚本是在popup.html中引入的,因此popup.js执行的上下文是popup.html页面,因此此时的setPageBackgroundColor函数中DOM解析的document.body.style.backgroundColor是指popup.html的背景色,而非原网页www.baidu.com的背景色。所以要改变js函数执行的上下文,我们要先用chrome.tabs.query({active: true, currentWindow: true});找到当前页面的上下文环境,然后用 1chrome.scripting.executeScript({ 2 target: {tabId: tab.id}, // 指定函数执行的上下为tabID所代表的环境 3 function: setPageBackgroundColor, 4 }); 来重定向函数function: setPageBackgroundColor执行时的上下文环境为target: {tabId: tab.id}所指代的上下文。这样修改背景颜色的效果才会作用到原网页中。 不过,我们也可以通过上下文的区别来提供更好的用户体验。比如修饰原有的按钮。为了让用户更直观的感受背景将要变换的颜色,我们可以提前将背景色放到按钮上,让用户感觉我点击该颜色的按钮,就可以将背景改成和按钮一样的颜色。其修改的popup.js代码如下,读者可借助下面的代码体会两种上下文的区别。 1//popup.js 2//第一,解析dom树,得到button表单元素。 3const button = document.getElementById("changeColor"); 4 5// 从storage取背景色并设到按钮上,以popup.html为上下文 6chrome.storage.sync.get("color", ({ color }) => { 7 button.style.backgroundColor = color; 8 }); 9 10//第二,注册监听按钮点击事件。 11button.addEventListener("click", onClickFunction); 12 13async function onClickFunction(){//注意由于Chrome API都是异步的,因此这里用async函数 14 //调用Chrome接口取出当前标签页 15 const [tab] = await chrome.tabs.query({active: true, currentWindow: true}); 16 // 以当前标签页为上下文,执行setPageBackgroundColor函数 17 chrome.scripting.executeScript({ 18 target: {tabId: tab.id},//指定访问的网页为上下文 19 function: setPageBackgroundColor, 20 }); 21} 22 23function setPageBackgroundColor(){ 24 //第三,从浏览器的本地存储中取出存储的颜色数据。 25 chrome.storage.sync.get("color", ({ color }) => { 26 //第四,修改页面背景的CSS属性。 27 document.body.style.backgroundColor = color; 28 }); 29} 效果如下: 选项设置页面 目前改背景颜色这个扩展只支持将背景色设为绿色,这样很不灵活。我们可以给插件添加选项设置页面,实现更加丰富的功能,比如提供不同的背景色。 选项设置页面也可以看作是扩展的配置页面,是扩展灵活性的体现。在manifest.json中,需要添加option_page字段指定选项页面,我们在目录中新建options.html作为选项页面的HTML文件。完整的mamifest.json文件如下: 1{ 2 "name": "Coloring", 3 "description": "Demo Extension to change background color!", 4 "version": "1.0", 5 "manifest_version": 3, 6 "background": { 7 "service_worker": "background.js" 8 }, 9 "permissions": ["storage","activeTab", "scripting"], 10 "action": { 11 "default_popup": "popup.html", 12 "default_icon": { 13 "16": "images/get_started16.png", 14 "32": "images/get_started32.png", 15 "48": "images/get_started48.png", 16 "128": "images/get_started128.png" 17 } 18 }, 19 "icons": { 20 "16": "images/get_started16.png", 21 "32": "images/get_started32.png", 22 "48": "images/get_started48.png", 23 "128": "images/get_started128.png" 24 }, 25 "options_page": "options.html" 26} 这时重新加载插件,如果扩展在扩展工具栏可见,则右键点击扩展图标,可以看到右键菜单中的选项菜单,点它就会打开我们添加的选项页options.html。如果扩展被收纳进扩展程序图标里,那么点击扩展程序图标,找到对应扩展右侧的三个竖点图标 $\vdots$,在下拉菜单中也可以找到选项入口。 现在我们还没给options.html添加任何内容,因此现在打开选项页是一个空白页。我们给其添加上相应的Html代码(体会个意思就不搞太复杂了^_^): 1<!DOCTYPE html> 2<html> 3 <head> 4 <style> 5 button { 6 height: 30px; 7 width: 30px; 8 outline: none; 9 margin: 10px; 10 } 11 </style> 12 </head> 13 <body> 14 <div id="buttonDiv"> 15 </div> 16 <div> 17 <p>Choose a different background color!</p> 18 </div> 19 </body> 20 <script src="options.js"></script> 21</html> 和popup.html类似,其主要功能也得依托Javascript实现,同时受限于CSP规则,js代码应该与Html代码分写在两个文件中,所以我们还要建立options.js实现具体功能。 为了方便示例,我们在选项页面中提供四种可选的背景色,用四个按钮表示。当我们点击选中的颜色的按钮时,会将对应的颜色存储到Chrome浏览器的存储空间chrome.storage.sync中。这样popup.html页面运行时就会从浏览器中选出选项页确定的颜色。其代码如下: 1//options.js 2 3//设置四种颜色:绿、红、黄、蓝 4const kButtonColors = ['#3aa757', '#e8453c', '#f9bb2d', '#4688f1']; 5 6//根据给定的颜色创建颜色按钮并注册监听事件 7function constructOptions(kButtonColors) { 8 //找到添加按钮的div 9 let page = document.getElementById('buttonDiv'); 10 //根据给定的颜色创建颜色按钮 11 for (let item of kButtonColors){ 12 let button = document.createElement('button'); 13 //设置按钮背景色 14 button.style.backgroundColor = item; 15 //为按钮注册监听事件,点击按钮则存储该颜色 16 button.addEventListener('click', ()=>{ 17 chrome.storage.sync.set({color:item}, ()=>{ 18 console.log('color is ' + item); 19 }) 20 }); 21 //添加元素 22 page.appendChild(button); 23 } 24} 25 26//运行创建按钮函数 27constructOptions(kButtonColors); 打开选项页面,就是四个不同颜色的按钮。 当我们点击蓝色按钮后,popup.html的按钮也会变成蓝色,点击后,网页的背景色也是蓝色。 总结 这篇文章中,我们相对完整的实现了一个改背景颜色的Chrome扩展,包含了清单文件manifest.json,后台脚本background.js,功能页面popup.html和配置页面options.html。基本走通了Chrome扩展开发的普遍流程,其他扩展开发的步骤也是大同小异。同时文章中也介绍了常见权限问题、上下文问题、CSP问题等常见坑,也算是经验的积累。改颜色扩展完整的目录结构如下: 该扩展主要参考了Chrome官方Manifest V2的一个教程,针对V3版本做了修改。 如果想把自己的扩展给更多人使用,可以将其打包上传的Chrome商店,发布自己的扩展,其步骤可参考Chrome教程https://developer.chrome.com/docs/webstore/publish/。如果只是小范围使用,可以直接把文件拷给别人就可以了。 参考文档 Google官方文档https://developer.chrome.com/docs/extensions/mv3/ MV2 教程老例子 https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/_archive/mv2/tutorials/get_started