Hook Vue3 app

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

Questo script non dovrebbe essere installato direttamente. È una libreria per altri script da includere con la chiave // @require https://update.greasyfork.org/scripts/449444/1081400/Hook%20Vue3%20app.js

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==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
    }
}