手把手教你从0到1开发浏览器插件

使用Chrome插件可以为Chrome浏览器带来一些功能性的扩展,进而提高使用体验;俗话说的好Chrome没插件,香味少一半,Chrome最大的优势还是其支持众多强大好用的扩展程序;今天就来了解一下插件是如何开发的,和普通的浏览器页面开发有什么区别,以及安利一些功能强大的插件。

注:本文为开发教程为chrome插件,后面为自己开发的二维码短链插件,有需要可以私信
 

浏览器插件种类

常见的浏览器插件种类可以分为以下几种类型:

  • 以chromium为内核的浏览器插件,如chrome、edge、opera

  • firefox浏览器插件

  • safari浏览器插件

本次只专注于以chromium为内核的浏览器插件,因为这类浏览器使用最为广泛。

Chrome插件简介

Chrome插件,官方名称extensions(扩展程序);为了方便理解,以下都称为Chrome插件,或者简称插件,那么什么是Chrome插件呢?

扩展程序是自定义浏览体验的小型软件程序。它们让用户可以通过多种方式定制Chrome的功能和行为。

插件程序可以提供以下几个功能:

  • 生产力工具:

  • 网页内容丰富

  • 信息聚合

  • 乐趣和游戏

可以通过点击更多 -> 扩展程序来查看所有安装的插件,或者直接打开插件标签页chrome://extensions/。

获取插件

  • 大多数Chrome用户从Chrome网上应用店获得插件程序。世界各地的开发人员会在Chrome网上应用店中发布他们的插件,经过Chrome的审查并向最终用户提供。

  • 但是官方渠道需要翻墙,下面分享国内版本以及超实用插件

优秀/实用插件分享

国内插件渠道

  • 扩展迷:https://extfans.com/

官方插件(站外)

  • https://chromewebstore.google.com/

AdGuard 广告拦截器

  • chrome应用商店渠道

  • 扩展迷渠道

还可以自定义拦截某个区域

FeHelper前端助手

  • 扩展迷渠道

  • chrome应用商店渠道

FE助手是由国人开发的一款前端工具集合的小插件,插件功能比较全面:包括字符串编解码、代码压缩、美化、JSON格式化、正则表达式、时间转换工具、二维码生成与解码、编码规范检测、页面性能检测、页面取色、页面截屏、Ajax接口调试。

  • 右击即可触发

Octotree

  • 扩展迷渠道

  • chrome应用商店渠道

在Github上查看源代码的体验十分糟糕,尤其是从一个目录跳转到另一个目录的时候,非常麻烦。Octotree是一款chrome插件,用于将Github项目代码以树形格式展示,而且在展示的列表中,我们可以下载指定的文件,而不需要下载整个项目。

Allow CORS

  • 扩展迷渠道

  • chrome应用商店渠道

我们开发过程中经常会遇到接口跨域的问题,通过Allow CORS: Access-Control-Allow-Origin这个插件,可以允许我们在接口的响应头轻松执行跨域请求,只需要激活插件并且执行。

沉浸式翻译

  • 扩展迷渠道

  • chrome应用商店渠道

可以中英对照全篇翻译、pdf翻译

Tampermonkey(油猴)

  • 扩展迷渠道

  • chrome应用商店渠道

  Tampermonkey(俗称油猴)是一款免费的浏览器扩展和最为流行的用户脚本管理器。虽然有些受支持的浏览器拥有原生的用户脚本支持,但 Tampermonkey将在您的用户脚本管理方面提供更多的便利。 它提供了诸如便捷脚本安装、自动更新检查、标签中的脚本运行状况速览、内置的编辑器等众多功能, 同时Tampermonkey还有可能正常运行原本并不兼容的脚本。

通过给管理器安装各类脚本,可以让大部分 HTML 为主的网页更方便易用,比如:全速下载网盘文件、视频会员网站去广告、悬停显示大图、Flash/HTML5 播放器转换、阅读模式等。有点像给Chrome的插件装上插件(这里又是一个套娃)。

油猴如何使用(视频会员为例)

插件如何工作?

  1. 插件是基于Web技术构建的,例如HTML、JavaScript和CSS。它们在单独的沙盒执行环境中运行并与Chrome浏览器进行交互。

  2. 插件允许我们通过使用API修改浏览器行为和访问Web内容来扩展和增强浏览器的功能。插件通过最终用户UI和开发人员API进行操作:

    1. 扩展用户界面,这为用户提供了一种一致的方式来管理他们的扩展。

    2. 扩展的API允许浏览器本身的扩展的代码访问功能:激活标签,修改网络请求等等。

  3. 要创建插件程序,我们需要组合构成插件程序的一些资源清单,例如JS文件和HTML文件、图像等。对于开发和测试,可以使用扩展开发者模式将这些“解压”加载到Chrome中。如果我们对自己开发出来的插件程序感到满意,就可以通过网上商店将其打包并分享给其他的用户。

