• chrome插件开发入门-保姆级攻略


    这里先插播一条消息

    Manifest version 2 is deprecated, and support will be removed in 2023. See https://developer.chrome.com/blog/mv2-transition/ for more details.

    MV2版本的chrome插件在2023年停止支持

    chrome插件应该包含哪些文件及文件夹

    D:.
    │  manifest.json
    │
    ├─html
    │      index.html
    │
    ├─images
    │      icon-128.png
    │      icon-16.png
    │
    ├─scripts
    │      background.js
    │
    ├─styles
    │      main.css
    │
    └─_locales
        ├─en
        │      messages.json
        │
        └─zh_CN
                messages.json
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. html:存放html页面
    2. images:存放插件图标
    3. scripts:存放js文件
    4. styles:存放样式
    5. _locales:存放多语言文件
    6. manifest.json:用来配置所有和插件相关的配置,作为chrome入口文件,必须放在根目录(必须存在

    分析

    1. 目录结构像一个web网页,他的本质上就是一个网站类应用,是一个webapp
    2. 相对于普通的webapp,还可以调用更多的浏览器层面的api,包括数钱、历史记录、网络请求拦截、截获用户输入等等

    重要配置说明

    manifest.json

    额外的配置参见https://blog.csdn.net/sysuzjz/article/details/51648163

    {
        "manifest_version": 3, // 清单版本号,建议使用 版本 3,版本 1和2 是旧的,已弃用,不建议使用
        "name": "first-test-plugin", // 插件名称
        "version": "0.0.1", // 插件版本
        "description": "这里是第一个测试插件", // 描述,可写可不写
        "icons":
        {
            "16": "images/custom/16x16.png",
            "48": "images/custom/48x48.png",
            "128": "images/custom/128x128.png"
        },
        // !!!browser_action和page_action只能添加一个
        "browser_action": //浏览器级别行为,所有页面均生效
        {
            "default_icon": "images/custom/16x16.png", // 图标的图片
            "default_title": "Hello lanfengqiuqian", // 鼠标移到图标显示的文字
            "default_popup": "html/popup.html" // 单击图标后弹窗页面
        },
        "page_action": //页面级别的行为,只在特定页面下生效
        {
            "default_icon":
            {
                "24": "images/custom/24x24.png",
                "38": "images/custom/38x38.png"
            },
            "default_popup": "html/popup.html",
            "default_title": "Hello lanfengqiuqian"
        },
        "author": "lanfengqiuqian", // 可选填写
        "automation": false, // 是否开启自动化
        "background": // 背景页的脚本路径,一般为插件目录的相对地址
        {
            "scripts": [
                "scripts/background.js",
                "scripts/devtools-page.js"
            ]
        },
        "devtools_page": "html/devtools-page.html", // 在开发工具中的页面
        "content_scripts": [ // 内容脚本一般植入会被植入到页面中, 并且可以控制页面中的dom
            {
                "js": ["js/else-insert.js"], // 这里面的数组都是可以放多个的
                "css": ["css/else-insert.css"],
                "matches": [""] // 被植入到页面,只在这些站点下 content_scripts会运行
            }
        ],
        "permissions": [ // 安装的时候提示㤇的权限
            "cookies", // 使用cookies
            "webRequest", // 使用web请求
            "http://*", // 可以通过executeScript或者insertCSS访问的网站地址。如: https://*.google.com/
            "management", //
            "storage", // 使用本地存储
            "tabs", // 操作标签
            "contextMenus" //右键菜单
        ]
        "default_locale ": "zh_CN" //默认语言(比如"zh_CN")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    开始手撸一个插件

    准备工作

    创建一个文件夹,如我的叫 extensions (之后说的根目录都是指这个目录下)
    文件夹下创建一个 img 目录,用于存放一些logo之类的图片

    放入一张图片,如logo.png

    文件夹下创建一个 html 目录,用于存放html文件
    文件夹下创建一个 js 目录,用于存放js文件

    这里如果你想先放一个jquery文件用于加载也是可以的,我后面为了方便使用的是script引入

    文件夹下创建一个 css 目录,用于存放css文件

    文件夹根目录下创建一个 manifest.json 文件

    {
        "manifest_version":3,
    	"name":"这是插件名称",
    	"version":"0.0.1",
    	"description":"这是插件描述",
        "action":{
    		"default_title":"这是鼠标移上去时提示文字",
            "default_popup":"html/popup.html"
    	},
    	"icons":{
    		"16":"img/logo.png",
    		"32":"img/logo.png",
    		"48":"img/logo.png",
    		"128":"img/logo.png"
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    然后chrome扩展程序【加载已解压的扩展程序】选择刚才创建的extensions目录

    效果如下

    pupup部分

    1. /html新建一个popup.html文件,然后在manifest.json中的action配置popup的路径

      "action":{
          "default_title":"这是鼠标移上去时提示文字",
          "default_popup":"html/popup.html"
      }
      
      • 1
      • 2
      • 3
      • 4
      DOCTYPE html>
      <html>
      <head>
          <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
          <link rel="stylesheet" type="text/css" href="../css/popup.css" />
      head>
      <body>
          <div class="btn">
              测试<input id="TEST" class="checkbtn" type="checkbox" />
          div>
      body>
      <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js">script>
      <script src="../js/popup.js">script>
      html>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    2. 在css和js目录中分别新建popup.csspopup.js文件

      /* popup.css */
      .btn{
          width: 100px;
          height: 30px;
          font-size: large;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      //popup.js
      $(".checkbtn").click(function(){
          alert($(this).attr('id'));
      });
      
      • 1
      • 2
      • 3
      • 4
    3. 然后重载扩展程序

      点击插件,效果如下

    4. 待解决问题

      每次勾选的checkbox都会被还原,所以接下来需要做一个本地存储来保存popup的改变

    background部分

    1. manifest.json中加入service_worker的配置路径和permissions

      "background":{
          "service_worker":"background.js"
      },
      "permissions":["storage"]
      
      • 1
      • 2
      • 3
      • 4

      注意:service_worker说明

      1. 这个是一直伴随插件运行的后台脚本
      2. 没有前端页面,不支持dom,所以不能引入jQuery和其他js
      3. 所有需要保持运行的脚本都需要直接卸载background.js文件里
      4. 同样也不支持XMLHttpRequest,所以需要使用fetch来替代xhr请求
      5. 一定要放在根目录(扩展文件根目录,不是电脑磁盘根目录),否则在使用的时候会出现service worker(无效)提示
      6. 可以在扩展程序=>查看视图点击弹出的控制台查看输出
    2. 在根目录写background.js文件

      //background.js
      chrome.runtime.onInstalled.addListener(() => {
          DBdata("clear");//清除插件保存的本地数据
      });
      //插件用的数据都存储在storage.local中
      function DBdata(mode,callback,data){//操作本地存储的函数
          if(mode=="set"){//保存本地数据
              console.log('set-LocalDB');
              chrome.storage.local.set({LocalDB: data});
          }else if(mode=="get"){//获取
              chrome.storage.local.get('LocalDB', function(response) {
                  typeof callback == 'function' ? callback(response) : null;
              });
          }else if(mode=="clear"){//清空
              chrome.storage.local.clear();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    3. 打开popup.js,把原来的点击事件删掉,在其中加入初始化和连接service_worker的脚本

      //popup.js
      window.bgCommunicationPort = chrome.runtime.connect();//初始化bgCommunicationPort
      $(".checkbtn").click(function(){
          bgCommunicationPort.postMessage({//发送到bg,键值可以自由设置
              Direct : $(this).attr('id'),//目标
              Content : '测试内容',//内容
              step : 0//步骤
          });
      });
      $(document).ready(function(){//打开popup时触发,读取之前存储的参数
          bgCommunicationPort.postMessage({fromPopup:'getDB'});//向background发送消息
          bgCommunicationPort.onMessage.addListener(function(receivedPortMsg) {//监听background
              console.log(receivedPortMsg);//这是background发来的内容
              if(receivedPortMsg&&receivedPortMsg.Direct){
                  $(".checkbtn").prop({'checked': false});//初始化按钮
                  $("#"+receivedPortMsg.Direct).prop({'checked': true});
              }
          });
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    4. 打开background.js,在其中加入监听popup的脚本(这里不要删除原来的哦,加到后面即可)

      //background.js
      chrome.runtime.onConnect.addListener(function(port) {//接收到popup
          port.onMessage.addListener(function(receivedMsg) {//监听popup发来的内容receivedMsg
              if(receivedMsg.fromPopup&&receivedMsg.fromPopup=='getDB'){//如果接收到了getDB,这里读取数据并返回相当于初始化popup页面
                  DBdata('get',function(res){
                      port.postMessage(res.LocalDB);//发送到popup
                  });
              }else{//如果不是,则说明是收到来自popup手动点击设置的数据,存入以用于popup打开时展示
                  DBdata('set','',receivedMsg)
              }
          })
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    5. 重载插件

      1. 这个时候会发现有两个报错

        发现是用script引入的jquery报跨域

        那么就改为文件引入呗

        1. 在js目录下新建jquery.js

          https://jquery.com/download/去下载production版本的js

          然后把他的内容放到jquery.js

        2. 修改popup.html文件中的jquery引入

          <script src="../js/jquery.js">script>
          
          • 1
      2. 重载插件,发现报错都好了

    6. 测试

      每次重置勾选的问题已经好了

    content部分

    content可以注入到浏览的网页,操作dom,所以就可以实现很多功能了

    1. manifest.json中加入content的配置
    "content_scripts":[{
        "js":["js/jquery.js","js/content.js"],/*content可以随意引入js,因为其内容会在浏览的网页上直接运行*/
        "matches":["*://localhost/*"],/*在哪些网页上运行*/
        "run_at":"document_end"/* 在页面加载完成时运行 */
    }]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意这里现在只是匹配的localhost

    1. 新建js/content.js,在content.js中写入

      //content.js   manifest匹配地址的页面在刷新时会直接执行这里的代码
      chrome.runtime.sendMessage(chrome.runtime.id, {//当页面刷新时发送到bg
          fromContent: 'getDB'
      });
      
      chrome.runtime.onMessage.addListener(function(senderRequest, sender, sendResponse) {//接收到bg
          console.log('demo已运行');
          var LocalDB=senderRequest.LocalDB;
          console.log(LocalDB);
          switch(LocalDB.Direct){
              case 'TEST':
                  console.log(123123);
                  break;
              default:
                  break;
          }
          // 不写会报错 Unchecked runtime.lastError: The message port closed before a response was received.
          sendResponse('这里是content返回值');
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    2. 然后background.js中加入监听content的代码

      //background.js
      chrome.runtime.onMessage.addListener(function (senderRequest, sender, sendResponse) {//接收到content
          // 不写会报错 Unchecked runtime.lastError: The message port closed before a response was received.
          sendResponse({ msg: '接收到content' });
          console.log(senderRequest);
          if (senderRequest.fromContent && senderRequest.fromContent == 'getDB') {//接收到fromContent:getDB
              DBdata('get', function (res) {//从本地取数据
                  if (res.LocalDB) {
                      var LocalDB = res.LocalDB;
                      switch (LocalDB.Direct) {
                          //如果是存入的TEST按钮
                          case 'TEST':
                              chrome.tabs.query({
                                  active: true,
                                  currentWindow: true
                              }, function (tabs) {
                                  chrome.tabs.sendMessage(tabs[0].id, { LocalDB: LocalDB }, function (res) {
                                      console.log('接收content的回调', res);
                                  });//发送到content		
                              });
                              break;
                          default:
                              break;
                      }
                  }
              });
          }
      });
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28

      这里注意sendResponse这个方法,有的没有在回调中加上这个参数,导致找不到

    3. 重载插件

    4. 代码执行顺序

      1. 插件初始化阶段

        1. 先执行插件初始化监听background.jsonInstalled,清除本地数据
      2. 手动点击插件图标,勾选插件

        1. 执行popue.jsready方法进行初始化
        2. 点击按钮触发click方法修改本地数据
      3. 网页刷新阶段

        1. 执行content.jssendMessage方法
        2. 执行background.jsonMessage方法
        3. 读取本地数据
        4. 根据本地数据决定是否sendMessagecontent

    去广告插件

    说明:这里对于广告的判断是类名为 .ad 的元素

    如我的vue页面

    <div>
        <h2 class="ad">第一条广告h2>
        <h2 class="ad">第二条广告h2>
        <h2 class="ad">第三条广告h2>
        <h2>这里是正常的数据h2>
    div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. popup.html中增加一个去广告按钮

      <div class="btn">
          去广告<input id="removeAD" class="checkbtn" type="checkbox" />
      div>
      
      • 1
      • 2
      • 3
    2. background.js中监听content部分增加对于removeAD的判断

      //如果是存入的removeAD按钮
      case 'removeAD':
          chrome.tabs.query({active: true, currentWindow: true
          }, function(tabs){
              chrome.tabs.sendMessage(tabs[0].id, {LocalDB: LocalDB});//发送到content		
          });
          break;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    3. content.js中监听background.js部分增加removeAD的判断

      case 'removeAD':
          //隐藏含有ad的元素,来达到去广告的效果
          $(".ad").hide();
          break;
      
      • 1
      • 2
      • 3
      • 4
    4. 重载插件,勾选页面中的去广告,然后刷新页面,发现广告已经没有了

    页面跳转和cookie

    popup一样,content关闭之后也不会保存数据,当我们在A页面获取数据之后想要放到B页面上去,在content直接跳转是不会把获取到的数据传过去的,所以和background链接保存本地数据就派上用场了

    案例:将csdn的cookie的UserNick显示在localhost:8081

    1. manifest.json中配置【域名脚本匹配】、【权限】和【主机权限】

      "permissions":["storage", "cookies"],
      "host_permissions": [
      	"*://www.csdn.net/*"
      ],
      "content_scripts":[{
      	"js":["js/jquery.js","js/content.js"],
      	"matches":["*://localhost/*", "*://www.csdn.net/*"],
      	"run_at":"document_end"
      }]
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      这里千万要注意的是一定要能够匹配上的url,否则可能引起页面脚本无反应或者获取不到cookie

    2. popup.html中增加按钮

      <div class="btn">
          csdn<input id="checkCsdnUserNick" class="checkbtn" type="checkbox" />
      div>
      
      • 1
      • 2
      • 3
    3. background.js中增加对于csdn按钮的判断

      case 'checkCsdnUserNick':
          console.log('LocalDB', LocalDB)
          //popup设置数据的时候有个step属性,在多步操作的时候就开始发挥作用了
          if(LocalDB.step==0){
              LocalDB.step = 1;//将step设置成1
              chrome.storage.local.set({
                  LocalDB: LocalDB//保存到本地数据
              },function() {
                  chrome.tabs.update(null, {//将前台页面跳转到设置的url
                      // 这里的url不用带斜杠 /
                      url: 'https://www.csdn.net'
                  });
              });
          }else if(LocalDB.step==1){//因为csdn的地址我们也匹配了所以content在跳转到csdn之后会还是会回来,不同的是step已经是1了
              chrome.cookies.get({//获取cookie
                  'url': "https://www.csdn.net",
                  'name': 'UserNick'
              }, function(cookie) {
                  console.log('cookie', cookie);
                  console.log(cookie.value);//获取到的值
                  LocalDB.cookie=cookie.value;//把获取到的值放到本地数据的cookie属性里
                  LocalDB.step = 2;//将step设置成2
                  chrome.storage.local.set({//获取到cookie之后跳转到第二个页面
                      LocalDB: LocalDB//保存到本地数据
                  },function() {
                      chrome.tabs.update(null, {//将前台页面跳转到设置的url
                          url: 'http://localhost:8081/'
                      });
                  });
              });
          }else if(LocalDB.step==2){//第二步
              chrome.tabs.query({active: true, currentWindow: true}, function(tabs){//发送到content
                  chrome.tabs.sendMessage(tabs[0].id, {LocalDB: LocalDB});		
              });
          }
          break;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
    4. content.js中增加对于csdn按钮的判断

      case 'checkCsdnUserNick':
          if(LocalDB.step==2){
              $("body").append('

      '+LocalDB.cookie+'

      '
      ); } break;
      • 1
      • 2
      • 3
      • 4
      • 5
    5. 重载插件,到csdn.net中开启插件,勾选csdn,然后刷新页面就能看到效果

      会获取csdn.netcookie中的昵称,然后跳转到localhost:8081,进行显示

    过程中问题记录

    1. Unchecked runtime.lastError: The message port closed before a response was received.

      这个问题通常是由于其他插件引起的,注意排查,找到受影响的插件禁用即可

      大多数人是由于【迅雷】插件或者【油猴】插件引起的

    2. 扩展【移除】旁边多了一个【错误】的按钮

      1. 如果有错误提示,根据提示排查即可

      2. 如果没有错误提示,尝试将扩展移除重新加载即可

      3. 检查是否没有把sendMessagesendResponse配套使用

        每一个sendMessage都需要和sendResponse进行呼应

        也就是说,在每一个chrome.runtime.onMessage.addListener的回调函数中,需要使用sendResponse进行返回

        可以参见https://blog.csdn.net/m0_37729058/article/details/89186257

    3. service worker看不到content.jsconsole.log

      原因是因为这个js是嵌入到页面中的,所以需要在使用的网页的控制台查看,而不是service worker

    4. 无法获取到cookie

      可能原因如下

      1. 没有授权host_permissions,控制台会报错Unchecked runtime.lastError: No host permissions for cookies at url: "https://www.csdn.net/".

        检查manifest.json中的此项配置,没有添加或者没有匹配上都会造成这个问题

      2. 检查chrome.cookies.get这个方法中的url是否完全正确

    代码地址

    github跳转

  • 相关阅读:
    SpringMVC概述,SpringMVC是什么,有什么优势?
    基于SpringBoot的人事管理系统
    可变形卷积 Deformable Convolution
    AVL的代码剖析(c++)
    三个月能学到多少网络安全知识?
    智元兔AI写作大师助你一分钟完成完美论文
    【定语从句练习题】定语从句的理解层次
    caffe-make_runtest
    SCRUM敏捷产品负责人(CSPO)认证培训课程
    shell动态生成.sql文件的方法进阶
  • 原文地址:https://blog.csdn.net/qq_43382853/article/details/126272794