• 怎么从零编写一个 v3 版本的 chrome 浏览器插件实现 CSDN 博客网站的暗黑和明亮主题切换?


    整体效果

    流沙插件主题切换演示:https://live.csdn.net/v/228888

    源码

    https://github.com/kaimo313/quicksand

    实现步骤

    1、新建 manifest.json 文件

    新建一个 chrome 文件夹,在文件夹里新建 manifest.json 文件,在文件里输入下面代码,给插件取名叫流沙,版本是 1.0。

    {
        "name": "流沙",
        "version": "1.0",
        "manifest_version": 3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    这里需要注意的是 manifest_version 必须是整数 2 或者 3,我们这里使用 3。

    2023 年 1 月后 MV2 插件不能再继续更新,MV2 插件将不能在 Chrome 中运行;2023 年 6 月后 即使使用企业策略,MV2 扩展程序也不再在 Chrome 中运行。目前已经不能再发布 MV2 版本的插件,相比于 MV2,MV3 有诸多不同,例如权限控制,API 的变动,发起请求的方式等。

    在这里插入图片描述

    2、将插件添加到扩展程序中

    打开 chrome 浏览器,输入 chrome://extensions/,点击加载已解压的扩展程序,选择刚刚新建的 chrome 文件夹即可。

    在这里插入图片描述

    选择好文件夹之后,流沙这个插件就被加载到浏览器中了。

    在这里插入图片描述

    3、配置 service-worker.js

    background script 是扩展的事件处理程序;它包含对扩展很重要的浏览器事件的侦听器。它处于休眠状态,直到触发事件,然后执行指示的逻辑。有效的后台脚本仅在需要时加载,并在空闲时卸载。

    先在 manifest.json 配置:

    {
        "name": "流沙",
        "version": "1.0",
        "manifest_version": 3,
        "background": {
            "service_worker": "service-worker.js"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    manifest.json 同级新建 service-worker.js 文件,里面添加下面代码

    console.log("流沙---> service-worker", chrome);
    
    chrome.action.onClicked.addListener(function () {
        console.log('点击了流沙插件图标');
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    然后我们刷新扩展程序页面,发现有个错误

    在这里插入图片描述

    错误如下:Uncaught TypeError: Cannot read properties of undefined (reading 'onClicked'),没有onClicked方法
    在这里插入图片描述

    chrome.action 文档:必须在 manifest 中声明才能使用此 api

    在 manifest 中配置 action

    {
        "name": "流沙",
        "version": "1.0",
        "manifest_version": 3,
        "background": {
            "service_worker": "service-worker.js"
        },
        "action": {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    刷新清除掉错误之后,点击 Service Worker,就可以看到打印的日志
    在这里插入图片描述
    点击右上角的流沙插件,就会打印日志出来。
    在这里插入图片描述

    如果没有这个图标的可以添加上来。

    在这里插入图片描述

    4、实现点击插件图标切换主题 icon

    manifest.json 配置

    {
        "name": "流沙",
        "version": "1.0",
        "manifest_version": 3,
        "description": "用于 CSDN 博客网站的暗黑和明亮主题切换",
        "author": "kaimo",
        "background": {
            "service_worker": "service-worker.js"
        },
        "icons": {
            "16": "icons/logo.png",
            "48": "icons/logo.png", 
            "128": "icons/logo.png"
        },
        "action": {
            "default_icon": {
                "32": "icons/popup_light_32.png"
            },
            "default_title": "流沙:明亮模式"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    service-worker.js 添加代码

    console.log("流沙---> service-worker", chrome);
    
    let themeType = "light";
    
    chrome.action.onClicked.addListener(function () {
        console.log('点击了流沙插件图标');
        // 修改初始值
        themeType = themeType === "light" ? "dark" : "light";
        chrome.action.setIcon({
            path: "icons/popup_" + themeType + "_32.png"
        });
        chrome.action.setTitle({
            title: themeType === "light" ? "流沙:明亮模式" : "流沙:暗黑模式"
        });
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    添加 icons 文件夹跟图片

    在这里插入图片描述

    效果如下:

    在这里插入图片描述

    流沙:明亮模式
    在这里插入图片描述
    点击切换到流沙:暗黑模式

    在这里插入图片描述

    5、让 CSDN 的网页加载插件

    先匹配 csdn 网站,在 manifest.json 里添加 content_scripts 的配置

    "content_scripts": [
        {
            "matches": ["https://*.blog.csdn.net/*"],
            "js": ["content-script.js"]
        }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后新建 content-script.js 文件,添加代码

    alert("匹配到了 CSDN 博客网站")
    
    • 1

    测试效果,访问 csdn 博客网站的时候,就会弹出。其他网址就不会。

    在这里插入图片描述

    6、插件里实现样式主题的切换

    我们发现 id 为 userSkin 的 dom 元素的类不同可以显示不同的主题的效果。

    暗黑主题效果:skin-blackwhale user-skin-Black
    在这里插入图片描述
    明亮主题效果:skin-yellow user-skin-White
    在这里插入图片描述

    具体代码如下:

    manifest.json 文件

    {
        "name": "流沙",
        "version": "1.0",
        "manifest_version": 3,
        "description": "用于 CSDN 博客网站的暗黑和明亮主题切换",
        "author": "kaimo",
        "background": {
            "service_worker": "service-worker.js"
        },
        "icons": {
            "16": "icons/logo.png",
            "48": "icons/logo.png", 
            "128": "icons/logo.png"
        },
        "action": {
            "default_icon": {
                "32": "icons/popup_light_32.png"
            },
            "default_title": "流沙:明亮模式"
        },
        "content_scripts": [
            {
                "matches": ["https://*.blog.csdn.net/*"],
                "js": ["content-script.js"]
            }
        ],
        "permissions": ["storage", "tabs"]
    }
    
    • 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

    service-worker.js 文件

    console.log("流沙---> service-worker", chrome);
    
    // 设置主题类型
    function setThemeType(type) {
        chrome.storage.local.set({ theme: type }, () => {
            console.log('设置主题模式为:', type);
        });
    }
    
    // 通过 tabs 发送消息改变主题类型
    // tabs api,必须被注册在 manifest 的 permissions 字段中给插件使用,这里不然获取不到 url。
    function changeThemeByTabs(themeType){
        chrome.tabs.query({}, tabs => {
            console.log("获取 tabs", tabs);
            for (var i = 0; i < tabs.length; i++) {
                console.log(`tabs[${i}].url`, tabs[i].url);
                try {
                    const location = new URL(tabs[i].url);
                    const host = location.host;
                    console.log(host, host.includes("blog.csdn.net"));
                    if (host.includes("blog.csdn.net")) {
                        console.log(tabs[i].id, tabs[i], themeType);
                        // 向选项卡发送消息
                        chrome.tabs.sendMessage(tabs[i].id, {
                            theme: themeType
                        }, response => {
                            // 将打印出"接收到主题切换";
                            console.log(response);
                        });
                    }
                }
                catch (e) {
                    console.error("报错--->", e);
                }
            }
        });
    }
    
    // 添加插件监听被安装事件
    // 在 onInstalled 监听器内部,扩展使用 storage API 设置一个值。这将允许多个扩展组件访问该值并进行更新。
    // 大部分 API,包括 storage api,必须被注册在 manifest 的 permissions 字段中给插件使用。
    chrome.runtime.onInstalled.addListener(() => {
        console.log("插件已安装");
        // 设置主题类型
        setThemeType("light");
    });
    
    // 添加图标点击事件监听
    chrome.action.onClicked.addListener(() => {
        console.log('1、点击了流沙插件图标');
        // 获取主题类型
        chrome.storage.local.get(["theme"], res => {
            console.log("2、缓存的theme", res);
            let { theme } = res;
            // 修改初始值
            theme = theme === "light" ? "dark" : "light";
            console.log("3、切换 theme 为:", theme);
            // 设置图标
            chrome.action.setIcon({
                path: "icons/popup_" + theme + "_32.png"
            });
            // 设置title
            chrome.action.setTitle({
                title: theme === "light" ? "流沙:明亮模式" : "流沙:暗黑模式"
            });
            // 设置主题类型
            setThemeType(theme);
            // 通过 tabs 发送消息改变主题类型
            changeThemeByTabs(theme);
        });
    });
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    content-script.js 文件

    console.log("流沙---> content-script", chrome);
    
    // 设置明亮主题
    function setLightThemes() {
        document.getElementById("userSkin").className = "skin-yellow user-skin-White";
    }
    
    // 设置暗黑主题
    function setDarkThemes() {
        document.getElementById("userSkin").className = "skin-blackwhale user-skin-Black";
    }
    
    // 切换主题
    function switchThemes(type = "light") {
        if(type === "dark") {
            setDarkThemes();
        } else {
            setLightThemes();
        }
    }
    
    // 初始化设置
    chrome.storage.local.get(['theme'], res => {
        let { theme } = res;
        console.log("初始化设置 theme--->", theme);
        switchThemes(theme);
    });
    
    // 监听消息
    chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
        console.log("监听消息--->", request, sender, sendResponse);
        // 接收信息返回给发送方
        sendResponse("接收到主题切换");
        const { theme } = request;
        switchThemes(theme);
    });
    
    • 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

    说明

    目前这里只实现了博客首页的部分内容样式,感兴趣的可以自己研究完善。

    另外就是打包扩展程序 crx 文件的生成。第一次可以不用选私有秘钥 .pem 文件。

    在这里插入图片描述
    浏览器会自动生成的

    在这里插入图片描述

    确定就会生成,不过这个 crx 不能直接放到扩展程序里使用。
    在这里插入图片描述

    该扩展程序未列在 Chrome 应用商店中,并可能是在您不知情的情况下添加的。
    在这里插入图片描述

    参考 manifest.json 所有配置项

    官网中给出所有配置项

      {
      // Required - 通俗易懂
      "manifest_version": 3,
      "name": "My Extension",
      "version": "versionString",
    
       // 『重点』action配置项主要用于点击图标弹出框,对于弹出框接受的是html文件
      "action": {
         "default_title": "Click to view a popup",
       	 "default_popup": "popup.html"
       }
        
      // 通俗易懂
      "default_locale": "en",
      "description": "A plain text description",
      "icons": {...},
      "author": ...,
    
      // 『重点』下面将出现的background.js 配置service work
      "background": {
        // Required
        "service_worker": "service-worker.js",
      },
    
        // 『重点』下面将出现content_script.js 应用于所有页面上下文的js
      "content_scripts": [
         {
           "matches": ["https://*.nytimes.com/*"],
           "css": ["my-styles.css"],
           "js": ["content-script.js"]
         }
       ],
    
        // 使用/添加devtools中的功能
      "devtools_page": "devtools.html",
    
        /**
        * 三个permission
        * host_permissions - 允许使用扩展的域名
        * permissions - 包含已知字符串列表中的项目 【只需一次弹框要求允许】
        * optional_permissions - 与常规类似permissions,但由扩展的用户在运行时授予,而不是提前授予【安全】
        * 列出常见选项
        * {
        *		activeTab: 当扩展卡选项被改变需要重新获取新的权限
        *		tabs: 操作选项卡api(改变位置等)
        *		downloads: 访问chrome.downloads API 的权限 便于下载但还是会受到跨域影响
        *		history: history api权限
        *		storage: 访问localstorage/sessionStorage权限
        * }
        */
      "host_permissions": ["http://*/*", "https://*/*"],
      "permissions": ["tabs"],
      "optional_permissions": ["downloads"],
    
        // 内部弹出可选页面 - 见fehelper操作页
      "options_page": "options.html",
      "options_ui": {
        "chrome_style": true,
        "page": "options.html"
      },
    }
    
    • 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
    • 57
    • 58
    • 59
    • 60
    • 61

    参考资料

  • 相关阅读:
    小凡复习笔记——第一章:初识java
    一个基与邮件的数据下载存储系统
    基础算法学习|高精度
    Python(10)列表例题重写<1>
    【算法|动态规划No.11】leetcode53. 最大子数组和
    (四)激光线扫描-光平面标定
    java 实现一个最小栈
    osi七层模型
    线性表的链式表示——单链表;头插,尾插,按值查找,按序号查找,插入,删除;
    设计模式 -- 中介者模式(17)
  • 原文地址:https://blog.csdn.net/kaimo313/article/details/126055704