steven026 发表于 2022-8-12 23:26:46

Hook Vue3 app v1.0.3 [Vue3 app劫持 油猴库]

本帖最后由 steven026 于 2022-8-14 23:10 编辑

## 【前言警告】

本脚本库作者完全没有接触过Vue2及Vue3正向开发,也不清楚其具体正向原理~~(脚本原理是倒推的)~~
本脚本库是作者根据自身JS经验及油猴中文网脚本开发指南加上断点逆向无意中发现的通用劫持方法而写,
局限于作者水平,可能存在诸多不合理之处,尽请谅解,另外欢迎交流、探讨。

***

## 【脚本原理】

根据Vue数据绑定机制,Vue为了保证数据同步,会对每个实例均创建一个app对象用于数据存储、监听、同步;
一旦该app对象中存储的变量发生变化,Vue便会监听到该变化并将其作用到相应的DOM元素中,以实现前端交互。
该app对象存在于闭包中,Vue2将其挂载在DOM元素中的`__vue__`属性中,可令油猴脚本直接获取;
但Vue3并未提供该方法,这导致通常油猴脚本无法直接获取app对象,加大针对Vue3框架的油猴脚本的开发难度。

本脚本库根据Vue3的数据绑定原理,通过劫持Proxy方法以暴露Vue3 app对象,并将其还原挂载到DOM元素中,
进而使油猴脚本可直接访问、操作该app对象,以实现油猴脚本与Vue3的快速交互。

***

## 【使用方法】

##### 全局变量

`vueHooked`
以WeakMap存储已劫持的app对象,DOM元素为key,app对象为value
(同一个元素可能有多个app对象,以数组形式存储,下同)

`vueUnhooked`
以WeakSet存储已获取到但未未劫持的app对象,作为debug用变量,正常情况WeakSet应为空

##### DOM元素属性

`DOMelement.__vue__`
已挂载到对应DOM元素属性的Vue app对象
(类似于Vue2的DOMelement.\_\_vue\_\_)

***

