chrome插件之tabs chrome插件之间会互相干扰吗
suiw9 2024-11-10 13:03 25 浏览 0 评论
一、前面的话
本文主要的内容是帮助读者朋友梳理chrome插件的tabs能力,如果您是第一次阅读本文,也建议您在阅读完本文后,尝试看看我下面的这些系列文章,它们可以更好的帮您认识和了解chrome插件:
- chrome插件之从0到1
- chrome插件之通信(V3版)
- chrome插件之manifest配置
- chrome插件之玩转action
1.能力
一款浏览器插件具备非常强大的能力,它不仅可以向当前所有的站点里注入脚本,对网站的功能进行扩展。更重要的是它还可以和浏览器的标签系统进行交互,从而创建、修改、管理每一个tab,而这一切都基于插件系统为我们提供的tabs相关的API,chrome不仅提供了我们用于操作和管理tabs的API,而且还提供了和content脚本之间的通信方法。
温馨提示:
Tabs API 只能在background脚本中以及option脚本、popup脚本、由chrome创建的tab中访问到,在content脚本中是无法访使用的。
换句话说,chrome有选择性的给不同的脚本环境注入了不同的chrome对象,导致提供的API具备差异性。
该图是我们在特定环境下可以通过chrome.tabs访问的所有的api,这些就是chrome为我们内置的提供给开发者的能力
2.权限
在之前的文章中我们提到过,如果要使用某些特别的API,我们需要在插件配置文件manifest.json中配置相应的权限声明,但幸运的是对于tabs相关的部分API不需要在manifest.json中显式的配置“tabs”就可以直接使用。比如说: 创建一个新的tab,重新加载某个tab,或者导航到另外一个URL等等。
但是下面的这些API在使用的时候,则需要加上相关的配置才可以使用,比如说:
- permission
- 如果你希望通过特定的条件找到某些tabs,你需要使用 chrome.tabs.query(queryInfo , callback) 这个API,这个时候就需要显示的在manifest.json中permission中添加“tabs”声明。
- host permission 如果你希望能够对指定的tab动态的在其中注入并执行一段脚本,或者注入、移除某一段css样式,那么你可能需要用到这些API:
chrome.tabs.executeScript() // 注入一段脚本并执行
chrome.tabs.insertCss() // 注入一段css样式
chrome.tabs.removeCss() // 移除一段css样式
- 这个时候就需要在manifest.json中显式的声明需要命中的url。
{
// manifest.json "host_permissions":[ "<all_urls>" ] // 支持正则匹配正则
}
二、API
接下来我们一一通过案例来认识他们,从而感受每一个API的具体行为以及他们的使用条件、注意事项等等。
1. 创建
我们可以通过这个API来创建一个新的tab,这个tab和普通的站点不一样,属于插件所属的页面,因此支持跨域请求、获取更多的chrome提供的方法。
// background.js
chrome.runtime.onInstalled.addListener(({reason}) => {
if (reason === 'install') {
chrome.tabs.create({
url: "newtab.html" // 相对于background脚本的路径下需要有一个newtab.html文件
});
}
});
上面的脚本意味着在插件第一次安装完成之后,就会立马创建一个新的标签页,所以如果我们想要在任何时候创建一个新的tab,就可以使用这个API,行业内很多插件的工作台都是创建一个新的tab页进行工作的,比如著名的代理插件SwitchySharp
该api默认支持,不需要额外的manifest配置
2. 查找
我们可能有这样的需要,获取当前浏览器窗口处于激活状态的tab页面,因为对于同一个窗口,有且只有会一个tab是展示在用户面前的,我们把这样的tab称为激活状态,这个时候我们就需要用到下面的api。
async function getCurrentTab() { let queryOptions = { active: true }; let [tab] = await chrome.tabs.query(queryOptions); return tab; }
调用上面的方法,你就可以获得当前窗口激活的那个tab的实例对象了,从这个对象中,你可以获取到对应的tab唯一的id、url、图标等信息。
值得注意的是,如果chrome浏览器打开了多个窗口,就意味着可能每一个窗口都会存在一个激活的tab,因此我们获取的tab就会是多个,这个时候如果只是解构出第一个可能是不够严谨的。
因此我们可以通过添加搜索条件来精确的查找:
async function getCurrentTab() {
let queryOptions = {
active: true , currentWindow:true
};
let [tab] = await
chrome.tabs.query(queryOptions);
return tab;
}
通过添加一个参数currentWindow,意味着只搜索脚本运行所在窗口的激活的tab,这个时候肯定只会查找出唯一的一个tab,解构第一个就不会有问题。
搜索条件除了上述之外,还有下面可以选择:
参数 | 类型 | 作用 |
active | boolean | 是否处于激活状态 |
audible | boolean | 是否处于播放音频状态 |
currentWindow | boolean | 是否处于脚本所在窗口内 |
groupId | number | 是否处于某个分组内 |
highlighted | boolean | 是否处于高亮状态 |
index | number | 窗口从左往右第index个tab |
pinned | boolean | 是否处于被固定的状态 |
status | unloaded/loading/complete | 匹配tab的status为该status的tabs |
title | string | 匹配tab的title为该title的tabs |
url | string | 匹配tab的url为该url的tabs |
windowId | number | 特定窗口下的tabs |
windowType | normal/popup/panel/app/devtools | 特定的窗口类型下所在的tabs |
被固定是指那些通过右键点击tab的时候,选择固定在最左侧的标签,并且可以固定多个。
该api默认支持,需要额外的manifest配置,需要显式声明“tabs”的permissions
3.发送消息
我们可以很方便的给指定的tab发送消息,一般来说我们可以在content脚本中做消息的监听,接收到消息后使其执行不同的业务逻辑。
chrome.tabs.sendMessage(
tabId: number, // 目标tab的id
message: any, // 发送信息
options?: object, // 其他配置项
callback?: function, // 回调函数 )
上面这个是V3版本的插件使用的,在V2版本中我们使用chrome.tabs.sendRquest()
// 在v3版本中已废弃
chrome.tabs.sendRequest(
tabId: number, // 目标tab的id
request: any, // 发送信息
callback?: function, // 回调函数
)
4.修改
如果我们希望修改一个tab的一些参数信息,我们可以选择使用下面这个API:
chrome.tabs.update( tabId?: number, updateProperties: object, callback?: function, )
其中updateProperties的值就是上面提到的queryOptions的属性保持一致,例如我们可以动态的更改指定tab的title、url、pinned等状态属性!
5.缩放比
当我们按住ctrl的同时再滑动鼠标滚轮的话就可以调整页面的缩放比例,这个可能大家平时都深有体会,但是实际上这个也可以通过插件给我们提供的API来动态的进行调整:
chrome.tabs.setZoom( tabId?: number, zoomFactor: number, // 缩放比例 callback?: function, )
6.移动/移除/刷新
我们介绍的第一个API就展示了如何创建一个新的tab,他会默认创建在最末尾,也就是最右侧,如果这个放置位置我们不满意,我们也可以将其放置在我们想要的位置。
移动
chrome.tabs.move(
tabIds: number | number[],
moveProperties: object,
callback?: function,
)
type moveProperties = { index?:number, // 想要移动至的index索引位置. `-1` 移动至窗口末尾.
windowId?:number // 移动至的窗口id
}
移除
chrome.tabs.remove( tabIds: number | number[], callback?: function, )
刷新
chrome.tabs.reload( tabId?: number, reloadProperties?: object, callback?: function, ) type reloadProperties = { bypassCache?:boolean // 是否绕过本地缓存 默认不绕过,也就是使用本地缓存。 }
7.导航
我们可以通过插件来控制一个tab的前进后退(如果他们都曾有过跳转的记录)
chrome.tabs.goBack( // 回到最近的一次历史记录 tabId?: number, callback?: function, ) chrome.tabs.goForward( // 去到下一个历史记录,如果有的话 tabId?: number, callback?: function, )
8.丢弃/复制
当我们的tab开的特别多的时候,浏览器会有个小优化,对于某些长时间不用的tab,浏览器会清空内存中对其的状态存贮,因此当我们再次将其激活时会重新加载!这个过程插件也提供了API可以帮助我们做到:
chrome.tabs.discard( tabId?: number, callback?: function, ) chrome.tabs.duplicate( // 这个API与discard相反,可以帮助我们复制一个一摸一样的tab标签 tabId: number, callback?: function, )
9.分组
如果我们希望将某些具备相似特征的网站分成一个组,使其能够在视图上更好的被察觉,那么我们就可以通过插件为我们提供的API来进行实现:
第一步:筛选出希望分到同一组的tabs
const tabs = await chrome.tabs.query({ url: [ "https://developer.chrome.com/*"], });
根据前面的知识,我们很容易就可以知道tabs就是域名为 "developer.chrome.com" 开头的所有站点的tab集合;
第二步:将他们分为一组
const tabIds = tabs.map(({ id }) => id); const group = await chrome.tabs.group({ tabIds });
上图中就可以看到所有符合条件的站点就被分为同一个组了,这个API的使用方式是:
chrome.tabs.group( options: GroupOptions, callback?: function, ) type GroupOptions = { tabIds?:number[], // 希望被分组的tab的id的集合 groupId?:number, // 已有的分组 createProperties?:{ windowId?:number // 希望分组被创建在那个窗口, 默认是脚本所在窗口 } }
额外的话:
如果我们希望在分组上再加上一个样式或者字样作为标记的话,也可以这样做:
// 第一步: 在manifest.json中添加“tabGrpups”的权限 { ... "permissions":[ "tabGroups" ] } //第二步: chrome.tabGroups.update(group, { title: "这是分组1" , color:'red' });
就可以修改这个分组的一些特征,上面是增加了一个标题,效果如下:
三、实战
以上我们介绍了基本的API,接下来我们通过一些案例来实际感受一下每个API的作用:
准备以下的项目:
manifest.json
{
"name": "tabs demo",
"description": "tabs demo",
"version": "1.0",
"manifest_version": 3,
"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"
}
},
"content_scripts": [
{
"js": ["content.js"],
"matches": ["<all_urls>"] } ],
"background":
{
"service_worker": "background.js"
},
"icons": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
},
"permissions": ["tabs", "tabGroups"] }
content.js / background.js
// content.js let color = ""; console.log("content.js"); chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { color = document.body.style.color; document.body.style.background = message; sendResponse("changed"); }); // background.js console.log(chrome);
newtab.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>chrome插件</title> </head> <body> <h1>我是一个由chrome插件创建的页面</h1> </body> </html>
popup.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> <section> <h1>创建新的页面</h1> <button id="create-tab">创建</button> </section> <section> <h1>查找符合条件的tab</h1> <div> <span>是否激活</span> <span>是</span> <input type="radio" name="isActive" value="1" /> <span>否</span> <input type="radio" name="isActive" value="0" /> </div> <div> <span>是否属于当前窗口:</span> <span>是</span> <input type="radio" name="isCurrentWindow" value="1" /> <span>否</span> <input type="radio" name="isCurrentWindow" value="0" /> </div> <div> <span>url(支持正则):</span> <input type="text" id="url" /> </div> <div> <span>title</span> <input type="text" id="title" /> </div> <div> <span>index</span> <input type="text" id="index" /> </div> <div> <span>是否被固定</span> <span>是</span> <input type="radio" name="pinned" value="1" /> <span>否</span> <input type="radio" name="pinned" value="0" /> </div> <div> <span>status</span> <select name="status" id="status"> <option value="unloaded">unloaded</option> <option value="loading">unloaded</option> <option value="complete">unloaded</option> </select> </div> <button id="query-tab">查找</button> <div> <div>查找结果:</div> <div id="search-result"></div> </div> </section> <section> <h1>发送消息</h1> <input type="color" id="send-value" /> <button id="send-btn">变色吧</button> </section> <section> <h1>删/改/移/丢弃/复制</h1> <div> <input type="text" id="move-index" /> <button id="move">移动当前的tab</button> </div> <div> <button id="remove">移除当前的tab</button> </div> <div> <button id="reload">刷新当前的tab</button> </div> <div> <input type="text" id="discard-value" /> <button id="discard">丢弃</button> </div> <div> <button id="duplicate">复制</button> </div> <div> <input type="text" id="update-value" /> <button id="update">更新</button> </div> </section> <section> <h1>缩放比</h1> <div> <input type="text" id="zoom" /> <button id="zoom-btn">调整</button> </div> </section> <section> <h1>分组</h1> <div> <input type="text" id="group-title" /> <button id="group">使用查询的结果进行分组</button> </div> </section> <section> <h1>导航</h1> <div> <button id="goForward">前进</button> <button id="goBack">前进</button> </div> </section> <script src="./popup.js"></script> </body> </html>
popup.js
document.getElementById("create-tab").addEventListener("click", () => { chrome.tabs.create({ url: "newtab.html", // 相对于background脚本的路径下需要有一个newtab.html文件 }); }); let Tabs = []; const getSelect = (list) => { const yes = list[0]; const no = list[1]; if (yes.checked) { return yes.defaultValue === "1"; } if (no.checked) { return no.defaultValue === "1"; } return false; }; document.getElementById("query-tab").addEventListener("click", async () => { const active = getSelect(document.getElementsByName("isActive")); const currentWindow = getSelect( document.getElementsByName("isCurrentWindow") ); const pinned = getSelect(document.getElementsByName("pinned")); const url = document.getElementById("url").value; const title = document.getElementById("title").value; const index = document.getElementById("index").value; const queryOptions = { active, currentWindow, pinned, }; if (url) { queryOptions.url = url; } if (title) { queryOptions.title = title; } if (index) { queryOptions.index = index - 0; } console.log(queryOptions); const tabs = await chrome.tabs.query(queryOptions); document.getElementById("search-result").innerHTML = JSON.stringify( tabs.map(({ id }) => ({ id })) ); Tabs = tabs; }); document.getElementById("send-btn").addEventListener("click", async () => { const color = document.getElementById("send-value").value; const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const response = await chrome.tabs.sendMessage(tab.id, color); console.log(color, response); }); const getCurrentTab = async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); return tab.id; }; document.getElementById("move").addEventListener("click", async () => { const index = document.getElementById("move-index").value - 0; const tabIds = await getCurrentTab(); chrome.tabs.move(tabIds, { index }); }); document.getElementById("remove").addEventListener("click", async () => { const tabIds = await getCurrentTab(); chrome.tabs.remove(tabIds); }); document.getElementById("reload").addEventListener("click", async () => { const tabId = await getCurrentTab(); chrome.tabs.reload(tabId); }); document.getElementById("discard").addEventListener("click", async () => { const tabId = document.getElementById("discard-value").value - 0; chrome.tabs.discard(tabId); }); document.getElementById("duplicate").addEventListener("click", async () => { const tabId = await getCurrentTab(); chrome.tabs.duplicate(tabId); }); document.getElementById("zoom-btn").addEventListener("click", async () => { const tabId = await getCurrentTab(); const zoomFactor = document.getElementById("zoom").value - 0; chrome.tabs.setZoom(tabId, zoomFactor); }); document.getElementById("group").addEventListener("click", async () => { const tabIds = Tabs.map(({ id }) => id); const title = document.getElementById("group-title").value; const group = await chrome.tabs.group({ tabIds }); chrome.tabGroups.update(group, { color: "red", title }); }); document.getElementById("goForward").addEventListener("click", async () => { const tabId = await getCurrentTab(); chrome.tabs.goForward(tabId); }); document.getElementById("goBack").addEventListener("click", async () => { const tabId = await getCurrentTab(); chrome.tabs.goBack(tabId); });
以上的资源我会放到github上,大家可以download下来直接在自己的浏览器上运行,查看效果,也希望有收获后给不吝star哈!。
下面是我本地的测试效果:
创建页面/发送消息
查询
删/改/更新
分组
有了以上的武器,就可以玩转tabs啦!一起开始开发chrome插件吧!
四、最后的话
以下是我的其他文章,欢迎阅读
保姆级讲解JS精度丢失问题(图文结合)
shell、bash、zsh、powershell、gitbash、cmd这些到底都是啥?
从0到1开发一个浏览器插件(通俗易懂)
用零碎时间个人建站(200+赞)
另外我有一个自己的网站,欢迎来看看 new-story.cn
创作不易,如果您觉得文章有任何帮助到您的地方,或者触碰到了自己的知识盲区,请帮我点赞收藏一下,或者关注我,我会产出更多高质量文章,最后感谢您的阅读,祝愿大家越来越好。
相关推荐
- 俄罗斯的 HTTPS 也要被废了?(俄罗斯网站关闭)
-
发布该推文的ScottHelme是一名黑客,SecurityHeaders和ReportUri的创始人、Pluralsight作者、BBC常驻黑客。他表示,CAs现在似乎正在停止为俄罗斯域名颁发...
- 如何强制所有流量使用 HTTPS一网上用户
-
如何强制所有流量使用HTTPS一网上用户使用.htaccess强制流量到https的最常见方法可能是使用.htaccess重定向请求。.htaccess是一个简单的文本文件,简称为“.h...
- https和http的区别(https和http有何区别)
-
“HTTPS和HTTP都是数据传输的应用层协议,区别在于HTTPS比HTTP安全”。区别在哪里,我们接着往下看:...
- 快码住!带你十分钟搞懂HTTP与HTTPS协议及请求的区别
-
什么是协议?网络协议是计算机之间为了实现网络通信从而达成的一种“约定”或“规则”,正是因为这个“规则”的存在,不同厂商的生产设备、及不同操作系统组成的计算机之间,才可以实现通信。简单来说,计算机与网络...
- 简述HTTPS工作原理(简述https原理,以及与http的区别)
-
https是在http协议的基础上加了一层SSL(由网景公司开发),加密由ssl实现,它的目的是为用户提供对网站服务器的身份认证(需要CA),以至于保护交换数据的隐私和完整性,原理如图示。1、客户端发...
- 21、HTTPS 有几次握手和挥手?HTTPS 的原理什么是(高薪 常问)
-
HTTPS是3次握手和4次挥手,和HTTP是一样的。HTTPS的原理...
- 一次安全可靠的通信——HTTPS原理
-
为什么HTTPS协议就比HTTP安全呢?一次安全可靠的通信应该包含什么东西呢,这篇文章我会尝试讲清楚这些细节。Alice与Bob的通信...
- 为什么有的网站没有使用https(为什么有的网站点不开)
-
有的网站没有使用HTTPS的原因可能涉及多个方面,以下是.com、.top域名的一些见解:服务器性能限制:HTTPS使用公钥加密和私钥解密技术,这要求服务器具备足够的计算能力来处理加解密操作。如果服务...
- HTTPS是什么?加密原理和证书。SSL/TLS握手过程
-
秘钥的产生过程非对称加密...
- 图解HTTPS「转」(图解http 完整版 彩色版 pdf)
-
我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取。所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议。...
- HTTP 和 HTTPS 有何不同?一文带你全面了解
-
随着互联网时代的高速发展,Web服务器和客户端之间的安全通信需求也越来越高。HTTP和HTTPS是两种广泛使用的Web通信协议。本文将介绍HTTP和HTTPS的区别,并探讨为什么HTTPS已成为We...
- HTTP与HTTPS的区别,详细介绍(http与https有什么区别)
-
HTTP与HTTPS介绍超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的...
- 一文让你轻松掌握 HTTPS(https详解)
-
一文让你轻松掌握HTTPS原文作者:UC国际研发泽原写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。...
- 如何在Spring Boot应用程序上启用HTTPS?
-
HTTPS是HTTP的安全版本,旨在提供传输层安全性(TLS)[安全套接字层(SSL)的后继产品],这是地址栏中的挂锁图标,用于在Web服务器和浏览器之间建立加密连接。HTTPS加密每个数据包以安全方...
- 一文彻底搞明白Http以及Https(http0)
-
早期以信息发布为主的Web1.0时代,HTTP已可以满足绝大部分需要。证书费用、服务器的计算资源都比较昂贵,作为HTTP安全扩展的HTTPS,通常只应用在登录、交易等少数环境中。但随着越来越多的重要...
你 发表评论:
欢迎- 一周热门
-
-
Linux:Ubuntu22.04上安装python3.11,简单易上手
-
宝马阿布达比分公司推出独特M4升级套件,整套升级约在20万
-
MATLAB中图片保存的五种方法(一)(matlab中保存图片命令)
-
别再傻傻搞不清楚Workstation Player和Workstation Pro的区别了
-
Linux上使用tinyproxy快速搭建HTTP/HTTPS代理器
-
如何提取、修改、强刷A卡bios a卡刷bios工具
-
Element Plus 的 Dialog 组件实现点击遮罩层不关闭对话框
-
日本组合“岚”将于2020年12月31日停止团体活动
-
SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索
-
tinymce 号称富文本编辑器世界第一,大家同意么?
-
- 最近发表
- 标签列表
-
- dialog.js (57)
- importnew (44)
- windows93网页版 (44)
- yii2框架的优缺点 (45)
- tinyeditor (45)
- qt5.5 (60)
- windowsserver2016镜像下载 (52)
- okhttputils (51)
- android-gif-drawable (53)
- 时间轴插件 (56)
- docker systemd (65)
- slider.js (47)
- android webview缓存 (46)
- pagination.js (59)
- loadjs (62)
- openssl1.0.2 (48)
- velocity模板引擎 (48)
- pcre library (47)
- zabbix微信报警脚本 (63)
- jnetpcap (49)
- pdfrenderer (43)
- fastutil (48)
- uinavigationcontroller (53)
- bitbucket.org (44)
- python websocket-client (47)