一、前言
chrome浏览器一直跑在所有浏览器前面,是所有现代浏览器当之无愧的霸主,今天我们来聊聊chrome的插件(也可以叫做扩展)
。
插件的目的是为了更好的个性化的服务用户,当我们在某个web页面浏览信息的时候,如果原web站点没能给用户提供想要的功能,那么这个时候就可以借助插件去扩充这个web页面的功能。
我们不妨举个例子,如果我在浏览一个页面的时候,我觉得这个站点的主题太暗了,伤眼睛。但是web站点并未提供这个调主题的功能。那么这个时候,我就想要切换主题颜色怎么办,我们就可以选择开发一款插件,专门用来切换站点的颜色。
甚至我们还可以将插件打造成一单独的服务,比如在浏览很多英文网页的时候,我想单独翻译成某个单词,这个时候就可以开发一个插件,于是就有了划词翻译
。
由于插件是扩展web的功能的,因此使用的技术也是web技术,因此对于前端开发者而言,我们可能又多了一个更好的上手的技术!我谢谢你啊,谷歌
综上所述:插件的本质是更好的扩展和丰富个性化的功能,为用户提供更好服务的一种手段。
二、插件结构
插件的结构如下图所示:
一个插件有多个独立的脚本运行环境,以下是最为主要的几种。
-
background
这个环境在manifest_version3(
以下简称v3
)中废弃了background.html,只有一个service worker环境,换句话说在之前的版本中,有一个background.html 里面引入一个xx.js塑造一个独立的dom环境;而现在变成一个javascript运行环境。因为发现原来的dom环境是没有意义的,因为background主要用来运行脚本,而非展示内容。 -
content
这个环境其实就是当前激活的浏览器tab的运行环境,是一个标准的dom环境,content中可以注入一些css、javascript脚本,并且可以访问和改变当前web的DOM属性,属于一个核心的特性。
既然是注入的样式和脚本,就可能会存在冲突,那冲突之后谁的优先级更高呢?
建立这样一个web站点!
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> div{ background:green; width:100px; height:100px; } </style> </head> <body> <div></div> <script> console.log("主web页面"); window.web = "我是window上的一个变量" const a = 'a' </script> </body> </html>
然后通过插件注入以下content.js
const div = document.createElement("div") div.innerText = `我是content.js创建的内容`; div.style.width = "100px" div.style.height = "100px"; div.style.border = "1px solid black"; document.body.appendChild(div); console.log(window.web , 'content.js'); console.log(a);
当然需要配置好下面的v3版的manifest.json
{ "name": "Getting Started Example", "description": "Build an Extension!", "version": "1.0", "manifest_version": 3, "background": { "service_worker": "background.js" }, "action": { "default_popup": "popup.html", "default_icon": { "16": "/images/get_started16.png", "32": "/images/get_started32.png", "48": "/images/get_started48.png", "128": "/images/get_started128.png" } }, "icons": { "16": "/images/get_started16.png", "32": "/images/get_started32.png", "48": "/images/get_started48.png", "128": "/images/get_started128.png" }, "options_page": "options.html", "content_scripts": [ { "js": [ "content.js"], "css":[ "content.css" ], "matches": ["<all_urls>"] } ] }
可以看到如下的结果:
可以得出以下结论:
- 主web站点的脚本会比插件先执行,这是正常的,这也是插件可以起作用的原因
- 插件注入的脚本不能共享主web站点的全局变量,即便挂载在window上也是不可以的
看完了js的优先级,那么css呢!
往上可以回顾一下manifest.json会发现,我们已经注入了如下的content.css:
body div{ background:red; }
这个我们主web站点的css是冲突的,且这个的css优先级更高,因此呈现出红色。但是假如主站点的css优先级和注入的css优先级一样高呢?我们试一下,将content.css改为:
div{ background:red; }
呈现出如下模样:
可以看到注入的css明显被主web站点的css覆盖了。可以得出以下结论:
- 主web站点的css与插件css优先级一致时,以主web站点优先,否则就是谁的优先级更高,谁优先。
- 插件的css在chrome控制台上有
注入的样式
,这样的字样作为提示。
-
popop
popop也是一个独立的dom环境,也就是我们点击插件图标时,会弹出来的那个悬浮框,当然前提是我们在manifest.json中进行了配置才可以。
三、通信
无论我们什么新的技术,我们或许都会了解通信这个内容,例如vue
和react
当中我们需要学习组件与组件之间的通信,在操作系统
中,我们需要学习不同进程之间如何进行通信。同样的,在插件开发
当中我们也需要了解不同环境之间是如何进行通信的。
通信的本质其实就是不同模块之间如何传递消息,从而更好的进行协作和沟通。所以只有知道popop、content、background之间如何传递消息,我们才能更自信的开发插件。以下内容针对v3版本:
他们的运行次数有什么区别呢?
content脚本会运行多份,每新开一个tab都会运行一份content脚本,生命周期也和当前的web站点一致,也就是说,如果刷新当前站点,那么content也会重新执行一次。
background脚本是全局唯一的,每次安装时或者重新加载时都会执行一次。
popop脚本,会在每次点击tab时弹出悬浮框时执行一次。
-
popop/background to content
浏览器一次性可以运行着多个tab,每一个tab都有自己的唯一的标识符,所以如果popop想要给tab通信时,先要确定想要发给那一个tab,一般情况下我们都选择给当前激活的也就是当前正在浏览的tab发送呢消息。如下的popop脚本:
popop.html
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="button.css"> </head> <body> <button id="btn1">way 1</button> <button id="btn2">way 2</button> <script src="popup.js"></script> </body> </html>
popop.js
btn1.addEventListener("click", async (event) => { let [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); chrome.tabs.sendMessage(tab.id, { action:'click' , payload:'i come form popop' }) })
content.js
chrome.runtime.onMessage.addListener((request , sender , sendResponse) => { const { action, payload } = request; console.log(request); sendResponse("content got!") })
就会有如下的效果:
content成功收到消息,根据不同消息可做不同的业务操作
-
content to popop/background
那conetent如何给popop以及background发送消息呢?
content.js
const btn = document.createElement("button"); btn.innerText = `send`; btn.addEventListener("click", () => { chrome.runtime.sendMessage({ action:'toPopop' , payload:'i come form popop' }) }) document.body.appendChild(btn);
popop与background都做以下的监听:
// popop.js chrome.runtime.onMessage.addListener((request , sender , sendResponse) => { const { action, payload } = request; console.log(action, payload,"popop got!"); sendResponse("popop got!") })
// background.js chrome.runtime.onMessage.addListener((request , sender , sendResponse) => { const { action, payload } = request; console.log(action, payload,"background got!"); sendResponse("background got!") })
效果如下:
可以看到background和popop都会收到消息,而只需要通过逻辑去判断谁该执行具体的业务就好了!
四、总结
通信核心API:
// 监听
chrome.runtime.onMessage.addListener((request , sender , sendResponse)=>{
// to do ...
})
//发送消息
// content background popop
chrome.runtime.sendMessage(/** 消息 **/)
// background popop
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.tabs.sendMessage(tab.id, /**消息**/ )
以上的例子如果希望亲手实践一下,可以克隆我准备代码看一下!(保熟)
更多插件相关的问题,欢迎评论区一起探讨!