## 【使用示例】
##### 必要元信息(脚本猫,注意版本号,示例可能会没有更新)
```
// @run-at       document-start
// @require      https://scriptcat.org/lib/567/1.0.3/Hook%20Vue3%20app.js
```
##### 必要元信息(greasyfork)
```
// @run-at       document-start
// @require      https://greasyfork.org/scripts/449444-hook-vue3-app/code/Hook%20Vue3%20app.js
```
##### 脚本示例(以Bilibili为例 下同)
```
// ==UserScript==
// @name         Bilibile VUE3
// @run-at       document-start
// @match      https://www.bilibili.com/*
// @require      https://scriptcat.org/lib/567/1.0.3/Hook%20Vue3%20app.js
// @grant      none
// ==/UserScript==

//暴露变量到全局
window._vueUnhooked_ = vueUnhooked;
window._vueHooked_ = vueHooked;

//等待元素加载
let timer = setInterval(() => {
    let app = document.querySelector(".bili-video-card")
    if (app?.__vue__) {
      clearInterval(timer)
      console.log("已加载元素", app)
    }
})
```
##### 实际应用
###### 遍历app
![遍历app.png](https://scriptcat.org/api/v1/resource/image/e9ebOCjU1EgyV1Cc)
###### 获取元信息
![获取元信息.png](https://scriptcat.org/api/v1/resource/image/VOa5AtjpNFhrcA9n)
###### 数据替换(仅演示可行性)
![数据替换.png](https://scriptcat.org/api/v1/resource/image/j3r3lM2twyJDNaIc)
###### 进阶应用:通过__vue__获取函数、内置方法 (自行研究,各页面均不相同)

***

## 【参考文献】
##### @李恒道 哥哥的指导与Vue2指南:
[[油猴脚本开发指南]VUE数据绑定的响应原理](https://bbs.tampermonkey.net.cn/thread-1077-1-1.html)
[[油猴脚本开发指南]Vue初探__vue__](https://bbs.tampermonkey.net.cn/thread-1425-1-1.html)
[[油猴脚本开发指南]通过__vue__获取数据](https://bbs.tampermonkey.net.cn/thread-1438-1-1.html)
##### @cxxjackie 哥哥的闭包劫持方法指导
[油猴劫持闭包方法](https://bbs.tampermonkey.net.cn/forum.php?mod=redirect&goto=findpost&ptid=2743&pid=30899)

王一之 发表于 2022-8-13 10:54:39

哥哥nb!

找了几个vue3的demo测试,好像有的可以 有的不行

https://github.com/newbee-ltd/newbee-mall-vue3-apphttp://47.99.134.126:5008/#/home 👌

https://github.com/weizhanzhan/vue3-ts-template-h5 https://vue3-ts-template-h5.vercel.app/home ❌

我是去github找demo的https://github.com/topics/vue3-demo

steven026 发表于 2022-8-13 11:41:19

本帖最后由 steven026 于 2022-8-13 11:47 编辑

王一之 发表于 2022-8-13 10:54
哥哥nb!

找了几个vue3的demo测试,好像有的可以 有的不行


第二个页面我试了下可以的,但是脚本自动还原的比较少,可能有点bug,控制台试了下能手动还原,我再研究一下

steven026 发表于 2022-8-13 12:12:33

王一之 发表于 2022-8-13 10:54
哥哥nb!

找了几个vue3的demo测试,好像有的可以 有的不行




又更新了一下,哥哥看看,之前写的代码有点BUG,现在基本都能还原了

cxxjackie 发表于 2022-8-13 12:33:22

我感觉vueHooked这个对象没有意义,只要遍历一下vueApp,谁没有el谁就没被还原吧,没必要专门弄一个对象。而且Vue中是存在同一个元素对应多个app的情况的,这样就会出现你以为他还原了,但又被另一个覆盖的情况,这样到底算不算“还原”呢?我觉得可以放弃在元素上附加额外属性的做法,改用一个WeakMap来记录,就是 元素->app数组 这样的对应关系,查询起来更方便,还可以应用到Vue2(不过Vue2是Object.defineProperty绑定的,需要改一下劫持方式)。

steven026 发表于 2022-8-13 13:34:19

cxxjackie 发表于 2022-8-13 12:33
我感觉vueHooked这个对象没有意义,只要遍历一下vueApp,谁没有el谁就没被还原吧,没必要专门弄一个对象。 ...

vueHooked其实算是一个debug用的对象

同一个元素多个app的情况,我原本是想做成和recordVue()一样,如果el已存在__vue__则把这个__vue__变成数组形式,但感觉还是有点问题,所以暂时没处理
我觉得element.__vue__操作起来比较方便,比较符合vue2的习惯
如果改成WeakMap形式,WeakMap.get(element),总觉得有点别扭

不知道vue2同一元素多个app的__vue__是怎么个处理情况,我似乎记得就是像我现在一样摆烂……

总结一下,现在有4种解决办法
1.摆烂,__vue__只赋值,不管覆不覆盖
2.保留__vue__,如果存在覆盖则转为数组
3.放弃__vue__,改为WeakMap
4.既保留__vue__数组,也新增一个WeakMap,让用户自由选择(这块内存应该占用不大吧 引用的实际上是同一个已存在的对象)
不知道哥哥怎么看?

李恒道 发表于 2022-8-13 14:48:57

{:4_86:}哥哥牛逼!

cxxjackie 发表于 2022-8-13 21:29:47

steven026 发表于 2022-8-13 13:34
vueHooked其实算是一个debug用的对象

同一个元素多个app的情况,我原本是想做成和recordVue()一样,如果 ...

这个问题主要是有性能方面的隐患,由于Vue组件存在指向元素的引用,即使元素已从页面删除,只要引用存在,元素就会一直留在内存中。在元素上绑属性的做法有一个问题,就是元素属性引用自身,即循环引用,这容易造成内存泄露,所以Vue2在销毁组件时会将__vue__置空。用对象来保存组件同样存在问题,永远有指向元素的引用,在增删改动频繁的页面中可能引发性能问题。WeakMap的做法我想了一下也不妥当,虽然键名是弱引用,但键值还是强引用,你可能不得不去监听Vue的销毁流程(Vue3我记得是unmount),把相关引用断开才行。

steven026 发表于 2022-8-13 21:48:17

cxxjackie 发表于 2022-8-13 21:29
这个问题主要是有性能方面的隐患,由于Vue组件存在指向元素的引用,即使元素已从页面删除,只要引用存在 ...

Vue3的app有个属性是isUnmounted: false
我用Object.defineProperty去监听这个变化吗?如果变为true就把__vue__和vueApp、vueHooked置空

但是Object.defineProperty这个监听过程本身又无法销毁(百度查了下似乎Proxy的监听可销毁?但我随便试了一下并未成功),是否陷入了死循环?或者算是节约一部分内存?

cxxjackie 发表于 2022-8-13 22:39:24

steven026 发表于 2022-8-13 21:48
Vue3的app有个属性是isUnmounted: false
我用Object.defineProperty去监听这个变化吗?如果变为true就把_ ...

我测试了一下,好像浏览器有对这个问题进行优化,循环引用的资源是能被正确释放的(主要是IE里有问题),那__vue__和WeakMap的做法应该都没问题,但用对象保存肯定是不靠谱的。Vue应该有专门的方法可以劫持,好像是beforeUnmount和unmounted,直接监听属性也行,Object.defineProperty不会影响引用计数的,但我还是不推荐用对象,WeakMap虽说不可枚举,但实际在chrome控制台中是可以展开的,也能起到查看所有组件的效果。
页: [1] 2
查看完整版本: Hook Vue3 app v1.0.3 [Vue3 app劫持 油猴库]