已实现的功能
- 全屏点击事件 √
- <input>,<textarea>输入事件 √
- 滚动事件 √
- <input>,<textarea>自定义key √
- 脚本和事件可编辑 √
- 脚本和事件的可视化 √
- 导入导出 √
- 批量导入导出 √
- tab传参调用另一tab中的脚本 √
社保网站的坑
之前说了做这个插件是给人事办理人员用的,然后我自认为做的差不多可以给别人用了,就问人事要了Ukey进行实际操作
但是万万没想到政府网站竟然是jsp
页面,还用了iframe
,有的还是动态加载和动态创建的,还有使用了html原生的select标签,我真是日了狗了.没办法只能硬着头皮啃了.
需要实现的功能
- 一层<iframe>中的上述事件
- <select>选择事件
一层<iframe>中的上述事件的录制和执行
首先怎么绑定iframe中的事件,有2种方法都要用到
- jquery的第2个参数,指定在哪个元素下找
input
$('input', doc).on('input', input)
复制代码
- 绑定this
contentWindow
为iframe的window
对象 doc为iframe的document
对象
doc.addEventListener('click', onclick.bind(contentWindow), true)
复制代码
再获取真实的鼠标坐标
/**
* this = contentWindow
* @param ev
*/
function onclick (ev) {
let {x, y} = getPosition_Iframe(ev, this) // 获取真实坐标
let delay = new Date().getTime() - window.startTime
if (delay > 5 && !window.running) {
window.startTime += delay
let event = {
x,
y,
clientX: ev.clientX,
clientY: ev.clientY,
type: 'click',
tagName: ev.target.tagName,
time: delay
}
chrome.runtime.sendMessage({
type: 'add-event',
event: event
})
console.log('onclick', event, ev)
}
}
/**
* 网上找的方法, 自己改了下能用
* 获取最底层iframe页面中鼠标点击的坐标
*/
function getPosition_Iframe (event = {}, contentWindow) {
var parentWindow = contentWindow.parent;
var tmpLocation = contentWindow.location;
var target = null;
var left = 0;
var top = 0;
while (parentWindow != null && typeof (parentWindow) != 'undefined' && tmpLocation.pathname != parentWindow.location.pathname) {
for (var x = 0; x < parentWindow.frames.length; x++) {
if (tmpLocation.pathname == parentWindow.frames[x].location.pathname) {
target = parentWindow.frames[x].frameElement;
break;
}
}
do {
left += target.offsetLeft || 0;
top += target.offsetTop || 0;
target = target.offsetParent;
} while (target)
tmpLocation = parentWindow.location;
parentWindow = parentWindow.parent;
}
let xy = {x: left + (event.clientX || 0), y: top + (event.clientY || 0)}
return xy
}
复制代码
修改event对象
event对象前后区别
// 之前xy就是鼠标点击的坐标
event = {
x: ev.clientX,
y: ev.clientY,
type: 'click',
tagName: ev.target.tagName,
time: delay
}
// 修改后的event
// xy表示在主窗口的鼠标点击坐标
// clientX,clientY表示 在iframe中的鼠标点击坐标
event = {
x,
y,
clientX: ev.clientX,
clientY: ev.clientY,
type: 'click',
tagName: ev.target.tagName,
time: delay
}
复制代码
给iframe
绑定事件
通用的绑定方法
function bindEvent (doc, contentWindow) {
// 不用 jquery on方法, useCapture设置为true在捕获时就触发, 是为了避免stopPropagation的情况
doc.addEventListener('click', onclick.bind(contentWindow), true)
$('input', doc).on('keyup', onkeyup)
$('input', doc).on('keydown', onkeydown)
$('input', doc).on('input', input)
$('input', doc).on('compositionstart', compositionstart)
$('input', doc).on('compositionend', compositionend)
$('textarea', doc).on('keyup', onkeyup)
$('textarea', doc).on('keydown', onkeydown)
$('textarea', doc).on('input', input)
$('textarea', doc).on('compositionstart', compositionstart)
$('textarea', doc).on('compositionend', compositionend)
// 绑定鼠标移动事件
doc.addEventListener('mousemove', throttle(setScrollWatcher.bind(contentWindow)), true)
}
复制代码
调用绑定方法
// 页面上的iframe集合
let iframes = new Set()
function bind () {
window.startTime = new Date().getTime()
// 绑定顶层页面
bindEvent(document, window)
$('iframe').each((index, iframe) => {
iframes.add(iframe)
// 给当前有的iframes添加事件
let iframeContentWindow = iframe.contentWindow
let doc = iframeContentWindow.document
doc.removeEventListener('click', onclick.bind(iframeContentWindow), true)
bindEvent(doc, iframeContentWindow)
// 给变化后的iframes添加事件
$(iframe).on('load', function (event) {
iframes.add(iframe)
iframeContentWindow = iframe.contentWindow
doc = iframeContentWindow.document
doc.removeEventListener('click', onclick.bind(iframeContentWindow), true)
bindEvent(doc, iframeContentWindow)
})
})
// 给动态生成的iframe绑定事件
$(document).bind('DOMNodeInserted', throttle(() => {
$('iframe').each((index, iframe) => {
if (!iframes.has(iframe)) {
let iframeContentWindow = iframe.contentWindow
let doc = iframeContentWindow.document
doc.removeEventListener('click', onclick.bind(iframeContentWindow), true)
bindEvent(doc, iframeContentWindow)
}
})
}, 500))
console.log('bind.js 已运行')
}
复制代码
执行event
对象
let focusTarget = null
function startEvent (item, i) {
let target = null
switch (item.type) {
case 'click':
let click
target = document.elementFromPoint(item.x, item.y)
// 通过真实的xy获取到的可能是iframe元素
if (target.tagName === 'IFRAME') {
// iframe的document用clientX,clientY获取一次
target = target.contentWindow.document.elementFromPoint(item.clientX, item.clientY)
click = new MouseEvent('click', {
clientX: item.clientX,
clientY: item.clientY,
bubbles: true,
cancelable: true
})
} else {
click = new MouseEvent('click', {
clientX: item.x,
clientY: item.y,
bubbles: true,
cancelable: true
})
}
target.dispatchEvent(click)
// 为下一个输入事件做准备
if (item.tagName === 'INPUT' || item.tagName === 'TEXTAREA') {
target.focus && target.focus()
focusTarget = target
} else if (item.tagName === 'SELECT') {
focusTarget = target
} else {
focusTarget = null
}
break
}
console.log(`自动化-${i}-${item.type} 后等待${item.time}毫秒`)
}
复制代码
高亮功能和这个类似
<select>选择事件
绑定select事件
select真的是一个奇葩的dom, 网上的所有方式都不能完美实现功能, 要么打不开下拉框, 要么打开的位置不对. 但是我通过测试找到了实现的方法. 我发现点击option
后返回的坐标是负的! 这样就能在click事件中做做文章了~ 修改后的onclick
方法
/**
* this = contentWindow
* @param ev
*/
function onclick (ev) {
let {x, y} = getPosition_Iframe(ev, this)
let delay = new Date().getTime() - window.startTime
if (delay > 5 && !window.running) {
window.startTime += delay
let event
// 原生<select>事件太奇葩, 所以把设置select值得时机放在点击<option>中,目前支持单选
if (ev.target.tagName === 'SELECT' && ev.clientX < 0 && ev.clientY < 0) {
if (ev.target.selectedOptions) {
// 获取到select选中的值
let val = []
for (let option of ev.target.selectedOptions) {
val.push(option.value)
}
if (val.length === 0) {
val = ''
} else if (val.length === 1) {
val = val[0]
}
event = {
type: 'set-select-value', // 新增一个事件类型
key: '',
value: val,
time: delay
}
chrome.runtime.sendMessage({
type: 'add-event',
event: event
})
}
} else {
event = {
x,
y,
clientX: ev.clientX,
clientY: ev.clientY,
type: 'click',
tagName: ev.target.tagName,
time: delay
}
chrome.runtime.sendMessage({
type: 'add-event',
event: event
})
}
console.log('onclick', event, ev)
}
}
复制代码
执行事件
用了和input一样的方式模拟InputEvent
事件
// 亲测有效
case 'set-select-value':
if (focusTarget) {
// 具体看 https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-onchange-event-in-react-js
let prototype = window.HTMLSelectElement.prototype
let nativeInputValueSetter = Object.getOwnPropertyDescriptor(prototype, "value").set;
nativeInputValueSetter.call(focusTarget, item.value);
let inputEvent = new InputEvent('input', {bubbles: true})
focusTarget.dispatchEvent(inputEvent)
}
break
复制代码
演示效果
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END