Firefox 关键词高亮插件的简单实现

目录

1、配置 manifest.json 文件

2、编写侧边栏结构

3、查找关键词并高亮的方法

3-1) 如果直接使用 innerHTML 进行替换

4、清除关键词高亮

5、页面脚本代码

6、参考


1、配置 manifest.json 文件

{"manifest_version": 2,"name": "key_word_plugin","version": "1.0","description": "find_key_word",// 添加权限"permissions":["*://*/*","activeTab"],"icons": {"48": "icons/flower.jpg"},"content_scripts": [{"matches": ["*://*/*"],"js": ["index.js"],"run_at":"document_idle"}],// 侧边栏"sidebar_action": {"default_title": "My tool","default_panel": "./sidebar/sidebar.html","default_icon": "./sidebar/sidebar_icon.png"},// 背景脚本"background": {"scripts": ["bg.js"],"persistent": false,"type": "module"}
}

 

2、编写侧边栏结构

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>/* 略 */</style><link rel="stylesheet" href="./top_area.css">
</head>
<body><div class="container"><!-- 关键词查找 --><div class="top-area"><section class="inp-area"><input class="inp" type="text" maxlength="10"><button class="find-btn">查找</button></section><section class="result-area"><p>共找到</p><p class="count"><!-- 将查找到的结果条目数量写入此处 --></p><p>处;</p></section><section class="btn-area"><input type="number" step="1" min="1" class="goto-keyword-inp usable"><button class="usable goto-btn">跳转</button></section><section class="btn-area"><button class="usable last-btn">上一个</button><button class="usable next-btn">下一个</button><button class="clear">清除所有标记</button></section></div></div>
</body>
<script src="keyword.js"></script>
</html>

        效果图 

 

3、查找关键词并高亮的方法

    // 获取当前激活的标签页面 使用 tabs 需要权限 activeTab,在 manifest 中配置browser.tabs.query({active: true, currentWindow: true}).then((logTabs,onError)=>{// 然后往当前页面中注入内容脚本,document将是当前页面的 documentbrowser.tabs.executeScript({code:`
