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

news2024/10/6 6:43:17

目录

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将是当前页面的 document
        browser.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将是当前页面的 document
        browser.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将是当前页面的 document
        browser.tabs.executeScript({
            code:`
document.querySelectorAll(".${KEYWORD_CLASS_NAME}")[${index}].scrollIntoView({
        behavior:'smooth'
})            
            `
        })
    })
}



6、参考

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

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

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

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

相关文章

HDLbits 刷题 --Always nolatches

学习: Your circuit has one 16-bit input, and four outputs. Build this circuit that recognizes these four scancodes and asserts the correct output. To avoid creating latches, all outputs must be assigned a value in all possible conditions (See also always…

内存管理是如何影响系统的性能的

大家好&#xff0c;今天给大家介绍内存管理是如何影响系统的性能的&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 内存管理对系统性能的影响至关重要&#xff0c;主要体现在以下…

课程设计项目3.2:基于振动信号分析的电机轴承故障检测

01.课程设计内容 02.代码效果图 获取代码请关注MATLAB科研小白的个人公众号&#xff08;即文章下方二维码&#xff09;&#xff0c;并回复&#xff1a;MATLAB课程设计本公众号致力于解决找代码难&#xff0c;写代码怵。各位有什么急需的代码&#xff0c;欢迎后台留言~不定时更新…

Js之运算符与表达式——②

运算符&#xff1a;也叫操作符&#xff0c;是一种符号。通过运算符可以对一个或多个值进行运算&#xff0c;并获取运算结果。 表达式&#xff1a;由数字、运算符、变量的组合&#xff08;组成的式子&#xff09;。 表达式最终都会有一个运算结果&#xff0c;我们将这个结果称…

【cache】卡常

来源于《国家集训队2024论文集》中的《论现代硬件上的常数优化》 个人总结&#xff1a; 不要开二的次幂作为维度的数组&#xff0c;否则常数会变大【存疑】。总是保证内存访问连续性更高。比如 st表&#xff0c;把log维放在第二维&#xff0c;会导致内存访问距离最大为N*LOG。…

权限提升技术:攻防实战与技巧

本次活动赠书1本&#xff0c;包邮到家。参与方式&#xff1a;点赞收藏文章即可。获奖者将以私信方式告知。 网络安全已经成为当今社会非常重要的话题&#xff0c;尤其是近几年来&#xff0c;我们目睹了越来越多的网络攻击事件&#xff0c;例如公民个人信息泄露&#xff0c;企业…

Vulnhub:MY FILE SERVER: 1

目录 信息收集 1、arp 2、nmap 3、whatweb WEB web信息收集 dirmap FTP匿名登录 enum4linux smbclient showmount FTP登录 ssh-kegen ssh登录 提权 系统信息收集 脏牛提权 get root 信息收集 1、arp ┌──(root㉿ru)-[~/kali/vulnhub] └─# arp-scan -l I…

信息咨询模块

信息资讯 资讯文章专题、互动评论、自定义多级分类&#xff0c;打造本地新闻圈 简介 本项目旨在打造一个本地新闻资讯平台&#xff0c;为用户提供以下功能&#xff1a; 资讯文章&#xff1a;提供本地新闻、生活、娱乐等各类资讯文章。专题&#xff1a;针对重要事件或热门话…

E4438C安捷伦E4438C信号发生器

181/2461/8938产品概述&#xff1a; Agilent / HP E4438C ESG 矢量信号发生器结合了出色的 RF 性能和复杂的基带生成功能&#xff0c;可在高达 6 GHz 的基带、IF 和 RF 频率上提供经过校准的测试信号。Agilent / HP E4438C ESG 矢量信号发生器提供具有任意波形和实时 I/Q 功能…

波士顿房价预测案例(python scikit-learn)---多元线性回归(多角度实验分析)

波士顿房价预测案例&#xff08;python scikit-learn&#xff09;—多元线性回归(多角度实验分析) 这次实验&#xff0c;我们主要从以下几个方面介绍&#xff1a; 一、相关框架介绍 二、数据集介绍 三、实验结果-优化算法对比实验&#xff0c;数据标准化对比实验&#xff0…

输入url到页面显示过程的优化

浏览器架构 线程&#xff1a;操作系统能够进行运算调度的最小单位。 进程&#xff1a;操作系统最核心的就是进程&#xff0c;他是操作系统进行资源分配和调度的基本单位。 一个进程就是一个程序的运行实例。启动一个程序的时候&#xff0c;操作系统会为该程序创建一块内存&a…

​AI大模型:产品经理的新助手,还是捣蛋鬼?

在科技飞速发展的今天&#xff0c;AI大模型成为了许多行业的“新宠儿”。它们不仅在语音识别、图像处理等方面表现出色&#xff0c;甚至还能写代码、写新闻&#xff0c;甚至写需求文档。这让许多产品经理心动不已&#xff0c;心想&#xff1a;有了AI大模型的帮助&#xff0c;岂…

完全没想到docker启动败在了这里!

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 故事背景 前几天帮同事部署一个环境&#xff0c;用他写的安装脚本部署&#xff0c;其中一台服务器就需要安装docker&#xff0c…

Day43 动态规划 part05

Day43 动态规划 part05 1049.最后一块石头的重量II 我的思路: 提示说和划分两个和相等的子集差不多&#xff0c;猛然想到&#xff0c;这道题不就是划分子集&#xff0c;用sum - 和最大*2 代码就是划分和相同的子集的变形 解答&#xff1a; class Solution {public int last…

jsp实现增删改查——(三)用Echarts图表统计学生信息

学生信息CRUD——Echarts显示生活费 目录结构 创建一个js文件夹&#xff0c;将echarts.min.js放到里面。 功能实现 与之前我们写的jsp文件&#xff08;含有html代码、Java代码&#xff09;不同的是&#xff0c;实现Echarts对生活费的显示&#xff0c;需要调用echarts.min.js…

MySQL事务以及并发访问隔离级别

MySQL事务以及并发问题 事务1.什么是事务2.MySQL如何开启事务3.事务提交方式4.事务原理5.事务的四大特性&#xff08;ACID&#xff09; 事务并发问题1.并发引起的三个问题2.事务隔离级别 事务 在 MySQL 中&#xff0c;事务支持是在引擎层实现的。MySQL 是一个支持多引擎的系统&…

关于搭建电商独立站跨境电商接入主流电商平台API商品接口对于商品功能模块的巨大应用

功能设计 首先我们来看下mall项目中商品功能的设计&#xff0c;主要包括商品管理、添加\编辑商品、商品分类、商品类型、品牌管理等功能&#xff0c;这里的功能同时涉及前台商城和后台管理系统。 商品管理【接入主流电商平台商品API接口丰富自建商城商品】 在mall项目的后台管…

微信批量群发软件有哪款比较好用的?

微信有自带的群发助手&#xff0c;只能群发200个好友&#xff0c;比较有局限性。 我是做销售的&#xff0c;对于群发的需求特别大&#xff0c;我自己平时经常用WeB微信批量群发软件有哪款比较好用的&#xff1f;WeB&#xff0c;它满足了我的群发需求&#xff0c;用起来特别方便…

cocos 3.x版本如何基于ts在检视面板显示Enum枚举类型下拉列表

前言 网上搜索到的大多是基于js或者旧版本cocos。这里记录一下ts显示Enum的方案&#xff0c;cocos版本使用的3.8.2。 解决方案 方案一&#xff1a; import { _decorator, Component, TweenEasing } from cc; const { ccclass, property } _decorator;const MyTweenEasing …

C#.net6.0手术麻醉信息管理系统源码,智慧手术室管理平台源码

手术麻醉信息管理系统源码&#xff0c;自主版权的手麻系统源码 手术麻醉信息管理系统包含了患者从预约申请手术到术前、术中、术后的流程控制。手术麻醉信息管理系统主要是由监护设备数据采集子系统和麻醉临床系统两个子部分组成。包括从手术申请到手术分配&#xff0c;再到术前…