chrome插件之通信(v3)

5,218 阅读6分钟

一、前言

chrome浏览器一直跑在所有浏览器前面,是所有现代浏览器当之无愧的霸主,今天我们来聊聊chrome的插件(也可以叫做扩展)

u=2591235827,3710801686&fm=253&fmt=auto&app=138&f=PNG.webp

插件的目的是为了更好的个性化的服务用户,当我们在某个web页面浏览信息的时候,如果原web站点没能给用户提供想要的功能,那么这个时候就可以借助插件去扩充这个web页面的功能。

我们不妨举个例子,如果我在浏览一个页面的时候,我觉得这个站点的主题太暗了,伤眼睛。但是web站点并未提供这个调主题的功能。那么这个时候,我就想要切换主题颜色怎么办,我们就可以选择开发一款插件,专门用来切换站点的颜色。

甚至我们还可以将插件打造成一单独的服务,比如在浏览很多英文网页的时候,我想单独翻译成某个单词,这个时候就可以开发一个插件,于是就有了划词翻译

由于插件是扩展web的功能的,因此使用的技术也是web技术,因此对于前端开发者而言,我们可能又多了一个更好的上手的技术!我谢谢你啊,谷歌

综上所述:插件的本质是更好的扩展和丰富个性化的功能,为用户提供更好服务的一种手段。

二、插件结构

插件的结构如下图所示:

截屏2023-01-14 下午4.57.32.png

一个插件有多个独立的脚本运行环境,以下是最为主要的几种。

  • 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>"]
          }
        ]
      }
    
    

    可以看到如下的结果: 截屏2023-01-14 下午10.28.38.png

    可以得出以下结论:

    1. 主web站点的脚本会比插件先执行,这是正常的,这也是插件可以起作用的原因
    2. 插件注入的脚本不能共享主web站点的全局变量,即便挂载在window上也是不可以的

    看完了js的优先级,那么css呢!

    往上可以回顾一下manifest.json会发现,我们已经注入了如下的content.css:

     body div{
        background:red;
     }
    

    这个我们主web站点的css是冲突的,且这个的css优先级更高,因此呈现出红色。但是假如主站点的css优先级和注入的css优先级一样高呢?我们试一下,将content.css改为:

     div{
        background:red;
     }
    

    呈现出如下模样:

截屏2023-01-14 下午10.38.55.png 可以看到注入的css明显被主web站点的css覆盖了。可以得出以下结论:

  1. 主web站点的css与插件css优先级一致时,以主web站点优先,否则就是谁的优先级更高,谁优先。
  2. 插件的css在chrome控制台上有注入的样式,这样的字样作为提示。
  • popop

    popop也是一个独立的dom环境,也就是我们点击插件图标时,会弹出来的那个悬浮框,当然前提是我们在manifest.json中进行了配置才可以。

三、通信

无论我们什么新的技术,我们或许都会了解通信这个内容,例如vuereact当中我们需要学习组件与组件之间的通信,在操作系统中,我们需要学习不同进程之间如何进行通信。同样的,在插件开发当中我们也需要了解不同环境之间是如何进行通信的。

通信的本质其实就是不同模块之间如何传递消息,从而更好的进行协作和沟通。所以只有知道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!")
    })
    

    就会有如下的效果:

    屏幕录制2023-01-15 下午12.00.05.gif 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!")
    })
    
    

    效果如下:

屏幕录制2023-01-15 下午12.31.35.gif

可以看到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, /**消息**/ )
 

以上的例子如果希望亲手实践一下,可以克隆我准备代码看一下!(保熟)

gittee项目地址

更多插件相关的问题,欢迎评论区一起探讨!