插件的原则

我们编写的插件想要发布到Chrome网上应用店中,就必须遵守网上应用店政策,它规定了以下几点:

  • 插件必须满足一个定义狭窄且易于理解的单一目的。单个插件可以包含多个组件和一系列功能,只要一切都有助于实现一个共同的目的。

  • 用户界面应该是最小的并且有意图。它们的范围可以从简单的图标到打开一个带有表单的新窗口。

如何写一个简易的浏览器插件

开发浏览器的前置要求

开发浏览器插件对开发者最基础要求为能够使用html、css、javascript。如果想要进一步学习如何使用框架开发的话需要开发者熟悉react开发。

Chrome 插件由不同的部件构造而成,包括:后台脚本、内容脚本、选项页、UI 元素以及各种逻辑文件。

参考文档:

  • chrome官方插件开发教程(v3版本)

  • 框架进阶开发浏览器插件

项目目录结构

manifest.json

所有的浏览器插件必须在根目录下新建文件manifest.json清单记录重要的元数据,定义资源,声明权限,并标识哪些文件在后台和页面上运行。这是插件的配置文件,说明了插件的各种信息;它的作用等同于小程序的app.json和前端项目的package.json。

service worker

service worker作用是处理和监听浏览器事件,相当于是在后台持续运行的脚本,可以使用浏览器的全部api;但是不能和页面内容直接交互。

content scripts

content scripts是在网页上执行的javascript,可以读取和修改网页上的dom元素,但只能使用部分的浏览器api,具体请见:content scripts。

插件页面

插件页面包括浏览器的图标以及其点击后的效果,如popup页面;options页面,也就是右键浏览器图标之后点击选项弹出的页面;其他页面,也就是除了上述两种页面的其他浏览器插件的页面。

所有的插件页面都可以使用浏览器api。

manifest.json

具体配置请参考文档manifest

manifest.json常用的配置如下:

{
  // 必须的
  "manifest_version": 3,
  "name": "插件名称",
  "version": "版本",
  // 常用
  "description": "插件描述",
  "author": "作者名",
  "background": { // 配置service worker
    "service_worker": "background.js"
  },
  "options_ui": { // 配置options页面
    "page": "options.html",
    "open_in_tab": true
  },
  "content_scripts": [ // 配置content scripts
    {
      "matches": [
        "*://www.baidu.com/*"
      ],
      "js": [
        "test.js"
      ],
      "all_frames": true,
      "css": []
    }
  ],
  "web_accessible_resources": [ // 配置Web可访问资源
    {
      "matches": [
        "*://www.baidu.com/*"
      ],
      "resources": [
        "test.css"
      ]
    }
  ],
  "host_permissions": [ // 与主机权限相关,推荐使用同样的设置
    "https://*/*"
  ],
  "permissions": [ // 配置插件所使用的api权限
    "storage",
    "contextMenus",
    "cookies"
  ],
  "action": { // 使用action api配置插件工具栏中图标、弹出页面等内容
    "default_icon": {
      "16": "icon16.png",
      "32": "icon32.png",
      "48": "icon48.png",
      "64": "icon64.png",
      "128": "icon128.png"
    },
    "default_popup": "popup.html",
    "icons": {
      "16": "icon16.png",
      "32": "icon32.png",
      "48": "icon48.png",
      "64": "icon64.png",
      "128": "icon128.png"
    }
  }
}
  • 我们在项目中创建一个最简单的manifest.json配置文件,manifest 描述插件的元数据,包括插件名、描述以及版本等等:

{
    // 插件名称
    "name": "一个简单的插件",
    // 插件的描述
    "description" : "Demo Extension to change background color!",
    // 插件的版本
    "version": "1.0",
    // 配置插件程序的版本号,最新是3
    "manifest_version": 3
}

加载未打包插件

为方便插件调试,Chrome在开发模式下,支持加载未打包的插件。只需指定插件的开发目录(包含manifest)即可完成加载,关键步骤如下:

  1. 打开插件管理页面,直接点这个访问这个链接即可:chrome://extensions;

    • 或者从菜单进入设置页,找到并点击扩展程序

    • 亦或从菜单移到更多工具,找到并点击扩展程序

  1. 点击位于右上角的开发模式开关,开启开发模式;

  2. 点击左上角的加载已解压的扩展程序,然后直接选择插件目录即可;