(function action(keyword, nodes){Array.from(nodes).forEach(node =>{let {nodeType, data : content} = node;if(nodeType === 3 && content.includes(keyword)){let parentNode = node.parentNode;let split_arr = node.data.trim().replaceAll(keyword,'-' + keyword + '-').split('-').filter(e => e);for(let item of split_arr){if(item === keyword){let strong = document.createElement('strong')strong.innerText = keyword;strong.classList.add('${KEYWORD_CLASS_NAME}')strong.style = '${__style}'parentNode.insertBefore(strong, node)}else{let text = document.createTextNode(item);parentNode.insertBefore(text, node)}}parentNode.removeChild(node)}else if(nodeType === 1 && node.textContent.includes(keyword)){action(keyword, node.childNodes)}})
})('${keyword}', Array.from(document.body.childNodes).filter((e)=>{return (e.textContent.includes('${keyword}') &&e.tagName !== 'SCRIPT')
}))
document.querySelectorAll('.__keyword_word__').length`}).then((onExecuted, onError)=>{
// onExecuted[0] 的内容就是document.querySelectorAll('.__keyword_word__').length的结果total = onExecuted[0]})})

        点击查找关键词后,页面脚本向当前的页面注入一段JavaScript代码。该代码包含一个立即执行的函数 和 一个关键词数量的获取。

        该立即执行的函数 action,接收一个 要匹配的关键词 keyword 和 当前搜索节点数组 nodes 作为参数。

       遍历每一个节点,取出节点的类型-->nodeType 和节点的文本内容 -->content。

        如果是纯文本节点,则该节点的 nodeType 为3,如果是元素节点,则为 1。

        如果有纯文本节点,并且该纯文本节点中的内容包含了关键词,那么构造出一个数组,使用该数组来区分非关键词内容和关键词内容,以及他们之间的位置关系。

let split_arr = content.trim().replaceAll(keyword, '-' + keyword + '-').split('-').filter(e => e);

        如关键词为 我们 ,纯文本节点的内容为:

我们的征途是星辰大海,请和我们一起,永远相信美好的事情即将发生

        那么构造的数组为:

[ "我们",  "的征途是星辰大海,请和",  "我们",  "一起,永远相信美好的事情即将发生"

    if(nodeType === 3 && content.includes(keyword)){let parentNode = node.parentNode;let split_arr = content.trim().replaceAll(keyword,'-' + keyword + '-').split('-        ').filter(e => e);for(let item of split_arr){if(item === keyword){let strong = document.createElement('strong')strong.innerText = keyword;strong.classList.add('${KEYWORD_CLASS_NAME}')strong.style = '${__style}'parentNode.insertBefore(strong, node)}else{let text = document.createTextNode(item);parentNode.insertBefore(text, node)}}parentNode.removeChild(node)}

          遍历构造的数组中的内容,如果当前值等于关键词,那么构造一个强调标签 Strong 将关键词作为 innerText,并添加指定的样式和样式类名,然后加入到当前所遍历的节点之前;如果该当前值与关键词不相等,则直接构造一个文本节点,将其添加到当前所遍历的节点之前......

        当遍历完构造的数组后,将当前遍历的节点从其父节点中删除。这样就将纯文本节点中的内容全部高亮处理了。

        没有包含关键词的纯文本节点直接跳过。 

        如果该节点不是纯文本结点,那么判断其 textContent 中是否包含关键词,如果是,那么让其所有子节点再参与 action 处理。否则就不用继续递归。

3-1) 如果直接使用 innerHTML 进行替换

如果标签中的属性出现了关键词,则会出现标签结构混乱的问题:

原代码: 

<body><div class="my_name"><img src="https://tse1-mm.cn.bing.net/th/id/OIP-C.duz6S7Fvygrqd6Yj_DcXAQHaF7?rs=1&pid=ImgDetMain" alt="我的图片"><p>我的图片</p><div>你的图片<p>我们的图片</p><span>都是</span>图片</div></div><script>document.body.innerHTML = document.body.innerHTML.replaceAll('图片','<strong style="color:red">图片</strong>')</script>
</body>

 

4、清除关键词高亮

browser.tabs.query({active: true, currentWindow: true}).then(()=>{browser.tabs.executeScript({code:`
(function action(keyword){document.querySelectorAll('.${KEYWORD_CLASS_NAME}').forEach(e =>{let parent = e.parentNode;let textNode = document.createTextNode(keyword);parent.replaceChild(textNode, e)})
})('${keyword}')`})})

        获取到所有 strong 强调标签(根据自定义的 class 名称),然后进行遍历,获取到每一个strong 的父元素。使用 createTextNode 创建一个纯文本节点,其内容就是关键词。然后将该文本节点替换掉 strong 标签即可。

5、页面脚本代码

// 简单封装document.querySelector
const getFirstEle = sign => document.querySelector(sign);// 关键词
var KEYWORD = '';// 总共找到多少处
var total = 0;  
const count_ele = getFirstEle('.count')
count_ele.innerText = '____'const KEYWORD_CLASS_NAME = '__keyword_word__'
const __style = `color: #b60404; background-color: #f9f906; text-decoration: underline; text-decoration-style: double;`
var INDEX = null;               // 当前记录的关键词索引,用于跳转 [1 ~ total]const find_btn = getFirstEle('.find-btn');
const clear_btn = getFirstEle('.clear');
const last_btn = getFirstEle('.last-btn');
const next_btn = getFirstEle('.next-btn');
const goto_keyword_inp = getFirstEle('.goto-keyword-inp')
const goto_btn = getFirstEle('.goto-btn')// 控制关键词跳转是否可用
const usables = document.querySelectorAll('.usable');
const set_usable = (res)=>{ usables.forEach(e => { e.disabled = !res; }) }// 默认不可用
set_usable(false);// 点击查找关键词
find_btn.addEventListener('click', (e)=>{// 获取用户的输入let keyword = document.querySelector('.inp').value.trim()if(!keyword) return;// 获取上次的关键词let last_keyword = sessionStorage.getItem('_keyword_');// 如果上次查找的关键词存在并且与当前的关键词相等if(last_keyword && last_keyword === keyword){ return; }// 如果上次的关键词与当前的关键词不相等,那么页面的高亮没有被清理// 因为上次的关键词session中没有被清除。先清理页面残留else if(last_keyword && last_keyword !== keyword){clear_action(last_keyword, false, false, false)}// 更新关键词sessionStorage.setItem('_keyword_', keyword)KEYWORD = keyword;// 获取当前激活的标签页面 使用 tabs 需要权限 activeTab,在 manifest 中配置browser.tabs.query({active: true, currentWindow: true}).then((logTabs,onError)=>{// 然后往当前页面中注入内容脚本,document将是当前页面的 documentbrowser.tabs.executeScript({code:`
(function action(keyword, nodes){Array.from(nodes).forEach(node =>{let {nodeType, textContent : content} = node;if(nodeType === 3 && content.includes(keyword)){let parentNode = node.parentNode;let split_arr = content.trim().replaceAll(keyword,'-' + keyword + '-').split('-').filter(e => e);for(let item of split_arr){if(item === keyword){let strong = document.createElement('strong')strong.innerText = keyword;strong.classList.add('${KEYWORD_CLASS_NAME}')strong.style = '${__style}'parentNode.insertBefore(strong, node)}else{let text = document.createTextNode(item);parentNode.insertBefore(text, node)}}parentNode.removeChild(node)}else if(nodeType === 1 && content.includes(keyword)){action(keyword, node.childNodes)}})
})('${keyword}', Array.from(document.body.childNodes).filter((e)=>{return (e.textContent.includes('${keyword}') &&e.tagName !== 'SCRIPT')
}))
document.querySelectorAll('.__keyword_word__').length`}).then((onExecuted, onError)=>{total = onExecuted[0]count_ele.innerText = total;// 开启跳转功能if(total > 0) set_usable(true);})})
})// 点击清除按钮 回归页面原始的状态
clear_btn.addEventListener('click', ()=>{let keyword = sessionStorage.getItem('_keyword_');clear_action(keyword)
})// 清除关键词标记
const clear_action = (keyword, clear_inp=true, clear_keyword_session=true, clear_count=true)=>{browser.tabs.query({active: true, currentWindow: true}).then(()=>{browser.tabs.executeScript({code:`
(function action(keyword){document.querySelectorAll('.${KEYWORD_CLASS_NAME}').forEach(e =>{let parent = e.parentNode;let textNode = document.createTextNode(keyword);parent.replaceChild(textNode, e)})
})('${keyword}')`})})if(clear_inp) document.querySelector('.inp').value = '';if(clear_keyword_session) sessionStorage.setItem('_keyword_', '');if(clear_count) count_ele.innerText = '_____';set_usable(false)KEYWORD = ''goto_keyword_inp.value = ''
}// 跳转到上一个关键词位置
last_btn.addEventListener('click', ()=>{if(!INDEX) INDEX = 1;else if(INDEX <= 1 ) INDEX = total;else if(INDEX >= total) INDEX = total - 1;else INDEX --;goto_keyword_site(INDEX - 1, KEYWORD);
})// 跳转到下一个关键词位置
next_btn.addEventListener('click', ()=>{if(!INDEX) INDEX = 1;else if(INDEX <= 1) INDEX = 2;else if(INDEX >= total) INDEX = 1;else INDEX ++;goto_keyword_site(INDEX - 1, KEYWORD);
})// 跳转到指定的位置
goto_btn.addEventListener('click', ()=>{let index = parseInt(goto_keyword_inp.value)if(!index) return;if(index > total) index = total;else if(index < 1) index = 1;goto_keyword_site(index - 1)INDEX = index;
})// 跳转到具体的关键词位置
const goto_keyword_site = (index) =>{goto_keyword_inp.value = index + 1;browser.tabs.query({active: true, currentWindow: true}).then((logTabs,onError)=>{// 然后往当前页面中注入内容脚本,document将是当前页面的 documentbrowser.tabs.executeScript({code:`
document.querySelectorAll(".${KEYWORD_CLASS_NAME}")[${index}].scrollIntoView({behavior:'smooth'
})            `})})
}

6、参考

[1]: 扩展是什么? - Mozilla | MDN

[2]: Firefox插件(拓展)开发_火狐浏览器插件开发-CSDN博客 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ES6展开运算符

1.展开可迭代对象&#xff08;简单理解为数组和伪数组&#xff09;&#xff0c;如数组、 NodeList 、arguments。 可以通过展开运算符把一个伪数组转换为数组 const a [...document.body.children]; console.log(a); console.log(Array.isArray(a));2.实现数组的浅拷贝 cons…

wireshark解析grpc/protobuf的方法

1&#xff0c;wireshark需要安装3.20以上 下载地址&#xff1a;https://www.wireshark.org/ 2&#xff0c;如果版本不对&#xff0c;需要卸载&#xff0c;卸载方法&#xff1a; sudo rm -rf /Applications/Wireshark.app sudo rm -rf $HOME/.config/wireshark sudo rm -rf /…

Linux笔记之制作基于ubuntu20.4的最小OpenGL C++开发docker镜像

Linux笔记之制作基于ubuntu20.4的最小OpenGL C开发docker镜像 —— 2024-04-03 夜 code review! 文章目录 Linux笔记之制作基于ubuntu20.4的最小OpenGL C开发docker镜像1.这里把这本书的例程代码放在了Dockerfile所在的文件夹内以使镜像预装例程代码2.创建Dockerfile3.构建Do…

【前端面试3+1】10 npm run dev 发生了什么、vue的自定义指令如何实现、js的数据类型有哪些及其不同、【最长公共前缀】

一、npm run dev发生了什么 运行npm run dev时&#xff0c;通常是在一个基于Node.js的项目中&#xff0c;用来启动开发服务器或者执行一些开发环境相关的任务。下面是一般情况下npm run dev会执行的步骤&#xff1a; 1. 查找package.json中的scripts字段&#xff1a; npm会在项…

redis之主从复制、哨兵模式

一 redis群集有三种模式 主从复制&#xff1a; 主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。 主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。 缺陷&#xff1a; 故障恢复无法自动化&…

VSCode安装及Python、Jupyter插件安装使用

VSCode 介绍 Visual Studio Code&#xff08;简称VSCode&#xff09;是一个由微软开发的免费、开源的代码编辑器。VSCode是一个轻量级但是非常强大的代码编辑器&#xff0c;它支持多种编程语言&#xff08;如C,C#&#xff0c;Java&#xff0c;Python&#xff0c;PHP&#xff0…

Redis的值有5种数据结构,不同数据结构的使用场景是什么?

文章目录 字符串缓存计数共享Session限速 哈希缓存 列表消息队列文章列表栈队列有限集合 集合标签抽奖社交需求 有序集合排行榜系统 字符串 缓存 &#xff08;1&#xff09;使用原生字符类型缓存 优点&#xff1a;简单直观&#xff0c;每个属性都支持更新操作 缺点&#xff1…

如何在本地搭建集成大语言模型Llama 2的聊天机器人并实现无公网IP远程访问

文章目录 1. 拉取相关的Docker镜像2. 运行Ollama 镜像3. 运行Chatbot Ollama镜像4. 本地访问5. 群晖安装Cpolar6. 配置公网地址7. 公网访问8. 固定公网地址 随着ChatGPT 和open Sora 的热度剧增,大语言模型时代,开启了AI新篇章,大语言模型的应用非常广泛&#xff0c;包括聊天机…

JAVAEE之Cookie/Session

1.Cookie HTTP 协议自身是属于 "无状态" 协议. "无状态" 的含义指的是: 默认情况下 HTTP 协议的客户端和服务器之间的这次通信, 和下次通信之间没有直接的联系. 但是实际开发中, 我们很多时候是需要知道请求之间的关联关系的. 例如登陆网站成功后, 第二…

自定义树形筛选选择组件

先上效果图 思路&#xff1a;刚开始最上面我用了el-input&#xff0c;选择框里面内容用了el-inputel-tree使用&#xff0c;但后面发现最上面那个可以输入&#xff0c;那岂不是可以不需要下拉就可以使用&#xff0c;岂不是违背了写这个组件的初衷&#xff0c;所以后面改成div自定…

基于springboot+vue实现的小区物业管理系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

yolov9文献阅读记录

本文记录了yolov9文献的阅读过程&#xff0c;对主要内容进行摘选翻译&#xff0c;帮助理解原理和应用&#xff0c;包括摘要、主要贡献、网络结构、主要模块&#xff0c;问题描述和试验对比等内容。 文献摘要前言摘选主要贡献相关工作可逆性结构辅助监督 问题描述信息瓶颈原理可…

Linux 恶意软件“Migo”针对 Redis 进行加密劫持攻击

安全研究人员遇到了一种新的加密劫持活动&#xff0c;该活动使用一种名为 Migo 的新恶意软件&#xff0c;该恶意软件针对 Linux 主机上的 Redis 服务器。在 Cado Security 研究人员注意到在野外利用 Redis 系统的新命令后&#xff0c;该活动曝光了。 初始访问 根据 Cado secu…

传统模型用腻了?GCN图卷积神经网络一键实现西储大学轴承故障诊断!发文新思路!

​ 声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 数据介绍与故障诊断讲解 1.数据…

实时获取 Pacific Time Zone (太平洋时区) 时间

实时获取 Pacific Time Zone [太平洋时区] 时间 1. Google -> Pacific Time2. Pacific Time - exact time nowReferences 1. Google -> Pacific Time 2. Pacific Time - exact time now https://time.is/zh/PT References [1] Yongqiang Cheng, https://yongqiang.blog…

原理图设计的通用规范

原理图各页内容依次为&#xff1a;封面、目录、电源、时钟、CPU、存储器、逻辑、背板&#xff08;母板&#xff09;接口等。 原理图上所有的文字方向应该统一&#xff0c;文字的上方应该朝向原理图的上方&#xff08;正放文字&#xff09;或左方&#xff08;侧放文字&#xff…

文件操作详解(二)

目录 一.文件的顺序读写1.顺序读写函数&#xff08;适合于所有的流&#xff09;1.1 fgetc(读字符)1.2 fputc(写字符)1.3 fgets(读字符串)1.4 fput(写字符串)1.5 fscanf(格式化地读)1.6 fprintf(格式化地写) 2.顺序读写函数&#xff08;只适用于文件流&#xff09;2.1 fread(二进…

蓝桥杯嵌入式学习笔记(9):RTC程序设计

目录 前言 1. RTC介绍 2. 使用CubeMx进行源工程配置 3. 代码编程 3.1 准备工作 3.2 进行bsp_rtc.h编写 3.3 进行bsp_rtc.c编写 3.4 main.c编写 3.4.1 头文件引用 3.4.2 变量声明 3.4.3 子函数声明 3.4.4 函数实现 3.4.5 main函数编写 4. 代码实验 5. 总结 前言 因本人备赛蓝…

分布式链路追踪与云原生可观测性

分布式链路追踪系统历史 Dapper, a Large-Scale Distributed Systems Tracing Infrastructure - Google Dapper&#xff0c;大规模分布式系统的跟踪系统大规模分布式系统的跟踪系统&#xff1a;Dapper设计给我们的启示 阿里巴巴鹰眼技术解密 - 周小帆京东云分布式链路追踪在金…

WPS二次开发专题:如何获取应用签名SHA256值

作者持续关注WPS二次开发专题系列&#xff0c;持续为大家带来更多有价值的WPS开发技术细节&#xff0c;如果能够帮助到您&#xff0c;请帮忙来个一键三连&#xff0c;更多问题请联系我&#xff08;QQ:250325397&#xff09; 在申请WPS SDK授权版时候需要开发者提供应用包名和签…