Hook Vue3 app

通过劫持Proxy方法,逆向还原Vue3 app元素到DOM

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/449444/1081400/Hook%20Vue3%20app.js

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Hook Vue3 app
// @version      1.0.3
// @description  通过劫持Proxy方法,逆向还原Vue3 app元素到DOM
// @author       DreamNya
// @license      MIT
// @namespace https://greasyfork.org/users/809466
// ==/UserScript==

const $window = window.unsafeWindow || document.defaultView || window
const realLog = $window.console.log; //反劫持console.log(大部分网站都会劫持console.log)
const realProxy = $window.Proxy; //劫持Proxy

var vueUnhooked = new WeakSet() //以WeakSet存储已获取到但未未劫持的app对象,作为debug用变量,正常情况WeakSet应为空
var vueHooked = new WeakMap() //以WeakMap存储已劫持的app对象,DOM元素为key,app对象为value

$window.Proxy = function () {
    let app = arguments[0]._
    if (app?.uid >= 0) { //判断app
        let el = app.vnode.el
        if (el) {
            recordVue(el, app) //记录到WeakMap
            recordDOM(el, app) //挂载到DOM
            watch_isUnmounted(app) //观察销毁
        } else {
            //realLog(app,el)
            vueUnhooked.add(app) //记录未劫持的app
            //realLog(vueUnhooked,app)
            watchEl(app.vnode) //不存在el则观察el
        }
    }
    return new realProxy(...arguments)
}

function watchEl(vnode) { //观察el 变动时还原到DOM
    let value = vnode.el
    let hooked = false
    Object.defineProperty(vnode, "el", {
        get() {
            return value
        },
        set(newValue) {
            value = newValue
            if (!hooked && this.el) {
                hooked = true
                recordVue(this.el, this.component)
                recordDOM(this.el, this.component)
                watch_isUnmounted(this.component)
                //realLog(this.component,"已还原")
            }
        }
    })
}

function watch_isUnmounted(app) { //观察isUnmounted 变动时销毁引用
    let value = app.isUnmounted
    let unhooked = false
    Object.defineProperty(app, "isUnmounted", {
        get() {
            return value
        },
        set(newValue) {
            value = newValue
            if (!unhooked && this.isUnmounted) {
                unhooked = true
                //realLog(this,"已删除")
                let el = this.vnode.el
                if (el) {
                    let DOMvalue = el.__vue__ //删除DOMelement.__vue__挂载
                    if (DOMvalue) {
                        if (Array.isArray(DOMvalue)) {
                            let index = DOMvalue.findIndex(i => i == this)
                            index > -1 && DOMvalue.splice(index, 1)
                            el.__vue__ = DOMvalue.length > 1 ? DOMvalue : DOMvalue[0]
                        } else {
                            if (DOMvalue == this) {
                                el.__vue__ = void 0
                            }
                        }
                    }
                    let WMvalue = vueHooked.get(el) //删除WeakMap存储
                    if (WMvalue) {
                        if (Array.isArray(WMvalue)) {
                            let index = WMvalue.findIndex(i => i == this)
                            index > -1 && WMvalue.splice(index, 1)
                            vueHooked.set(el, WMvalue.length > 1 ? WMvalue : WMvalue[0])
                        } else {
                            if (WMvalue == this) {
                                vueHooked.delete(el)
                            }
                        }
                    }
                }
            }
        }
    })
}

function recordVue(el, app) { //将app记录到WeakMap中
    vueUnhooked.delete(app)
    if (vueHooked.has(el)) {
        let value = vueHooked.get(el)
        if (Array.isArray(value)) {
            if (value.findIndex(i => i == app) == -1) {
                vueHooked.set(el, vueHooked.get(el).push(app))
            }
        } else {
            if (value != app) {
                vueHooked.set(el, [value, app])
            }
        }
    } else {
        vueHooked.set(el, app)
    }
}

function recordDOM(el, app) { //将app挂载到DOMelement.__vue__
    if (el.__vue__) {
        let value = el.__vue__
        if (Array.isArray(value)) {
            if (value.findIndex(i => i == app) == -1) {
                el.__vue__ = value.push(app)
            }
        } else {
            if (value != app) {
                el.__vue__ = [value, app]
            }
        }
    } else {
        el.__vue__ = app
    }
}