插件安装成功!虽然它现在还什么都干不了,但总算迈出了第一步。由于还没在 manifest 配置文件里注册图标,Chrome 会为插件显示默认图标。

添加简单功能

插件虽已安装,却啥也没干,因为还没添加执行逻辑。我们先写一些代码,让插件保存背景颜色值。

注册后台执行脚本

跟其他重要部件一样,后台脚本也必须注册到 manifest 配置文件。在 manifest 注册后台脚本,相当于告诉插件去哪找后台脚本,以及脚本应该执行什么事情。

Service Worker

{
  "name": "一个简单的插件",
  "description": "Demo Extension to change background color!",
  "version": "1.0",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js",
    // 开启模块化
    "type": "module"
  }
}

manifest 加上 background 配置后,Chrome 现在知道插件包含一个 服务执行器service worker )。当你重新加载插件时,Chrome 会找到注册的后台脚本,执行其中的额外指令,比如监听一些重要事件。

编写后台脚本

在插件目录创建 background.js 文件,并写上代码:

const color = "#3aa757";

chrome.runtime.onInstalled.addListener(() => {
    console.log("插件已被安装");
    chrome.storage.sync.set({ color });
    console.log(`[Coloring] default background color is set to: ${color}`);
});

这段代码通过常量定义默认的背景色,然后通过 Chrome API 注册 onInstalled 事件监听函数。监听函数将在插件安装的时候执行,调用 storage 存储接口,保存默认的背景色。

之所以用 storage 来保存背景色,是为了能够在不同的部件中访问或者更新它。

现在点击插件右下角的转圈,重新加载插件,但我们发现插件报错了。因为插件使用了 storage 接口,但还没获得 Chrome 的授权。

调试后台脚本

点击重新加载插件后,插件卡片会显示查看视图新字段,包含一个可以点击的链接Service Worker。点击该链接,即可打开对应后台脚本的调试终端,从中可以查到报错细节:

由此可见,chrome.storage 是 undefined ,因为 Chrome 还没将 storage 接口授权给插件。

storage存储接口授权

我们在插件安装时在storage中设置一个值,这将允许多个插件组件访问该值并进行更新操作。大部分接口,包括 storage 接口,必须注册在 manifest 配置文件 permissions 字段下,获得授权之后才能使用:

 
{
  "name": "一个简单的插件",
  "description": "Demo Extension to change background color!",
  "version": "1.0",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  // 授权
  "permissions": ["storage"]
}

重新加载插件,可以看到 background.js 执行无误,并如预期那样输出日志

弹窗操作以及选项界面

Chrome 插件可能使用各种形式的用户界面,我们先试试 Popup 弹出窗口。在插件目录创建一个名为 popup.html 的文件,在创建个button.css:

 
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="button.css">
  </head>
  <body>
    <button id="changeColor">1233</button>
  </body>
</html>

options.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    Options Page
</body>
</html>

button {
  height: 30px;
  width: 60px;
  outline: none;
  margin: 10px;
  border: none;
  background: #ff6700;
  border-radius: 2px;
}

button.current {
  box-shadow: 0 0 0 2px white, 0 0 0 4px black;
}

Chrome 插件用户界面也是用 HTML 来编写,前端工程师应该再熟悉不过了。

跟后台脚本一样,用户界面文件也要在 manifest 中注册后,才能被 Chrome 识别。我们在 manifest 中添加一个 action 对象,并将 popup.html 文件作为 default_popup 属性注册进去:

 
{
    "name": "一个简单的插件",
    "description": "Demo Extension to change background color!",
    "version": "1.0",
    "manifest_version": 3,
    "background": {
      "service_worker": "background.js"
    },
    "action": {
      "default_popup": "popup.html" // 配置弹窗操作界面
    },
     "options_ui": {
      "page": "options.html", // 选项
      "open_in_tab": true
    }
  }

效果如图:

配置icon图标

配置扩展程序图标

制作插件:在线Chrome插件图标生成器 - UU在线工具

