最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 如何从零开始开发一个 Chrome 扩展?

    正文概述 掘金(字节前端)   2021-02-24   407

    什么是浏览器插件

    简单来说浏览器插件,是浏览器上的一种工具,可以提供一些浏览器没有的功能,帮你做一些有趣的事情。开发者可以根据自己的喜欢,去实现一些功能。插件基于Web技术(html、css、js)构建。

    举个栗子?

    FeHelper.JSON插件

    如何从零开始开发一个 Chrome 扩展?

    功能:格式化JSON、编码转化、markdown、代码压缩等功能。

    二维码生成器

    如何从零开始开发一个 Chrome 扩展?

    功能:可以根据当前浏览的网页地址,生成一个二维码。

    SwitchyOmega Proxy

    如何从零开始开发一个 Chrome 扩展?

    功能:你懂的。

    Hello World

    manifest.json

    Chrome 浏览器插件没有严格的文件结构约束,只需要保证文件夹根目录有 manifest.json 文件**,**该文件的内容会概括插件所需的资源、权限等等。

    一个段简单的示例:

    {
        "manifest_version": 2, // 必填
        "name": "my-plugin", // 必填
        "version": "0.1.0" // 必填
    }
    

    manifest_version:代表了manifest文件的版本,浏览器会根据这个值去指定该版本拥有的功能。

    name:插件的名称。

    version:插件版本。

    将manifest.json文件放到一个文件夹内。

    chrome://extensions/

    在浏览器地址栏输入chrome://extensions/打开“拓展程序”页面。

    注意:需要启用右上角的 “开发者模式” 才能加载已解压的插件文件:

    如何从零开始开发一个 Chrome 扩展?

    加载已解压的插件

    启用之后点击加载已解压的拓展程序,选择刚刚我们放入了manifest.json的文件夹,之后你会看到:

    如何从零开始开发一个 Chrome 扩展?

    新增了一个我们刚刚添加的插件,而且浏览器右上角也会有我们的一个图标:

    如何从零开始开发一个 Chrome 扩展?

    此时已经加载了一个插件了,但是这个插件除了占用浏览器的一个位置除外,没有任何作用。

    如果没有设置插件图标,那么插件的第一个字符会成为插件的默认icon。

    让插件看起来更“插件”一点

    为了让这个插件更“完善”一点,我们给它加一个icon和描述,并且点击出现一个popup页面,popup 页面一般用来承载临时性的交互,且生命周期很短:单击图标打开popup,焦点离开又立即关闭,可以通过default_popup字段来定义。

    {
      .....
    
      "description": "这是一段描述",
      // 插件管理页面的icon
      "icons": {
        "84": "./icon/ball.png"
      },
      // 浏览器右上角的图标和内容
      "browser_action": {
        "default_icon": "./icon/ball.png",
        "default_title": "我的插件",
        "default_popup": "./html/popup.html"
      }
    }
    

    此时我们的目录结构也变成了这样:

    如何从零开始开发一个 Chrome 扩展?

    给popup.html加上内容:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>my-plugin</title>
    </head>
    <body>
        <p style="width: 200px;text-align:center;">hello world!!</p>
    </body>
    </html>
    

    之后,我们点击插件右下角的“刷新”按钮:

    如何从零开始开发一个 Chrome 扩展?

    你会发现插件有了icon和描述:

    如何从零开始开发一个 Chrome 扩展?

    并且右上角的icon也变了,点击一下,会弹出我们刚刚编写的popup.html页面:

    如何从零开始开发一个 Chrome 扩展?

    现在,我们一个“完整”的插件就已经做好了。

    manifest.json 配置介绍

    background

    {
        ...
        "background": {
            // 提供一个页面给background
            "page": "./html/background.html"
            // 或者若干个js文件,后台会默认生成一个空白的html
            "scripts": ["./js/background.js"]
        }
    }
    

    background配置项,为插件的后台常驻页面,生命周期随着浏览器的生命周期一样,浏览器一启动,后台页面就会开始运行,直到浏览器被关闭;或者在插件管理页面,将该插件禁用了,后台页面也会停止运行。

    另外,background拥有的权限比较高,几乎可以调用所有的Chrome扩展API(除了devtools),同时拥有直接跨域的能力。

    page:指定一个网页为后台页面。

    scripts:指定若干个js文件,后台会自动生成一个html,并按顺序调用这些js文件。

    注意:pagescripts 选项只能二选一,不然会报错。

    配置好之后,属性插件,会出现一个背景页选项:

    如何从零开始开发一个 Chrome 扩展?

    我使用的是一个background.js文件:

    function _back() {
      console.log('background.js')
    }
    console.log('running...')
    

    点进去看看里面装的什么玩意:

    如何从零开始开发一个 Chrome 扩展?

    没错,是一个普通的后台页面,如果background.js和其他页面有通信,则可以在这里进行查看请求或者调试代码。

    如果使用page选项,打开也是这个样子。

    另外:由于background是一直在后台运行的,为了优化性能,可以增加一个配置:

    {
        ...
        "background": {
            ...
            "persistent": false
        }
    }
    

    这样,插件就会在被需要时加载,在空闲时被关闭。比如安装、更新插件的时候,或者有其他页面与background通信的时候才会被加载。

    content-scripts

    content-scripts能够在合适的时机(页面载入前、载入后、空闲时)注入脚本,允许内容脚本更改其JavaScript环境,而不与页面或其他内容脚本发生冲突。

    例如,原页面有个按钮,并且给按钮添加了一个点击事件:

    <html>
        <button id="mybutton">click me</button>
        <script>
          var greeting = "hello, ";
          var button = document.getElementById("mybutton");
          button.person_name = "Bob";
          button.addEventListener("click", function() {
            alert(greeting + button.person_name + ".");
          }, false);
        </script>
      </html>
    

    在content-scripts中,加入以下代码:

    var greeting = "hola, ";
    var button = document.getElementById("mybutton");
    button.person_name = "Roberto";
    button.addEventListener("click", function() {
    alert(greeting + button.person_name + ".");
    }, false);
    

    当页面运行之后,脚本内容也会在插件定义的时间运行,当页面点击按钮时,会出现两次弹窗。

    content-scripts配置:

    {
        ...
        "content_scripts": [
            {
              // 在匹配的URL中运行,<all_urls>表示所有的URL都会运行。
              "matches": ["<all_urls>"],
              // 注入的js,会按顺序运行。
              "js": ["./js/content.js"],
              // css引入需谨慎,因为可能会影响全局的样式,同样也能接收多个css文件,会按顺序插入到页面中
              "css": ["./css/style.css"],
              // 代码注入的时机,可选值: "document_start", "document_end", or "document_idle",最后一个表示页面空闲时,默认document_idle
              "run_at": "document_start"
            },
            {
              "matches": ["https://www.baidu.com/"],
              "js": ["./js/other.js"],
              "run_at": "document_start"
            }
          ],
        ...
    }
    

    content.js代码如下:

    console.log('hello, from content.js');
    

    other.js代码如下:

    console.log('hello, from other.js...')
    

    更新插件,当在 bytedance.feishu.cn/drive/home/…

    如何从零开始开发一个 Chrome 扩展?

    因为【 bytedance.feishu.cn/drive/home/…<all_urls>的规则,所以之后运行content.js

    当在https://www.baidu.com/运行时:

    如何从零开始开发一个 Chrome 扩展?

    同时命中了2个规则,所以content.js和other.js都会运行,顺序也是正确的。

    content-scripts 和原始页面共享DOM,但是不共享JS,如要访问页面JS(例如某个JS变量),只能通过inject-scripts来实现。content-scripts能够访问的Chrome API的权限也比较低,只能访问以下四个API:

    • chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)
    • chrome.i18n
    • chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)
    • chrome.storage

    Inject-scripts

    inject-scripts 是通过DOM操作插入的JS代码,通常在content-scripts只能操作DOM,但是却无法访问页面的JS,借助content-scripts可以操作DOM的能力,往页面中插入JS文件,给页面提供调用插件API的能力,以及和background通信的能力。

    在插入之前,需配置一下web可访问的资源,同时content-scripts的调用时机换成"document_end"或者"document_idle",不然会无法获取DOM,导致插入失败。在manifest.json中添加以下内容:

    {
        ...
        "content_scripts": [
            {
              "matches": ["<all_urls>"],
              "js": ["./js/content.js"],
              "run_at": "document_end"
            },
            ...
         ],
        "web_accessible_resources": ["js/inject.js"],
        ...
    }
    

    inject.js的内容如下:

    function mockApi () {
      console.log('this is from inject.js')
    }
    

    content.js增加以下代码:

    (function () {
      let path = 'js/inject.js';
      let script = document.createElement('script');
      script.setAttribute('type', 'text/javascript');
      // 注意,路径需用Chrome API 生成,这个方法可以获得插件的资源的真实路径。
      // 类似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.js
      script.src = chrome.extension.getURL(path);
      script.onload = function () {
        // 在执行完代码之后移除script标签
        this.parentNode.removeChild(this);
      }
      document.body.appendChild(script);
    })();
    

    更新插件后,页面就可以访问inject.js的方法:

    如何从零开始开发一个 Chrome 扩展?

    permissions

    插件后台有的操作需要配置相应的权限,例如本地存储、网络请求、通知等等,示例:

    {
        ...
        "permissions": [
            "contextMenus", // 右键菜单
            "tabs", // 标签
            "notifications", // 通知
            "webRequest", // web请求
            "webRequestBlocking",
            "storage" // 插件本地存储
        ],
        ...
    }
    

    完整的manifest配置

    官方文档:https://developer.chrome.com/extensions/manifest

    通信

    popup和background通信

    popup可以通过 chrome.extension.getBackgroundPage() API 直接获取到background的上下文,从而调用background的方法来通信:

    // popup.js
    var backend = chrome.extension.getBackgroundPage();
    backend.test(); // 访问bbackground的函数
    

    background可以通过chrome.extension.getViews({type:'popup'}) 获取到popup的上下文,前提是popup页面是打开的状态下。

    let views = chrome.extension.getViews({type:'popup'});
    let popup = null
    if(views.length > 0) {
        popup = views[0];
        // 直接访问popup的函数
        popup.test();
    }
    

    这里需要注意一点:

    在popup页面,你如果想编写js,请将js编写在一个文件里面,然后引入进来,不然会报错,这是因为Chrome的安全政策规定的:https://developer.chrome.com/extensions/contentSecurityPolicy

    popup错误示范:

    <!DOCTYPE html>
    <html lang="en">
    <head>
       ...
    </head>
    <body>
        <p style="width: 200px;text-align:center;">hello world!!</p>
        <script>
            // 不能直接在里面写
        </script>
    </body>
    </html>
    

    正确姿势:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        ...
    </head>
    <body>
        <p style="width: 200px;text-align:center;">hello world!!</p>
        <script src="../js/popup.js"></script>
    </body>
    </html>
    

    content-scripts和background通信

    content-scripts可以通过 chrome.runtime.sendMessage(message) 给background发送消息:

    chrome.runtime.sendMessage('message content', (res) => {
        console.log('from background:', res)
    });
    

    background通过chrome.runtime.onMessage.addListener()监听content-scripts发送的消息:

    chrome.runtime.onMessage.addListener(function(message, sender, callback) {
       console.log(mesasge); // meesage content
       callback && callback('yes this from background')
    });
    

    background主动给content-scripts发消息,首先得查找要给哪个tab发消息,使用chrome.tabs.query 这个方法查找到tab,再使用chrome.tabs.sendMessage 方法给tab发消息:

    // {active: true, currentWindow: true} 表示查找当前屏幕下的active状态的tab;
    chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
        chrome.tabs.sendMessage(tabs[0].id, 'message content', (res) => {
            console.log('from content:', res)
        });
    });
    

    content-scripts通过chrome.runtime.onMessage.addListener 去监听事件:

    chrome.runtime.onMessage.addListener(function (message, sender, callback) {
        console.log(message, sender)
        callback && callback('yes this from content')
    });
    

    注意:

    1.消息内容可以直接发送JSON格式的对象。

    2.popup和content的通信方式与上面一样。

    3.如果popup和background都监听了从content发来的消息,两者都能收到监听消息,但是callback只会触发一次,被谁触发取决与谁先发送。

    inject-scripts和content-scripts

    inject-scripts和content-scripts通信有两种方法:

    1.window.postMessage发送,window.addEventListener接收

    2.还有一种是自定义的DOM事件;

    但是很少情况会是content-scripts去调inject-scripts,因为,可以,但是没必要....content-scripts完全可以自己处理一些API的事件监听,况且inject-scripts也只是content-scripts生成并插入到DOM里面的,所以在content-scripts眼里,inject-scripts就是个弟弟...

    但是,很多用户触发的事件,需要通过inject-scripts告诉content-scripts,content-scripts再给background通信并且去做一些事情,然后再发消息告诉inject-scripts,从这个角度看:content-scripts就是一个inject-scripts的工具人!

    (扯平了,完美。)

    inject-scripts给content-scripts发消息:

    window.postMessage({"test": '你好!工具人!'}, '*');
    

    content-scripts接收消息:

    window.addEventListener("message", function(message) {
        console.log('来了老弟!', message.data);
    }, false);
    

    同样的,content-scripts给inject-scripts发消息是一样的。

    练练手:HTTP Header 插件

    实现一个HTTP Header 插件,可以实现动态添加header,并且给网络请求自动加上header,header参数可以配置。

    示例图:

    如何从零开始开发一个 Chrome 扩展?

    Background 功能设计

    background复制存储、操作headers,对所有浏览器请求做一层拦截,并加上启用的headers。

    注意:因为涉及到网络请求,所以需在manifest.json中添加权限:

    {
        ...
        "permissions": [
            "storage", // 本地存储
            "webRequest", // 网络请求
            "webRequestBlocking", // 网络请求 阻塞式
            "<all_urls>" // 匹配的URL
        ]
        ...
    }
    

    Background 功能伪代码:

    // headers数据结构, 附带默认值;(可以改为本地存储)。
    const headers = [
        {
        key: 'Content-Type',
        value: 'application/x-www-form-urlencoded',
        enable: false,
      },
      {
        key: 'Test-Header',
        value: '按F进入坦克',
        enable: true,
      },
    ];
    
    // 获取、新增、删除、启用禁用
    function getHeaders () {
        return headers;
    }
    function addHeader (header) {
        headers.push(header);
    }
    function deleteHeader (index) {
        headers.splice(index, 1);
    }
    
    function toggleHeader(index) {
      headers[index].enable = !headers[index].enable;
    }
    ...
    
    // 请求拦截器
    // On install 在被安装的时候去初始化
    chrome.runtime.onInstalled.addListener(function(){
        // 添加事件  
        chrome.webRequest.onBeforeSendHeaders.addListener(requestHandle, {
            urls: ["<all_urls>"],// 拦截所有URL的请求
        },["blocking", "requestHeaders"]); // 阻塞式
        console.log('load');
    });
    
    // 添加header
    function requestHandle(request) {
        let requestHeaders = request.requestHeaders;
        // 添加headers
        headers.forEach(item => {
            if (item.enable) {
                requestHeaders.push({
                    name: item.key,
                    value: item.value,
                });
            }
        });
        return {requestHeaders};
    }
    

    chrome.webRequest的生命周期:

    如何从零开始开发一个 Chrome 扩展?

    详细参考:https://developer.chrome.com/extensions/webRequest

    popup 页面设计

    popup页面提供增加、删除、启用禁用功能接口,并且在每次打开popup页面的时候去background获取最新的header数据,展示在前台。

    popup.js 功能伪代码:

    // popup页面被打开时,去后台获取最新header
    window.onload = function () {
        let backend = chrome.extension.getBackgroundPage();
        // 调用background方法,获得headers
        let headers = backend.getHeaders();
        // 渲染header
        createElement(headers);
    }
    
    // 增加按钮
    function addHeader() {
        let backend = chrome.extension.getBackgroundPage();
        let key = document.querySelector('.key');
        let value =  document.querySelector('.value');
        let header = {
            key: key.value,
            value: value.value,
            enable: true
          }
        // 调用background方法,新增headers
        backend.addHeader(header);
        createElement(header);
    }
    // 启用禁用、删除功能
    function toggleHeader(index) {
      let backend = chrome.extension.getBackgroundPage();
      backend.toggleHeader(index);
    }
    
    function delHeader(index) {
      let backend = chrome.extension.getBackgroundPage();
      backend.deleteHeader(index);
    }
    

    效果

    打开popup,添加一个header:

    如何从零开始开发一个 Chrome 扩展?

    随便打开一个网页,打开控制台查看RequestHeaders:

    如何从零开始开发一个 Chrome 扩展?

    总结

    • 很多权限、功能需要在manifest.json配置。
    • content-scripts、popup、background、inject-scripts拥有的权限不一样,通信方式也不一样,理解各个脚本的特点,组合使用。
    • 开发调试可在后台背景页查看信息,popup、inject-scripts、content-scripts可直接审查元素调试。

    Chrome 插件还有很多功能这里没有详细介绍,例如devtools。感兴趣的同学可以查阅下面的参考文档。

    参考文档

    官方文档:https://developer.chrome.com/extensions

    参考博客:https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html 

    ------

    欢迎关注「 字节前端 ByteFE 」

    简历投递联系邮箱「 tech@bytedance.com 」


    起源地下载网 » 如何从零开始开发一个 Chrome 扩展?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元