Hook Vue3 app

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

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/449444/1081400/Hook%20Vue3%20app.js

  1. // ==UserScript==
  2. // @name Hook Vue3 app
  3. // @version 1.0.3
  4. // @description 通过劫持Proxy方法,逆向还原Vue3 app元素到DOM
  5. // @author DreamNya
  6. // @license MIT
  7. // @namespace https://greasyfork.org/users/809466
  8. // ==/UserScript==
  9.  
  10. const $window = window.unsafeWindow || document.defaultView || window
  11. const realLog = $window.console.log; //反劫持console.log(大部分网站都会劫持console.log)
  12. const realProxy = $window.Proxy; //劫持Proxy
  13.  
  14. var vueUnhooked = new WeakSet() //以WeakSet存储已获取到但未未劫持的app对象,作为debug用变量,正常情况WeakSet应为空
  15. var vueHooked = new WeakMap() //以WeakMap存储已劫持的app对象,DOM元素为key,app对象为value
  16.  
  17. $window.Proxy = function () {
  18. let app = arguments[0]._
  19. if (app?.uid >= 0) { //判断app
  20. let el = app.vnode.el
  21. if (el) {
  22. recordVue(el, app) //记录到WeakMap
  23. recordDOM(el, app) //挂载到DOM
  24. watch_isUnmounted(app) //观察销毁
  25. } else {
  26. //realLog(app,el)
  27. vueUnhooked.add(app) //记录未劫持的app
  28. //realLog(vueUnhooked,app)
  29. watchEl(app.vnode) //不存在el则观察el
  30. }
  31. }
  32. return new realProxy(...arguments)
  33. }
  34.  
  35. function watchEl(vnode) { //观察el 变动时还原到DOM
  36. let value = vnode.el
  37. let hooked = false
  38. Object.defineProperty(vnode, "el", {
  39. get() {
  40. return value
  41. },
  42. set(newValue) {
  43. value = newValue
  44. if (!hooked && this.el) {
  45. hooked = true
  46. recordVue(this.el, this.component)
  47. recordDOM(this.el, this.component)
  48. watch_isUnmounted(this.component)
  49. //realLog(this.component,"已还原")
  50. }
  51. }
  52. })
  53. }
  54.  
  55. function watch_isUnmounted(app) { //观察isUnmounted 变动时销毁引用
  56. let value = app.isUnmounted
  57. let unhooked = false
  58. Object.defineProperty(app, "isUnmounted", {
  59. get() {
  60. return value
  61. },
  62. set(newValue) {
  63. value = newValue
  64. if (!unhooked && this.isUnmounted) {
  65. unhooked = true
  66. //realLog(this,"已删除")
  67. let el = this.vnode.el
  68. if (el) {
  69. let DOMvalue = el.__vue__ //删除DOMelement.__vue__挂载
  70. if (DOMvalue) {
  71. if (Array.isArray(DOMvalue)) {
  72. let index = DOMvalue.findIndex(i => i == this)
  73. index > -1 && DOMvalue.splice(index, 1)
  74. el.__vue__ = DOMvalue.length > 1 ? DOMvalue : DOMvalue[0]
  75. } else {
  76. if (DOMvalue == this) {
  77. el.__vue__ = void 0
  78. }
  79. }
  80. }
  81. let WMvalue = vueHooked.get(el) //删除WeakMap存储
  82. if (WMvalue) {
  83. if (Array.isArray(WMvalue)) {
  84. let index = WMvalue.findIndex(i => i == this)
  85. index > -1 && WMvalue.splice(index, 1)
  86. vueHooked.set(el, WMvalue.length > 1 ? WMvalue : WMvalue[0])
  87. } else {
  88. if (WMvalue == this) {
  89. vueHooked.delete(el)
  90. }
  91. }
  92. }
  93. }
  94. }
  95. }
  96. })
  97. }
  98.  
  99. function recordVue(el, app) { //将app记录到WeakMap中
  100. vueUnhooked.delete(app)
  101. if (vueHooked.has(el)) {
  102. let value = vueHooked.get(el)
  103. if (Array.isArray(value)) {
  104. if (value.findIndex(i => i == app) == -1) {
  105. vueHooked.set(el, vueHooked.get(el).push(app))
  106. }
  107. } else {
  108. if (value != app) {
  109. vueHooked.set(el, [value, app])
  110. }
  111. }
  112. } else {
  113. vueHooked.set(el, app)
  114. }
  115. }
  116.  
  117. function recordDOM(el, app) { //将app挂载到DOMelement.__vue__
  118. if (el.__vue__) {
  119. let value = el.__vue__
  120. if (Array.isArray(value)) {
  121. if (value.findIndex(i => i == app) == -1) {
  122. el.__vue__ = value.push(app)
  123. }
  124. } else {
  125. if (value != app) {
  126. el.__vue__ = [value, app]
  127. }
  128. }
  129. } else {
  130. el.__vue__ = app
  131. }
  132. }