{
    "name": "一个简单的插件",
    "description": "Demo Extension to change background color!",
    "version": "1.0",
    "manifest_version": 3,
    "background": {
      "service_worker": "background.js"
    },
    "permissions": ["storage"],
    "action": {
      "default_popup": "popup.html",
      "default_icon": {
        "16": "image/icon16.gif",
        "32": "image/icon32.gif",
        "48": "image/icon48.gif",
        "128": "image/icon128.gif"
      }
    },
    "icons": {
      "16": "image/icon16.gif",
        "32": "image/icon32.gif",
        "48": "image/icon48.gif",
        "128": "image/icon128.gif"
    }
  }

案例:

chrome-plugin-base.zip

content scripts

参考文档content scripts

content scripts运行拓展将逻辑注入页面

api权限

content scripts只能使用部分的浏览器api,最常用的api为storage与runtime两个api。

具体请参考content scripts可使的api

所以在进行一些特定操作时需要通过runtime.sendMessage向service worker发送信息来执行某些操作

运行环境

content scripts存在于一个孤立的环境中,允许内容脚本对其 JavaScript 环境进行更改,而不会与页面或其他扩展的content scripts发生冲突。

比如content scripts的变量对于主机页面和其他插件的content scripts是不可见的;又或者通过content scripts改写了页面的某个api,而页面原本的元素在触发这个api时并没有按照预期的效果执行,其原因就是content scripts修改的只是自身环境的api,而不是原页面的api。

注入方式

注入方式分两种:静态注入动态注入

静态注入

manifest.json中配置的脚本即为静态注入,在匹配的页面上自动注入脚本。

 
{
  "name": "test",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.test.com/*"],
      "css": ["styles.css"],
      "js": ["content-script.js"]
    }
  ],
  ...
}

配置项

详情

matches

指定此内容脚本将被注入到哪些页面,详见match_patterns

js

要注入匹配页面的 JavaScript 文件列表。它们按照它们在此数组中出现的顺序注入(比如后者依赖前者,就得按顺序引入)

css

要注入匹配页面的 CSS 文件列表。在为页面构造或显示任何 DOM 之前,它们按照它们在此数组中出现的顺序注入

all_frames

默认为false,表示仅匹配顶部框架。如果指定true,它将注入所有框架,即使该框架不是选项卡中最顶层的框架。每个框架都独立检查 URL 要求,如果不满足 URL 要求,它不会注入子框架。

run_at

document_idle

参考:运行

document_start

document_end

动态注入

使用chrome.scripting.executeScript进行动态注入

 
chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    world: "MAIN",
    files: ['content-script.js']
  });
});

动态注入的脚本可以通过设置world属性为MAIN直接进入网页文档,而不是独立的环境。这时在主页面就可以访问到脚本的变量等信息

如果想要主动将脚本注入主页面,可以在background监听页面进行注入;或者在静态注入的脚本中动态插入script标签并且将src设置为想要注入主页面的脚本的url。

案例:

向百度注入一些脚本:

content-scripts.zip

UI elements

参考文档UI elements

该内容主要是针对于插件在工具栏的图标所展开。由于内容太多,这里只粗略介绍有哪些常用功能。

指定工具栏图标

在manifest.json的action配置项配置图标

{
  "name": "My Extension",
  ...
  "action": {
    "default_icon": {
      "16": "image_16.png",
      "32": "image_32.png",
      "48": "image_48.png",
        "128": "image_128.png"
    }
  }
  ...
}

Action badge

在图标右下角可以显示一些内容,最多展示四个字符,效果如下图:

chrome.action.setBadgeText({text: 'ON'}); // 配置文本
chrome.action.setBadgeBackgroundColor({color: '#4688F1'}); // 配置背景颜色

popup页面

弹出窗口是一个 HTML 文件,当用户单击操作图标时,它会显示在一个特殊的窗口中。弹出窗口的工作方式与网页非常相似;它可以包含指向样式表和脚本标签的链接,但不允许内联 JavaScript。

{
  "name": "Drink Water Event",
  ...
  "action": {
    "default_popup": "popup.html"
  }
  ...
}

弹出窗口也可以通过调用动态设置action.setPopup。

 
chrome.storage.local.get('signed_in', (data) => {
  if (data.signed_in) {
    chrome.action.setPopup({popup: 'popup.html'});
  } else {
    chrome.action.setPopup({popup: 'popup_sign_in.html'});
  }
});

Tooltip

将鼠标悬停在操作图标上时,使用工具提示向用户提供简短说明或说明。默认情况下,提示会显示扩展的名称。

可以通过default_title设置。

{
  "name": "Tab Flipper",
  ...
  "action": {
    "default_title": "default_title"
  }
  ...
}
也可以通过action.setTitle设置。

图标点击事件

 
chrome.action.onClicked.addListener(function(tab) {
  chrome.action.setTitle({tabId: tab.id, title: "You are on tab:" + tab.id});
});

Omnibox

用户可以通过Omnibox API调用扩展功能。在清单中包含该"omnibox"字段并指定关键字。多功能框新标签搜索示例:tt用作关键字。

 
chrome.omnibox.onInputEntered.addListener((text) => {
  // Encode user input for special characters , / ? : @ & = + $ #
  const newURL = 'https://www.google.com/search?q=' + encodeURIComponent(text);
  chrome.tabs.create({ url: newURL });
});
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "0.0.1",
  "description": "插件描述",
  "author": "gaolu",
    "background": {
        "service_worker": "background.js"
    },
    "host_permissions": [
        "https://*/*"
    ],
    "omnibox": { "keyword" : "tt" }
}

 

