谷歌浏览器插件开发指南涉及以下几个方面:
1. 开发环境准备:首先需要安装Chrome浏览器和开发者工具。进入Chrome应用商店,搜索“Extensions Reloader”和“Manifest Viewer”两个插件进行安装,这两个插件可以方便开发和调试。
2. 创建插件:创建插件的方式有两种。第一种是从零开始创建,需要编写插件的各种文件,包括manifest.json、popup.html等。第二种是使用生成工具,如Yeoman,它可以自动生成插件文件和代码结构。
3. 编写插件代码:插件代码可以使用HTML、CSS、JavaScript等,需要根据插件的功能进行编写。
4. 调试和测试:在Chrome浏览器中可以使用开发者工具进行调试和测试,可以查看插件的运行状态,以及对代码进行修改和调试。
5. 发布和分发:发布插件需要一个Google开发者账号,并进行相应的审核和测试。插件发布后可以通过Chrome应用商店进行分发和安装。
总的来说,谷歌浏览器插件开发需要学习HTML、CSS、JavaScript等相关知识,并掌握Chrome插件开发的基本流程和技能。
Chrome插件本质上也是一个web页面的功能开发,因此需要熟悉以下基本的技能:
下面开始正题。
ChromeAPI是Chrome浏览器提供的JavaScriptAPI,在插件开发中使用这些API可以调用Chrome浏览器的提供的诸多功能,完成我们定制化的需求。
也叫插件清单,可以把它理解为整个插件的配置文件。manifest文件一定要放在根目录下,不可或缺,主要记录了插件的重要元数据、资源定义、权限声明,以及指定要在后台运行和页面运行的文件等。
// manifest.json文件
{
"manifest_version": 3,
"name": "Reading time",
"version": "1.0",
"description": "Add the reading time to Chrome Extension documentation articles"
}
其中manifest_version
、name
、version
是必须的。
也就是后台服务,主要是负责处理和监听浏览器的各类事件。后台服务可以使用所有ChromeAPI,但是不能直接与网页内容交互。
要使用后台服务,需要先在manifest文件中注册background:
{
...
"background": {
"service_worker": "background.js"
}
}
后台服务在浏览器运行起来后,就会一直在后台运行back.js脚本。
也叫内容脚本。上面提到后台服务无法直接与网页内容进行交互,而内容脚本便来接替了这部分工作。内容脚本可以读取、修改或注入页面DOM,也可以使用一部分的ChromeAPI,但是不可使用的那部分ChromeAPI可以通过与后台服务的通讯来完成数据或消息的传递交互。
要使用内容脚本,需要先在manifest文件中注册content_scripts:
{
...
"content_scripts": [
{
"js": ["scripts/content.js"],
"matches": [
"https://developer.chrome.com/docs/extensions/*",
"https://developer.chrome.com/docs/webstore/*"
]
}
]
}
其中,matches表示匹配的网站,js表示执行的脚本。即如果网站地址在matches列表中,则会执行js列表中的脚本。
页面包含了popup的弹窗页面、option页面以及其他页面,这些页面也是就是前端开发中的HTML文件。这些页面都可以访问ChromeAPI。
以上便是Chrome插件开发中很重要且必要的几个概念,也是一个插件工程中主要的几个文件,因此请务必记住这几个概念。接下来,我们将一步一步开发一个插件,来逐步的了解Chrome插件开发的工作。
开发前,我们先来看看这个插件需要完成那些功能。
1、阅读时间:
2、聚焦模式:
3、标签管理:
思路:在内容脚本中使用js统计当前页面的文字数count,并假设阅读速度是500个/分钟,计算出预估时间:
time=count/500
先配置manifest文件:
{
"manifest_version": 3, // 版本号,目前2版本已经停止支持,推广的是3版本
"name": "toolKits", // 插件名称
"description": "小工具集", // 插件描述
"version": "1.0", // 插件开发的版本号,自定义即可
"icons": { // 插件的图标
"16": "images/icon-16.png",
"32": "images/icon-32.png",
"48": "images/icon-48.png",
"128": "images/icon-128.png"
},
"content_scripts": [ // 内容脚本注册
{
"js": [
"scripts/baikeReadTime.js"
],
"matches": [
"https://baike.baidu.com/item/*"
]
}
]
}
从配置中可得知内容脚本的注册规则:当网址匹配上“https://baike.baidu.com/item/*”时,运行scripts/baikeReadTime.js脚本。
编写内容脚本,内容脚本是往页面中注入预估的阅读时间元素,代码如下:
// baikeReadTime.js
// 通过检查百度百科页面元素,确定大部分内容都是在class="main-content J-content"的div标签中,因此先获取到内容标签的dom
const mainContent = document.getElementsByClassName("main-content J-content")[0]
// 如果对应内容dom存在
if (mainContent) {
const text = mainContent.textContent; // 获取内容dom中所有的文本内容
const chineseWordRegExp = /[\u4e00-\u9fa5]/g; // 通过正则表达式匹配出所有的汉字
const chineseWords = text.matchAll(chineseWordRegExp);
const count = [...chineseWords].length; // 计算汉字长度
const readTime = Math.round(count / 500); // 计算预计阅读时间
const badge = document.createElement('span'); // 创建一个<span>标签
badge.classList.add('collect-text'); // 给<spn>标签添加样式 样式尽量跟周围的标签样式一致
badge.textContent = `️ 总字数约${count}, 预计阅读耗时:${readTime} 分钟`; // 给<span>标签添加文本内容
const topTool = document.getElementsByClassName("top-tool")[0] // 找到class="top-tool"的dom
topTool.insertAdjacentElement('beforebegin', badge); // 把<span>标签插入到topTool的dom前面
}
演示:打开Chrome浏览器,安装插件后,打开任意百度百科的内容页,即可看到我们注入到页面的预估阅读时间元素。比如我们打开百度百科,搜索“亚运会”,页面展示图待更新。
还是以百度百科页面为例,我们通过插件实现去掉右侧边栏和底部广告栏的信息
思路:分析页面元素,找到右侧边栏和底部广告栏的元素,再通过内容脚本对其进行样式隐藏即可。
1、首先给插件的图标设置一个徽标,用于展示聚焦模式的开关状态。
因为聚集模式的状态应该是在浏览器打开的时候就显示,因此设置状态徽标的逻辑应该放在background.js脚本中。具体代码如下:
chrome.runtime.onInstalled.addListener(() => {
chrome.action.setBadgeText({ "text": "OFF" })
})
调用ChromeAPI,给插件设置徽标。大致意思就是添加一个安装事件的监控器,当插件安装后就给插件设置一个徽标,内容是“OFF”。
监听插件的点击事件。当点击插件的时候,判断当前浏览器标签页(tab页)的网址,如果符合要求,就将插件徽标设置为“ON”。继续在background.js脚本中添加以下代码:
...
const baike = "baike.baidu.com/item/"
chrome.action.onClicked.addListener(async (tab) => {
if (tab.url.includes(baike)) { // 如果标签页的网址中包含baike的网址,则将插件徽标设置为“ON”
const prevState = await chrome.action.getBadgeText({ tabId: tab.id })
const nextState = prevState === "ON" ? "OFF" : "ON"
await chrome.action.setBadgeText({
tabId: tab.id,
text: nextState
})
}
})
以上两步使用到了Chrome的activeTab权限和scripting权限,因此我们需要再manifest文件中配置权限声明:
{
...
"permissions": [
"activeTab",
"scripting"
],
...
}
此时,重新加载插件后,如果当前标签页是百科页面,点击插件,就能看到切换徽标状态的效果的。
2、注入css,隐藏元素。
当徽标状态为ON时,给百科页面注入css,隐藏掉右侧边栏。继续在background.js脚本中添加以下代码:
...
chrome.action.onClicked.addListener(async (tab) => {
if (tab.url.includes(baike)) {
...
if (nextState === "ON") {
await chrome.scripting.insertCSS({
files: ["focus-mode.css"],
target: { tabId: tab.id },
});
} else if (nextState === "OFF") {
await chrome.scripting.removeCSS({
files: ["focus-mode.css"],
target: { tabId: tab.id },
});
}
}
})
判断徽标状态,如果是“ON”,则往目标标签页插入css文件,否则就移除掉插入到目标页的css文件。上例中插入了focus-mode.css
编写注入的css文件,已达到隐藏侧边栏和底部广告栏的目的。通过分析网页的html代码可知,class="side-content"的标签即为侧边栏的元素标签,id="tashuo_bottom"的标签即为底部广告栏的标签,因此代码如下:
// focus-mode.css
.content {
.side-content {
display: none;
}
}
#tashuo_bottom {
display: none;
}
3、演示:
浏览器中重新加载插件后,在百科页点击插件,就能看到默认插件的聚集模式是“OFF”状态,点击后徽标更新为“ON”状态,且页面的侧边栏和底部栏都消失了,图待更新。
上面两个案例,我们分别体验了内容脚本和后台服务脚本的能力,接下来我们看看插件打开的页面相关的能力。
该功能我们期望点击插件,能打开一个弹窗页,在这个弹窗页中展示当前浏览器窗口的所有标签页,然后我们可以选择某些标签页将他们放在一个标签组中。
思路:实现popup页面,并在页面中展示当前浏览器窗口的所有标签页,然后页面中有按钮,可创建一个标签组,并将选中的标签页放到这个标签组中。
1、首先我们需要一个popup页面
html代码如下:
# popup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./popup.css" />
</head>
<body>
<template id="li_template">
<li>
<a>
<h3 class="title">Tab Title</h3>
<p class="pathname">Tab Pathname</p>
</a>
</li>
</template>
<h1>Google Dev Docs</h1>
<button>新建/取消组合</button>
<ul></ul>
<script src="./popup.js" type="module"></script>
</body>
</html>
需要注意的是:这个popup.html无法运行内联 JavaScript,因此我们只能通过导入的方式,引入JavaScript脚本。
css代码如下:
# popup.css
body {
width: 20rem;
}
ul {
list-style-type: none;
padding-inline-start: 0;
margin: 1rem 0;
}
li {
padding: 0.25rem;
}
li:nth-child(odd) {
background: #80808030;
}
li:nth-child(even) {
background: #ffffff;
}
h3,
p {
margin: 0;
}
2、点击插件,触发弹窗
要实现点击插件就能打开popup弹窗页,有两中方式:
方法1:可以在manifest文件中的action字段中配置默认弹窗,代码如下:
{
...
"action": {
"default_popup": "popup.html"
},
...
}
该配置意思是点击插件时默认打开的弹窗就是“popup.html”文件。
方法2:后台脚本中通过监听点击事件,然后调用ChromeActionAPI的setPopup
方法实现。本插件因为之前实现了百度百科页面的聚焦模式功能,因此采用这种方式触发弹窗。对之前的代码做了些调整,具体如下:
// background.js
// 监听运行时和安装完成的事件
chrome.runtime.onInstalled.addListener(async () => {
// 获取当前浏览器窗口激活的标签页
tabs = await chrome.tabs.query({active: true, currentWindow: true})
// 如果标签页的url中包含百度百科的地址,就设置徽标,否则不设置徽标
if(tabs[0].url.includes("baike.baidu.com/item/")){
chrome.action.setBadgeText({ "text": "OFF" })
}
})
// 监听点击事件
chrome.action.onClicked.addListener(async (tab) => {
// 如果当前页面是百度百科的页面,就更新徽标状态,并完成聚焦模式的相关功能
if (tab.url.includes("baike.baidu.com/item/")) {
const prevState = await chrome.action.getBadgeText({ tabId: tab.id })
const nextState = prevState === "ON" ? "OFF" : "ON"
await chrome.action.setBadgeText({
tabId: tab.id,
text: nextState
})
if (nextState === "ON") {
await chrome.scripting.insertCSS({
files: ["focus-mode.css"],
target: { tabId: tab.id },
});
} else if (nextState === "OFF") {
await chrome.scripting.removeCSS({
files: ["focus-mode.css"],
target: { tabId: tab.id },
});
}
}
// 如果当前页是菜鸟教程网站的页面,就弹出popup弹窗
else if(tab.url.includes("www.runoob.com/")){
chrome.action.setPopup({ popup: "popup/popup.html" })
}
})
tips:如果在manifest文件中配置了点击默认弹窗页面,那么action.onClicked事件将不会生效。
3、将所有的标签页的title和path展示在popup弹窗页中
交互逻辑通过popup.js实现
3.1 调用ChromeAPI查询当前浏览器窗口中的所有标签页:
// poppu.js
// 查询符合条件的所有tabs
const tabs = await chrome.tabs.query({
currentWindow: true,
url: ["https://www.runoob.com/*"]
});
...
3.2 将标签页列表插入到popup页面的元素中:
...
// 按照默认顺序对tabs列表中的元素进行排序
const collator = new Intl.Collator();
tabs.sort((a, b) => collator.compare(a.title, b.title));
const template = document.getElementById("li_template"); // 从页面获取模版节点
const elements = new Set();
for (const tab of tabs) {
const element = template.content.firstElementChild.cloneNode(true); // 复制模版第一个节点
const title = tab.title.split("-")[0].trim(); // 获取tab中的标题
const pathname = new URL(tab.url).pathname.slice("/docs".length); // 获取tab中的路径
element.querySelector(".title").textContent = title; // 复制出来的节点中class=“title”的子节点,写入内容
element.querySelector(".pathname").textContent = pathname; // 复制出来的节点中class=“pathname”的子节点,写入内容
element.querySelector("a").addEventListener("click", async () => { // 复制出来的节点中<a>标签子节点添加点击事件
// 点击的时候,更新对应的tab和窗口为激活状态
await chrome.tabs.update(tab.id, { active: true });
await chrome.windows.update(tab.windowId, { focused: true });
});
elements.add(element);
}
document.querySelector("ul").append(...elements);
以上代码中使用到了ChromeTabsAPI,该API 中的许多方法无需请求任何权限即可使用。但是,如果要访问标签页的标题和 URL这些敏感属性,就需要声明授权许可。如果我们请求“标签页”权限,导致所有标签页的敏感属性都有权限访问,但是由于我们仅仅管理特定网站的选项卡,因此我们请求更小范围的host权限。
host权限可以授权我们获取指定网站的敏感信息(包含title和URL),以此来缩小权限范围,进而保护用户隐私。在manifest文件中配置host权限:
{
...
"host_permissions": [
"https://www.runoob.com/*"
],
...
}
注意:如果插件中使用了tabs权限或host权限,那么用户在安装插件时都会有弹窗提示。如下图:
3.3 对标签页进行分组
该功能需要使用到ChromeTabGroupsAPI,该API将允许插件对标签组进行命名和设置颜色。同样的,要使用该API也需要在manifest文件中声明权限:
{
...
"permissions": [
...
"tabGroups"
]
...
}
3.4 实现按钮的交互
在 popup.js 中,创建一个按钮,该按钮将使用 tabs.group() 对所有选项卡进行分组并将它们移动到当前窗口中。具体代码如下:
...
const button = document.querySelector("button"); // 获取button标签
// 给button标签添加点击事件
button.addEventListener("click", async () => {
const tabIds = tabs.map(({ id }) => id); // 解构出tabs中每个对象的id属性,并组合成tabIds
const group = await chrome.tabs.group({ tabIds }); // 将tabIds新建成标签组
await chrome.tabGroups.update(groupId, { title: "菜鸟", color: "yellow" }); // 更新标签组的标题和颜色
});
如果标签页已经在分组中,则移出分组
要将标签页移出分组需要使用到ChomeTabsAPI,因此要在manifest文件中声明tabs权限:
{
...
"permissions": [
...
"tabGroups",
"tabs"
]
...
}
因为在点击按钮时需要先判断是否已经有标签组了,所以对popo.js做如下改造:
...
const button = document.querySelector("button"); // 获取添加组合的button标签
// 给button标签添加点击事件
button.addEventListener("click", async () => {
const tabGroups = await chrome.tabGroups.query({ title: "菜鸟" }) // 找到菜鸟标签组
// 如果菜鸟标签组存在,则取消标签组,否则添加菜鸟标签组
if (tabGroups.length) {
const tabs = await chrome.tabs.query({ groupId: tabGroups[0].id })
const tabIds = tabs.map(({ id }) => id)
await chrome.tabs.ungroup(tabIds)
} else {
const tabIds = tabs.map(({ id }) => id); // 解构出tabs中每个对象的id属性,并组合成tabIds
const groupId = await chrome.tabs.group({ tabIds }); // 将tabIds新建成标签组
await chrome.tabGroups.update(groupId, { title: "菜鸟", color: "yellow" }); // 更新标签组的标题和颜色
}
});
------但是在调试过程中发现,后台服务中的点击事件存在一些Bug,点击事件中如果有根据网站来判断弹哪个页面的话,第一个条件规则触发后,后面的条件规则将不会覆盖第一个条件规则。目前没有找到原因,后续再继续研究下官方文档。
参考文档:zhuanlan.zhihu.com/p/655456499
文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别
文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具
文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量
文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置
文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖
文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...
文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序
文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码
文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型
文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件
文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令
文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线