油猴的本质以及 document 的处理
如果我们创建一个脚本,并对脚本进行 debugger 逐步调试
可能很多人就会有疑问了,为什么显示的代码跟我们在管理器内写的代码不一致?
我们可以细心读一下这里的代码,window["p7356196.837629042"]
上赋值了一个自执行的函数
传入了一个 proxy 的 windows 叫 context,powers 是一些信息数据,fapply 根据名字是设置函数的 this 指针用的引用
我们可以看一下 context、powers
传入之后首先创建了一个 with context,设定了一下作用域,然后将我们脚本作为一个函数,传入到上方的 module 变量中
这里可以看到我们的脚本本质上就是一个 js 文件,而所谓的脚本头在经过油猴管理器进行解析出来数据后由管理器进行分析和处理,实际最终执行只是一个简单的注释。
传入函数之后将我们的脚本作为 module 变量,然后调用 fapply
然后我们追到 fapply 里看,可以发现 fapply 有三个参数,e,t,r
其中 e 是我们传入的的第一个参数,t 是第二个参数,即 context,而 r 则是后部分的数组参数
我们看看 r 的参数,发现有[undefined, undefined, powers.CDATA, powers.uneval, powers.define, powers.module, powers.exports, context, context, powers.unsafeWindow, powers.cloneInto, powers.exportFunction, powers.createObjectIn, powers.GM, powers.GM_info]
,截图如下
可以看到是传入了一堆数据以及一些 gm_info 等信息,但具体目前不清楚所有含义是什么,我们暂且跳过,以后有机会再分析
回到刚才,fapply 调用了(e,t,r)=>n(o, e, t, r)
,其中 n 为 call 函数、o 为 apply 函数、e 为脚本函数、t 为 context、r 为 powers 等数组信息
那这里的语法为call(apply,脚本函数,context,arguments参数信息)
,但是实际不存在这种语法的,应该是通过逐步封装产生的这种调用方式。根据实际的调试结果可以知道 context 设置为了脚本函数的 this,而 arguments 函数则作为了参数。
在这里我们可以看到,this 已经变成了经过 proxy 代理的 window。
而且函数内 window 也变成了包装后的 window,这时候 unsafewindow 没有被包装,这就是 unsafeWindow 与 window 之间的区别
那包装后的 window 实际都做了什么呢?我们可以研究一下!
document 之谜
tampermonkey 管理器目前已经取消了关于 document 的处理
所以该部分仅供思路的参考
当我们在脚本写上了console.log(document.addEventListener)
就会导致EventTarget.prototype.addEventListener
的劫持不生效
那么到底发生了什么呢?
我们先思考一下 document 到底从哪里来的?
因为这里是在脚本的作用域内,所以通常会从 with 内优先查找,也就是 context 中
context 是一个 proxy 的 windows 对象,如果读取属性会触发 get 函数,所以我们可以跳进去看看
跳进去发现了一个属性读取的处理的函数,然后我们打上断点,等待触发 document
多次的执行终于执行到了 document 的读取
我们查看 e 可以发现是一个函数列表
然后可以看到首先获取 e 的列表内是否存在,如果存在,则查看是否有 vlaue,
有 value 则执行返回,如果有 get 则调用 get
如果都没有则继续执行其他逻辑
而 document 刚好是有 get 函数的,所以我们直接看 get 的部分
这里可以看到获取了 document 对象,然后执行 ee 函数,而 ee 函数为一个箭头函数 获取到了 document 分别传给了 N、M、I
接下来就是具体的逻辑处理了,我们就不继续深入。
总结
可以知道油猴的执行与普通 js 代码不同
油猴会将 this 和 window 在执行作用域内设置为代理的 window
同时如果访问一些属性,将会触发 get 或 value 函数
在 get 函数内做进一步的处理。