Context menu

可以通过在清单中授予权限来使用ContextMenus API 。

 
{
  "permissions": [
    "contextMenus",
    "storage"
  ]
}

contextMenus.create()通过调用后台脚本创建上下文菜单。这应该在runtime.onInstalled侦听器事件下完成。

Notifications

您可以通过直接在用户的系统托盘中显示通知来向用户传达相关信息。

要使用notifications API,您必须在清单中声明权限notifications

 
{ 
  "permissions": [
    "notifications",
  ],
}

浏览器 api

插件除了使用网页的web api外,也可以使用浏览器的api

api文档:chrome api

大多数的api都是异步的,所以使用其返回的结果有两种方式:

  • 通过回调函数

  • 通过异步操作(推荐)

 
// Promise
chrome.tabs.query(queryOptions)
.then((tabs) => {
  chrome.tabs.update(tabs[0].id, {url: newUrl});
  someOtherFunction();
});

// async-await
async function queryTab() {
  let tabs = await chrome.tabs.query(queryOptions);
  chrome.tabs.update(tabs[0].id, {url: newUrl});
  someOtherFunction();
}

// callback
chrome.tabs.query(object queryInfo, function callback)

有些api的应用需要在manifestpermissions字段设置对应权限

通信

文档: https://developer.chrome.com/docs/extensions/mv3/messaging/

由于content_scripts运行在网页中,而不是浏览器插件的环境,所以它需要一些特殊的方式来和浏览器插件的内容进行通信。

通信的方式包括用于一次性请求的简单通信和用于长期连接的长连接通信。

简单通信

简单通信可以使用runtime.sendMessage()或tabs.sendMessage()与插件的其他部分进行通信。

上述api可以将json字段从content_scripts发送给插件,当然也可以从插件将信息发送给content_scripts。如果要处理返回的数据,需要使用返回的Promise;也可以设置回调函数。

从content_scripts发送请求:
 
(async () => {
  const response = await chrome.runtime.sendMessage({
    data: 'hello',
  });
  console.log(response);
})();

接收信息

在事件接收端需要设置一个runtime.onMessage事件侦听器来处理消息。

 
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  console.log(request, sender);
  if (request.greeting === "hello") {
    sendResponse({farewell: "goodbye"});
  }
});

需要注意的是:sendResponse是同步调用的,如果想要在异步调用需要在onMessage的事件处理函数中添加return true;

 

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    // console.log(request, sender);
    (async () => {
    if (request.greeting === "hello") {
          const data = await getData();
          sendResponse(data);
    }
    })();
    return true;
});

如果多个页面侦听onMessage事件,则只有第一个调用sendResponse()特定事件的页面才能成功发送响应。其他对该事件的所有其他响应事件将被忽略。

长连接通信

某些情况下需要持续事件更长的通信,这时可以使用长连接通信,分别使用runtime.connect或tabs.connect打开一个从content_scripts到插件的长期通道或插件到content_scripts的长期通道。

建立连接

建立连接时,每一端都会获得一个runtime.Port对象,用于发送和接收消息。

 
const port = chrome.runtime.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener((msg) => {
  if (msg.question === "Who's there?")
    port.postMessage({answer: "Madame"});
  else if (msg.question === "Madame who?")
    port.postMessage({answer: "Madame... Bovary"});
});

从插件向content_scripts发送请求是类似的,只需要指定要连接到哪个选项卡。只需将上例中的连接调用替换为tabs.connect()即可。

处理连接信息

