Browser Extension v3 简介

extension
Created 9/17/2022
Updated 4/14/2023

Browser Extension v3 简介

参考

浏览器(该系列笔记主要指 Chrome 浏览器)为插件提供的特有 API

浏览器扩展 extension 是一个小型的程序,它可以使用浏览器提供的 API 和 web 技术以增强浏览器功能,例如页面内容增强、信息聚合等。

一般的主流浏览器都提供一个平台,以供使用者查找和安装所需的插件,例如 Chrome 应用商店Edge 外接程序Firefox 附加附件

此外浏览器一般还支持加载/安装已解压的扩展程序,一般用于开发调试。

说明

如果需要在 chrome 网上应用店分发自己开发的扩展程序,要先注册为 Chrome 网上应用店开发者(支付一次性 5 美元的注册费),再根据官方教程将程序进行打包发布。

通过官方渠道分发的扩展程序应该遵循 Chrome 网上应用店政策,主要的关注点如下:

  • 单一目的原则:扩展程序可以包含很多组件和功能,但应该只为了服务于一个目的,这是为了保持 Chrome 用户体验的良好质量。
  • 用户界面需要简介且意图明确:交互界面可以是一个简单的图标,或是一个模态框。
提示

主题 theme 是一种特殊的扩展程序,用以改变浏览器的外观,和一般的扩展程序一样打包分发,但是它们不包含 JavaScript 或 HTML 代码。

同样可以在 Chrome 网上应用店找到丰富的主题。

如果你要开发主题可以参考官方样例

架构概览

扩展程序是一个包含着 HTML、JavaScript、CSS 文档,以及图片等各种其他静态资源的压缩包,它使用 web 主流技术进行开发,可以串接浏览器所提供的 API,以实现增强浏览器功能的目的。

扩展程序依其功能的不同,项目的结构和所包含的文件类型也不同,但是它们一般都由以下部分构成:

  • Manifest:每一个扩展程序都必须有一个配置清单 manifest.json 文档,在其中清楚地列明该扩展程序的相关信息和所需使用的权限
  • Background Script:后台脚本,它主要包含一堆事件监听器和相应的处理函数。通过事件监听-响应的交互模型,可以让脚本在后台静默休眠,当相应的事件被触发时再执行相应的回调函数,以此让扩展程序有更佳的性能
  • UI Elements:扩展程序提供多种用户交互控件,例如 browser actionpage actioncontext menus快捷键;也允许通过页面展示内容,例如 popup;或通过浏览器提供的 API tab.create 或通用方法 window.open() 打开一个指定的页面
  • Content Script:内容脚本,它是可以植入页面运行的 JavaScript 脚本,可以用于读取页面内容,或向页面插入内容
  • Options Page:设置页面,每一个扩展程序允许有一个配置页面,以供用户定制扩展程序的参数

以上有的组成部分可以通过交换信息的方式来互相沟通

各部分之间相互交换信息
各部分之间相互交换信息

浏览器除了提供通用的 web API 接口,还为扩展程序提供特有的 API,这些 Chrome API 大部分都是异步的,需要通过异步编程(promise 或 async/await)来使用它们。

扩展程序也可以使用 Chrome API 的 storage 或 HTML5 提供的 web storage API 存储数据,当然也可以访问外部服务器存读数据。

说明

扩展程序在隐身模式下一般不能运行,除非满足隐身模式的约束(不跟踪、不记录用户行为数据)

扩展程序生态愿景

为了改善和提升 Chrome 扩展程序平台这个充满活力的生态系统,Chrome 团队在这一篇文章里阐述了祂们的长期愿景,以便开发者理解并拥抱扩展程序平台的未来方向。

扩展程序以前是基于 "webby" 模型,可以让 web 开发者快速上手;然后转为 permissions 模型,可以让用户在安装时,以更细颗粒度控制扩展程序可访问的权限和资源,而且每一个扩展程序运行在一个隔离的沙盒环境中,更加安全。

