编码小记——框架编程与浏览器插件的代码入侵问题解决

时间:2024-11-23 13:17:16

一、问题来源与需求分析

编码小记——框架编程与浏览器插件的代码入侵问题解决_框架原理

1、问题来源

需求:接到一个需求,需要写一个浏览器插件去注入系统页面,去实现自动登录的功能。

没错,在我们的生活中,总是能收到这种一句话的需求,开局一句话,内容全靠想

2、需求分析

浏览器插件好说(再次强调,后面会出一个系列注入系统页面也好说,只要使用 content.js 写一下注入脚本,然后在 mainifest.json 里一定义,直接齐活,这没什么难的。

{
  "manifest_version": 2,
  "name": "xxx",
  "version": "1.0",
  "description": "xxx",
  "content_scripts": [
    {
      "matches": ["http://*/*", "https://*/*"],
      "js": ["content.js"],
      "css": ["styles.css"],
      "run_at": "document_end"
    }
  ],

}

自动登录理论上也好说,一般来说登录页面的input元素都有定制化的样式逻辑什么的,因此类名、id什么的,应该很容易找到他的dom元素。那么理论上来说,这个需求应该没有那么难实现

注意到我划横线的话了么,这是一个程序员的思维重点,以后我的思维重点都会用下划线表示,培养思维很重要

需要考虑的技术栈有:

  • vue3
  • angular


3、开始操作挖坑

找到发布的系统登录入口,开始寻找有特点的类名,发现 angular 的输入框,都被定义了 login 的类名,通过这个和input 的type,就可以找到两个input,但是找 vue3 的时候发现,都是统一的标准input:没有id,class名是:el-input,好好好....

既然常规玩法不同,那就玩点非常规的,发现系统的入口都有提示语:请输入xxx,明白了,就用这个就可以找到了!

    const user = document.querySelector( 'input[placeholder="请输入账户名"]' )
    const pass = document.querySelector( 'input[placeholder="请输入密码"]' )

找到了input元素,接下来找 button ,不出我所料,也是非常标准的 antd 和 element 组件类名,可以看出来用的都是组件库的按钮组件,这组件里甚至还套了一层 <span>,都不能用我的 innerHTML 大法了,但是不出我所料,果然登录页只有一个按钮,那好说了,直接就是一波获取。

const loginButton = document.querySelector( 'button' )

ok,元素获取完了,接下来理一下思路:

第一步:获取元素

第二步:写入用户名密码

第三步:点击登录按钮

接下来开始写入用户名密码:相信学过一个礼拜JS的都知道如何搞:

      user.value = '小刘'
      pass.value = 'https://blog.51cto.com/u_13981731/12619250'

直接搞定,那么开始第三步,点击登录按钮,其实也简单,学过一个月JS的都知道怎么搞:

      setTimeout( () => {
        loginButton.click()
      }, 0)

看到了这个 setTimeout 了没有,学过一年JS的都知道这里面的门道有多深,这一手体现了我对JS那似海深般的理解(手动狗头)。

ok,问题解决,直接上效果

3、开始踩坑

不运行不知道,一运行吓一跳,首先,脚本根本执行不到,元素根本获取不到!!!

坑点一:资源加载问题

一开始我是直接写在js里的,肯定获取不到,忽然想到当年写 jQuery时,js 都是写在 onload 里的,原来如此,赶紧试试

window.onload = () => {
	// 写入逻辑
}

发现好像解决了又好像没解决,有时候能获取到,有时候获取不到,输出了日志,发现dom一会儿是 原始的 public/index.html 的内容,一会儿又是渲染后的内容,尤其是angular,好好好,那我问豆包了:

js 如何等待所有的资源都执行完后,再执行?

 

 

  1. 使用window.onload事件
  • window.onload事件会在整个页面(包括 HTML 文档、样式表、脚本、图像等所有资源)加载完成后触发。
  • 示例:
window.onload = function() {
    console.log("所有资源加载完成后执行此函数");
    // 在这里可以安全地访问和操作页面中的所有元素
    var element = document.getElementById("myElement");
    if (element) {
        element.style.color = "red";
    }
};


  • 在这个示例中,当所有资源加载完成后,会在控制台打印一条消息,并且如果页面上存在idmyElement的元素,会将其颜色设置为红色。不过要注意,window.onload只能设置一个函数,如果多次设置,后面的设置会覆盖前面的。


  1. 使用addEventListener监听load事件
  • 这种方式和window.onload类似,但是它可以添加多个监听器,不会出现覆盖的情况。
  • 示例:
function myFunction() {
    console.log("所有资源加载完成后执行此函数");
    // 可以在这里对页面元素进行操作
    var element = document.getElementById("myElement");
    if (element) {
        element.style.color = "blue";
    }
}
window.addEventListener("load", myFunction);


  • 首先定义了一个函数myFunction,然后使用addEventListener将这个函数添加到window对象的load事件监听器列表中。当所有资源加载完成后,myFunction就会被执行,从而可以对页面元素进行操作。


  1. 使用Promisedocument.readyState(较为复杂但灵活)
  • 可以创建一个Promise,通过检查document.readyState的状态来确定页面是否完全加载。document.readyState有三个可能的值:loading(文档正在加载)、interactive(文档已被解析,用户可以与部分内容交互,但图像等资源可能还没加载完)和complete(文档和所有资源都已加载完成)。
  • 示例
function waitForAllResources() {
    return new Promise((resolve, reject) => {
        function checkState() {
            if (document.readyState === "complete") {
                resolve();
            } else {
                setTimeout(checkState, 100);
            }
        }
        checkState();
    });
}
waitForAllResources().then(() => {
    console.log("所有资源加载完成后执行此函数");
    var element = document.getElementById("myElement");
    if (element) {
        element.style.color = "green";
    }
});


  • 在这里,waitForAllResources函数返回一个Promise。在这个Promise内部,定义了一个checkState函数来检查document.readyState。如果状态是complete,就调用resolve来表示加载完成,否则通过setTimeout每隔 100 毫秒再次检查状态。当Promise被解决(resolve)后,then方法中的函数就会被执行,从而可以对页面元素进行操作。

一二点和我的水平差不多,解决不了,那就看第三种方法,看着就高级:

简单来说,就是 document 有一个 readyState 的状态来控制是否加载完成,只要他完成了就完成了,我们需要搞个监听去监听他,但是js是单线程的啊,直接监听直接阻塞啊,所以要搞个 promise (看看多细节)。

再附一下我的代码:

function waitForAllResources() {
    return new Promise((resolve, reject) => {
        function checkState() {
            if (document.readyState === "complete") {
                resolve();
            } else {
                setTimeout(checkState, 100);
            }
        }
        checkState();
    });
}
waitForAllResources().then(() => {
  console.log( "所有资源加载完成后执行此函数" );
  console.log("你是谁", window.location.href)
}

再运行,终于可以获取到元素了,然后也可以写入数据了,然后也可以点确定,然后...

编码小记——框架编程与浏览器插件的代码入侵问题解决_javascript_02

坑点二:model 未更新

还是想简单了,光想着把值写进 input ,没想登录一般都是需要校验的啊,使用框架,都是双向绑定的数据,光是改了 input 的 value ,框架的 model 并没有更新,校验校验过不了,即使登录也没有值啊(手动大哭)

做到这里,其实有点想放弃了,只是想简单写个脚本,怎么可能动摇框架的逻辑根基.......

突然灵光乍现:那我做操作触发model 更新不久好了么,什么能触发更新呢,尝试一下输入框的 input 事件吧!

那么问题来了,怎么才能在js 里手动触发一个事件呢?豆包~~

  1. 使用dispatchEvent方法(现代浏览器)
  • 首先,需要获取到input元素。可以通过document.getElementByIddocument.querySelector等方法来获取。例如,假设你的input元素有一个idmyInput,可以这样获取它:


收起


javascript

复制

const inputElement = document.getElementById('myInput');


  • 然后创建一个ChangeEventchange事件)对象。在 JavaScript 中,可以使用new Event或者new CustomEvent来创建事件对象。对于change事件,使用new Event就足够了。


收起


javascript

复制

const changeEvent = new Event('change');


  • 最后,使用inputElement.dispatchEvent方法来触发这个change事件。


收起


javascript

复制

inputElement.dispatchEvent(changeEvent);


  • 完整的示例代码如下:


收起


javascript

复制

window.onload = function () {
  const inputElement = document.getElementById('myInput');
  const changeEvent = new Event('change');
  inputElement.dispatchEvent(changeEvent);
};


  • 这里的代码假设input元素已经存在于页面上,并且window.onload事件确保页面加载完成后再执行代码。


  1. 使用fireEvent(旧版本 IE 浏览器)
  • 在旧版本的 IE 浏览器(IE 9 及以下)中,不支持dispatchEvent方法。可以使用fireEvent来触发事件。不过,需要注意的是,现代浏览器已经不再支持fireEvent,如果要兼容旧版本 IE 浏览器,可能需要进行浏览器检测。
  • 同样先获取input元素:


收起


javascript

复制

const inputElement = document.getElementById('myInput');


  • 然后使用fireEvent触发change事件:


收起


javascript

复制

if (inputElement.fireEvent) {
  inputElement.fireEvent('onchange');
}


  • 完整的示例代码如下:


收起


javascript

复制

window.onload = function () {
  const inputElement = document.getElementById('myInput');
  if (inputElement.fireEvent) {
    inputElement.fireEvent('onchange');
  }
};


  • 这种方法主要是为了兼容旧版本的 IE 浏览器,在实际开发中,尽量使用现代浏览器的标准方法(dispatchEvent)来触发事件,并且在需要兼容旧版本 IE 时,可以考虑使用工具或者代码库来进行适当的处理

对不起,不用旧IE,果断选择第一种:

      user.value = 'superadmin'
      pass.value = 'wld8600'
      user.dispatchEvent( new Event( "input" ) )
      pass.dispatchEvent(new Event("input"))

神奇!!!问题解决!

一套连招,输入点击,登录,丝滑~