需要设置一个runtime.onConnect()事件侦听器。当插件的另一部分调用connect()时,将触发此事件,其中回调函数的参数是用来通过连接发送和接收消息的runtime.Port对象

 
chrome.runtime.onConnect.addListener((port) => {
  console.assert(port.name === "knockknock");
  port.onMessage.addListener((msg) => {
    if (msg.joke === "Knock knock")
      port.postMessage({question: "Who's there?"});
    else if (msg.answer === "Madame")
      port.postMessage({question: "Madame who?"});
    else if (msg.answer === "Madame... Bovary")
      port.postMessage({question: "I don't get it."});
  });
});

案例:

message.zip

浏览器插件Storage

与网页类似,浏览器插件也有用来储存数据的空间:我们可以调用浏览器插件的storage api,以此来持久化用户数据和状态。

关于浏览器插件储存有一些特性:

  • 浏览器的所有页面(包括service worker、content script)都可以使用storage api

  • 读取和写入是异步的

  • 用户清除缓存和历史记录,数据也不会收到影响

储存区域

浏览器插件的storage api有localStorage和sessionStorage两个区域,用于储存不同用途的数据。插件的storage api也有不同的储存区域;常用的storage区域为:

  1. local:本地储存,卸载插件时会清除,储存上限为5MB

  2. sync:如果用户启用的账号同步功能,储存的数据会同步到用户登录的任何chrome浏览器中。如果用户禁用了同步,则该api功能与local类似。储存上限为100KB,每一条的上限是8KB,最多能存512条数据。并且一段时间内最大写入数也有限制,具体详见sync限制。可以用于储存一些需要跨浏览器储存的数据。

  3. session:在浏览器运行期间会将数据存储在内存中,默认情况下不会将数据暴露给content script,但可以通过调用chrome.storage.session.setAccessLevel() api,让content script可以访问到storage.session中的数据。储存上限为10MB,可以用他来存一些临时的全局变量

使用storage api

想要使用storage api首先要在manifestpermissions字段中配置:

 
{
  "name": "My extension",
  ...
  "permissions": [
    "storage"
  ],
  ...
}

local:

 
chrome.storage.local.set({ key: value }).then(() => {
  console.log("Value is set");
});

chrome.storage.local.get(["key"]).then((result) => {
  console.log("Value currently is " + result.key);
});

sync:

 
chrome.storage.sync.set({ key: value }).then(() => {
  console.log("Value is set");
});

chrome.storage.sync.get(["key"]).then((result) => {
  console.log("Value currently is " + result.key);
});

session:

 
chrome.storage.session.set({ key: value }).then(() => {
  console.log("Value was set");
});

chrome.storage.session.get(["key"]).then((result) => {
  console.log("Value currently is " + result.key);
});

监听储存变化

使用chrome.storage.onChanged.addListener添加监听的回调函数

 
chrome.storage.onChanged.addListener((changes, namespace) => {
  for (let [key, { oldValue, newValue }] of Object.entries(changes)) {
    console.log(
      `Storage key "${key}" in namespace "${namespace}" changed.`,
      `Old value was "${oldValue}", new value is "${newValue}".`
    );
  }
});

插件架构

一个浏览器插件主要是由五部分组成

  • manifest:插件配置文件

  • content_script:注入到浏览器页面的脚本

  • service_worker:插件自己的后台脚本

  • extension page:插件的各个页面,比如popup、options、devtools、sidepanel

  • browser api:可以通过api调用浏览器的功能(存储、历史记录、通信等)

  1. 首先manifest是一个json文件,配置了插件的信息,其余部分的内容都需要在这里配置(当然也可以在service_worker中动态配置);需要放在插件的根目录下

  2. browser api是插件与浏览器交互的通道;根据浏览器不同,可能有些差异,但大部分的api是一致的

  3. content_script是注入到浏览器页面的脚本可以实现修改页面的dom,获取页面元素等操作。他能使用的browser api是有限的,想实现一些功能需要与service_worker或者extension page通信,由这两部分的代码代为执行一些操作。

  4. service_worker相当于是插件的后端,也就是一个在后台运行的脚本,能够访问到所有的browser api控制浏览器的操作都可以在这里运行

  5. extension page是插件内部的一些页面。有一些页面是插件规定的,比如popup、options、devtools、sidePanel等;还有一些页面是插件自定义的页面,可以通过browser api打开这些页面。在extension page也可以使用所有的browser api

数据流

  • 浏览器插件的通信是注入到网页的脚本(content_script)插件(service_worker和extension page)使用browser api传递数据。

  • 插件向脚本发送信息需要知道对应脚本所在标签页的id,才可以发送信息,并且获得脚本返回的响应

  • 而脚本向插件发送信息则可以直接发送,并获取插件返回的响应

