整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

chrome插件之tabs

chrome插件之tabs

、前面的话

本文主要的内容是帮助读者朋友梳理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

创作不易,如果您觉得文章有任何帮助到您的地方,或者触碰到了自己的知识盲区,请帮我点赞收藏一下,或者关注我,我会产出更多高质量文章,最后感谢您的阅读,祝愿大家越来越好。

写Express接下来的内容前,先来一篇轻松点儿的文章,之前我总结过一篇关于nth-child&nth-of-type的文章(还记得那篇文章的结论吗?对,就是两句话)。这一篇我依然来处理一个有关CSS的问题。

说到tab切换,咱们很熟悉,最常用的做法是使用JS处理显示与隐藏,听说利用CSS3的伪类选择器也能实现相同的tab切换效果,于是我就试一把。

这个方法主要是借助input和checked属性控制其后面元素的显示与否,涉及到的需要搞懂的知识点就三个:

1、nth-of-type的使用

2、input的id和label的for属性联系

3、在css3中‘~’表示兄弟元素,‘+’表示相邻元素

下面是实例区:

知道了上面这三点,那这个问题就简单了,先看一下例子的DOM结构:

从上图可以看出DOM结构很简单,但是要注意两点,第一:input的id和label中的for对应(如tab1),第二:input必须和其内容div.tab在同一级,也就是说它们必须拥有同一个父元素。

下面看一下本例的重点部分,样式:

从样式代码上可以看出,当input处理checked时,通过“+”符给label加样式,使用nth-of-type定位,在通过“~”将对应的内容区设置为display:block。

如果想让这个切换看起来更动感,可以使用transition给切换加个动画。

写在最后的总结:

通过测试,这种方法在IE8下不兼容,这没关系,因为我们可以在H5开发中用的爽爽的。哈哈

短内容,说完整事,哪怕只读一篇也能学知识。点击关注小郑搞码事,说的都是那堆代码。谢谢您的鼓励!

插件安装方式,ctrl+shift+p输入install packages

CSS3:支持CSS3里的语法高亮。(Sublime3里自带的CSS高亮不够用)。安装后, 打开一个CSS文件,然后按照下面GIF操作,将CSS3高亮作为CSS文件的默认高亮。

livestyle:调试后实时更新页面。安装这个还需要安装谷歌的插件。

Emmet:快速生成代码,用过h5build的应该知道。ul*li*3相当于:

<ul><li></li><li></li><li></li></ul>

记下语法就ok.

ConvertToUTF8:编码转utf8

CSScomb:css属性排序

html5:html规范包,输入html+tab生成html规范文档

Alignment:代码对齐,快捷键ctrl alt a

Autoprefixer插件:css3私有前缀自动补全插件

AutoFileName:自动完成文件名的输入,如图片选取,快捷输入文件名路径补全

less sass插件

JsFormat js格式化插件

Terminal 命令行插件

Minify 代码美化压缩插件

Color Highlighter 颜色选择插件

快捷键

Ctrl+D 选中光标所占的文本,继续操作则会选中下一个相同的文本。

Alt+F3 选中文本按下快捷键,即可一次性选择全部的相同文本进行同时编辑。举个栗子:快速选中并更改所有相同的变量名、函数名等。

Ctrl+L 选中整行,继续操作则继续选择下一行,效果和 Shift+↓ 效果一样。

Ctrl+Shift+L 先选中多行,再按下快捷键,会在每行行尾插入光标,即可同时编辑这些行。

Ctrl+Shift+M 选择括号内的内容(继续选择父括号)。举个栗子:快速选中删除函数中的代码,重写函数体代码或重写括号内里的内容。

Ctrl+M 光标移动至括号内结束或开始的位置。

Ctrl+Enter 在下一行插入新行。举个栗子:即使光标不在行尾,也能快速向下插入一行。

Ctrl+Shift+Enter 在上一行插入新行。举个栗子:即使光标不在行首,也能快速向上插入一行。

Ctrl+Shift+[ 选中代码,按下快捷键,折叠代码。

Ctrl+Shift+] 选中代码,按下快捷键,展开代码。

Ctrl+K+0 展开所有折叠代码。

Ctrl+← 向左单位性地移动光标,快速移动光标。

Ctrl+→ 向右单位性地移动光标,快速移动光标。

shift+↑ 向上选中多行。

shift+↓ 向下选中多行。

Shift+← 向左选中文本。

Shift+→ 向右选中文本。

Ctrl+Shift+← 向左单位性地选中文本。

Ctrl+Shift+→ 向右单位性地选中文本。

Ctrl+Shift+↑ 将光标所在行和上一行代码互换(将光标所在行插入到上一行之前)。

Ctrl+Shift+↓ 将光标所在行和下一行代码互换(将光标所在行插入到下一行之后)。

Ctrl+Alt+↑ 向上添加多行光标,可同时编辑多行。

Ctrl+Alt+↓ 向下添加多行光标,可同时编辑多行。

Ctrl+J 合并选中的多行代码为一行。举个栗子:将多行格式的CSS属性合并为一行。

Ctrl+Shift+D 复制光标所在整行,插入到下一行。

Tab 向右缩进。

Shift+Tab 向左缩进。

Ctrl+K+K 从光标处开始删除代码至行尾。

Ctrl+Shift+K 删除整行。

Ctrl+/ 注释单行。

Ctrl+Shift+/ 注释多行。

Ctrl+K+U 转换大写。

Ctrl+K+L 转换小写。

Ctrl+Z 撤销。

Ctrl+Y 恢复撤销。

Ctrl+U 软撤销,感觉和 Gtrl+Z 一样。

Ctrl+F2 设置书签

Ctrl+T 左右字母互换。

F6 单词检测拼写

Ctrl+F 打开底部搜索框,查找关键字。

Ctrl+shift+F 在文件夹内查找,与普通编辑器不同的地方是sublime允许添加多个文件夹进行查找,略高端,未研究。

Ctrl+P 打开搜索框。举个栗子:1、输入当前项目中的文件名,快速搜索文件,2、输入@和关键字,查找文件中函数名,3、输入:和数字,跳转到文件中该行代码,4、输入#和关键字,查找变量名。

Ctrl+G 打开搜索框,自动带:,输入数字跳转到该行代码。举个栗子:在页面代码比较长的文件中快速定位。

Ctrl+R 打开搜索框,自动带@,输入关键字,查找文件中的函数名。举个栗子:在函数较多的页面快速查找某个函数。

Ctrl+: 打开搜索框,自动带#,输入关键字,查找文件中的变量名、属性名等。

Ctrl+Shift+P 打开命令框。场景栗子:打开命名框,输入关键字,调用sublime text或插件的功能,例如使用package安装插件。

Esc 退出光标多行选择,退出搜索框,命令框等。

Ctrl+Tab 按文件浏览过的顺序,切换当前窗口的标签页。

Ctrl+PageDown 向左切换当前窗口的标签页。

Ctrl+PageUp 向右切换当前窗口的标签页。

Alt+Shift+1 窗口分屏,恢复默认1屏(非小键盘的数字)

Alt+Shift+2 左右分屏-2列

Alt+Shift+3 左右分屏-3列

Alt+Shift+4 左右分屏-4列

Alt+Shift+5 等分4屏

Alt+Shift+8 垂直分屏-2屏

Alt+Shift+9 垂直分屏-3屏

Ctrl+K+B 开启/关闭侧边栏。

F11 全屏模式

Shift+F11 免打扰模式