扩展程序的历史
扩展程序的历史

随着越来越多的应用发布,有部分扩展程序会利用这个平台来非法获得对用户数据和元数据,因此浏览器厂商的愿景是不断改进扩展程序的安全性、性能和隐私,同时保持扩展程序的可拓展性、灵活性,并让 web 开发者有更加友善的 webby 开发环境。

扩展程序的愿景
扩展程序的愿景

  • 隐私:未来会更常使用临时的、基于内容的权限许可,限制扩展程序对用户数据的被动/隐式访问,其中 activeTab 许可(一个临时授权,许可扩展程序访问当前页面)就是是重要的一步。此外在扩展程序运行时,以通知的方式告知用户它正在请求访问哪些数据,这是十分重要的,因为这种方式可以让用户知道程序获取哪些数据,并有主动权去保护自己的数据
  • 安全:以更严格的协议限制扩展程序获取外部资源
  • 性能:确保扩展程序在不同的设备中可以运行良好
  • Webbiness:除了提供一些扩展程序专属的技术(如浏览器为扩展程序提供的特殊的 API),还会不断跟进主流的 web 技术,对它们提供兼容支持,让 web 开发者可以快速上手,例如在 Manifest V3 版本的 Chrome 扩展程序中,支持 service works 和 promises
  • 能力:让扩展程序可以实现更强、更丰富的功能
说明

在文章更新的时间点,目前最新的扩展程序配置版本是 Manifest V3

扩展程序样例