开发生成二维码插件

背景

开发移动端的同学往往需要多个设备来会测试,市面上的二维码生成对于长链接生成的二维码密度过于密集,所以想实现一个可以生成短链的插件,帮助我们能直接访问链接或者文本。

需要实现的功能

前端插件:

生成二维码/生成短链接

服务端功能:

  • 根据浏览器指纹- 生成动态短链(10分钟有效)

  • 定时10分钟删除短链数据

  • 短链响应二维码内容,如果是链接那就302,如果不是直接body展示

已实现效果:

点击插件会自动生成当前页面的二维码,也可以手动生成二维码,如果觉得二维码生成的密度太大,可以点击生成短链,可以生成一个固定短链的二维码(10分钟内可保存本地,不管替换成什么链接都可用这个二维码)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/675509.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言基础——数组(2)

ʕ • ᴥ • ʔ づ♡ど &#x1f389; 欢迎点赞支持&#x1f389; 个人主页&#xff1a;励志不掉头发的内向程序员&#xff1b; 专栏主页&#xff1a;C语言基础&#xff1b; 文章目录 前言 一、二维数组的创建 1.1 二维数组的概念 1.2二维数组的创建 二、二维数组…

MySQL中获取时间的方法

大家好&#xff0c;在MySQL数据库开发中&#xff0c;获取时间是一个常见的需求。MySQL提供了多种方法来获取当前日期、时间和时间戳&#xff0c;并且可以对时间进行格式化、计算和转换。 以下是一些常用的MySQL时间函数及其示例&#xff1a; 1、NOW()&#xff1a;用于获取当前…

汇舟问卷:国外问卷调查怎么样?

互联网的发展为我们提供了无数的赚钱机会&#xff0c;其中不乏一些投资小、易上手的小项目&#xff0c;可以让大家充分的利用起业余的时间&#xff0c;赚到日常工作之外的收入。 ​这些项目不仅操作简单&#xff0c;而且时间灵活&#xff0c;非常适合想要利用闲余时间赚外快的…

云渲染农场什么是线程模式?

许多设计师在选择云渲染农场时&#xff0c;常常会遇到48线程、56线程、72线程等选项&#xff0c;然而&#xff0c;不少新手在面对这些选择时&#xff0c;往往无法直观地感受到不同线程数量之间的差异。接下来&#xff0c;我们将共同探讨线程的作用和影响&#xff0c;帮助大家更…

生产制造过程中操作人员引起的不稳定性

获取更多资讯&#xff0c;赶快关注上面的公众号吧&#xff01; 文章目录 工艺成熟度操作成熟度 生产制造过程中&#xff0c;操作人员可能引起一些生产不稳定性&#xff0c;本文将主要介绍两类。 工艺成熟度 以前在工厂做项目时&#xff0c;明明都已经是可全部自动化的高级数…

1347:【例4-8】格子游戏

【解题思路】 该题为判断无向图是否有环。可以使用并查集来完成。学习并查集时&#xff0c;每个元素都由一个整数来表示。而该问题中每个元素是一个坐标点&#xff0c;由(x, y)两个整数构成。 将二维坐标变为一个整数,通过一个公式将二维坐标换算为一个整数&…

如何不用口吐莲花,照样成为社交达人

一、教程描述 每个人的一生&#xff0c;70%的时候都在沟通&#xff0c;与老板沟通、与家人沟通、与朋友沟通、与陌生人沟通&#xff0c;等等&#xff0c;但是你真的会沟通么&#xff1f;不论是工作上跟上司、同事和客户间的沟通&#xff0c;还是生活中与家人、朋友、伴侣间的沟…

B端产品无爆款,说有的都是忽悠和外行!

前言&#xff1a;网上经常有人讲运营&#xff0c;把C端那一套硬搬到B端&#xff0c;讲的自我陶醉&#xff0c;稍微有点常识的人就知道不能这么玩。 一、什么是B端和C端 B端&#xff08;Business-to-Business&#xff09;是指面向企业客户的市场和产品。B端产品或服务主要是为…

kafka命令--简单粗暴有效

zookeeper bin目录下执行 启动&#xff1a;./zkServer.sh start 停止&#xff1a;./zkServer.sh stop 重启&#xff1a;./zkServer.sh restart 状态&#xff1a;./zkServer.sh status kafka bin目录下执行 启动&#xff1a;./kafka-server-start.sh -daemon …/config/server.…

【excel】设置可变下拉菜单(一级联动下拉菜单)

