文章

Chrome系列12 - Vue Tampermonkey操作知识库合集

完整示例代码

// ==UserScript==
// @name         Form Auto Fill
// @match        https://example.com/*
// @grant        none
// ==/UserScript==

(function() {
  'use strict'

  // 获取原生 setter
  const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
    window.HTMLInputElement.prototype, 'value'
  ).set
  
  const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(
    window.HTMLTextAreaElement.prototype, 'value'
  ).set

  // 输入文本
  function setInputValue(selector, value) {
    const element = document.querySelector(selector)
    if (!element) return console.error('Element not found:', selector)
    
    if (element instanceof HTMLTextAreaElement) {
      nativeTextAreaValueSetter.call(element, value)
    } else if (element instanceof HTMLInputElement) {
      nativeInputValueSetter.call(element, value)
    }
    
    element.dispatchEvent(new Event('input', { bubbles: true }))
    element.dispatchEvent(new Event('change', { bubbles: true }))
  }

  // 点击按钮
  function clickButton(selector) {
    const element = document.querySelector(selector)
    if (!element) return console.error('Element not found:', selector)
    
    element.scrollIntoView({ block: 'center' })
    element.focus()
    
    element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }))
    element.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }))
    element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
  }

  // 下拉框选择
  function selectOption(selector, optionText) {
    const select = document.querySelector(selector)
    if (!select || !(select instanceof HTMLSelectElement)) return
    
    const option = Array.from(select.options).find(
      opt => opt.textContent?.trim() === optionText.trim()
    )
    if (option) {
      select.value = option.value
      select.dispatchEvent(new Event('change', { bubbles: true }))
    }
  }

  // 使用示例
  setTimeout(() => {
    setInputValue('#username', 'test@example.com')
    setInputValue('#password', 'mypassword')
    selectOption('#country', 'China')
    clickButton('#submit-btn')
  }, 1000)
})()

方案1:输入框赋值(使用原生 setter)

// 获取原生的 value setter
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
  window.HTMLInputElement.prototype,
  'value'
).set

const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(
  window.HTMLTextAreaElement.prototype,
  'value'
).set

// 正确的赋值方式
function setInputValue(element, value) {
  // 1. 使用原生 setter 设置值(绕过框架重写)
  if (element instanceof HTMLTextAreaElement) {
    nativeTextAreaValueSetter.call(element, value)
  } else {
    nativeInputValueSetter.call(element, value)
  }
  
  // 2. 触发 input 事件,让框架感知到变化
  element.dispatchEvent(new Event('input', { bubbles: true }))
  
  // 3. 如果是 select 下拉框
  if (element instanceof HTMLSelectElement) {
    element.dispatchEvent(new Event('change', { bubbles: true }))
  }
}
​

方案2:按钮点击(完整事件序列)

async function clickElement(element) {
  // 1. 滚动到可见区域
  element.scrollIntoView({ behavior: 'auto', block: 'center' })
  await sleep(100)
  
  // 2. 模拟完整的鼠标事件序列
  element.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true, cancelable: true }))
  element.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, cancelable: true }))
  element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }))
  
  // 3. 聚焦元素
  element.focus()
  
  element.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }))
  element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
  
  await sleep(200)
}
​

为什么这套方案可行?

问题 原因 解决方案原理 输入框赋值无效 框架重写了 value setter 来追踪变化,直接赋值绕过了框架 原生 setter 直接操作 DOM 底层,然后 dispatchEvent 通知框架 按钮点击无效 框架可能使用事件委托或需要完整交互流程 完整事件序列 模拟真实用户行为, bubbles: true 确保事件冒泡到父元素

关键点解释

  1. Object.getOwnPropertyDescriptor 获取原生 setter
    在页面加载时,框架会重写 HTMLInputElement.prototype.value 的 setter。我们通过这个 API 获取未被重写的原生 setter。

  2. bubbles: true 事件冒泡
    React 等框架使用事件委托,事件监听器绑定在 document 或根节点上。 bubbles: true 确保事件能冒泡到这些监听器。

  3. 完整事件序列
    真实用户点击会触发: mouseenter → mouseover → mousedown → focus → mouseup → click 。某些按钮可能监听 mousedown 而非 click 。