以下是创建一个简单的扩展程序的流程,该扩展程序可以让用户修改当前页面的背景色

  • 创建一个空目录作为项目文件夹,也可以从官网下载完整项目源码的压缩包
  • 在项目的根目录下,创建一个 manifest.json 文档,它是扩展程序的配置清单,最基本的配置如下:
    manifest.json
    json
    {
      "name": "Getting Started Example",
      "description": "Build an Extension!",
      "version": "1.0",
      "manifest_version": 3
    }
    
    说明
    • 字段 name 是扩展程序的名称
    • 字段 description 是扩展程序的描述
    • 字段 version 是扩展程序的版本
    • 字段 manifest_version 是清单文件的版本,当前最新的版本号是 3
  • 然后在开启了「开发者模式」下,通过「加载已解压的扩展程序」的方式安装该扩展程序
  • 后台脚本 background scripts 是扩展程序的一个重要组成部分,它包含一堆事件监听和处理函数,基于事件监听-响应的模型可以让扩展程序具备响应性,且有很好的性能。
    在该示例中相应的脚本文件是 background.js
    需要先在 manifest.json 中对所使用的后台脚本文件进行声明/注册
    manifest.json
    json
    {
      "name": "Getting Started Example",
      "description": "Build an Extension!",
      "version": "1.0",
      "manifest_version": 3,
      "background": {
        "service_worker": "background.js"
      }
    }
    
    说明

    在开发过程中,如果修改了扩展程序的源码(保存后),需要点击重载按钮,以让新增代码生效

    重载扩展程序
    重载扩展程序

    也可以借助一些框架/插件来开发扩展程序,它们一般会提供 HMR 热重载等功能,这样就可以省去手动刷新的繁琐步骤,可以有更佳的开发体验

  • 我们需要在扩展程序安装后马上设置一个变量,以存储背景颜色。因此需要在后台脚本 background.js 监听 onInstalled 事件,其回调函数调用浏览器提供的存储 API 保存一个默认背景色。
    background.js
    js
    let color = '#3aa757'; // 默认背景色是绿色
    
    chrome.runtime.onInstalled.addListener(() => {
      chrome.storage.sync.set({ color });
      // 输出带颜色的文字
      console.log('Default background color set to %cgreen', `color: ${color}`);
    });
    

    由于使用了存储 API,需要在配置清单 manifest.json 中的选项 permissions 中声明/注册,以获得用户的许可
    background.js
    js
    {
      "name": "Getting Started Example",
      "description": "Build an Extension!",
      "version": "1.0",
      "manifest_version": 3,
      "background": {
        "service_worker": "background.js"
      },
      "permissions": ["storage"]
    }
    
  • 点击 service worker 可以打开后台脚本的调试界面(一个开发者调试工具)
    调试后台脚本
    调试后台脚本

    重新加载扩展程序后,可以在开发者工具的终端看到成功输出了预期的内容
    后台脚本在终端的输出
    后台脚本在终端的输出
  • 扩展程序提供多种用户交互界面,其中之一是弹出框 popup,它会在用户点击浏览器右上角的扩展程序图标是弹出,并在失去焦点时消失。弹出框本质上是一个 HTML 页面。
    在项目中创建一个 popup.html 文档。就像 web 开发一样,popup.html 可以引用样式文件对网页外观进行设置,在项目中创建一个样式文档 button.css
    popup.html
    html
    <!DOCTYPE html>
    <html>
      <head>
        <link rel="stylesheet" href="button.css">
      </head>
      <body>
        <button id="changeColor"></button>
      </body>
    </html>
    
    button.css
    css
    button {
      height: 30px;
      width: 30px;
      outline: none;
      margin: 10px;
      border: none;
      border-radius: 2px;
    }
    
    button.current {
      box-shadow: 0 0 0 2px white,
                  0 0 0 4px black;
    }
    

    同样也需要在配置清单 manifest.json 中声明/注册弹出框(所对应的 HTML 文件),通过字段 action.default_popup 进行配置
    manifest.json
    json
    {
      "name": "Getting Started Example",
      "description": "Build an Extension!",
      "version": "1.0",
      "manifest_version": 3,
      "background": {
        "service_worker": "background.js"
      },
      "permissions": ["storage"],
      "action": {
        "default_popup": "popup.html"
      }
    }
    

    可以为扩展程序设置在浏览器工具栏(右侧)中显示的图标(相关图片文件可以在这里下载,解压后将文件夹 📁 images 放到项目的根目录下)
    同样需要在配置清单 manifest.json 指定所使用的图片的路径,通过字段 action.default_icon 进行设置,最好提供多个尺寸的版本,以便用于不同的场景中
    json
    {
      "name": "Getting Started Example",
      "description": "Build an Extension!",
      "version": "1.0",
      "manifest_version": 3,
      "background": {
        "service_worker": "background.js"
      },
      "permissions": ["storage"],
      "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"
        }
      }
    }
    
    提示

    该图标也可以在浏览器的扩展程序管理界面中显示,以及出现在获取许可的弹出框中,还可以作为扩展程序的配置页面的 favicon,可以在配置清单 manifest.json 的字段 icons 进行设置

    manifest.json
    json
    {
      "name": "Getting Started Example",
      "description": "Build an Extension!",
      "version": "1.0",
      "manifest_version": 3,
      "background": {
        "service_worker": "background.js"
      },
      "permissions": ["storage"],
      "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"
      }
    }
    
    提示

    刚安装的扩展程序的图标默认不会显示在浏览器工具栏(右侧)上,可以点击扩展程序的图钉按钮将它们显示出来。

    将扩展程序钉在工具栏上
    将扩展程序钉在工具栏上


    然后我们可以点击该图标,就会显示一个弹出框。
    显示弹出框
    显示弹出框
  • 可以在 popup.html 里引入 JavaScript 实现交互,在项目中创建一个 popup.js 文档。
    该脚本会读取之前在后台脚本中所存储的颜色值,并将它设置为按钮的背景色;还对按钮的点击事件进行监听,当用户点击按钮时,以在**当前页面植入脚本**的方式,将页面的背景色设置为与按钮颜色相同
    popup.js
    js
    // Initialize button with user's preferred color
    let changeColor = document.getElementById("changeColor");
    
    chrome.storage.sync.get("color", ({ color }) => {
      changeColor.style.backgroundColor = color;
    });
    
    // When the button is clicked, inject setPageBackgroundColor into current page
    changeColor.addEventListener("click", async () => {
      let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
    
      chrome.scripting.executeScript({
        target: { tabId: tab.id },
        function: setPageBackgroundColor,
      });
    });
    
    // The body of this function will be executed as a content script inside the
    // current page
    function setPageBackgroundColor() {
      chrome.storage.sync.get("color", ({ color }) => {
        document.body.style.backgroundColor = color;
      });
    }
    

    记得在 popup.html 文档中引入 popup.js 脚本
    popup.html
    html
    <!DOCTYPE html>
    <html>
      <head>
        <link rel="stylesheet" href="button.css">
      </head>
      <body>
        <button id="changeColor"></button>
        <script src="popup.js"></script>
      </body>
    </html>
    

    由于需要访问当前的页面,并且使用程序性植入脚本,需要在配置清单 manifest.json字段 permissions 中声明/注册,以获得用户的许可
    manifest.json
    json
    {
      "name": "Getting Started Example",
      "description": "Build an Extension!",
      "version": "1.0",
      "manifest_version": 3,
      "background": {
        "service_worker": "background.js"
      },
      "permissions": ["storage", "activeTab", "scripting"],
      "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.htmloptions.js
    options.html
    html
    <!DOCTYPE html>
    <html>
      <head>
        <link rel="stylesheet" href="button.css">
      </head>
      <body>
        <div id="buttonDiv">
        </div>
        <div>
          <p>Choose a different background color!</p>
        </div>
      </body>
      <script src="options.js"></script>
    </html>
    
    options.js
    js
    let page = document.getElementById("buttonDiv");
    let selectedClassName = "current";
    const presetButtonColors = ["#3aa757", "#e8453c", "#f9bb2d", "#4688f1"];
    
    // Reacts to a button click by marking the selected button and saving
    // the selection
    function handleButtonClick(event) {
      // Remove styling from the previously selected color
      let current = event.target.parentElement.querySelector(
        `.${selectedClassName}`
      );
      if (current && current !== event.target) {
        current.classList.remove(selectedClassName);
      }
    
      // Mark the button as selected
      let color = event.target.dataset.color;
      event.target.classList.add(selectedClassName);
      chrome.storage.sync.set({ color });
    }
    
    // Add a button to the page for each supplied color
    function constructOptions(buttonColors) {
      chrome.storage.sync.get("color", (data) => {
        let currentColor = data.color;
        // For each color we were provided…
        for (let buttonColor of buttonColors) {
          // …create a button with that color…
          let button = document.createElement("button");
          button.dataset.color = buttonColor;
          button.style.backgroundColor = buttonColor;
    
          // …mark the currently selected color…
          if (buttonColor === currentColor) {
            button.classList.add(selectedClassName);
          }
    
          // …and register a listener for when that button is clicked
          button.addEventListener("click", handleButtonClick);
          page.appendChild(button);
        }
      });
    }
    
    // Initialize the page by constructing the color options
    constructOptions(presetButtonColors);
    

    该页面可以认为是插件的配置页面,需要在配置清单 manifest.json 的字段 options_page 中进行声明/注册
    manifest.json
    json
    {
      "name": "Getting Started Example",
      "description": "Build an Extension!",
      "version": "1.0",
      "manifest_version": 3,
      "background": {
        "service_worker": "background.js"
      },
      "options_page": "options.html",
      "permissions": ["storage", "activeTab", "scripting" ],
      "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"
      }
    }
    

    重载扩展程序后,在浏览器工具栏的扩展程序的图标上点击右键,并在弹出菜单中选择「选项」,进入扩展程序的设置页面。也可在 chrome://extensions 页面中,点击该扩展程序选项卡中的「详情」进入详情页面,在其中点击「扩展程序选项」,最后也可以进入相应的页面。
    1/0
    扩展程序选项-1
    扩展程序选项-2
    扩展程序选项-3

Copyright © 2024 Ben

Theme BlogiNote

Icons from Icônes