文章目录 【需求】制作动态下拉菜单&#xff0c;显示无重复的“班级”列表【思路】设置辅助列&#xff0c;使用UNIQUE()函数去重&#xff0c;并用FILTER()去掉结果中的“0”【步骤】step1 辅助列step2 设置下拉菜单 【总结】 在这个一级下拉菜单后&#xff0c;我又写了二级联动…

【十年java搬砖路】Jumpserver docker版安装及配置Ldap登陆认证

Jumpserver docker 安装启动教程 拉取镜像 docker pull JumpServer启动进行前确保有Redis 和Mysql 创建jumperServer数据库 在MYSQL上执行 创建数据库 登陆MYSQL mysql -u root -p 创建Jumperserveri库 create database jumpserver default charset utf8mb4;可以为jumperSe…

PTA输入字符串str,识别字符串中字符(0-9A-Za-z),并对识别出的字符串按照按升序进行排序。

输入字符串str&#xff0c;识别字符串中指定范围内的字符(0-9A-Za-z)构成新的字符串str2&#xff0c;对字符串str2按照按升序进行排序。 输入格式: fafOgerPNM-mgg<6254 输出格式: 2456MNOPaeffgggmr #include<stdio.h> #include<string.h> int main() {cha…

SiT : Self-supervised vision Transformer

从NLP Transformer中借鉴而来的视觉 Transformer 在使用大规模监督数据或某种形式的协同监督&#xff08;例如教师网络&#xff09;进行预训练时已被证明是有效的。这些经过监督预训练的视觉Transformer在下游任务中通过最小的改动就能取得出色的结果。 随着监督预训练&#x…

springboot + Vue前后端项目(第十四记)

项目实战第十三记 写在前面1. 建立字典表2. 后端DictController3. Menu.vue4. 建立sys_role_menu中间表5.分配菜单接口6. 前端Role.vue改动总结写在最后 写在前面 本篇主要讲解动态分配菜单第二章节 菜单页面优化 引入图标 角色界面优化 角色自主分配菜单&#xff0c;并保存至…

PTA字符串str1在第i个位置插入字符串str2

字符串str1在第i个位置插入字符串str2&#xff0c;如在字符串1234567890第2位插入ABC。 输入格式: 1234567890 ABC 2 输出格式: 12ABC34567890 #include<stdio.h> #include<string.h> int main() {char s1[100],s2[100];int w;scanf("%s%s%d",s1,s2,…

Docker 基础使用(2) 镜像与容器

文章目录 镜像的含义镜像的构成镜像的作用镜像的指令容器的含义容器的状态容器的指令 Docker 基础使用&#xff08;0&#xff09;基础认识 Docker 基础使用 (1) 使用流程概览 Docker 基础使用&#xff08;2&#xff09; 镜像与容器 Docker 基础使用&#xff08;3&#xff09; 存…

关于stm32的复用和重映射问题

目录 需求IO口的复用和重映射使用复用复用加重映射 总结参考资料 需求 一开始使用stm32c8t6&#xff0c;想实现pwm输出&#xff0c;但是原电路固定在芯片的引脚PB10和PB11上&#xff0c;查看了下引脚的功能&#xff0c;需要使用到复用功能。让改引脚作为定时器PWM的输出IO口。…

tinyrenderer-切线空间法线贴图

法线贴图 法线贴图分两种&#xff0c;一种是模型空间中的&#xff0c;一种是切线空间中的 模型空间中的法线贴图的rgb代表着每个渲染像素法线的xyz&#xff0c;与顶点坐标处于一个空间&#xff0c;图片是五颜六色的。 切线空间中的法线贴图的rgb同样对应xyz&#xff0c;是切线…

排序算法(C++)

参考C算法&#xff0c;这里面有些写法也值得商榷。 1. 冒泡排序算法 冒泡排序算法代码和思路比较简单&#xff0c;大家如果在面试时被要求实现排序时&#xff0c;可以用这种方法来实现。 该算法里&#xff0c;会统一地遍历待排序的数据&#xff0c;每次比较两个相邻的数据&a…

零基础也能学!在RK平台下的OpenHarmony分区镜像烧录

开源鸿蒙硬件方案领跑者 触觉智能 本文适用于在Purple Pi OH开发板进行分区镜像烧录。触觉智能的Purple Pi OH鸿蒙开源主板&#xff0c;是华为Laval官方社区主荐的一款鸿蒙开发主板。 该主板主要针对学生党&#xff0c;极客&#xff0c;工程师&#xff0c;极大降低了开源鸿蒙开…