vue-debug-helper

Vue components debug helper

当前为 2022-05-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name vue-debug-helper
  3. // @name:en vue-debug-helper
  4. // @name:zh Vue调试分析助手
  5. // @name:zh-TW Vue調試分析助手
  6. // @name:ja Vueデバッグ分析アシスタント
  7. // @namespace https://github.com/xxxily/vue-debug-helper
  8. // @homepage https://github.com/xxxily/vue-debug-helper
  9. // @version 0.0.15
  10. // @description Vue components debug helper
  11. // @description:en Vue components debug helper
  12. // @description:zh Vue组件探测、统计、分析辅助脚本
  13. // @description:zh-TW Vue組件探測、統計、分析輔助腳本
  14. // @description:ja Vueコンポーネントの検出、統計、分析補助スクリプト
  15. // @author ankvps
  16. // @icon https://cdn.jsdelivr.net/gh/xxxily/vue-debug-helper@main/logo.png
  17. // @match http://*/*
  18. // @match https://*/*
  19. // @grant unsafeWindow
  20. // @grant GM_getResourceText
  21. // @grant GM_addStyle
  22. // @grant GM_setValue
  23. // @grant GM_getValue
  24. // @grant GM_deleteValue
  25. // @grant GM_listValues
  26. // @grant GM_addValueChangeListener
  27. // @grant GM_removeValueChangeListener
  28. // @grant GM_registerMenuCommand
  29. // @grant GM_unregisterMenuCommand
  30. // @grant GM_getTab
  31. // @grant GM_saveTab
  32. // @grant GM_getTabs
  33. // @grant GM_openInTab
  34. // @grant GM_download
  35. // @grant GM_xmlhttpRequest
  36. // @require https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js
  37. // @require https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/core.js
  38. // @require https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/md5.js
  39. // @require https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js
  40. // @require https://cdn.jsdelivr.net/npm/jquery-contextmenu@2.9.2/dist/jquery.contextMenu.min.js
  41. // @require https://cdn.jsdelivr.net/npm/jquery-contextmenu@2.9.2/dist/jquery.ui.position.min.js
  42. // @resource contextMenuCss https://cdn.jsdelivr.net/npm/jquery-contextmenu@2.9.2/dist/jquery.contextMenu.min.css
  43. // @run-at document-start
  44. // @connect 127.0.0.1
  45. // @license GPL
  46. // ==/UserScript==
  47. (function (w) { if (w) { w._vueDebugHelper_ = 'https://github.com/xxxily/vue-debug-helper'; } })();
  48.  
  49. class AssertionError extends Error {}
  50. AssertionError.prototype.name = 'AssertionError';
  51.  
  52. /**
  53. * Minimal assert function
  54. * @param {any} t Value to check if falsy
  55. * @param {string=} m Optional assertion error message
  56. * @throws {AssertionError}
  57. */
  58. function assert (t, m) {
  59. if (!t) {
  60. var err = new AssertionError(m);
  61. if (Error.captureStackTrace) Error.captureStackTrace(err, assert);
  62. throw err
  63. }
  64. }
  65.  
  66. /* eslint-env browser */
  67.  
  68. let ls;
  69. if (typeof window === 'undefined' || typeof window.localStorage === 'undefined') {
  70. // A simple localStorage interface so that lsp works in SSR contexts. Not for persistant storage in node.
  71. const _nodeStorage = {};
  72. ls = {
  73. getItem (name) {
  74. return _nodeStorage[name] || null
  75. },
  76. setItem (name, value) {
  77. if (arguments.length < 2) throw new Error('Failed to execute \'setItem\' on \'Storage\': 2 arguments required, but only 1 present.')
  78. _nodeStorage[name] = (value).toString();
  79. },
  80. removeItem (name) {
  81. delete _nodeStorage[name];
  82. }
  83. };
  84. } else {
  85. ls = window.localStorage;
  86. }
  87.  
  88. var localStorageProxy = (name, opts = {}) => {
  89. assert(name, 'namepace required');
  90. const {
  91. defaults = {},
  92. lspReset = false,
  93. storageEventListener = true
  94. } = opts;
  95.  
  96. const state = new EventTarget();
  97. try {
  98. const restoredState = JSON.parse(ls.getItem(name)) || {};
  99. if (restoredState.lspReset !== lspReset) {
  100. ls.removeItem(name);
  101. for (const [k, v] of Object.entries({
  102. ...defaults
  103. })) {
  104. state[k] = v;
  105. }
  106. } else {
  107. for (const [k, v] of Object.entries({
  108. ...defaults,
  109. ...restoredState
  110. })) {
  111. state[k] = v;
  112. }
  113. }
  114. } catch (e) {
  115. console.error(e);
  116. ls.removeItem(name);
  117. }
  118.  
  119. state.lspReset = lspReset;
  120.  
  121. if (storageEventListener && typeof window !== 'undefined' && typeof window.addEventListener !== 'undefined') {
  122. state.addEventListener('storage', (ev) => {
  123. // Replace state with whats stored on localStorage... it is newer.
  124. for (const k of Object.keys(state)) {
  125. delete state[k];
  126. }
  127. const restoredState = JSON.parse(ls.getItem(name)) || {};
  128. for (const [k, v] of Object.entries({
  129. ...defaults,
  130. ...restoredState
  131. })) {
  132. state[k] = v;
  133. }
  134. opts.lspReset = restoredState.lspReset;
  135. state.dispatchEvent(new Event('update'));
  136. });
  137. }
  138.  
  139. function boundHandler (rootRef) {
  140. return {
  141. get (obj, prop) {
  142. if (typeof obj[prop] === 'object' && obj[prop] !== null) {
  143. return new Proxy(obj[prop], boundHandler(rootRef))
  144. } else if (typeof obj[prop] === 'function' && obj === rootRef && prop !== 'constructor') {
  145. // this returns bound EventTarget functions
  146. return obj[prop].bind(obj)
  147. } else {
  148. return obj[prop]
  149. }
  150. },
  151. set (obj, prop, value) {
  152. obj[prop] = value;
  153. try {
  154. ls.setItem(name, JSON.stringify(rootRef));
  155. rootRef.dispatchEvent(new Event('update'));
  156. return true
  157. } catch (e) {
  158. console.error(e);
  159. return false
  160. }
  161. }
  162. }
  163. }
  164.  
  165. return new Proxy(state, boundHandler(state))
  166. };
  167.  
  168. /**
  169. * 对特定数据结构的对象进行排序
  170. * @param {object} obj 一个对象,其结构应该类似于:{key1: [], key2: []}
  171. * @param {boolean} reverse -可选 是否反转、降序排列,默认为false
  172. * @param {object} opts -可选 指定数组的配置项,默认为{key: 'key', value: 'value'}
  173. * @param {object} opts.key -可选 指定对象键名的别名,默认为'key'
  174. * @param {object} opts.value -可选 指定对象值的别名,默认为'value'
  175. * @returns {array} 返回一个数组,其结构应该类似于:[{key: key1, value: []}, {key: key2, value: []}]
  176. */
  177. const objSort = (obj, reverse, opts = { key: 'key', value: 'value' }) => {
  178. const arr = [];
  179. for (const key in obj) {
  180. if (Object.prototype.hasOwnProperty.call(obj, key) && Array.isArray(obj[key])) {
  181. const tmpObj = {};
  182. tmpObj[opts.key] = key;
  183. tmpObj[opts.value] = obj[key];
  184. arr.push(tmpObj);
  185. }
  186. }
  187.  
  188. arr.sort((a, b) => {
  189. return a[opts.value].length - b[opts.value].length
  190. });
  191.  
  192. reverse && arr.reverse();
  193. return arr
  194. };
  195.  
  196. /**
  197. * 根据指定长度创建空白数据
  198. * @param {number} size -可选 指str的重复次数,默认为1024次,如果str为单个单字节字符,则意味着默认产生1Mb的空白数据
  199. * @param {string|number|any} str - 可选 指定数据的字符串,默认为'd'
  200. */
  201. function createEmptyData (count = 1024, str = 'd') {
  202. const arr = [];
  203. arr.length = count + 1;
  204. return arr.join(str)
  205. }
  206.  
  207. /**
  208. * 将字符串分隔的过滤器转换为数组形式的过滤器
  209. * @param {string|array} filter - 必选 字符串或数组,字符串支持使用 , |符号对多个项进行分隔
  210. * @returns {array}
  211. */
  212. function toArrFilters (filter) {
  213. filter = filter || [];
  214.  
  215. /* 如果是字符串,则支持通过, | 两个符号来指定多个组件名称的过滤器 */
  216. if (typeof filter === 'string') {
  217. /* 移除前后的, |分隔符,防止出现空字符的过滤规则 */
  218. filter.replace(/^(,|\|)/, '').replace(/(,|\|)$/, '');
  219.  
  220. if (/\|/.test(filter)) {
  221. filter = filter.split('|');
  222. } else {
  223. filter = filter.split(',');
  224. }
  225. }
  226.  
  227. filter = filter.map(item => item.trim());
  228.  
  229. return filter
  230. }
  231.  
  232. /**
  233. * 字符串过滤器和字符串的匹配方法
  234. * @param {string} filter -必选 过滤器的字符串
  235. * @param {string} str -必选 要跟过滤字符串进行匹配的字符串
  236. * @returns
  237. */
  238. function stringMatch (filter, str) {
  239. let isMatch = false;
  240.  
  241. if (!filter || !str) {
  242. return isMatch
  243. }
  244.  
  245. filter = String(filter);
  246. str = String(str);
  247.  
  248. /* 带星表示进行模糊匹配,且不区分大小写 */
  249. if (/\*/.test(filter)) {
  250. filter = filter.replace(/\*/g, '').toLocaleLowerCase();
  251. if (str.toLocaleLowerCase().indexOf(filter) > -1) {
  252. isMatch = true;
  253. }
  254. } else if (str.includes(filter)) {
  255. isMatch = true;
  256. }
  257.  
  258. return isMatch
  259. }
  260.  
  261. /**
  262. * 判断某个字符串是否跟filters相匹配
  263. * @param {array|string} filters - 必选 字符串或数组,字符串支持使用 , |符号对多个项进行分隔
  264. * @param {string|number} str - 必选 一个字符串或数字,用于跟过滤器进行匹配判断
  265. */
  266. function filtersMatch (filters, str) {
  267. if (!filters || !str) {
  268. return false
  269. }
  270.  
  271. filters = Array.isArray(filters) ? filters : toArrFilters(filters);
  272. str = String(str);
  273.  
  274. let result = false;
  275. for (let i = 0; i < filters.length; i++) {
  276. const filter = String(filters[i]);
  277.  
  278. if (stringMatch(filter, str)) {
  279. result = true;
  280. break
  281. }
  282. }
  283.  
  284. return result
  285. }
  286.  
  287. const inBrowser = typeof window !== 'undefined';
  288.  
  289. function getVueDevtools () {
  290. return inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__
  291. }
  292.  
  293. window.vueDebugHelper = {
  294. /* 存储全部未被销毁的组件对象 */
  295. components: {},
  296. /* 存储全部创建过的组件的概要信息,即使销毁了概要信息依然存在 */
  297. componentsSummary: {},
  298. /* 基于componentsSummary的组件情况统计 */
  299. componentsSummaryStatistics: {},
  300. /* 已销毁的组件概要信息列表 */
  301. destroyList: [],
  302. /* 基于destroyList的组件情况统计 */
  303. destroyStatistics: {},
  304.  
  305. config: {
  306. inspect: {
  307. enabled: false
  308. },
  309.  
  310. performanceObserver: {
  311. enabled: false,
  312. // https://runebook.dev/zh-CN/docs/dom/performanceentry/entrytype
  313. entryTypes: ['element', 'navigation', 'resource', 'mark', 'measure', 'paint', 'longtask']
  314. },
  315.  
  316. /* 控制接口缓存 */
  317. ajaxCache: {
  318. enabled: false,
  319. filters: ['*'],
  320.  
  321. /* 设置缓存多久失效,默认为1天 */
  322. expires: 1000 * 60 * 60 * 24
  323. },
  324.  
  325. /* 测量选择器时间差 */
  326. measureSelectorInterval: {
  327. selector1: '',
  328. selector2: ''
  329. },
  330.  
  331. /* 是否在控制台打印组件生命周期的相关信息 */
  332. lifecycle: {
  333. show: false,
  334. filters: ['created'],
  335. componentFilters: []
  336. },
  337.  
  338. /* 查找组件的过滤器配置 */
  339. findComponentsFilters: [],
  340.  
  341. /* 阻止组件创建的过滤器 */
  342. blockFilters: [],
  343.  
  344. devtools: true,
  345.  
  346. /* 改写Vue.component */
  347. hackVueComponent: false,
  348.  
  349. /* 给组件注入空白数据的配置信息 */
  350. dd: {
  351. enabled: false,
  352. filters: [],
  353. count: 1024
  354. }
  355. }
  356. };
  357.  
  358. const helper = window.vueDebugHelper;
  359.  
  360. /* 配置信息跟localStorage联动 */
  361. const state = localStorageProxy('vueDebugHelperConfig', {
  362. defaults: helper.config,
  363. lspReset: false,
  364. storageEventListener: false
  365. });
  366. helper.config = state;
  367.  
  368. const methods = {
  369. objSort,
  370. createEmptyData,
  371. /* 清除全部helper的全部记录数据,以便重新统计 */
  372. clearAll () {
  373. helper.components = {};
  374. helper.componentsSummary = {};
  375. helper.componentsSummaryStatistics = {};
  376. helper.destroyList = [];
  377. helper.destroyStatistics = {};
  378. },
  379.  
  380. /**
  381. * 对当前的helper.components进行统计与排序
  382. * 如果一直没运行过清理函数,则表示统计页面创建至今依然存活的组件对象
  383. * 运行过清理函数,则表示统计清理后新创建且至今依然存活的组件对象
  384. */
  385. componentsStatistics (reverse = true) {
  386. const tmpObj = {};
  387.  
  388. Object.keys(helper.components).forEach(key => {
  389. const component = helper.components[key];
  390.  
  391. tmpObj[component._componentName]
  392. ? tmpObj[component._componentName].push(component)
  393. : (tmpObj[component._componentName] = [component]);
  394. });
  395.  
  396. return objSort(tmpObj, reverse, {
  397. key: 'componentName',
  398. value: 'componentInstance'
  399. })
  400. },
  401.  
  402. /**
  403. * 对componentsSummaryStatistics进行排序输出,以便可以直观查看组件的创建情况
  404. */
  405. componentsSummaryStatisticsSort (reverse = true) {
  406. return objSort(helper.componentsSummaryStatistics, reverse, {
  407. key: 'componentName',
  408. value: 'componentsSummary'
  409. })
  410. },
  411.  
  412. /**
  413. * 对destroyList进行排序输出,以便可以直观查看组件的销毁情况
  414. */
  415. destroyStatisticsSort (reverse = true) {
  416. return objSort(helper.destroyStatistics, reverse, {
  417. key: 'componentName',
  418. value: 'destroyList'
  419. })
  420. },
  421.  
  422. /**
  423. * 对destroyList进行排序输出,以便可以直观查看组件的销毁情况
  424. */
  425. getDestroyByDuration (duration = 1000) {
  426. const destroyList = helper.destroyList;
  427. const destroyListLength = destroyList.length;
  428. const destroyListDuration = destroyList.map(item => item.duration).sort();
  429. const maxDuration = Math.max(...destroyListDuration);
  430. const minDuration = Math.min(...destroyListDuration);
  431. const avgDuration = destroyListDuration.reduce((a, b) => a + b, 0) / destroyListLength;
  432. const durationRange = maxDuration - minDuration;
  433. const durationRangePercent = (duration - minDuration) / durationRange;
  434.  
  435. return {
  436. destroyList,
  437. destroyListLength,
  438. destroyListDuration,
  439. maxDuration,
  440. minDuration,
  441. avgDuration,
  442. durationRange,
  443. durationRangePercent
  444. }
  445. },
  446.  
  447. /**
  448. * 获取组件的调用链信息
  449. */
  450. getComponentChain (component, moreDetail = false) {
  451. const result = [];
  452. let current = component;
  453. let deep = 0;
  454.  
  455. while (current && deep < 50) {
  456. deep++;
  457.  
  458. /**
  459. * 由于脚本注入的运行时间会比应用创建时间晚,所以会导致部分先创建的组件缺少相关信息
  460. * 这里尝试对部分信息进行修复,以便更好的查看组件的创建情况
  461. */
  462. if (!current._componentTag) {
  463. const tag = current.$vnode?.tag || current.$options?._componentTag || current._uid;
  464. current._componentTag = tag;
  465. current._componentName = isNaN(Number(tag)) ? tag.replace(/^vue-component-\d+-/, '') : 'anonymous-component';
  466. }
  467.  
  468. if (moreDetail) {
  469. result.push({
  470. tag: current._componentTag,
  471. name: current._componentName,
  472. componentsSummary: helper.componentsSummary[current._uid] || null
  473. });
  474. } else {
  475. result.push(current._componentName);
  476. }
  477.  
  478. current = current.$parent;
  479. }
  480.  
  481. if (moreDetail) {
  482. return result
  483. } else {
  484. return result.join(' -> ')
  485. }
  486. },
  487.  
  488. printLifeCycleInfo (lifecycleFilters, componentFilters) {
  489. lifecycleFilters = toArrFilters(lifecycleFilters);
  490. componentFilters = toArrFilters(componentFilters);
  491.  
  492. helper.config.lifecycle = {
  493. show: true,
  494. filters: lifecycleFilters,
  495. componentFilters: componentFilters
  496. };
  497. },
  498. notPrintLifeCycleInfo () {
  499. helper.config.lifecycle.show = false;
  500. },
  501.  
  502. /**
  503. * 查找组件
  504. * @param {string|array} filters 组件名称或组件uid的过滤器,可以是字符串或者数组,如果是字符串多个过滤选可用,或|分隔
  505. * 如果过滤项是数字,则跟组件的id进行精确匹配,如果是字符串,则跟组件的tag信息进行模糊匹配
  506. * @returns {object} {components: [], componentNames: []}
  507. */
  508. findComponents (filters) {
  509. filters = toArrFilters(filters);
  510.  
  511. /* 对filters进行预处理,如果为纯数字则表示通过id查找组件 */
  512. filters = filters.map(filter => {
  513. if (/^\d+$/.test(filter)) {
  514. return Number(filter)
  515. } else {
  516. return filter
  517. }
  518. });
  519.  
  520. helper.config.findComponentsFilters = filters;
  521.  
  522. const result = {
  523. components: [],
  524. globalComponents: [],
  525. destroyedComponents: []
  526. };
  527.  
  528. /* 在helper.components里进行组件查找 */
  529. const components = helper.components;
  530. const keys = Object.keys(components);
  531. for (let i = 0; i < keys.length; i++) {
  532. const component = components[keys[i]];
  533.  
  534. for (let j = 0; j < filters.length; j++) {
  535. const filter = filters[j];
  536.  
  537. if (typeof filter === 'number' && component._uid === filter) {
  538. result.components.push(component);
  539. break
  540. } else if (typeof filter === 'string') {
  541. const { _componentTag, _componentName } = component;
  542.  
  543. if (stringMatch(filter, _componentTag) || stringMatch(filter, _componentName)) {
  544. result.components.push(component);
  545. break
  546. }
  547. }
  548. }
  549. }
  550.  
  551. /* 进行全局组件查找 */
  552. const globalComponentsKeys = Object.keys(helper.Vue.options.components);
  553. for (let i = 0; i < globalComponentsKeys.length; i++) {
  554. const key = String(globalComponentsKeys[i]);
  555. const component = helper.Vue.options.components[globalComponentsKeys[i]];
  556.  
  557. if (filtersMatch(filters, key)) {
  558. const tmpObj = {};
  559. tmpObj[key] = component;
  560. result.globalComponents.push(tmpObj);
  561. }
  562. }
  563.  
  564. helper.destroyList.forEach(item => {
  565. for (let j = 0; j < filters.length; j++) {
  566. const filter = filters[j];
  567.  
  568. if (typeof filter === 'number' && item.uid === filter) {
  569. result.destroyedComponents.push(item);
  570. break
  571. } else if (typeof filter === 'string') {
  572. if (stringMatch(filter, item.tag) || stringMatch(filter, item.name)) {
  573. result.destroyedComponents.push(item);
  574. break
  575. }
  576. }
  577. }
  578. });
  579.  
  580. return result
  581. },
  582.  
  583. findNotContainElementComponents () {
  584. const result = [];
  585. const keys = Object.keys(helper.components);
  586. keys.forEach(key => {
  587. const component = helper.components[key];
  588. const elStr = Object.prototype.toString.call(component.$el);
  589. if (!/(HTML|Comment)/.test(elStr)) {
  590. result.push(component);
  591. }
  592. });
  593.  
  594. return result
  595. },
  596.  
  597. /**
  598. * 阻止组件的创建
  599. * @param {string|array} filters 组件名称过滤器,可以是字符串或者数组,如果是字符串多个过滤选可用,或|分隔
  600. */
  601. blockComponents (filters) {
  602. filters = toArrFilters(filters);
  603. helper.config.blockFilters = filters;
  604. },
  605.  
  606. /**
  607. * 给指定组件注入大量空数据,以便观察组件的内存泄露情况
  608. * @param {Array|string} filter -必选 指定组件的名称,如果为空则表示注入所有组件
  609. * @param {number} count -可选 指定注入空数据的大小,单位Kb,默认为1024Kb,即1Mb
  610. * @returns
  611. */
  612. dd (filter, count = 1024) {
  613. filter = toArrFilters(filter);
  614. helper.config.dd = {
  615. enabled: true,
  616. filters: filter,
  617. count
  618. };
  619. },
  620. /* 禁止给组件注入空数据 */
  621. undd () {
  622. helper.config.dd = {
  623. enabled: false,
  624. filters: [],
  625. count: 1024
  626. };
  627.  
  628. /* 删除之前注入的数据 */
  629. Object.keys(helper.components).forEach(key => {
  630. const component = helper.components[key];
  631. component.$data && delete component.$data.__dd__;
  632. });
  633. },
  634.  
  635. toggleDevtools () {
  636. helper.config.devtools = !helper.config.devtools;
  637. }
  638. };
  639.  
  640. helper.methods = methods;
  641.  
  642. class Debug {
  643. constructor (msg, printTime = false) {
  644. const t = this;
  645. msg = msg || 'debug message:';
  646. t.log = t.createDebugMethod('log', null, msg);
  647. t.error = t.createDebugMethod('error', null, msg);
  648. t.info = t.createDebugMethod('info', null, msg);
  649. t.warn = t.createDebugMethod('warn', null, msg);
  650. }
  651.  
  652. create (msg) {
  653. return new Debug(msg)
  654. }
  655.  
  656. createDebugMethod (name, color, tipsMsg) {
  657. name = name || 'info';
  658.  
  659. const bgColorMap = {
  660. info: '#2274A5',
  661. log: '#95B46A',
  662. warn: '#F5A623',
  663. error: '#D33F49'
  664. };
  665.  
  666. const printTime = this.printTime;
  667.  
  668. return function () {
  669. if (!window._debugMode_) {
  670. return false
  671. }
  672.  
  673. const msg = tipsMsg || 'debug message:';
  674.  
  675. const arg = Array.from(arguments);
  676. arg.unshift(`color: white; background-color: ${color || bgColorMap[name] || '#95B46A'}`);
  677.  
  678. if (printTime) {
  679. const curTime = new Date();
  680. const H = curTime.getHours();
  681. const M = curTime.getMinutes();
  682. const S = curTime.getSeconds();
  683. arg.unshift(`%c [${H}:${M}:${S}] ${msg} `);
  684. } else {
  685. arg.unshift(`%c ${msg} `);
  686. }
  687.  
  688. window.console[name].apply(window.console, arg);
  689. }
  690. }
  691.  
  692. isDebugMode () {
  693. return Boolean(window._debugMode_)
  694. }
  695. }
  696.  
  697. var Debug$1 = new Debug();
  698.  
  699. var debug = Debug$1.create('vueDebugHelper:');
  700.  
  701. /**
  702. * 打印生命周期信息
  703. * @param {Vue} vm vue组件实例
  704. * @param {string} lifeCycle vue生命周期名称
  705. * @returns
  706. */
  707. function printLifeCycle (vm, lifeCycle) {
  708. const lifeCycleConf = helper.config.lifecycle || { show: false, filters: ['created'], componentFilters: [] };
  709.  
  710. if (!vm || !lifeCycle || !lifeCycleConf.show) {
  711. return false
  712. }
  713.  
  714. const file = vm.options?.__file || vm.$options?.__file || '';
  715.  
  716. const { _componentTag, _componentName, _componentChain, _createdHumanTime, _uid } = vm;
  717. let info = `[${lifeCycle}] tag: ${_componentTag}, uid: ${_uid}, createdTime: ${_createdHumanTime}, chain: ${_componentChain}`;
  718.  
  719. if (file) {
  720. info += `, file: ${file}`;
  721. }
  722.  
  723. const matchComponentFilters = lifeCycleConf.componentFilters.length === 0 || filtersMatch(lifeCycleConf.componentFilters, _componentName);
  724. if (lifeCycleConf.filters.includes(lifeCycle) && matchComponentFilters) {
  725. debug.log(info);
  726. }
  727. }
  728.  
  729. function mixinRegister (Vue) {
  730. if (!Vue || !Vue.mixin) {
  731. debug.error('未检查到VUE对象,请检查是否引入了VUE,且将VUE对象挂载到全局变量window.Vue上');
  732. return false
  733. }
  734.  
  735. Vue.mixin({
  736. beforeCreate: function () {
  737. // const tag = this.$options?._componentTag || this.$vnode?.tag || this._uid
  738. const tag = this.$vnode?.tag || this.$options?._componentTag || this._uid;
  739. const chain = helper.methods.getComponentChain(this);
  740. this._componentTag = tag;
  741. this._componentChain = chain;
  742. this._componentName = isNaN(Number(tag)) ? tag.replace(/^vue-component-\d+-/, '') : 'anonymous-component';
  743. this._createdTime = Date.now();
  744.  
  745. /* 增加人类方便查看的时间信息 */
  746. const timeObj = new Date(this._createdTime);
  747. this._createdHumanTime = `${timeObj.getHours()}:${timeObj.getMinutes()}:${timeObj.getSeconds()}`;
  748.  
  749. /* 判断是否为函数式组件,函数式组件无状态 (没有响应式数据),也没有实例,也没生命周期概念 */
  750. if (this._componentName === 'anonymous-component' && !this.$parent && !this.$vnode) {
  751. this._componentName = 'functional-component';
  752. }
  753.  
  754. helper.components[this._uid] = this;
  755.  
  756. /**
  757. * 收集所有创建过的组件信息,此处只存储组件的基础信息,没销毁的组件会包含组件实例
  758. * 严禁对组件内其它对象进行引用,否则会导致组件实列无法被正常回收
  759. */
  760. const componentSummary = {
  761. uid: this._uid,
  762. name: this._componentName,
  763. tag: this._componentTag,
  764. createdTime: this._createdTime,
  765. createdHumanTime: this._createdHumanTime,
  766. // 0 表示还没被销毁
  767. destroyTime: 0,
  768. // 0 表示还没被销毁,duration可持续当当前查看时间
  769. duration: 0,
  770. component: this,
  771. chain
  772. };
  773. helper.componentsSummary[this._uid] = componentSummary;
  774.  
  775. /* 添加到componentsSummaryStatistics里,生成统计信息 */
  776. Array.isArray(helper.componentsSummaryStatistics[this._componentName])
  777. ? helper.componentsSummaryStatistics[this._componentName].push(componentSummary)
  778. : (helper.componentsSummaryStatistics[this._componentName] = [componentSummary]);
  779.  
  780. printLifeCycle(this, 'beforeCreate');
  781. },
  782. created: function () {
  783. /* 增加空白数据,方便观察内存泄露情况 */
  784. if (helper.config.dd.enabled) {
  785. let needDd = false;
  786.  
  787. if (helper.config.dd.filters.length === 0) {
  788. needDd = true;
  789. } else {
  790. for (let index = 0; index < helper.config.dd.filters.length; index++) {
  791. const filter = helper.config.dd.filters[index];
  792. if (filter === this._componentName || String(this._componentName).endsWith(filter)) {
  793. needDd = true;
  794. break
  795. }
  796. }
  797. }
  798.  
  799. if (needDd) {
  800. const count = helper.config.dd.count * 1024;
  801. const componentInfo = `tag: ${this._componentTag}, uid: ${this._uid}, createdTime: ${this._createdHumanTime}`;
  802.  
  803. /* 此处必须使用JSON.stringify对产生的字符串进行消费,否则没法将内存占用上去 */
  804. this.$data.__dd__ = JSON.stringify(componentInfo + ' ' + helper.methods.createEmptyData(count, this._uid));
  805.  
  806. console.log(`[dd success] ${componentInfo} chain: ${this._componentChain}`);
  807. }
  808. }
  809.  
  810. printLifeCycle(this, 'created');
  811. },
  812. beforeMount: function () {
  813. printLifeCycle(this, 'beforeMount');
  814. },
  815. mounted: function () {
  816. printLifeCycle(this, 'mounted');
  817. },
  818. beforeUpdate: function () {
  819. printLifeCycle(this, 'beforeUpdate');
  820. },
  821. activated: function () {
  822. printLifeCycle(this, 'activated');
  823. },
  824. deactivated: function () {
  825. printLifeCycle(this, 'deactivated');
  826. },
  827. updated: function () {
  828. printLifeCycle(this, 'updated');
  829. },
  830. beforeDestroy: function () {
  831. printLifeCycle(this, 'beforeDestroy');
  832. },
  833. destroyed: function () {
  834. printLifeCycle(this, 'destroyed');
  835.  
  836. if (this._componentTag) {
  837. const uid = this._uid;
  838. const name = this._componentName;
  839. const destroyTime = Date.now();
  840.  
  841. /* helper里的componentSummary有可能通过调用clear函数而被清除掉,所以需进行判断再更新赋值 */
  842. const componentSummary = helper.componentsSummary[this._uid];
  843. if (componentSummary) {
  844. /* 补充/更新组件信息 */
  845. componentSummary.destroyTime = destroyTime;
  846. componentSummary.duration = destroyTime - this._createdTime;
  847.  
  848. helper.destroyList.push(componentSummary);
  849.  
  850. /* 统计被销毁的组件信息 */
  851. Array.isArray(helper.destroyStatistics[name])
  852. ? helper.destroyStatistics[name].push(componentSummary)
  853. : (helper.destroyStatistics[name] = [componentSummary]);
  854.  
  855. /* 删除已销毁的组件实例 */
  856. delete componentSummary.component;
  857. }
  858.  
  859. // 解除引用关系
  860. delete this._componentTag;
  861. delete this._componentChain;
  862. delete this._componentName;
  863. delete this._createdTime;
  864. delete this._createdHumanTime;
  865. delete this.$data.__dd__;
  866. delete helper.components[uid];
  867. } else {
  868. console.error('存在未被正常标记的组件,请检查组件采集逻辑是否需完善', this);
  869. }
  870. }
  871. });
  872. }
  873.  
  874. /*!
  875. * @name menuCommand.js
  876. * @version 0.0.1
  877. * @author Blaze
  878. * @date 2019/9/21 14:22
  879. */
  880.  
  881. const monkeyMenu = {
  882. on (title, fn, accessKey) {
  883. return window.GM_registerMenuCommand && window.GM_registerMenuCommand(title, fn, accessKey)
  884. },
  885. off (id) {
  886. return window.GM_unregisterMenuCommand && window.GM_unregisterMenuCommand(id)
  887. },
  888. /* 切换类型的菜单功能 */
  889. switch (title, fn, defVal) {
  890. const t = this;
  891. t.on(title, fn);
  892. }
  893. };
  894.  
  895. /**
  896. * 简单的i18n库
  897. */
  898.  
  899. class I18n {
  900. constructor (config) {
  901. this._languages = {};
  902. this._locale = this.getClientLang();
  903. this._defaultLanguage = '';
  904. this.init(config);
  905. }
  906.  
  907. init (config) {
  908. if (!config) return false
  909.  
  910. const t = this;
  911. t._locale = config.locale || t._locale;
  912. /* 指定当前要是使用的语言环境,默认无需指定,会自动读取 */
  913. t._languages = config.languages || t._languages;
  914. t._defaultLanguage = config.defaultLanguage || t._defaultLanguage;
  915. }
  916.  
  917. use () {}
  918.  
  919. t (path) {
  920. const t = this;
  921. let result = t.getValByPath(t._languages[t._locale] || {}, path);
  922.  
  923. /* 版本回退 */
  924. if (!result && t._locale !== t._defaultLanguage) {
  925. result = t.getValByPath(t._languages[t._defaultLanguage] || {}, path);
  926. }
  927.  
  928. return result || ''
  929. }
  930.  
  931. /* 当前语言值 */
  932. language () {
  933. return this._locale
  934. }
  935.  
  936. languages () {
  937. return this._languages
  938. }
  939.  
  940. changeLanguage (locale) {
  941. if (this._languages[locale]) {
  942. this._languages = locale;
  943. return locale
  944. } else {
  945. return false
  946. }
  947. }
  948.  
  949. /**
  950. * 根据文本路径获取对象里面的值
  951. * @param obj {Object} -必选 要操作的对象
  952. * @param path {String} -必选 路径信息
  953. * @returns {*}
  954. */
  955. getValByPath (obj, path) {
  956. path = path || '';
  957. const pathArr = path.split('.');
  958. let result = obj;
  959.  
  960. /* 递归提取结果值 */
  961. for (let i = 0; i < pathArr.length; i++) {
  962. if (!result) break
  963. result = result[pathArr[i]];
  964. }
  965.  
  966. return result
  967. }
  968.  
  969. /* 获取客户端当前的语言环境 */
  970. getClientLang () {
  971. return navigator.languages ? navigator.languages[0] : navigator.language
  972. }
  973. }
  974.  
  975. var zhCN = {
  976. about: '关于',
  977. issues: '反馈',
  978. setting: '设置',
  979. hotkeys: '快捷键',
  980. donate: '赞赏',
  981. debugHelper: {
  982. viewVueDebugHelperObject: 'vueDebugHelper对象',
  983. componentsStatistics: '当前存活组件统计',
  984. destroyStatisticsSort: '已销毁组件统计',
  985. componentsSummaryStatisticsSort: '全部组件混合统计',
  986. getDestroyByDuration: '组件存活时间信息',
  987. clearAll: '清空统计信息',
  988. printLifeCycleInfo: '打印组件生命周期信息',
  989. notPrintLifeCycleInfo: '取消组件生命周期信息打印',
  990. printLifeCycleInfoPrompt: {
  991. lifecycleFilters: '输入要打印的生命周期名称,多个可用,或|分隔,支持的值:beforeCreate|created|beforeMount|mounted|beforeUpdate|updated|activated|deactivated|beforeDestroy|destroyed',
  992. componentFilters: '输入要打印的组件名称,多个可用,或|分隔,不输入则打印所有组件,字符串后面加*可执行模糊匹配'
  993. },
  994. findComponents: '查找组件',
  995. findComponentsPrompt: {
  996. filters: '输入要查找的组件名称,或uid,多个可用,或|分隔,字符串后面加*可执行模糊匹配'
  997. },
  998. findNotContainElementComponents: '查找不包含DOM对象的组件',
  999. blockComponents: '阻断组件的创建',
  1000. blockComponentsPrompt: {
  1001. filters: '输入要阻断的组件名称,多个可用,或|分隔,输入为空则取消阻断,字符串后面加*可执行模糊匹配'
  1002. },
  1003. dd: '数据注入(dd)',
  1004. undd: '取消数据注入(undd)',
  1005. ddPrompt: {
  1006. filter: '组件过滤器(如果为空,则对所有组件注入)',
  1007. count: '指定注入数据的重复次数(默认1024)'
  1008. },
  1009. toggleHackVueComponent: '改写/还原Vue.component',
  1010. hackVueComponent: {
  1011. hack: '改写Vue.component',
  1012. unhack: '还原Vue.component'
  1013. },
  1014. toggleInspect: '切换Inspect',
  1015. togglePerformanceObserver: '开启/关闭性能观察',
  1016. performanceObserverPrompt: {
  1017. entryTypes: '输入要观察的类型,多个类型可用,或|分隔,支持的类型有:element,navigation,resource,mark,measure,paint,longtask',
  1018. notSupport: '当前浏览器不支持性能观察'
  1019. },
  1020. enableAjaxCacheTips: '接口缓存功能已开启',
  1021. disableAjaxCacheTips: '接口缓存功能已关闭',
  1022. toggleAjaxCache: '开启/关闭接口缓存',
  1023. clearAjaxCache: '清空接口缓存数据',
  1024. clearAjaxCacheTips: '接口缓存数据已清空',
  1025. jaxCachePrompt: {
  1026. filters: '输入要缓存的接口地址,多个可用,或|分隔,字符串后面加*可执行模糊匹配',
  1027. expires: '输入缓存过期时间,单位为分钟,默认为1440分钟(即24小时)'
  1028. },
  1029. measureSelectorInterval: '测量选择器时间差',
  1030. measureSelectorIntervalPrompt: {
  1031. selector1: '输入起始选择器',
  1032. selector2: '输入结束选择器'
  1033. },
  1034. selectorReadyTips: '元素已就绪',
  1035. devtools: {
  1036. enabled: '自动开启vue-devtools',
  1037. disable: '禁止开启vue-devtools'
  1038. }
  1039. }
  1040. };
  1041.  
  1042. var enUS = {
  1043. about: 'about',
  1044. issues: 'feedback',
  1045. setting: 'settings',
  1046. hotkeys: 'Shortcut keys',
  1047. donate: 'donate',
  1048. debugHelper: {
  1049. viewVueDebugHelperObject: 'vueDebugHelper object',
  1050. componentsStatistics: 'Current surviving component statistics',
  1051. destroyStatisticsSort: 'Destroyed component statistics',
  1052. componentsSummaryStatisticsSort: 'All components mixed statistics',
  1053. getDestroyByDuration: 'Component survival time information',
  1054. clearAll: 'Clear statistics',
  1055. dd: 'Data injection (dd)',
  1056. undd: 'Cancel data injection (undd)',
  1057. ddPrompt: {
  1058. filter: 'Component filter (if empty, inject all components)',
  1059. count: 'Specify the number of repetitions of injected data (default 1024)'
  1060. }
  1061. }
  1062. };
  1063.  
  1064. var zhTW = {
  1065. about: '關於',
  1066. issues: '反饋',
  1067. setting: '設置',
  1068. hotkeys: '快捷鍵',
  1069. donate: '讚賞',
  1070. debugHelper: {
  1071. viewVueDebugHelperObject: 'vueDebugHelper對象',
  1072. componentsStatistics: '當前存活組件統計',
  1073. destroyStatisticsSort: '已銷毀組件統計',
  1074. componentsSummaryStatisticsSort: '全部組件混合統計',
  1075. getDestroyByDuration: '組件存活時間信息',
  1076. clearAll: '清空統計信息',
  1077. dd: '數據注入(dd)',
  1078. undd: '取消數據注入(undd)',
  1079. ddPrompt: {
  1080. filter: '組件過濾器(如果為空,則對所有組件注入)',
  1081. count: '指定注入數據的重複次數(默認1024)'
  1082. }
  1083. }
  1084. };
  1085.  
  1086. const messages = {
  1087. 'zh-CN': zhCN,
  1088. zh: zhCN,
  1089. 'zh-HK': zhTW,
  1090. 'zh-TW': zhTW,
  1091. 'en-US': enUS,
  1092. en: enUS,
  1093. };
  1094.  
  1095. /*!
  1096. * @name i18n.js
  1097. * @description vue-debug-helper的国际化配置
  1098. * @version 0.0.1
  1099. * @author xxxily
  1100. * @date 2022/04/26 14:56
  1101. * @github https://github.com/xxxily
  1102. */
  1103.  
  1104. const i18n = new I18n({
  1105. defaultLanguage: 'en',
  1106. /* 指定当前要是使用的语言环境,默认无需指定,会自动读取 */
  1107. // locale: 'zh-TW',
  1108. languages: messages
  1109. });
  1110.  
  1111. /*!
  1112. * @name index.js
  1113. * @description hookJs JS AOP切面编程辅助库
  1114. * @version 0.0.1
  1115. * @author Blaze
  1116. * @date 2020/10/22 17:40
  1117. * @github https://github.com/xxxily
  1118. */
  1119.  
  1120. const win = typeof window === 'undefined' ? global : window;
  1121. const toStr = Function.prototype.call.bind(Object.prototype.toString);
  1122. /* 特殊场景,如果把Boolean也hook了,很容易导致调用溢出,所以是需要使用原生Boolean */
  1123. const toBoolean = Boolean.originMethod ? Boolean.originMethod : Boolean;
  1124. const util = {
  1125. toStr,
  1126. isObj: obj => toStr(obj) === '[object Object]',
  1127. /* 判断是否为引用类型,用于更宽泛的场景 */
  1128. isRef: obj => typeof obj === 'object',
  1129. isReg: obj => toStr(obj) === '[object RegExp]',
  1130. isFn: obj => obj instanceof Function,
  1131. isAsyncFn: fn => toStr(fn) === '[object AsyncFunction]',
  1132. isPromise: obj => toStr(obj) === '[object Promise]',
  1133. firstUpperCase: str => str.replace(/^\S/, s => s.toUpperCase()),
  1134. toArr: arg => Array.from(Array.isArray(arg) ? arg : [arg]),
  1135.  
  1136. debug: {
  1137. log () {
  1138. let log = win.console.log;
  1139. /* 如果log也被hook了,则使用未被hook前的log函数 */
  1140. if (log.originMethod) { log = log.originMethod; }
  1141. if (win._debugMode_) {
  1142. log.apply(win.console, arguments);
  1143. }
  1144. }
  1145. },
  1146. /* 获取包含自身、继承、可枚举、不可枚举的键名 */
  1147. getAllKeys (obj) {
  1148. const tmpArr = [];
  1149. for (const key in obj) { tmpArr.push(key); }
  1150. const allKeys = Array.from(new Set(tmpArr.concat(Reflect.ownKeys(obj))));
  1151. return allKeys
  1152. }
  1153. };
  1154.  
  1155. class HookJs {
  1156. constructor (useProxy) {
  1157. this.useProxy = useProxy || false;
  1158. this.hookPropertiesKeyName = '_hookProperties' + Date.now();
  1159. }
  1160.  
  1161. hookJsPro () {
  1162. return new HookJs(true)
  1163. }
  1164.  
  1165. _addHook (hookMethod, fn, type, classHook) {
  1166. const hookKeyName = type + 'Hooks';
  1167. const hookMethodProperties = hookMethod[this.hookPropertiesKeyName];
  1168. if (!hookMethodProperties[hookKeyName]) {
  1169. hookMethodProperties[hookKeyName] = [];
  1170. }
  1171.  
  1172. /* 注册(储存)要被调用的hook函数,同时防止重复注册 */
  1173. let hasSameHook = false;
  1174. for (let i = 0; i < hookMethodProperties[hookKeyName].length; i++) {
  1175. if (fn === hookMethodProperties[hookKeyName][i]) {
  1176. hasSameHook = true;
  1177. break
  1178. }
  1179. }
  1180.  
  1181. if (!hasSameHook) {
  1182. fn.classHook = classHook || false;
  1183. hookMethodProperties[hookKeyName].push(fn);
  1184. }
  1185. }
  1186.  
  1187. _runHooks (parentObj, methodName, originMethod, hookMethod, target, ctx, args, classHook, hookPropertiesKeyName) {
  1188. const hookMethodProperties = hookMethod[hookPropertiesKeyName];
  1189. const beforeHooks = hookMethodProperties.beforeHooks || [];
  1190. const afterHooks = hookMethodProperties.afterHooks || [];
  1191. const errorHooks = hookMethodProperties.errorHooks || [];
  1192. const hangUpHooks = hookMethodProperties.hangUpHooks || [];
  1193. const replaceHooks = hookMethodProperties.replaceHooks || [];
  1194. const execInfo = {
  1195. result: null,
  1196. error: null,
  1197. args: args,
  1198. type: ''
  1199. };
  1200.  
  1201. function runHooks (hooks, type) {
  1202. let hookResult = null;
  1203. execInfo.type = type || '';
  1204. if (Array.isArray(hooks)) {
  1205. hooks.forEach(fn => {
  1206. if (util.isFn(fn) && classHook === fn.classHook) {
  1207. hookResult = fn(args, parentObj, methodName, originMethod, execInfo, ctx);
  1208. }
  1209. });
  1210. }
  1211. return hookResult
  1212. }
  1213.  
  1214. const runTarget = (function () {
  1215. if (classHook) {
  1216. return function () {
  1217. // eslint-disable-next-line new-cap
  1218. return new target(...args)
  1219. }
  1220. } else {
  1221. return function () {
  1222. return target.apply(ctx, args)
  1223. }
  1224. }
  1225. })();
  1226.  
  1227. const beforeHooksResult = runHooks(beforeHooks, 'before');
  1228. /* 支持终止后续调用的指令 */
  1229. if (beforeHooksResult && beforeHooksResult === 'STOP-INVOKE') {
  1230. return beforeHooksResult
  1231. }
  1232.  
  1233. if (hangUpHooks.length || replaceHooks.length) {
  1234. /**
  1235. * 当存在hangUpHooks或replaceHooks的时候是不会触发原来函数的
  1236. * 本质上来说hangUpHooks和replaceHooks是一样的,只是外部的定义描述不一致和分类不一致而已
  1237. */
  1238. runHooks(hangUpHooks, 'hangUp');
  1239. runHooks(replaceHooks, 'replace');
  1240. } else {
  1241. if (errorHooks.length) {
  1242. try {
  1243. execInfo.result = runTarget();
  1244. } catch (err) {
  1245. execInfo.error = err;
  1246. const errorHooksResult = runHooks(errorHooks, 'error');
  1247. /* 支持执行错误后不抛出异常的指令 */
  1248. if (errorHooksResult && errorHooksResult === 'SKIP-ERROR') ; else {
  1249. throw err
  1250. }
  1251. }
  1252. } else {
  1253. execInfo.result = runTarget();
  1254. }
  1255. }
  1256.  
  1257. /**
  1258. * 执行afterHooks,如果返回的是Promise,理论上应该进行进一步的细分处理
  1259. * 但添加细分处理逻辑后发现性能下降得比较厉害,且容易出现各种异常,所以决定不在hook里处理Promise情况
  1260. * 下面是原Promise处理逻辑,添加后会导致以下网站卡死或无法访问:
  1261. * wenku.baidu.com
  1262. * https://pubs.rsc.org/en/content/articlelanding/2021/sc/d1sc01881g#!divAbstract
  1263. * https://www.elsevier.com/connect/coronavirus-information-center
  1264. */
  1265. // if (execInfo.result && execInfo.result.then && util.isPromise(execInfo.result)) {
  1266. // execInfo.result.then(function (data) {
  1267. // execInfo.result = data
  1268. // runHooks(afterHooks, 'after')
  1269. // return Promise.resolve.apply(ctx, arguments)
  1270. // }).catch(function (err) {
  1271. // execInfo.error = err
  1272. // runHooks(errorHooks, 'error')
  1273. // return Promise.reject.apply(ctx, arguments)
  1274. // })
  1275. // }
  1276.  
  1277. runHooks(afterHooks, 'after');
  1278.  
  1279. return execInfo.result
  1280. }
  1281.  
  1282. _proxyMethodcGenerator (parentObj, methodName, originMethod, classHook, context, proxyHandler) {
  1283. const t = this;
  1284. const useProxy = t.useProxy;
  1285. let hookMethod = null;
  1286.  
  1287. /* 存在缓存则使用缓存的hookMethod */
  1288. if (t.isHook(originMethod)) {
  1289. hookMethod = originMethod;
  1290. } else if (originMethod[t.hookPropertiesKeyName] && t.isHook(originMethod[t.hookPropertiesKeyName].hookMethod)) {
  1291. hookMethod = originMethod[t.hookPropertiesKeyName].hookMethod;
  1292. }
  1293.  
  1294. if (hookMethod) {
  1295. if (!hookMethod[t.hookPropertiesKeyName].isHook) {
  1296. /* 重新标注被hook状态 */
  1297. hookMethod[t.hookPropertiesKeyName].isHook = true;
  1298. util.debug.log(`[hook method] ${util.toStr(parentObj)} ${methodName}`);
  1299. }
  1300. return hookMethod
  1301. }
  1302.  
  1303. /* 使用Proxy模式进行hook可以获得更多特性,但性能也会稍差一些 */
  1304. if (useProxy && Proxy) {
  1305. /* 注意:使用Proxy代理,hookMethod和originMethod将共用同一对象 */
  1306. const handler = { ...proxyHandler };
  1307.  
  1308. /* 下面的写法确定了proxyHandler是无法覆盖construct和apply操作的 */
  1309. if (classHook) {
  1310. handler.construct = function (target, args, newTarget) {
  1311. context = context || this;
  1312. return t._runHooks(parentObj, methodName, originMethod, hookMethod, target, context, args, true, t.hookPropertiesKeyName)
  1313. };
  1314. } else {
  1315. handler.apply = function (target, ctx, args) {
  1316. ctx = context || ctx;
  1317. return t._runHooks(parentObj, methodName, originMethod, hookMethod, target, ctx, args, false, t.hookPropertiesKeyName)
  1318. };
  1319. }
  1320.  
  1321. hookMethod = new Proxy(originMethod, handler);
  1322. } else {
  1323. hookMethod = function () {
  1324. /**
  1325. * 注意此处不能通过 context = context || this
  1326. * 然后通过把context当ctx传递过去
  1327. * 这将导致ctx引用错误
  1328. */
  1329. const ctx = context || this;
  1330. return t._runHooks(parentObj, methodName, originMethod, hookMethod, originMethod, ctx, arguments, classHook, t.hookPropertiesKeyName)
  1331. };
  1332.  
  1333. /* 确保子对象和原型链跟originMethod保持一致 */
  1334. const keys = Reflect.ownKeys(originMethod);
  1335. keys.forEach(keyName => {
  1336. try {
  1337. Object.defineProperty(hookMethod, keyName, {
  1338. get: function () {
  1339. return originMethod[keyName]
  1340. },
  1341. set: function (val) {
  1342. originMethod[keyName] = val;
  1343. }
  1344. });
  1345. } catch (err) {
  1346. // 设置defineProperty的时候出现异常,可能导致hookMethod部分功能确实,也可能不受影响
  1347. util.debug.log(`[proxyMethodcGenerator] hookMethod defineProperty abnormal. hookMethod:${methodName}, definePropertyName:${keyName}`, err);
  1348. }
  1349. });
  1350. hookMethod.prototype = originMethod.prototype;
  1351. }
  1352.  
  1353. const hookMethodProperties = hookMethod[t.hookPropertiesKeyName] = {};
  1354.  
  1355. hookMethodProperties.originMethod = originMethod;
  1356. hookMethodProperties.hookMethod = hookMethod;
  1357. hookMethodProperties.isHook = true;
  1358. hookMethodProperties.classHook = classHook;
  1359.  
  1360. util.debug.log(`[hook method] ${util.toStr(parentObj)} ${methodName}`);
  1361.  
  1362. return hookMethod
  1363. }
  1364.  
  1365. _getObjKeysByRule (obj, rule) {
  1366. let excludeRule = null;
  1367. let result = rule;
  1368.  
  1369. if (util.isObj(rule) && rule.include) {
  1370. excludeRule = rule.exclude;
  1371. rule = rule.include;
  1372. result = rule;
  1373. }
  1374.  
  1375. /**
  1376. * for in、Object.keys与Reflect.ownKeys的区别见:
  1377. * https://es6.ruanyifeng.com/#docs/object#%E5%B1%9E%E6%80%A7%E7%9A%84%E9%81%8D%E5%8E%86
  1378. */
  1379. if (rule === '*') {
  1380. result = Object.keys(obj);
  1381. } else if (rule === '**') {
  1382. result = Reflect.ownKeys(obj);
  1383. } else if (rule === '***') {
  1384. result = util.getAllKeys(obj);
  1385. } else if (util.isReg(rule)) {
  1386. result = util.getAllKeys(obj).filter(keyName => rule.test(keyName));
  1387. }
  1388.  
  1389. /* 如果存在排除规则,则需要进行排除 */
  1390. if (excludeRule) {
  1391. result = Array.isArray(result) ? result : [result];
  1392. if (util.isReg(excludeRule)) {
  1393. result = result.filter(keyName => !excludeRule.test(keyName));
  1394. } else if (Array.isArray(excludeRule)) {
  1395. result = result.filter(keyName => !excludeRule.includes(keyName));
  1396. } else {
  1397. result = result.filter(keyName => excludeRule !== keyName);
  1398. }
  1399. }
  1400.  
  1401. return util.toArr(result)
  1402. }
  1403.  
  1404. /**
  1405. * 判断某个函数是否已经被hook
  1406. * @param fn {Function} -必选 要判断的函数
  1407. * @returns {boolean}
  1408. */
  1409. isHook (fn) {
  1410. if (!fn || !fn[this.hookPropertiesKeyName]) {
  1411. return false
  1412. }
  1413. const hookMethodProperties = fn[this.hookPropertiesKeyName];
  1414. return util.isFn(hookMethodProperties.originMethod) && fn !== hookMethodProperties.originMethod
  1415. }
  1416.  
  1417. /**
  1418. * 判断对象下的某个值是否具备hook的条件
  1419. * 注意:具备hook条件和能否直接修改值是两回事,
  1420. * 在进行hook的时候还要检查descriptor.writable是否为false
  1421. * 如果为false则要修改成true才能hook成功
  1422. * @param parentObj
  1423. * @param keyName
  1424. * @returns {boolean}
  1425. */
  1426. isAllowHook (parentObj, keyName) {
  1427. /* 有些对象会设置getter,让读取值的时候就抛错,所以需要try catch 判断能否正常读取属性 */
  1428. try { if (!parentObj[keyName]) return false } catch (e) { return false }
  1429. const descriptor = Object.getOwnPropertyDescriptor(parentObj, keyName);
  1430. return !(descriptor && descriptor.configurable === false)
  1431. }
  1432.  
  1433. /**
  1434. * hook 核心函数
  1435. * @param parentObj {Object} -必选 被hook函数依赖的父对象
  1436. * @param hookMethods {Object|Array|RegExp|string} -必选 被hook函数的函数名或函数名的匹配规则
  1437. * @param fn {Function} -必选 hook之后的回调方法
  1438. * @param type {String} -可选 默认before,指定运行hook函数回调的时机,可选字符串:before、after、replace、error、hangUp
  1439. * @param classHook {Boolean} -可选 默认false,指定是否为针对new(class)操作的hook
  1440. * @param context {Object} -可选 指定运行被hook函数时的上下文对象
  1441. * @param proxyHandler {Object} -可选 仅当用Proxy进行hook时有效,默认使用的是Proxy的apply handler进行hook,如果你有特殊需求也可以配置自己的handler以实现更复杂的功能
  1442. * 附注:不使用Proxy进行hook,可以获得更高性能,但也意味着通用性更差些,对于要hook HTMLElement.prototype、EventTarget.prototype这些对象里面的非实例的函数往往会失败而导致被hook函数执行出错
  1443. * @returns {boolean}
  1444. */
  1445. hook (parentObj, hookMethods, fn, type, classHook, context, proxyHandler) {
  1446. classHook = toBoolean(classHook);
  1447. type = type || 'before';
  1448.  
  1449. if ((!util.isRef(parentObj) && !util.isFn(parentObj)) || !util.isFn(fn) || !hookMethods) {
  1450. return false
  1451. }
  1452.  
  1453. const t = this;
  1454.  
  1455. hookMethods = t._getObjKeysByRule(parentObj, hookMethods);
  1456. hookMethods.forEach(methodName => {
  1457. if (!t.isAllowHook(parentObj, methodName)) {
  1458. util.debug.log(`${util.toStr(parentObj)} [${methodName}] does not support modification`);
  1459. return false
  1460. }
  1461.  
  1462. const descriptor = Object.getOwnPropertyDescriptor(parentObj, methodName);
  1463. if (descriptor && descriptor.writable === false) {
  1464. Object.defineProperty(parentObj, methodName, { writable: true });
  1465. }
  1466.  
  1467. const originMethod = parentObj[methodName];
  1468. let hookMethod = null;
  1469.  
  1470. /* 非函数无法进行hook操作 */
  1471. if (!util.isFn(originMethod)) {
  1472. return false
  1473. }
  1474.  
  1475. hookMethod = t._proxyMethodcGenerator(parentObj, methodName, originMethod, classHook, context, proxyHandler);
  1476.  
  1477. const hookMethodProperties = hookMethod[t.hookPropertiesKeyName];
  1478. if (hookMethodProperties.classHook !== classHook) {
  1479. util.debug.log(`${util.toStr(parentObj)} [${methodName}] Cannot support functions hook and classes hook at the same time `);
  1480. return false
  1481. }
  1482.  
  1483. /* 使用hookMethod接管需要被hook的方法 */
  1484. if (parentObj[methodName] !== hookMethod) {
  1485. parentObj[methodName] = hookMethod;
  1486. }
  1487.  
  1488. t._addHook(hookMethod, fn, type, classHook);
  1489. });
  1490. }
  1491.  
  1492. /* 专门针对new操作的hook,本质上是hook函数的别名,可以少传classHook这个参数,并且明确语义 */
  1493. hookClass (parentObj, hookMethods, fn, type, context, proxyHandler) {
  1494. return this.hook(parentObj, hookMethods, fn, type, true, context, proxyHandler)
  1495. }
  1496.  
  1497. /**
  1498. * 取消对某个函数的hook
  1499. * @param parentObj {Object} -必选 要取消被hook函数依赖的父对象
  1500. * @param hookMethods {Object|Array|RegExp|string} -必选 要取消被hook函数的函数名或函数名的匹配规则
  1501. * @param type {String} -可选 默认before,指定要取消的hook类型,可选字符串:before、after、replace、error、hangUp,如果不指定该选项则取消所有类型下的所有回调
  1502. * @param fn {Function} -必选 取消指定的hook回调函数,如果不指定该选项则取消对应type类型下的所有回调
  1503. * @returns {boolean}
  1504. */
  1505. unHook (parentObj, hookMethods, type, fn) {
  1506. if (!util.isRef(parentObj) || !hookMethods) {
  1507. return false
  1508. }
  1509.  
  1510. const t = this;
  1511. hookMethods = t._getObjKeysByRule(parentObj, hookMethods);
  1512. hookMethods.forEach(methodName => {
  1513. if (!t.isAllowHook(parentObj, methodName)) {
  1514. return false
  1515. }
  1516.  
  1517. const hookMethod = parentObj[methodName];
  1518.  
  1519. if (!t.isHook(hookMethod)) {
  1520. return false
  1521. }
  1522.  
  1523. const hookMethodProperties = hookMethod[t.hookPropertiesKeyName];
  1524. const originMethod = hookMethodProperties.originMethod;
  1525.  
  1526. if (type) {
  1527. const hookKeyName = type + 'Hooks';
  1528. const hooks = hookMethodProperties[hookKeyName] || [];
  1529.  
  1530. if (fn) {
  1531. /* 删除指定类型下的指定hook函数 */
  1532. for (let i = 0; i < hooks.length; i++) {
  1533. if (fn === hooks[i]) {
  1534. hookMethodProperties[hookKeyName].splice(i, 1);
  1535. util.debug.log(`[unHook ${hookKeyName} func] ${util.toStr(parentObj)} ${methodName}`, fn);
  1536. break
  1537. }
  1538. }
  1539. } else {
  1540. /* 删除指定类型下的所有hook函数 */
  1541. if (Array.isArray(hookMethodProperties[hookKeyName])) {
  1542. hookMethodProperties[hookKeyName] = [];
  1543. util.debug.log(`[unHook all ${hookKeyName}] ${util.toStr(parentObj)} ${methodName}`);
  1544. }
  1545. }
  1546. } else {
  1547. /* 彻底还原被hook的函数 */
  1548. if (util.isFn(originMethod)) {
  1549. parentObj[methodName] = originMethod;
  1550. delete parentObj[methodName][t.hookPropertiesKeyName];
  1551.  
  1552. // Object.keys(hookMethod).forEach(keyName => {
  1553. // if (/Hooks$/.test(keyName) && Array.isArray(hookMethod[keyName])) {
  1554. // hookMethod[keyName] = []
  1555. // }
  1556. // })
  1557. //
  1558. // hookMethod.isHook = false
  1559. // parentObj[methodName] = originMethod
  1560. // delete parentObj[methodName].originMethod
  1561. // delete parentObj[methodName].hookMethod
  1562. // delete parentObj[methodName].isHook
  1563. // delete parentObj[methodName].isClassHook
  1564.  
  1565. util.debug.log(`[unHook method] ${util.toStr(parentObj)} ${methodName}`);
  1566. }
  1567. }
  1568. });
  1569. }
  1570.  
  1571. /* 源函数运行前的hook */
  1572. before (obj, hookMethods, fn, classHook, context, proxyHandler) {
  1573. return this.hook(obj, hookMethods, fn, 'before', classHook, context, proxyHandler)
  1574. }
  1575.  
  1576. /* 源函数运行后的hook */
  1577. after (obj, hookMethods, fn, classHook, context, proxyHandler) {
  1578. return this.hook(obj, hookMethods, fn, 'after', classHook, context, proxyHandler)
  1579. }
  1580.  
  1581. /* 替换掉要hook的函数,不再运行源函数,换成运行其他逻辑 */
  1582. replace (obj, hookMethods, fn, classHook, context, proxyHandler) {
  1583. return this.hook(obj, hookMethods, fn, 'replace', classHook, context, proxyHandler)
  1584. }
  1585.  
  1586. /* 源函数运行出错时的hook */
  1587. error (obj, hookMethods, fn, classHook, context, proxyHandler) {
  1588. return this.hook(obj, hookMethods, fn, 'error', classHook, context, proxyHandler)
  1589. }
  1590.  
  1591. /* 底层实现逻辑与replace一样,都是替换掉要hook的函数,不再运行源函数,只不过是为了明确语义,将源函数挂起不再执行,原则上也不再执行其他逻辑,如果要执行其他逻辑请使用replace hook */
  1592. hangUp (obj, hookMethods, fn, classHook, context, proxyHandler) {
  1593. return this.hook(obj, hookMethods, fn, 'hangUp', classHook, context, proxyHandler)
  1594. }
  1595. }
  1596.  
  1597. var hookJs = new HookJs();
  1598.  
  1599. /*!
  1600. * @name vueHooks.js
  1601. * @description 对Vue对象进行的hooks封装
  1602. * @version 0.0.1
  1603. * @author xxxily
  1604. * @date 2022/05/10 14:11
  1605. * @github https://github.com/xxxily
  1606. */
  1607.  
  1608. const hookJsPro = hookJs.hookJsPro();
  1609.  
  1610. let vueComponentHook = null;
  1611.  
  1612. const vueHooks = {
  1613. /* 对extend进行hooks封装,以便进行组件阻断 */
  1614. blockComponents (Vue, config) {
  1615. hookJsPro.before(Vue, 'extend', (args, parentObj, methodName, originMethod, execInfo, ctx) => {
  1616. const extendOpts = args[0];
  1617. // extendOpts.__file && debug.info(`[extendOptions:${extendOpts.name}]`, extendOpts.__file)
  1618.  
  1619. const hasBlockFilter = config.blockFilters && config.blockFilters.length;
  1620. if (hasBlockFilter && extendOpts.name && filtersMatch(config.blockFilters, extendOpts.name)) {
  1621. debug.info(`[block component]: name: ${extendOpts.name}`);
  1622. return 'STOP-INVOKE'
  1623. }
  1624. });
  1625.  
  1626. /* 禁止因为阻断组件的创建而导致的错误提示输出,减少不必要的信息噪音 */
  1627. hookJsPro.before(Vue.util, 'warn', (args) => {
  1628. const msg = args[0];
  1629. if (msg.includes('STOP-INVOKE')) {
  1630. return 'STOP-INVOKE'
  1631. }
  1632. });
  1633. },
  1634.  
  1635. hackVueComponent (Vue, callback) {
  1636. if (vueComponentHook) {
  1637. debug.warn('[Vue.component] you have already hacked');
  1638. return
  1639. }
  1640.  
  1641. vueComponentHook = (args, parentObj, methodName, originMethod, execInfo, ctx) => {
  1642. const name = args[0];
  1643. const opts = args[1];
  1644.  
  1645. if (callback instanceof Function) {
  1646. callback.apply(Vue, args);
  1647. } else {
  1648. /* 打印全局组件的注册信息 */
  1649. if (Vue.options.components[name]) {
  1650. debug.warn(`[Vue.component][REPEAT][old-cid:${Vue.options.components[name].cid}]`, name, opts);
  1651. } else {
  1652. debug.log('[Vue.component]', name, opts);
  1653. }
  1654. }
  1655. };
  1656.  
  1657. hookJsPro.before(Vue, 'component', vueComponentHook);
  1658. debug.log(i18n.t('debugHelper.hackVueComponent.hack') + ' (success)');
  1659. },
  1660.  
  1661. unHackVueComponent (Vue) {
  1662. if (vueComponentHook) {
  1663. hookJsPro.unHook(Vue, 'component', 'before', vueComponentHook);
  1664. vueComponentHook = null;
  1665. debug.log(i18n.t('debugHelper.hackVueComponent.unhack') + ' (success)');
  1666. } else {
  1667. debug.warn('[Vue.component] you have not hack vue component, not need to unhack');
  1668. }
  1669. },
  1670.  
  1671. hackVueUpdate () {
  1672. //
  1673. }
  1674. };
  1675.  
  1676. /*
  1677. * author: wendux
  1678. * email: 824783146@qq.com
  1679. * source code: https://github.com/wendux/Ajax-hook
  1680. */
  1681.  
  1682. // Save original XMLHttpRequest as _rxhr
  1683. var realXhr = '_rxhr';
  1684.  
  1685. var events = ['load', 'loadend', 'timeout', 'error', 'readystatechange', 'abort'];
  1686.  
  1687. function configEvent (event, xhrProxy) {
  1688. var e = {};
  1689. for (var attr in event) e[attr] = event[attr];
  1690. // xhrProxy instead
  1691. e.target = e.currentTarget = xhrProxy;
  1692. return e
  1693. }
  1694.  
  1695. function hook (proxy, win) {
  1696. win = win || window;
  1697. // Avoid double hookAjax
  1698. win[realXhr] = win[realXhr] || win.XMLHttpRequest;
  1699.  
  1700. win.XMLHttpRequest = function () {
  1701. // We shouldn't hookAjax XMLHttpRequest.prototype because we can't
  1702. // guarantee that all attributes are on the prototype。
  1703. // Instead, hooking XMLHttpRequest instance can avoid this problem.
  1704.  
  1705. var xhr = new win[realXhr]();
  1706.  
  1707. // Generate all callbacks(eg. onload) are enumerable (not undefined).
  1708. for (var i = 0; i < events.length; ++i) {
  1709. if (xhr[events[i]] === undefined) xhr[events[i]] = null;
  1710. }
  1711.  
  1712. for (var attr in xhr) {
  1713. var type = '';
  1714. try {
  1715. type = typeof xhr[attr]; // May cause exception on some browser
  1716. } catch (e) {
  1717. }
  1718. if (type === 'function') {
  1719. // hookAjax methods of xhr, such as `open`、`send` ...
  1720. this[attr] = hookFunction(attr);
  1721. } else {
  1722. Object.defineProperty(this, attr, {
  1723. get: getterFactory(attr),
  1724. set: setterFactory(attr),
  1725. enumerable: true
  1726. });
  1727. }
  1728. }
  1729. var that = this;
  1730. xhr.getProxy = function () {
  1731. return that
  1732. };
  1733. this.xhr = xhr;
  1734. };
  1735.  
  1736. Object.assign(win.XMLHttpRequest, { UNSENT: 0, OPENED: 1, HEADERS_RECEIVED: 2, LOADING: 3, DONE: 4 });
  1737.  
  1738. // Generate getter for attributes of xhr
  1739. function getterFactory (attr) {
  1740. return function () {
  1741. var v = this.hasOwnProperty(attr + '_') ? this[attr + '_'] : this.xhr[attr];
  1742. var attrGetterHook = (proxy[attr] || {}).getter;
  1743. return attrGetterHook && attrGetterHook(v, this) || v
  1744. }
  1745. }
  1746.  
  1747. // Generate setter for attributes of xhr; by this we have an opportunity
  1748. // to hookAjax event callbacks (eg: `onload`) of xhr;
  1749. function setterFactory (attr) {
  1750. return function (v) {
  1751. var xhr = this.xhr;
  1752. var that = this;
  1753. var hook = proxy[attr];
  1754. // hookAjax event callbacks such as `onload`、`onreadystatechange`...
  1755. if (attr.substring(0, 2) === 'on') {
  1756. that[attr + '_'] = v;
  1757. xhr[attr] = function (e) {
  1758. e = configEvent(e, that);
  1759. var ret = proxy[attr] && proxy[attr].call(that, xhr, e);
  1760. ret || v.call(that, e);
  1761. };
  1762. } else {
  1763. // If the attribute isn't writable, generate proxy attribute
  1764. var attrSetterHook = (hook || {}).setter;
  1765. v = attrSetterHook && attrSetterHook(v, that) || v;
  1766. this[attr + '_'] = v;
  1767. try {
  1768. // Not all attributes of xhr are writable(setter may undefined).
  1769. xhr[attr] = v;
  1770. } catch (e) {
  1771. }
  1772. }
  1773. }
  1774. }
  1775.  
  1776. // Hook methods of xhr.
  1777. function hookFunction (fun) {
  1778. return function () {
  1779. var args = [].slice.call(arguments);
  1780. if (proxy[fun]) {
  1781. var ret = proxy[fun].call(this, args, this.xhr);
  1782. // If the proxy return value exists, return it directly,
  1783. // otherwise call the function of xhr.
  1784. if (ret) return ret
  1785. }
  1786. return this.xhr[fun].apply(this.xhr, args)
  1787. }
  1788. }
  1789.  
  1790. // Return the real XMLHttpRequest
  1791. return win[realXhr]
  1792. }
  1793.  
  1794. function unHook (win) {
  1795. win = win || window;
  1796. if (win[realXhr]) win.XMLHttpRequest = win[realXhr];
  1797. win[realXhr] = undefined;
  1798. }
  1799.  
  1800. /*
  1801. * author: wendux
  1802. * email: 824783146@qq.com
  1803. * source code: https://github.com/wendux/Ajax-hook
  1804. */
  1805.  
  1806. var eventLoad = events[0];
  1807. var eventLoadEnd = events[1];
  1808. var eventTimeout = events[2];
  1809. var eventError = events[3];
  1810. var eventReadyStateChange = events[4];
  1811. var eventAbort = events[5];
  1812.  
  1813. var singleton;
  1814. var prototype = 'prototype';
  1815.  
  1816. function proxy (proxy, win) {
  1817. if (singleton) {
  1818. throw new Error('Proxy already exists')
  1819. }
  1820.  
  1821. singleton = new Proxy$1(proxy, win);
  1822. return singleton
  1823. }
  1824.  
  1825. function unProxy (win) {
  1826. singleton = null;
  1827. unHook(win);
  1828. }
  1829.  
  1830. function trim (str) {
  1831. return str.replace(/^\s+|\s+$/g, '')
  1832. }
  1833.  
  1834. function getEventTarget (xhr) {
  1835. return xhr.watcher || (xhr.watcher = document.createElement('a'))
  1836. }
  1837.  
  1838. function triggerListener (xhr, name) {
  1839. var xhrProxy = xhr.getProxy();
  1840. var callback = 'on' + name + '_';
  1841. var event = configEvent({ type: name }, xhrProxy);
  1842. xhrProxy[callback] && xhrProxy[callback](event);
  1843. var evt;
  1844. if (typeof (Event) === 'function') {
  1845. evt = new Event(name, { bubbles: false });
  1846. } else {
  1847. // https://stackoverflow.com/questions/27176983/dispatchevent-not-working-in-ie11
  1848. evt = document.createEvent('Event');
  1849. evt.initEvent(name, false, true);
  1850. }
  1851. getEventTarget(xhr).dispatchEvent(evt);
  1852. }
  1853.  
  1854. function Handler (xhr) {
  1855. this.xhr = xhr;
  1856. this.xhrProxy = xhr.getProxy();
  1857. }
  1858.  
  1859. Handler[prototype] = Object.create({
  1860. resolve: function resolve (response) {
  1861. var xhrProxy = this.xhrProxy;
  1862. var xhr = this.xhr;
  1863. xhrProxy.readyState = 4;
  1864. xhr.resHeader = response.headers;
  1865. xhrProxy.response = xhrProxy.responseText = response.response;
  1866. xhrProxy.statusText = response.statusText;
  1867. xhrProxy.status = response.status;
  1868. triggerListener(xhr, eventReadyStateChange);
  1869. triggerListener(xhr, eventLoad);
  1870. triggerListener(xhr, eventLoadEnd);
  1871. },
  1872. reject: function reject (error) {
  1873. this.xhrProxy.status = 0;
  1874. triggerListener(this.xhr, error.type);
  1875. triggerListener(this.xhr, eventLoadEnd);
  1876. }
  1877. });
  1878.  
  1879. function makeHandler (next) {
  1880. function sub (xhr) {
  1881. Handler.call(this, xhr);
  1882. }
  1883.  
  1884. sub[prototype] = Object.create(Handler[prototype]);
  1885. sub[prototype].next = next;
  1886. return sub
  1887. }
  1888.  
  1889. var RequestHandler = makeHandler(function (rq) {
  1890. var xhr = this.xhr;
  1891. rq = rq || xhr.config;
  1892. xhr.withCredentials = rq.withCredentials;
  1893. xhr.open(rq.method, rq.url, rq.async !== false, rq.user, rq.password);
  1894. for (var key in rq.headers) {
  1895. xhr.setRequestHeader(key, rq.headers[key]);
  1896. }
  1897. xhr.send(rq.body);
  1898. });
  1899.  
  1900. var ResponseHandler = makeHandler(function (response) {
  1901. this.resolve(response);
  1902. });
  1903.  
  1904. var ErrorHandler = makeHandler(function (error) {
  1905. this.reject(error);
  1906. });
  1907.  
  1908. function Proxy$1 (proxy, win) {
  1909. var onRequest = proxy.onRequest;
  1910. var onResponse = proxy.onResponse;
  1911. var onError = proxy.onError;
  1912.  
  1913. function handleResponse (xhr, xhrProxy) {
  1914. var handler = new ResponseHandler(xhr);
  1915. var ret = {
  1916. response: xhrProxy.response,
  1917. status: xhrProxy.status,
  1918. statusText: xhrProxy.statusText,
  1919. config: xhr.config,
  1920. headers: xhr.resHeader || xhr.getAllResponseHeaders().split('\r\n').reduce(function (ob, str) {
  1921. if (str === '') return ob
  1922. var m = str.split(':');
  1923. ob[m.shift()] = trim(m.join(':'));
  1924. return ob
  1925. }, {})
  1926. };
  1927. if (!onResponse) return handler.resolve(ret)
  1928. onResponse(ret, handler);
  1929. }
  1930.  
  1931. function onerror (xhr, xhrProxy, error, errorType) {
  1932. var handler = new ErrorHandler(xhr);
  1933. error = { config: xhr.config, error: error, type: errorType };
  1934. if (onError) {
  1935. onError(error, handler);
  1936. } else {
  1937. handler.next(error);
  1938. }
  1939. }
  1940.  
  1941. function preventXhrProxyCallback () {
  1942. return true
  1943. }
  1944.  
  1945. function errorCallback (errorType) {
  1946. return function (xhr, e) {
  1947. onerror(xhr, this, e, errorType);
  1948. return true
  1949. }
  1950. }
  1951.  
  1952. function stateChangeCallback (xhr, xhrProxy) {
  1953. if (xhr.readyState === 4 && xhr.status !== 0) {
  1954. handleResponse(xhr, xhrProxy);
  1955. } else if (xhr.readyState !== 4) {
  1956. triggerListener(xhr, eventReadyStateChange);
  1957. }
  1958. return true
  1959. }
  1960.  
  1961. return hook({
  1962. onload: preventXhrProxyCallback,
  1963. onloadend: preventXhrProxyCallback,
  1964. onerror: errorCallback(eventError),
  1965. ontimeout: errorCallback(eventTimeout),
  1966. onabort: errorCallback(eventAbort),
  1967. onreadystatechange: function (xhr) {
  1968. return stateChangeCallback(xhr, this)
  1969. },
  1970. open: function open (args, xhr) {
  1971. var _this = this;
  1972. var config = xhr.config = { headers: {} };
  1973. config.method = args[0];
  1974. config.url = args[1];
  1975. config.async = args[2];
  1976. config.user = args[3];
  1977. config.password = args[4];
  1978. config.xhr = xhr;
  1979. var evName = 'on' + eventReadyStateChange;
  1980. if (!xhr[evName]) {
  1981. xhr[evName] = function () {
  1982. return stateChangeCallback(xhr, _this)
  1983. };
  1984. }
  1985.  
  1986. // 如果有请求拦截器,则在调用onRequest后再打开链接。因为onRequest最佳调用时机是在send前,
  1987. // 所以我们在send拦截函数中再手动调用open,因此返回true阻止xhr.open调用。
  1988. //
  1989. // 如果没有请求拦截器,则不用阻断xhr.open调用
  1990. if (onRequest) return true
  1991. },
  1992. send: function (args, xhr) {
  1993. var config = xhr.config;
  1994. config.withCredentials = xhr.withCredentials;
  1995. config.body = args[0];
  1996. if (onRequest) {
  1997. // In 'onRequest', we may call XHR's event handler, such as `xhr.onload`.
  1998. // However, XHR's event handler may not be set until xhr.send is called in
  1999. // the user's code, so we use `setTimeout` to avoid this situation
  2000. var req = function () {
  2001. onRequest(config, new RequestHandler(xhr));
  2002. };
  2003. config.async === false ? req() : setTimeout(req);
  2004. return true
  2005. }
  2006. },
  2007. setRequestHeader: function (args, xhr) {
  2008. // Collect request headers
  2009. xhr.config.headers[args[0].toLowerCase()] = args[1];
  2010. return true
  2011. },
  2012. addEventListener: function (args, xhr) {
  2013. var _this = this;
  2014. if (events.indexOf(args[0]) !== -1) {
  2015. var handler = args[1];
  2016. getEventTarget(xhr).addEventListener(args[0], function (e) {
  2017. var event = configEvent(e, _this);
  2018. event.type = args[0];
  2019. event.isTrusted = true;
  2020. handler.call(_this, event);
  2021. });
  2022. return true
  2023. }
  2024. },
  2025. getAllResponseHeaders: function (_, xhr) {
  2026. var headers = xhr.resHeader;
  2027. if (headers) {
  2028. var header = '';
  2029. for (var key in headers) {
  2030. header += key + ': ' + headers[key] + '\r\n';
  2031. }
  2032. return header
  2033. }
  2034. },
  2035. getResponseHeader: function (args, xhr) {
  2036. var headers = xhr.resHeader;
  2037. if (headers) {
  2038. return headers[(args[0] || '').toLowerCase()]
  2039. }
  2040. }
  2041. }, win)
  2042. }
  2043.  
  2044. /*!
  2045. * @name cacheStore.js
  2046. * @description 接口请求缓存存储管理模块
  2047. * @version 0.0.1
  2048. * @author xxxily
  2049. * @date 2022/05/13 09:36
  2050. * @github https://github.com/xxxily
  2051. */
  2052. const localforage = window.localforage;
  2053. const CryptoJS = window.CryptoJS;
  2054.  
  2055. function md5 (str) {
  2056. return CryptoJS.MD5(str).toString()
  2057. }
  2058.  
  2059. function createHash (config) {
  2060. if (config._hash_) {
  2061. return config._hash_
  2062. }
  2063.  
  2064. let url = config.url || '';
  2065.  
  2066. /**
  2067. * 如果检测到url使用了时间戳来防止缓存,则进行替换,进行缓存
  2068. * TODO
  2069. * 注意,这很可能会导致误伤,例如url上的时间戳并不是用来清理缓存的,而是某个时间点的参数
  2070. */
  2071. if (/=\d{13}/.test(url)) {
  2072. url = url.replace(/=\d{13}/, '=cache');
  2073. }
  2074.  
  2075. let hashStr = url;
  2076.  
  2077. if (config.method.toUpperCase() === 'POST') {
  2078. hashStr += JSON.stringify(config.data) + JSON.stringify(config.body);
  2079. }
  2080.  
  2081. const hash = md5(hashStr);
  2082. config._hash_ = hash;
  2083.  
  2084. return hash
  2085. }
  2086.  
  2087. class CacheStore {
  2088. constructor (opts = {
  2089. localforageConfig: {}
  2090. }) {
  2091. this.store = localforage.createInstance(Object.assign({
  2092. name: 'vue-debug-helper-cache',
  2093. storeName: 'ajax-cache'
  2094. }, opts.localforageConfig));
  2095.  
  2096. /* 外部应该使用同样的hash生成方法,否则无法正常命中缓存规则 */
  2097. this.createHash = createHash;
  2098. }
  2099.  
  2100. async getCache (config) {
  2101. const hash = createHash(config);
  2102. const data = await this.store.getItem(hash);
  2103. return data
  2104. }
  2105.  
  2106. async setCache (response, filter) {
  2107. const headers = response.headers || {};
  2108. if (String(headers['content-type']).includes(filter || 'application/json')) {
  2109. const hash = createHash(response.config);
  2110. await this.store.setItem(hash, response.response);
  2111.  
  2112. /* 设置缓存的时候顺便更新缓存相关的基础信息,注意,该信息并不能100%被同步到本地 */
  2113. await this.updateCacheInfo(response.config);
  2114.  
  2115. debug.log(`[cacheStore setCache] ${response.config.url}`, response);
  2116. }
  2117. }
  2118.  
  2119. async getCacheInfo (config) {
  2120. const hash = config ? this.createHash(config) : '';
  2121. if (this._cacheInfo_) {
  2122. return hash ? this._cacheInfo_[hash] : this._cacheInfo_
  2123. }
  2124.  
  2125. /* 在没将cacheInfo加载到内存前,只能单线程获取cacheInfo,防止多线程获取cacheInfo时出现问题 */
  2126. if (this._takeingCacheInfo_) {
  2127. const getCacheInfoHanderList = this._getCacheInfoHanderList_ || [];
  2128. const P = new Promise((resolve, reject) => {
  2129. getCacheInfoHanderList.push({
  2130. resolve,
  2131. config
  2132. });
  2133. });
  2134. this._getCacheInfoHanderList_ = getCacheInfoHanderList;
  2135. return P
  2136. }
  2137.  
  2138. this._takeingCacheInfo_ = true;
  2139. const cacheInfo = await this.store.getItem('ajaxCacheInfo') || {};
  2140. this._cacheInfo_ = cacheInfo;
  2141.  
  2142. delete this._takeingCacheInfo_;
  2143. if (this._getCacheInfoHanderList_) {
  2144. this._getCacheInfoHanderList_.forEach(async (handler) => {
  2145. handler.resolve(await this.getCacheInfo(handler.config));
  2146. });
  2147. delete this._getCacheInfoHanderList_;
  2148. }
  2149.  
  2150. return hash ? cacheInfo[hash] : cacheInfo
  2151. }
  2152.  
  2153. async updateCacheInfo (config) {
  2154. const cacheInfo = await this.getCacheInfo();
  2155.  
  2156. if (config) {
  2157. const hash = createHash(config);
  2158. if (hash && config) {
  2159. const info = {
  2160. url: config.url,
  2161. cacheTime: Date.now()
  2162. };
  2163.  
  2164. // 增加或更新缓存的基本信息
  2165. cacheInfo[hash] = info;
  2166. }
  2167. }
  2168.  
  2169. if (!this._updateCacheInfoIsWorking_) {
  2170. this._updateCacheInfoIsWorking_ = true;
  2171. await this.store.setItem('ajaxCacheInfo', cacheInfo);
  2172. this._updateCacheInfoIsWorking_ = false;
  2173. }
  2174. }
  2175.  
  2176. /**
  2177. * 清理已过期的缓存数据
  2178. * @param {number} expires 指定过期时间,单位:毫秒
  2179. * @returns
  2180. */
  2181. async cleanCache (expires) {
  2182. if (!expires) {
  2183. return
  2184. }
  2185.  
  2186. const cacheInfo = await this.getCacheInfo();
  2187. const cacheInfoKeys = Object.keys(cacheInfo);
  2188. const now = Date.now();
  2189.  
  2190. const storeKeys = await this.store.keys();
  2191.  
  2192. const needKeepKeys = cacheInfoKeys.filter(key => now - cacheInfo[key].cacheTime < expires);
  2193. needKeepKeys.push('ajaxCacheInfo');
  2194.  
  2195. const clearResult = [];
  2196.  
  2197. /* 清理不需要的数据 */
  2198. storeKeys.forEach((key) => {
  2199. if (!needKeepKeys.includes(key)) {
  2200. clearResult.push(this._cacheInfo_[key] || key);
  2201.  
  2202. this.store.removeItem(key);
  2203. delete this._cacheInfo_[key];
  2204. }
  2205. });
  2206.  
  2207. /* 更新缓存信息 */
  2208. if (clearResult.length) {
  2209. await this.updateCacheInfo();
  2210. debug.log('[cacheStore cleanCache] clearResult:', clearResult);
  2211. }
  2212. }
  2213.  
  2214. async get (key) {
  2215. const data = await this.store.getItem(key);
  2216. debug.log('[cacheStore]', key, data);
  2217. return data
  2218. }
  2219.  
  2220. async set (key, data) {
  2221. await this.store.setItem(key, data);
  2222. debug.log('[cacheStore]', key, data);
  2223. }
  2224.  
  2225. async remove (key) {
  2226. await this.store.removeItem(key);
  2227. debug.log('[cacheStore]', key);
  2228. }
  2229.  
  2230. async clear () {
  2231. await this.store.clear();
  2232. debug.log('[cacheStore] clear');
  2233. }
  2234.  
  2235. async keys () {
  2236. const keys = await this.store.keys();
  2237. debug.log('[cacheStore] keys', keys);
  2238. return keys
  2239. }
  2240. }
  2241.  
  2242. var cacheStore = new CacheStore();
  2243.  
  2244. /*!
  2245. * @name ajaxHooks.js
  2246. * @description 底层请求hook
  2247. * @version 0.0.1
  2248. * @author xxxily
  2249. * @date 2022/05/12 17:46
  2250. * @github https://github.com/xxxily
  2251. */
  2252.  
  2253. /**
  2254. * 判断是否符合进行缓存控制操作的条件
  2255. * @param {object} config
  2256. * @returns {boolean}
  2257. */
  2258. function useCache (config) {
  2259. const ajaxCache = helper.config.ajaxCache;
  2260. if (ajaxCache.enabled) {
  2261. return filtersMatch(ajaxCache.filters, config.url)
  2262. } else {
  2263. return false
  2264. }
  2265. }
  2266.  
  2267. let ajaxHooksWin = window;
  2268.  
  2269. const ajaxHooks = {
  2270. hook (win = ajaxHooksWin) {
  2271. proxy({
  2272. onRequest: async (config, handler) => {
  2273. let hitCache = false;
  2274.  
  2275. if (useCache(config)) {
  2276. const cacheInfo = await cacheStore.getCacheInfo(config);
  2277. const cache = await cacheStore.getCache(config);
  2278.  
  2279. if (cache && cacheInfo) {
  2280. const isExpires = Date.now() - cacheInfo.cacheTime > helper.config.ajaxCache.expires;
  2281.  
  2282. if (!isExpires) {
  2283. handler.resolve({
  2284. config: config,
  2285. status: 200,
  2286. headers: { 'content-type': 'application/json' },
  2287. response: cache
  2288. });
  2289.  
  2290. hitCache = true;
  2291. }
  2292. }
  2293. }
  2294.  
  2295. if (hitCache) {
  2296. debug.warn(`[ajaxHooks] use cache:${config.method} ${config.url}`, config);
  2297. } else {
  2298. handler.next(config);
  2299. }
  2300. },
  2301.  
  2302. onError: (err, handler) => {
  2303. handler.next(err);
  2304. },
  2305.  
  2306. onResponse: async (response, handler) => {
  2307. if (useCache(response.config)) {
  2308. // 加入缓存
  2309. cacheStore.setCache(response, 'application/json');
  2310. }
  2311.  
  2312. handler.next(response);
  2313. }
  2314. }, win);
  2315. },
  2316.  
  2317. unHook (win = ajaxHooksWin) {
  2318. unProxy(win);
  2319. },
  2320.  
  2321. init (win) {
  2322. ajaxHooksWin = win;
  2323.  
  2324. if (helper.config.ajaxCache.enabled) {
  2325. ajaxHooks.hook(ajaxHooksWin);
  2326. }
  2327.  
  2328. /* 定时清除接口的缓存数据,防止不断堆积 */
  2329. setTimeout(() => {
  2330. cacheStore.cleanCache(helper.config.ajaxCache.expires);
  2331. }, 1000 * 10);
  2332. }
  2333. };
  2334.  
  2335. /*!
  2336. * @name performanceObserver.js
  2337. * @description 进行性能监测结果的打印
  2338. * @version 0.0.1
  2339. * @author xxxily
  2340. * @date 2022/05/11 10:39
  2341. * @github https://github.com/xxxily
  2342. */
  2343.  
  2344. const performanceObserver = {
  2345. observer: null,
  2346. init () {
  2347. if (typeof PerformanceObserver === 'undefined') {
  2348. debug.log(i18n.t('debugHelper.performanceObserver.notSupport'));
  2349. return false
  2350. }
  2351.  
  2352. if (performanceObserver.observer && performanceObserver.observer.disconnect) {
  2353. performanceObserver.observer.disconnect();
  2354. }
  2355.  
  2356. /* 不进行性能观察 */
  2357. if (!helper.config.performanceObserver.enabled) {
  2358. performanceObserver.observer = null;
  2359. return false
  2360. }
  2361.  
  2362. // https://developer.mozilla.org/zh-CN/docs/Web/API/PerformanceObserver/observe
  2363. performanceObserver.observer = new PerformanceObserver(function (list, observer) {
  2364. if (!helper.config.performanceObserver.enabled) {
  2365. return
  2366. }
  2367.  
  2368. const entries = list.getEntries();
  2369. for (let i = 0; i < entries.length; i++) {
  2370. const entry = entries[i];
  2371. debug.info(`[performanceObserver ${entry.entryType}]`, entry);
  2372. }
  2373. });
  2374.  
  2375. // https://runebook.dev/zh-CN/docs/dom/performanceentry/entrytype
  2376. performanceObserver.observer.observe({ entryTypes: helper.config.performanceObserver.entryTypes });
  2377. }
  2378. };
  2379.  
  2380. /*!
  2381. * @name inspect.js
  2382. * @description vue组件审查模块
  2383. * @version 0.0.1
  2384. * @author xxxily
  2385. * @date 2022/05/10 18:25
  2386. * @github https://github.com/xxxily
  2387. */
  2388.  
  2389. const $ = window.$;
  2390. let currentComponent = null;
  2391.  
  2392. const inspect = {
  2393. findComponentsByElement (el) {
  2394. let result = null;
  2395. let deep = 0;
  2396. let parent = el;
  2397. while (parent) {
  2398. if (deep >= 50) {
  2399. break
  2400. }
  2401.  
  2402. if (parent.__vue__) {
  2403. result = parent;
  2404. break
  2405. }
  2406.  
  2407. deep++;
  2408. parent = parent.parentNode;
  2409. }
  2410.  
  2411. return result
  2412. },
  2413.  
  2414. initContextMenu () {
  2415. if (this._hasInitContextMenu_) {
  2416. return
  2417. }
  2418.  
  2419. $.contextMenu({
  2420. selector: 'body.vue-debug-helper-inspect-mode',
  2421. zIndex: 2147483647,
  2422. build: function ($trigger, e) {
  2423. const vueComponent = currentComponent ? currentComponent.__vue__ : null;
  2424.  
  2425. let componentMenu = {};
  2426. if (vueComponent) {
  2427. componentMenu = {
  2428. consoleComponent: {
  2429. name: `查看组件:${vueComponent._componentName}`,
  2430. icon: 'fa-eye',
  2431. callback: function (key, options) {
  2432. debug.log(`[vueComponent] ${vueComponent._componentTag}`, vueComponent);
  2433. }
  2434. },
  2435. consoleComponentData: {
  2436. name: `查看组件数据:${vueComponent._componentName}`,
  2437. icon: 'fa-eye',
  2438. callback: function (key, options) {
  2439. debug.log(`[vueComponentData] ${vueComponent._componentTag}`, vueComponent.$data);
  2440. }
  2441. },
  2442. consoleComponentProps: {
  2443. name: `查看组件props${vueComponent._componentName}`,
  2444. icon: 'fa-eye',
  2445. callback: function (key, options) {
  2446. debug.log(`[vueComponentProps] ${vueComponent._componentTag}`, vueComponent.$props);
  2447. }
  2448. },
  2449. consoleComponentChain: {
  2450. name: `查看组件调用链:${vueComponent._componentName}`,
  2451. icon: 'fa-eye',
  2452. callback: function (key, options) {
  2453. debug.log(`[vueComponentMethods] ${vueComponent._componentTag}`, vueComponent._componentChain);
  2454. }
  2455. },
  2456. componentMenuSeparator: '---------'
  2457. };
  2458. }
  2459.  
  2460. const commonMenu = {
  2461. componentsStatistics: {
  2462. name: i18n.t('debugHelper.componentsStatistics'),
  2463. icon: 'fa-thin fa-info-circle',
  2464. callback: functionCall.componentsStatistics
  2465. },
  2466. componentsSummaryStatisticsSort: {
  2467. name: i18n.t('debugHelper.componentsSummaryStatisticsSort'),
  2468. icon: 'fa-thin fa-info-circle',
  2469. callback: functionCall.componentsSummaryStatisticsSort
  2470. },
  2471. destroyStatisticsSort: {
  2472. name: i18n.t('debugHelper.destroyStatisticsSort'),
  2473. icon: 'fa-regular fa-trash',
  2474. callback: functionCall.destroyStatisticsSort
  2475. },
  2476. clearAll: {
  2477. name: i18n.t('debugHelper.clearAll'),
  2478. icon: 'fa-regular fa-close',
  2479. callback: functionCall.clearAll
  2480. },
  2481. statisticsSeparator: '---------',
  2482. printLifeCycleInfo: {
  2483. name: i18n.t('debugHelper.printLifeCycleInfo'),
  2484. icon: 'fa-regular fa-life-ring',
  2485. callback: functionCall.printLifeCycleInfo
  2486. },
  2487. notPrintLifeCycleInfo: {
  2488. name: i18n.t('debugHelper.notPrintLifeCycleInfo'),
  2489. icon: 'fa-regular fa-life-ring',
  2490. callback: functionCall.notPrintLifeCycleInfo
  2491. },
  2492. findComponents: {
  2493. name: i18n.t('debugHelper.findComponents'),
  2494. icon: 'fa-regular fa-search',
  2495. callback: functionCall.findComponents
  2496. },
  2497. blockComponents: {
  2498. name: i18n.t('debugHelper.blockComponents'),
  2499. icon: 'fa-regular fa-ban',
  2500. callback: functionCall.blockComponents
  2501. },
  2502. dd: {
  2503. name: i18n.t('debugHelper.dd'),
  2504. icon: 'fa-regular fa-arrows-alt',
  2505. callback: functionCall.dd
  2506. },
  2507. undd: {
  2508. name: i18n.t('debugHelper.undd'),
  2509. icon: 'fa-regular fa-arrows-alt',
  2510. callback: functionCall.undd
  2511. },
  2512. toggleHackVueComponent: {
  2513. name: i18n.t('debugHelper.toggleHackVueComponent'),
  2514. icon: 'fa-regular fa-bug',
  2515. callback: functionCall.toggleHackVueComponent
  2516. },
  2517. toggleInspect: {
  2518. name: i18n.t('debugHelper.toggleInspect'),
  2519. icon: 'fa-regular fa-eye',
  2520. callback: functionCall.toggleInspect
  2521. },
  2522. togglePerformanceObserver: {
  2523. name: i18n.t('debugHelper.togglePerformanceObserver'),
  2524. icon: 'fa-regular fa-paint-brush',
  2525. callback: functionCall.togglePerformanceObserver
  2526. },
  2527. commonEndSeparator: '---------'
  2528. };
  2529.  
  2530. const menu = {
  2531. callback: function (key, options) {
  2532. debug.log(`[contextMenu] ${key}`);
  2533. },
  2534. items: {
  2535. refresh: {
  2536. name: '刷新页面',
  2537. icon: 'fa-refresh',
  2538. callback: function (key, options) {
  2539. window.location.reload();
  2540. }
  2541. },
  2542. sep0: '---------',
  2543. ...componentMenu,
  2544. ...commonMenu,
  2545. // edit: {
  2546. // name: '',
  2547. // icon: 'edit',
  2548. // // superseeds "global" callback
  2549. // callback: function (itemKey, opt, e) {
  2550. // var m = 'edit was clicked'
  2551. // window.console && console.log(m)
  2552. // }
  2553. // },
  2554. // cut: { name: 'Cut', icon: 'cut' },
  2555. // copy: { name: 'Copy', icon: 'copy' },
  2556. // paste: { name: 'Paste', icon: 'paste' },
  2557. // delete: { name: 'Delete', icon: 'delete' },
  2558. quit: {
  2559. name: 'Quit',
  2560. icon:
  2561. function ($element, key, item) {
  2562. return 'context-menu-icon context-menu-icon-quit'
  2563. }
  2564. }
  2565. }
  2566. };
  2567.  
  2568. return menu
  2569. }
  2570. });
  2571.  
  2572. this._hasInitContextMenu_ = true;
  2573. },
  2574.  
  2575. setOverlay (el) {
  2576. let overlay = document.querySelector('#vue-debugger-overlay');
  2577. if (!overlay) {
  2578. overlay = document.createElement('div');
  2579. overlay.id = 'vue-debugger-overlay';
  2580. overlay.style.position = 'fixed';
  2581. overlay.style.backgroundColor = 'rgba(65, 184, 131, 0.35)';
  2582. overlay.style.zIndex = 2147483647;
  2583. overlay.style.pointerEvents = 'none';
  2584. document.body.appendChild(overlay);
  2585. }
  2586.  
  2587. const rect = el.getBoundingClientRect();
  2588.  
  2589. overlay.style.width = rect.width + 'px';
  2590. overlay.style.height = rect.height + 'px';
  2591. overlay.style.left = rect.x + 'px';
  2592. overlay.style.top = rect.y + 'px';
  2593. overlay.style.display = 'block';
  2594.  
  2595. // overlay.parentElement.addEventListener('contextmenu', function (e) {
  2596. // debug.log('overlay contextmenu')
  2597. // e.preventDefault()
  2598. // e.stopPropagation()
  2599. // }, true)
  2600.  
  2601. $(document.body).addClass('vue-debug-helper-inspect-mode');
  2602.  
  2603. inspect.initContextMenu();
  2604. // console.log(el, rect, el.__vue__._componentTag)
  2605. },
  2606.  
  2607. clearOverlay () {
  2608. $(document.body).removeClass('vue-debug-helper-inspect-mode');
  2609. const overlay = document.querySelector('#vue-debugger-overlay');
  2610. if (overlay) {
  2611. overlay.style.display = 'none';
  2612. }
  2613. },
  2614.  
  2615. init (Vue) {
  2616. document.body.addEventListener('mouseover', (event) => {
  2617. if (!helper.config.inspect.enabled) {
  2618. return
  2619. }
  2620.  
  2621. const componentEl = inspect.findComponentsByElement(event.target);
  2622.  
  2623. if (componentEl) {
  2624. currentComponent = componentEl;
  2625. inspect.setOverlay(componentEl);
  2626. } else {
  2627. currentComponent = null;
  2628. }
  2629. });
  2630. }
  2631. };
  2632.  
  2633. /**
  2634. * 元素监听器
  2635. * @param selector -必选
  2636. * @param fn -必选,元素存在时的回调
  2637. * @param shadowRoot -可选 指定监听某个shadowRoot下面的DOM元素
  2638. * 参考:https://javascript.ruanyifeng.com/dom/mutationobserver.html
  2639. */
  2640. function ready (selector, fn, shadowRoot) {
  2641. const win = window;
  2642. const docRoot = shadowRoot || win.document.documentElement;
  2643. if (!docRoot) return false
  2644. const MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
  2645. const listeners = docRoot._MutationListeners || [];
  2646.  
  2647. function $ready (selector, fn) {
  2648. // 储存选择器和回调函数
  2649. listeners.push({
  2650. selector: selector,
  2651. fn: fn
  2652. });
  2653.  
  2654. /* 增加监听对象 */
  2655. if (!docRoot._MutationListeners || !docRoot._MutationObserver) {
  2656. docRoot._MutationListeners = listeners;
  2657. docRoot._MutationObserver = new MutationObserver(() => {
  2658. for (let i = 0; i < docRoot._MutationListeners.length; i++) {
  2659. const item = docRoot._MutationListeners[i];
  2660. check(item.selector, item.fn);
  2661. }
  2662. });
  2663.  
  2664. docRoot._MutationObserver.observe(docRoot, {
  2665. childList: true,
  2666. subtree: true
  2667. });
  2668. }
  2669.  
  2670. // 检查节点是否已经在DOM中
  2671. check(selector, fn);
  2672. }
  2673.  
  2674. function check (selector, fn) {
  2675. const elements = docRoot.querySelectorAll(selector);
  2676. for (let i = 0; i < elements.length; i++) {
  2677. const element = elements[i];
  2678. element._MutationReadyList_ = element._MutationReadyList_ || [];
  2679. if (!element._MutationReadyList_.includes(fn)) {
  2680. element._MutationReadyList_.push(fn);
  2681. fn.call(element, element);
  2682. }
  2683. }
  2684. }
  2685.  
  2686. const selectorArr = Array.isArray(selector) ? selector : [selector];
  2687. selectorArr.forEach(selector => $ready(selector, fn));
  2688. }
  2689.  
  2690. /*!
  2691. * @name functionCall.js
  2692. * @description 统一的提供外部功能调用管理模块
  2693. * @version 0.0.1
  2694. * @author xxxily
  2695. * @date 2022/04/27 17:42
  2696. * @github https://github.com/xxxily
  2697. */
  2698.  
  2699. const functionCall = {
  2700. viewVueDebugHelperObject () {
  2701. debug.log(i18n.t('debugHelper.viewVueDebugHelperObject'), helper);
  2702. },
  2703. componentsStatistics () {
  2704. const result = helper.methods.componentsStatistics();
  2705. let total = 0;
  2706.  
  2707. /* 提供友好的可视化展示方式 */
  2708. console.table && console.table(result.map(item => {
  2709. total += item.componentInstance.length;
  2710. return {
  2711. componentName: item.componentName,
  2712. count: item.componentInstance.length
  2713. }
  2714. }));
  2715.  
  2716. debug.log(`${i18n.t('debugHelper.componentsStatistics')} (total:${total})`, result);
  2717. },
  2718. destroyStatisticsSort () {
  2719. const result = helper.methods.destroyStatisticsSort();
  2720. let total = 0;
  2721.  
  2722. /* 提供友好的可视化展示方式 */
  2723. console.table && console.table(result.map(item => {
  2724. const durationList = item.destroyList.map(item => item.duration);
  2725. const maxDuration = Math.max(...durationList);
  2726. const minDuration = Math.min(...durationList);
  2727. const durationRange = maxDuration - minDuration;
  2728. total += item.destroyList.length;
  2729.  
  2730. return {
  2731. componentName: item.componentName,
  2732. count: item.destroyList.length,
  2733. avgDuration: durationList.reduce((pre, cur) => pre + cur, 0) / durationList.length,
  2734. maxDuration,
  2735. minDuration,
  2736. durationRange,
  2737. durationRangePercent: (1000 - minDuration) / durationRange
  2738. }
  2739. }));
  2740.  
  2741. debug.log(`${i18n.t('debugHelper.destroyStatisticsSort')} (total:${total})`, result);
  2742. },
  2743. componentsSummaryStatisticsSort () {
  2744. const result = helper.methods.componentsSummaryStatisticsSort();
  2745. let total = 0;
  2746.  
  2747. /* 提供友好的可视化展示方式 */
  2748. console.table && console.table(result.map(item => {
  2749. total += item.componentsSummary.length;
  2750. return {
  2751. componentName: item.componentName,
  2752. count: item.componentsSummary.length
  2753. }
  2754. }));
  2755.  
  2756. debug.log(`${i18n.t('debugHelper.componentsSummaryStatisticsSort')} (total:${total})`, result);
  2757. },
  2758. getDestroyByDuration () {
  2759. const destroyInfo = helper.methods.getDestroyByDuration();
  2760. console.table && console.table(destroyInfo.destroyList);
  2761. debug.log(i18n.t('debugHelper.getDestroyByDuration'), destroyInfo);
  2762. },
  2763. clearAll () {
  2764. helper.methods.clearAll();
  2765. debug.log(i18n.t('debugHelper.clearAll'));
  2766. },
  2767.  
  2768. printLifeCycleInfo () {
  2769. const lifecycleFilters = window.prompt(i18n.t('debugHelper.printLifeCycleInfoPrompt.lifecycleFilters'), helper.config.lifecycle.filters.join(','));
  2770. const componentFilters = window.prompt(i18n.t('debugHelper.printLifeCycleInfoPrompt.componentFilters'), helper.config.lifecycle.componentFilters.join(','));
  2771.  
  2772. if (lifecycleFilters !== null && componentFilters !== null) {
  2773. debug.log(i18n.t('debugHelper.printLifeCycleInfo'));
  2774. helper.methods.printLifeCycleInfo(lifecycleFilters, componentFilters);
  2775. }
  2776. },
  2777.  
  2778. notPrintLifeCycleInfo () {
  2779. debug.log(i18n.t('debugHelper.notPrintLifeCycleInfo'));
  2780. helper.methods.notPrintLifeCycleInfo();
  2781. },
  2782.  
  2783. findComponents () {
  2784. const filters = window.prompt(i18n.t('debugHelper.findComponentsPrompt.filters'), helper.config.findComponentsFilters.join(','));
  2785. if (filters !== null) {
  2786. debug.log(i18n.t('debugHelper.findComponents'), helper.methods.findComponents(filters));
  2787. }
  2788. },
  2789.  
  2790. findNotContainElementComponents () {
  2791. debug.log(i18n.t('debugHelper.findNotContainElementComponents'), helper.methods.findNotContainElementComponents());
  2792. },
  2793.  
  2794. blockComponents () {
  2795. const filters = window.prompt(i18n.t('debugHelper.blockComponentsPrompt.filters'), helper.config.blockFilters.join(','));
  2796. if (filters !== null) {
  2797. helper.methods.blockComponents(filters);
  2798. debug.log(i18n.t('debugHelper.blockComponents'), filters);
  2799. }
  2800. },
  2801.  
  2802. dd () {
  2803. const filter = window.prompt(i18n.t('debugHelper.ddPrompt.filter'), helper.config.dd.filters.join(','));
  2804. const count = window.prompt(i18n.t('debugHelper.ddPrompt.count'), helper.config.dd.count);
  2805.  
  2806. if (filter !== null && count !== null) {
  2807. debug.log(i18n.t('debugHelper.dd'));
  2808. helper.methods.dd(filter, Number(count));
  2809. }
  2810. },
  2811.  
  2812. undd () {
  2813. debug.log(i18n.t('debugHelper.undd'));
  2814. helper.methods.undd();
  2815. },
  2816.  
  2817. toggleHackVueComponent () {
  2818. helper.config.hackVueComponent ? vueHooks.unHackVueComponent() : vueHooks.hackVueComponent();
  2819. helper.config.hackVueComponent = !helper.config.hackVueComponent;
  2820. },
  2821.  
  2822. toggleInspect () {
  2823. helper.config.inspect.enabled = !helper.config.inspect.enabled;
  2824. debug.log(`${i18n.t('debugHelper.toggleInspect')} success (${helper.config.inspect.enabled})`);
  2825.  
  2826. if (!helper.config.inspect.enabled) {
  2827. inspect.clearOverlay();
  2828. }
  2829. },
  2830.  
  2831. togglePerformanceObserver () {
  2832. helper.config.performanceObserver.enabled = !helper.config.performanceObserver.enabled;
  2833.  
  2834. if (helper.config.performanceObserver.enabled) {
  2835. let entryTypes = window.prompt(i18n.t('debugHelper.performanceObserverPrompt.entryTypes'), helper.config.performanceObserver.entryTypes.join(','));
  2836. if (entryTypes) {
  2837. const entryTypesArr = toArrFilters(entryTypes);
  2838. const supportEntryTypes = ['element', 'navigation', 'resource', 'mark', 'measure', 'paint', 'longtask'];
  2839.  
  2840. /* 过滤出支持的entryTypes */
  2841. entryTypes = entryTypesArr.filter(item => supportEntryTypes.includes(item));
  2842.  
  2843. if (entryTypes.length !== entryTypesArr.length) {
  2844. debug.warn(`some entryTypes not support, only support: ${supportEntryTypes.join(',')}`);
  2845. }
  2846.  
  2847. helper.config.performanceObserver.entryTypes = entryTypes;
  2848.  
  2849. performanceObserver.init();
  2850. } else {
  2851. alert('entryTypes is empty');
  2852. }
  2853. }
  2854.  
  2855. debug.log(`${i18n.t('debugHelper.togglePerformanceObserver')} success (${helper.config.performanceObserver.enabled})`);
  2856. },
  2857.  
  2858. useAjaxCache () {
  2859. helper.config.ajaxCache.enabled = true;
  2860.  
  2861. const filters = window.prompt(i18n.t('debugHelper.jaxCachePrompt.filters'), helper.config.ajaxCache.filters.join(','));
  2862. const expires = window.prompt(i18n.t('debugHelper.jaxCachePrompt.expires'), helper.config.ajaxCache.expires / 1000 / 60);
  2863.  
  2864. if (filters && expires) {
  2865. helper.config.ajaxCache.filters = toArrFilters(filters);
  2866.  
  2867. if (!isNaN(Number(expires))) {
  2868. helper.config.ajaxCache.expires = Number(expires) * 1000 * 60;
  2869. }
  2870.  
  2871. ajaxHooks.hook();
  2872.  
  2873. debug.log(`${i18n.t('debugHelper.enableAjaxCacheTips')}`);
  2874. }
  2875. },
  2876.  
  2877. disableAjaxCache () {
  2878. helper.config.ajaxCache.enabled = false;
  2879. ajaxHooks.unHook();
  2880. debug.log(`${i18n.t('debugHelper.disableAjaxCacheTips')}`);
  2881. },
  2882.  
  2883. toggleAjaxCache () {
  2884. if (helper.config.ajaxCache.enabled) {
  2885. functionCall.disableAjaxCache();
  2886. } else {
  2887. functionCall.useAjaxCache();
  2888. }
  2889. },
  2890.  
  2891. async clearAjaxCache () {
  2892. await cacheStore.store.clear();
  2893. debug.log(`${i18n.t('debugHelper.clearAjaxCacheTips')}`);
  2894. },
  2895.  
  2896. addMeasureSelectorInterval (selector1, selector2) {
  2897. let result = {};
  2898. if (!functionCall._measureSelectorArr) {
  2899. functionCall._measureSelectorArr = [];
  2900. }
  2901.  
  2902. function measure (element) {
  2903. // debug.log(`[measure] ${i18n.t('debugHelper.measureSelectorInterval')}`, element)
  2904.  
  2905. const selector1 = helper.config.measureSelectorInterval.selector1;
  2906. const selector2 = helper.config.measureSelectorInterval.selector2;
  2907. const selectorArr = [selector1, selector2];
  2908. selectorArr.forEach(selector => {
  2909. if (element.parentElement && element.parentElement.querySelector(selector)) {
  2910. result[selector] = {
  2911. time: Date.now(),
  2912. element: element
  2913. };
  2914.  
  2915. debug.info(`${i18n.t('debugHelper.selectorReadyTips')}: ${selector}`, element);
  2916. }
  2917. });
  2918.  
  2919. if (Object.keys(result).length >= 2) {
  2920. const time = ((result[selector2].time - result[selector1].time) / 1000).toFixed(2);
  2921.  
  2922. debug.info(`[[${selector1}] -> [${selector2}]] time: ${time}s`);
  2923. result = {};
  2924. }
  2925. }
  2926.  
  2927. if (selector1 && selector2) {
  2928. helper.config.measureSelectorInterval.selector1 = selector1;
  2929. helper.config.measureSelectorInterval.selector2 = selector2;
  2930.  
  2931. const selectorArr = [selector1, selector2];
  2932. selectorArr.forEach(selector => {
  2933. if (!functionCall._measureSelectorArr.includes(selector)) {
  2934. // 防止重复注册
  2935. functionCall._measureSelectorArr.push(selector);
  2936.  
  2937. ready(selector, measure);
  2938. }
  2939. });
  2940. } else {
  2941. debug.log('selector is empty, please input selector');
  2942. }
  2943. },
  2944.  
  2945. initMeasureSelectorInterval () {
  2946. const selector1 = helper.config.measureSelectorInterval.selector1;
  2947. const selector2 = helper.config.measureSelectorInterval.selector2;
  2948. if (selector1 && selector2) {
  2949. functionCall.addMeasureSelectorInterval(selector1, selector2);
  2950. debug.log('[measureSelectorInterval] init success');
  2951. }
  2952. },
  2953.  
  2954. measureSelectorInterval () {
  2955. const selector1 = window.prompt(i18n.t('debugHelper.measureSelectorIntervalPrompt.selector1'), helper.config.measureSelectorInterval.selector1);
  2956. const selector2 = window.prompt(i18n.t('debugHelper.measureSelectorIntervalPrompt.selector2'), helper.config.measureSelectorInterval.selector2);
  2957. functionCall.addMeasureSelectorInterval(selector1, selector2);
  2958. }
  2959. };
  2960.  
  2961. /*!
  2962. * @name menu.js
  2963. * @description vue-debug-helper的菜单配置
  2964. * @version 0.0.1
  2965. * @author xxxily
  2966. * @date 2022/04/25 22:28
  2967. * @github https://github.com/xxxily
  2968. */
  2969.  
  2970. function menuRegister (Vue) {
  2971. if (!Vue) {
  2972. monkeyMenu.on('not detected ' + i18n.t('issues'), () => {
  2973. window.GM_openInTab('https://github.com/xxxily/vue-debug-helper/issues', {
  2974. active: true,
  2975. insert: true,
  2976. setParent: true
  2977. });
  2978. });
  2979. return false
  2980. }
  2981.  
  2982. /* 批量注册菜单 */
  2983. Object.keys(functionCall).forEach(key => {
  2984. const text = i18n.t(`debugHelper.${key}`);
  2985. if (text && functionCall[key] instanceof Function) {
  2986. monkeyMenu.on(text, functionCall[key]);
  2987. }
  2988. });
  2989.  
  2990. /* 是否开启vue-devtools的菜单 */
  2991. const devtoolsText = helper.config.devtools ? i18n.t('debugHelper.devtools.disable') : i18n.t('debugHelper.devtools.enabled');
  2992. monkeyMenu.on(devtoolsText, helper.methods.toggleDevtools);
  2993.  
  2994. // monkeyMenu.on('i18n.t('setting')', () => {
  2995. // window.alert('功能开发中,敬请期待...')
  2996. // })
  2997.  
  2998. monkeyMenu.on(i18n.t('issues'), () => {
  2999. window.GM_openInTab('https://github.com/xxxily/vue-debug-helper/issues', {
  3000. active: true,
  3001. insert: true,
  3002. setParent: true
  3003. });
  3004. });
  3005.  
  3006. // monkeyMenu.on(i18n.t('donate'), () => {
  3007. // window.GM_openInTab('https://cdn.jsdelivr.net/gh/xxxily/vue-debug-helper@main/donate.png', {
  3008. // active: true,
  3009. // insert: true,
  3010. // setParent: true
  3011. // })
  3012. // })
  3013. }
  3014.  
  3015. const isff = typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase().indexOf('firefox') > 0 : false;
  3016.  
  3017. // 绑定事件
  3018. function addEvent (object, event, method) {
  3019. if (object.addEventListener) {
  3020. object.addEventListener(event, method, false);
  3021. } else if (object.attachEvent) {
  3022. object.attachEvent(`on${event}`, () => { method(window.event); });
  3023. }
  3024. }
  3025.  
  3026. // 修饰键转换成对应的键码
  3027. function getMods (modifier, key) {
  3028. const mods = key.slice(0, key.length - 1);
  3029. for (let i = 0; i < mods.length; i++) mods[i] = modifier[mods[i].toLowerCase()];
  3030. return mods
  3031. }
  3032.  
  3033. // 处理传的key字符串转换成数组
  3034. function getKeys (key) {
  3035. if (typeof key !== 'string') key = '';
  3036. key = key.replace(/\s/g, ''); // 匹配任何空白字符,包括空格、制表符、换页符等等
  3037. const keys = key.split(','); // 同时设置多个快捷键,以','分割
  3038. let index = keys.lastIndexOf('');
  3039.  
  3040. // 快捷键可能包含',',需特殊处理
  3041. for (; index >= 0;) {
  3042. keys[index - 1] += ',';
  3043. keys.splice(index, 1);
  3044. index = keys.lastIndexOf('');
  3045. }
  3046.  
  3047. return keys
  3048. }
  3049.  
  3050. // 比较修饰键的数组
  3051. function compareArray (a1, a2) {
  3052. const arr1 = a1.length >= a2.length ? a1 : a2;
  3053. const arr2 = a1.length >= a2.length ? a2 : a1;
  3054. let isIndex = true;
  3055.  
  3056. for (let i = 0; i < arr1.length; i++) {
  3057. if (arr2.indexOf(arr1[i]) === -1) isIndex = false;
  3058. }
  3059. return isIndex
  3060. }
  3061.  
  3062. // Special Keys
  3063. const _keyMap = {
  3064. backspace: 8,
  3065. tab: 9,
  3066. clear: 12,
  3067. enter: 13,
  3068. return: 13,
  3069. esc: 27,
  3070. escape: 27,
  3071. space: 32,
  3072. left: 37,
  3073. up: 38,
  3074. right: 39,
  3075. down: 40,
  3076. del: 46,
  3077. delete: 46,
  3078. ins: 45,
  3079. insert: 45,
  3080. home: 36,
  3081. end: 35,
  3082. pageup: 33,
  3083. pagedown: 34,
  3084. capslock: 20,
  3085. num_0: 96,
  3086. num_1: 97,
  3087. num_2: 98,
  3088. num_3: 99,
  3089. num_4: 100,
  3090. num_5: 101,
  3091. num_6: 102,
  3092. num_7: 103,
  3093. num_8: 104,
  3094. num_9: 105,
  3095. num_multiply: 106,
  3096. num_add: 107,
  3097. num_enter: 108,
  3098. num_subtract: 109,
  3099. num_decimal: 110,
  3100. num_divide: 111,
  3101. '⇪': 20,
  3102. ',': 188,
  3103. '.': 190,
  3104. '/': 191,
  3105. '`': 192,
  3106. '-': isff ? 173 : 189,
  3107. '=': isff ? 61 : 187,
  3108. ';': isff ? 59 : 186,
  3109. '\'': 222,
  3110. '[': 219,
  3111. ']': 221,
  3112. '\\': 220
  3113. };
  3114.  
  3115. // Modifier Keys
  3116. const _modifier = {
  3117. // shiftKey
  3118. '⇧': 16,
  3119. shift: 16,
  3120. // altKey
  3121. '⌥': 18,
  3122. alt: 18,
  3123. option: 18,
  3124. // ctrlKey
  3125. '⌃': 17,
  3126. ctrl: 17,
  3127. control: 17,
  3128. // metaKey
  3129. '⌘': 91,
  3130. cmd: 91,
  3131. command: 91
  3132. };
  3133. const modifierMap = {
  3134. 16: 'shiftKey',
  3135. 18: 'altKey',
  3136. 17: 'ctrlKey',
  3137. 91: 'metaKey',
  3138.  
  3139. shiftKey: 16,
  3140. ctrlKey: 17,
  3141. altKey: 18,
  3142. metaKey: 91
  3143. };
  3144. const _mods = {
  3145. 16: false,
  3146. 18: false,
  3147. 17: false,
  3148. 91: false
  3149. };
  3150. const _handlers = {};
  3151.  
  3152. // F1~F12 special key
  3153. for (let k = 1; k < 20; k++) {
  3154. _keyMap[`f${k}`] = 111 + k;
  3155. }
  3156.  
  3157. // https://github.com/jaywcjlove/hotkeys
  3158.  
  3159. let _downKeys = []; // 记录摁下的绑定键
  3160. let winListendFocus = false; // window是否已经监听了focus事件
  3161. let _scope = 'all'; // 默认热键范围
  3162. const elementHasBindEvent = []; // 已绑定事件的节点记录
  3163.  
  3164. // 返回键码
  3165. const code = (x) => _keyMap[x.toLowerCase()] ||
  3166. _modifier[x.toLowerCase()] ||
  3167. x.toUpperCase().charCodeAt(0);
  3168.  
  3169. // 设置获取当前范围(默认为'所有')
  3170. function setScope (scope) {
  3171. _scope = scope || 'all';
  3172. }
  3173. // 获取当前范围
  3174. function getScope () {
  3175. return _scope || 'all'
  3176. }
  3177. // 获取摁下绑定键的键值
  3178. function getPressedKeyCodes () {
  3179. return _downKeys.slice(0)
  3180. }
  3181.  
  3182. // 表单控件控件判断 返回 Boolean
  3183. // hotkey is effective only when filter return true
  3184. function filter (event) {
  3185. const target = event.target || event.srcElement;
  3186. const { tagName } = target;
  3187. let flag = true;
  3188. // ignore: isContentEditable === 'true', <input> and <textarea> when readOnly state is false, <select>
  3189. if (
  3190. target.isContentEditable ||
  3191. ((tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') && !target.readOnly)
  3192. ) {
  3193. flag = false;
  3194. }
  3195. return flag
  3196. }
  3197.  
  3198. // 判断摁下的键是否为某个键,返回true或者false
  3199. function isPressed (keyCode) {
  3200. if (typeof keyCode === 'string') {
  3201. keyCode = code(keyCode); // 转换成键码
  3202. }
  3203. return _downKeys.indexOf(keyCode) !== -1
  3204. }
  3205.  
  3206. // 循环删除handlers中的所有 scope(范围)
  3207. function deleteScope (scope, newScope) {
  3208. let handlers;
  3209. let i;
  3210.  
  3211. // 没有指定scope,获取scope
  3212. if (!scope) scope = getScope();
  3213.  
  3214. for (const key in _handlers) {
  3215. if (Object.prototype.hasOwnProperty.call(_handlers, key)) {
  3216. handlers = _handlers[key];
  3217. for (i = 0; i < handlers.length;) {
  3218. if (handlers[i].scope === scope) handlers.splice(i, 1);
  3219. else i++;
  3220. }
  3221. }
  3222. }
  3223.  
  3224. // 如果scope被删除,将scope重置为all
  3225. if (getScope() === scope) setScope(newScope || 'all');
  3226. }
  3227.  
  3228. // 清除修饰键
  3229. function clearModifier (event) {
  3230. let key = event.keyCode || event.which || event.charCode;
  3231. const i = _downKeys.indexOf(key);
  3232.  
  3233. // 从列表中清除按压过的键
  3234. if (i >= 0) {
  3235. _downKeys.splice(i, 1);
  3236. }
  3237. // 特殊处理 cmmand 键,在 cmmand 组合快捷键 keyup 只执行一次的问题
  3238. if (event.key && event.key.toLowerCase() === 'meta') {
  3239. _downKeys.splice(0, _downKeys.length);
  3240. }
  3241.  
  3242. // 修饰键 shiftKey altKey ctrlKey (command||metaKey) 清除
  3243. if (key === 93 || key === 224) key = 91;
  3244. if (key in _mods) {
  3245. _mods[key] = false;
  3246.  
  3247. // 将修饰键重置为false
  3248. for (const k in _modifier) if (_modifier[k] === key) hotkeys[k] = false;
  3249. }
  3250. }
  3251.  
  3252. function unbind (keysInfo, ...args) {
  3253. // unbind(), unbind all keys
  3254. if (!keysInfo) {
  3255. Object.keys(_handlers).forEach((key) => delete _handlers[key]);
  3256. } else if (Array.isArray(keysInfo)) {
  3257. // support like : unbind([{key: 'ctrl+a', scope: 's1'}, {key: 'ctrl-a', scope: 's2', splitKey: '-'}])
  3258. keysInfo.forEach((info) => {
  3259. if (info.key) eachUnbind(info);
  3260. });
  3261. } else if (typeof keysInfo === 'object') {
  3262. // support like unbind({key: 'ctrl+a, ctrl+b', scope:'abc'})
  3263. if (keysInfo.key) eachUnbind(keysInfo);
  3264. } else if (typeof keysInfo === 'string') {
  3265. // support old method
  3266. // eslint-disable-line
  3267. let [scope, method] = args;
  3268. if (typeof scope === 'function') {
  3269. method = scope;
  3270. scope = '';
  3271. }
  3272. eachUnbind({
  3273. key: keysInfo,
  3274. scope,
  3275. method,
  3276. splitKey: '+'
  3277. });
  3278. }
  3279. }
  3280.  
  3281. // 解除绑定某个范围的快捷键
  3282. const eachUnbind = ({
  3283. key, scope, method, splitKey = '+'
  3284. }) => {
  3285. const multipleKeys = getKeys(key);
  3286. multipleKeys.forEach((originKey) => {
  3287. const unbindKeys = originKey.split(splitKey);
  3288. const len = unbindKeys.length;
  3289. const lastKey = unbindKeys[len - 1];
  3290. const keyCode = lastKey === '*' ? '*' : code(lastKey);
  3291. if (!_handlers[keyCode]) return
  3292. // 判断是否传入范围,没有就获取范围
  3293. if (!scope) scope = getScope();
  3294. const mods = len > 1 ? getMods(_modifier, unbindKeys) : [];
  3295. _handlers[keyCode] = _handlers[keyCode].filter((record) => {
  3296. // 通过函数判断,是否解除绑定,函数相等直接返回
  3297. const isMatchingMethod = method ? record.method === method : true;
  3298. return !(
  3299. isMatchingMethod &&
  3300. record.scope === scope &&
  3301. compareArray(record.mods, mods)
  3302. )
  3303. });
  3304. });
  3305. };
  3306.  
  3307. // 对监听对应快捷键的回调函数进行处理
  3308. function eventHandler (event, handler, scope, element) {
  3309. if (handler.element !== element) {
  3310. return
  3311. }
  3312. let modifiersMatch;
  3313.  
  3314. // 看它是否在当前范围
  3315. if (handler.scope === scope || handler.scope === 'all') {
  3316. // 检查是否匹配修饰符(如果有返回true)
  3317. modifiersMatch = handler.mods.length > 0;
  3318.  
  3319. for (const y in _mods) {
  3320. if (Object.prototype.hasOwnProperty.call(_mods, y)) {
  3321. if (
  3322. (!_mods[y] && handler.mods.indexOf(+y) > -1) ||
  3323. (_mods[y] && handler.mods.indexOf(+y) === -1)
  3324. ) {
  3325. modifiersMatch = false;
  3326. }
  3327. }
  3328. }
  3329.  
  3330. // 调用处理程序,如果是修饰键不做处理
  3331. if (
  3332. (handler.mods.length === 0 &&
  3333. !_mods[16] &&
  3334. !_mods[18] &&
  3335. !_mods[17] &&
  3336. !_mods[91]) ||
  3337. modifiersMatch ||
  3338. handler.shortcut === '*'
  3339. ) {
  3340. if (handler.method(event, handler) === false) {
  3341. if (event.preventDefault) event.preventDefault();
  3342. else event.returnValue = false;
  3343. if (event.stopPropagation) event.stopPropagation();
  3344. if (event.cancelBubble) event.cancelBubble = true;
  3345. }
  3346. }
  3347. }
  3348. }
  3349.  
  3350. // 处理keydown事件
  3351. function dispatch (event, element) {
  3352. const asterisk = _handlers['*'];
  3353. let key = event.keyCode || event.which || event.charCode;
  3354.  
  3355. // 表单控件过滤 默认表单控件不触发快捷键
  3356. if (!hotkeys.filter.call(this, event)) return
  3357.  
  3358. // Gecko(Firefox)的command键值224,在Webkit(Chrome)中保持一致
  3359. // Webkit左右 command 键值不一样
  3360. if (key === 93 || key === 224) key = 91;
  3361.  
  3362. /**
  3363. * Collect bound keys
  3364. * If an Input Method Editor is processing key input and the event is keydown, return 229.
  3365. * https://stackoverflow.com/questions/25043934/is-it-ok-to-ignore-keydown-events-with-keycode-229
  3366. * http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html
  3367. */
  3368. if (_downKeys.indexOf(key) === -1 && key !== 229) _downKeys.push(key);
  3369. /**
  3370. * Jest test cases are required.
  3371. * ===============================
  3372. */
  3373. ['ctrlKey', 'altKey', 'shiftKey', 'metaKey'].forEach((keyName) => {
  3374. const keyNum = modifierMap[keyName];
  3375. if (event[keyName] && _downKeys.indexOf(keyNum) === -1) {
  3376. _downKeys.push(keyNum);
  3377. } else if (!event[keyName] && _downKeys.indexOf(keyNum) > -1) {
  3378. _downKeys.splice(_downKeys.indexOf(keyNum), 1);
  3379. } else if (keyName === 'metaKey' && event[keyName] && _downKeys.length === 3) {
  3380. /**
  3381. * Fix if Command is pressed:
  3382. * ===============================
  3383. */
  3384. if (!(event.ctrlKey || event.shiftKey || event.altKey)) {
  3385. _downKeys = _downKeys.slice(_downKeys.indexOf(keyNum));
  3386. }
  3387. }
  3388. });
  3389. /**
  3390. * -------------------------------
  3391. */
  3392.  
  3393. if (key in _mods) {
  3394. _mods[key] = true;
  3395.  
  3396. // 将特殊字符的key注册到 hotkeys 上
  3397. for (const k in _modifier) {
  3398. if (_modifier[k] === key) hotkeys[k] = true;
  3399. }
  3400.  
  3401. if (!asterisk) return
  3402. }
  3403.  
  3404. // 将 modifierMap 里面的修饰键绑定到 event 中
  3405. for (const e in _mods) {
  3406. if (Object.prototype.hasOwnProperty.call(_mods, e)) {
  3407. _mods[e] = event[modifierMap[e]];
  3408. }
  3409. }
  3410. /**
  3411. * https://github.com/jaywcjlove/hotkeys/pull/129
  3412. * This solves the issue in Firefox on Windows where hotkeys corresponding to special characters would not trigger.
  3413. * An example of this is ctrl+alt+m on a Swedish keyboard which is used to type μ.
  3414. * Browser support: https://caniuse.com/#feat=keyboardevent-getmodifierstate
  3415. */
  3416. if (event.getModifierState && (!(event.altKey && !event.ctrlKey) && event.getModifierState('AltGraph'))) {
  3417. if (_downKeys.indexOf(17) === -1) {
  3418. _downKeys.push(17);
  3419. }
  3420.  
  3421. if (_downKeys.indexOf(18) === -1) {
  3422. _downKeys.push(18);
  3423. }
  3424.  
  3425. _mods[17] = true;
  3426. _mods[18] = true;
  3427. }
  3428.  
  3429. // 获取范围 默认为 `all`
  3430. const scope = getScope();
  3431. // 对任何快捷键都需要做的处理
  3432. if (asterisk) {
  3433. for (let i = 0; i < asterisk.length; i++) {
  3434. if (
  3435. asterisk[i].scope === scope &&
  3436. ((event.type === 'keydown' && asterisk[i].keydown) ||
  3437. (event.type === 'keyup' && asterisk[i].keyup))
  3438. ) {
  3439. eventHandler(event, asterisk[i], scope, element);
  3440. }
  3441. }
  3442. }
  3443. // key 不在 _handlers 中返回
  3444. if (!(key in _handlers)) return
  3445.  
  3446. for (let i = 0; i < _handlers[key].length; i++) {
  3447. if (
  3448. (event.type === 'keydown' && _handlers[key][i].keydown) ||
  3449. (event.type === 'keyup' && _handlers[key][i].keyup)
  3450. ) {
  3451. if (_handlers[key][i].key) {
  3452. const record = _handlers[key][i];
  3453. const { splitKey } = record;
  3454. const keyShortcut = record.key.split(splitKey);
  3455. const _downKeysCurrent = []; // 记录当前按键键值
  3456. for (let a = 0; a < keyShortcut.length; a++) {
  3457. _downKeysCurrent.push(code(keyShortcut[a]));
  3458. }
  3459. if (_downKeysCurrent.sort().join('') === _downKeys.sort().join('')) {
  3460. // 找到处理内容
  3461. eventHandler(event, record, scope, element);
  3462. }
  3463. }
  3464. }
  3465. }
  3466. }
  3467.  
  3468. // 判断 element 是否已经绑定事件
  3469. function isElementBind (element) {
  3470. return elementHasBindEvent.indexOf(element) > -1
  3471. }
  3472.  
  3473. function hotkeys (key, option, method) {
  3474. _downKeys = [];
  3475. const keys = getKeys(key); // 需要处理的快捷键列表
  3476. let mods = [];
  3477. let scope = 'all'; // scope默认为all,所有范围都有效
  3478. let element = document; // 快捷键事件绑定节点
  3479. let i = 0;
  3480. let keyup = false;
  3481. let keydown = true;
  3482. let splitKey = '+';
  3483.  
  3484. // 对为设定范围的判断
  3485. if (method === undefined && typeof option === 'function') {
  3486. method = option;
  3487. }
  3488.  
  3489. if (Object.prototype.toString.call(option) === '[object Object]') {
  3490. if (option.scope) scope = option.scope; // eslint-disable-line
  3491. if (option.element) element = option.element; // eslint-disable-line
  3492. if (option.keyup) keyup = option.keyup; // eslint-disable-line
  3493. if (option.keydown !== undefined) keydown = option.keydown; // eslint-disable-line
  3494. if (typeof option.splitKey === 'string') splitKey = option.splitKey; // eslint-disable-line
  3495. }
  3496.  
  3497. if (typeof option === 'string') scope = option;
  3498.  
  3499. // 对于每个快捷键进行处理
  3500. for (; i < keys.length; i++) {
  3501. key = keys[i].split(splitKey); // 按键列表
  3502. mods = [];
  3503.  
  3504. // 如果是组合快捷键取得组合快捷键
  3505. if (key.length > 1) mods = getMods(_modifier, key);
  3506.  
  3507. // 将非修饰键转化为键码
  3508. key = key[key.length - 1];
  3509. key = key === '*' ? '*' : code(key); // *表示匹配所有快捷键
  3510.  
  3511. // 判断key是否在_handlers中,不在就赋一个空数组
  3512. if (!(key in _handlers)) _handlers[key] = [];
  3513. _handlers[key].push({
  3514. keyup,
  3515. keydown,
  3516. scope,
  3517. mods,
  3518. shortcut: keys[i],
  3519. method,
  3520. key: keys[i],
  3521. splitKey,
  3522. element
  3523. });
  3524. }
  3525. // 在全局document上设置快捷键
  3526. if (typeof element !== 'undefined' && !isElementBind(element) && window) {
  3527. elementHasBindEvent.push(element);
  3528. addEvent(element, 'keydown', (e) => {
  3529. dispatch(e, element);
  3530. });
  3531. if (!winListendFocus) {
  3532. winListendFocus = true;
  3533. addEvent(window, 'focus', () => {
  3534. _downKeys = [];
  3535. });
  3536. }
  3537. addEvent(element, 'keyup', (e) => {
  3538. dispatch(e, element);
  3539. clearModifier(e);
  3540. });
  3541. }
  3542. }
  3543.  
  3544. function trigger (shortcut, scope = 'all') {
  3545. Object.keys(_handlers).forEach((key) => {
  3546. const data = _handlers[key].find((item) => item.scope === scope && item.shortcut === shortcut);
  3547. if (data && data.method) {
  3548. data.method();
  3549. }
  3550. });
  3551. }
  3552.  
  3553. const _api = {
  3554. setScope,
  3555. getScope,
  3556. deleteScope,
  3557. getPressedKeyCodes,
  3558. isPressed,
  3559. filter,
  3560. trigger,
  3561. unbind,
  3562. keyMap: _keyMap,
  3563. modifier: _modifier,
  3564. modifierMap
  3565. };
  3566. for (const a in _api) {
  3567. if (Object.prototype.hasOwnProperty.call(_api, a)) {
  3568. hotkeys[a] = _api[a];
  3569. }
  3570. }
  3571.  
  3572. if (typeof window !== 'undefined') {
  3573. const _hotkeys = window.hotkeys;
  3574. hotkeys.noConflict = (deep) => {
  3575. if (deep && window.hotkeys === hotkeys) {
  3576. window.hotkeys = _hotkeys;
  3577. }
  3578. return hotkeys
  3579. };
  3580. window.hotkeys = hotkeys;
  3581. }
  3582.  
  3583. /*!
  3584. * @name hotKeyRegister.js
  3585. * @description vue-debug-helper的快捷键配置
  3586. * @version 0.0.1
  3587. * @author xxxily
  3588. * @date 2022/04/26 14:37
  3589. * @github https://github.com/xxxily
  3590. */
  3591.  
  3592. function hotKeyRegister () {
  3593. const hotKeyMap = {
  3594. 'shift+alt+i': functionCall.toggleInspect,
  3595. 'shift+alt+a,shift+alt+ctrl+a': functionCall.componentsSummaryStatisticsSort,
  3596. 'shift+alt+l': functionCall.componentsStatistics,
  3597. 'shift+alt+d': functionCall.destroyStatisticsSort,
  3598. 'shift+alt+c': functionCall.clearAll,
  3599. 'shift+alt+e': function (event, handler) {
  3600. if (helper.config.dd.enabled) {
  3601. functionCall.undd();
  3602. } else {
  3603. functionCall.dd();
  3604. }
  3605. }
  3606. };
  3607.  
  3608. Object.keys(hotKeyMap).forEach(key => {
  3609. hotkeys(key, hotKeyMap[key]);
  3610. });
  3611. }
  3612.  
  3613. /*!
  3614. * @name vueDetector.js
  3615. * @description 检测页面是否存在Vue对象
  3616. * @version 0.0.1
  3617. * @author xxxily
  3618. * @date 2022/04/27 11:43
  3619. * @github https://github.com/xxxily
  3620. */
  3621.  
  3622. function mutationDetector (callback, shadowRoot) {
  3623. const win = window;
  3624. const MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
  3625. const docRoot = shadowRoot || win.document.documentElement;
  3626. const maxDetectTries = 1500;
  3627. const timeout = 1000 * 10;
  3628. const startTime = Date.now();
  3629. let detectCount = 0;
  3630. let detectStatus = false;
  3631.  
  3632. if (!MutationObserver) {
  3633. debug.warn('MutationObserver is not supported in this browser');
  3634. return false
  3635. }
  3636.  
  3637. let mObserver = null;
  3638. const mObserverCallback = (mutationsList, observer) => {
  3639. if (detectStatus) {
  3640. return
  3641. }
  3642.  
  3643. /* 超时或检测次数过多,取消监听 */
  3644. if (Date.now() - startTime > timeout || detectCount > maxDetectTries) {
  3645. debug.warn('mutationDetector timeout or detectCount > maxDetectTries, stop detect');
  3646. if (mObserver && mObserver.disconnect) {
  3647. mObserver.disconnect();
  3648. mObserver = null;
  3649. }
  3650. }
  3651.  
  3652. for (let i = 0; i < mutationsList.length; i++) {
  3653. detectCount++;
  3654. const mutation = mutationsList[i];
  3655. if (mutation.target && mutation.target.__vue__) {
  3656. let Vue = Object.getPrototypeOf(mutation.target.__vue__).constructor;
  3657. while (Vue.super) {
  3658. Vue = Vue.super;
  3659. }
  3660.  
  3661. /* 检测成功后销毁观察对象 */
  3662. if (mObserver && mObserver.disconnect) {
  3663. mObserver.disconnect();
  3664. mObserver = null;
  3665. }
  3666.  
  3667. detectStatus = true;
  3668. callback && callback(Vue);
  3669. break
  3670. }
  3671. }
  3672. };
  3673.  
  3674. mObserver = new MutationObserver(mObserverCallback);
  3675. mObserver.observe(docRoot, {
  3676. attributes: true,
  3677. childList: true,
  3678. subtree: true
  3679. });
  3680. }
  3681.  
  3682. /**
  3683. * 检测页面是否存在Vue对象,方法参考:https://github.com/vuejs/devtools/blob/main/packages/shell-chrome/src/detector.js
  3684. * @param {window} win windwod对象
  3685. * @param {function} callback 检测到Vue对象后的回调函数
  3686. */
  3687. function vueDetect (win, callback) {
  3688. let delay = 1000;
  3689. let detectRemainingTries = 10;
  3690. let detectSuc = false;
  3691.  
  3692. // Method 1: MutationObserver detector
  3693. mutationDetector((Vue) => {
  3694. if (!detectSuc) {
  3695. debug.info(`------------- Vue mutation detected (${Vue.version}) -------------`);
  3696. detectSuc = true;
  3697. callback(Vue);
  3698. }
  3699. });
  3700.  
  3701. function runDetect () {
  3702. if (detectSuc) {
  3703. return false
  3704. }
  3705.  
  3706. // Method 2: Check Vue 3
  3707. const vueDetected = !!(win.__VUE__);
  3708. if (vueDetected) {
  3709. debug.info(`------------- Vue global detected (${win.__VUE__.version}) -------------`);
  3710. detectSuc = true;
  3711. callback(win.__VUE__);
  3712. return
  3713. }
  3714.  
  3715. // Method 3: Scan all elements inside document
  3716. const all = document.querySelectorAll('*');
  3717. let el;
  3718. for (let i = 0; i < all.length; i++) {
  3719. if (all[i].__vue__) {
  3720. el = all[i];
  3721. break
  3722. }
  3723. }
  3724. if (el) {
  3725. let Vue = Object.getPrototypeOf(el.__vue__).constructor;
  3726. while (Vue.super) {
  3727. Vue = Vue.super;
  3728. }
  3729. debug.info(`------------- Vue dom detected (${Vue.version}) -------------`);
  3730. detectSuc = true;
  3731. callback(Vue);
  3732. return
  3733. }
  3734.  
  3735. if (detectRemainingTries > 0) {
  3736. detectRemainingTries--;
  3737.  
  3738. if (detectRemainingTries >= 7) {
  3739. setTimeout(() => {
  3740. runDetect();
  3741. }, 40);
  3742. } else {
  3743. setTimeout(() => {
  3744. runDetect();
  3745. }, delay);
  3746. delay *= 5;
  3747. }
  3748. }
  3749. }
  3750.  
  3751. setTimeout(() => {
  3752. runDetect();
  3753. }, 40);
  3754. }
  3755.  
  3756. /*!
  3757. * @name vueConfig.js
  3758. * @description 对Vue的配置进行修改
  3759. * @version 0.0.1
  3760. * @author xxxily
  3761. * @date 2022/05/10 15:15
  3762. * @github https://github.com/xxxily
  3763. */
  3764.  
  3765. function vueConfigInit (Vue, config) {
  3766. if (Vue.config) {
  3767. /* 自动开启Vue的调试模式 */
  3768. if (config.devtools) {
  3769. Vue.config.debug = true;
  3770. Vue.config.devtools = true;
  3771. Vue.config.performance = true;
  3772.  
  3773. setTimeout(() => {
  3774. const devtools = getVueDevtools();
  3775. if (devtools) {
  3776. if (!devtools.enabled) {
  3777. devtools.emit('init', Vue);
  3778. debug.info('vue devtools init emit.');
  3779. }
  3780. } else {
  3781. // debug.info(
  3782. // 'Download the Vue Devtools extension for a better development experience:\n' +
  3783. // 'https://github.com/vuejs/vue-devtools'
  3784. // )
  3785. debug.info('vue devtools check failed.');
  3786. }
  3787. }, 200);
  3788. } else {
  3789. Vue.config.debug = false;
  3790. Vue.config.devtools = false;
  3791. Vue.config.performance = false;
  3792. }
  3793. } else {
  3794. debug.log('Vue.config is not defined');
  3795. }
  3796. }
  3797.  
  3798. /**
  3799. * 判断是否处于Iframe中
  3800. * @returns {boolean}
  3801. */
  3802. function isInIframe () {
  3803. return window !== window.top
  3804. }
  3805.  
  3806. /**
  3807. * 由于tampermonkey对window对象进行了封装,我们实际访问到的window并非页面真实的window
  3808. * 这就导致了如果我们需要将某些对象挂载到页面的window进行调试的时候就无法挂载了
  3809. * 所以必须使用特殊手段才能访问到页面真实的window对象,于是就有了下面这个函数
  3810. * @returns {Promise<void>}
  3811. */
  3812. async function getPageWindow () {
  3813. return new Promise(function (resolve, reject) {
  3814. if (window._pageWindow) {
  3815. return resolve(window._pageWindow)
  3816. }
  3817.  
  3818. const listenEventList = ['load', 'mousemove', 'scroll', 'get-page-window-event'];
  3819.  
  3820. function getWin (event) {
  3821. window._pageWindow = this;
  3822. // debug.log('getPageWindow succeed', event)
  3823. listenEventList.forEach(eventType => {
  3824. window.removeEventListener(eventType, getWin, true);
  3825. });
  3826. resolve(window._pageWindow);
  3827. }
  3828.  
  3829. listenEventList.forEach(eventType => {
  3830. window.addEventListener(eventType, getWin, true);
  3831. });
  3832.  
  3833. /* 自行派发事件以便用最短的时候获得pageWindow对象 */
  3834. window.dispatchEvent(new window.Event('get-page-window-event'));
  3835. })
  3836. }
  3837. // getPageWindow()
  3838.  
  3839. /**
  3840. * 通过同步的方式获取pageWindow
  3841. * 注意同步获取的方式需要将脚本写入head,部分网站由于安全策略会导致写入失败,而无法正常获取
  3842. * @returns {*}
  3843. */
  3844. function getPageWindowSync () {
  3845. if (document._win_) return document._win_
  3846.  
  3847. const head = document.head || document.querySelector('head');
  3848. const script = document.createElement('script');
  3849. script.appendChild(document.createTextNode('document._win_ = window'));
  3850. head.appendChild(script);
  3851.  
  3852. return document._win_
  3853. }
  3854.  
  3855. let registerStatus = 'init';
  3856. window._debugMode_ = true;
  3857.  
  3858. /* 注入相关样式到页面 */
  3859. if (window.GM_getResourceText && window.GM_addStyle) {
  3860. const contextMenuCss = window.GM_getResourceText('contextMenuCss');
  3861. window.GM_addStyle(contextMenuCss);
  3862. }
  3863.  
  3864. function init (win) {
  3865. if (isInIframe()) {
  3866. debug.log('running in iframe, skip init', window.location.href);
  3867. return false
  3868. }
  3869.  
  3870. if (registerStatus === 'initing') {
  3871. return false
  3872. }
  3873.  
  3874. registerStatus = 'initing';
  3875.  
  3876. vueDetect(win, function (Vue) {
  3877. /* 挂载到window上,方便通过控制台调用调试 */
  3878. helper.Vue = Vue;
  3879. win.vueDebugHelper = helper;
  3880.  
  3881. /* 注册阻断Vue组件的功能 */
  3882. vueHooks.blockComponents(Vue, helper.config);
  3883.  
  3884. /* 注册打印全局组件注册信息的功能 */
  3885. if (helper.config.hackVueComponent) {
  3886. vueHooks.hackVueComponent(Vue);
  3887. }
  3888.  
  3889. /* 注册接口拦截功能和接近数据缓存功能 */
  3890. ajaxHooks.init(win);
  3891.  
  3892. /* 注册性能观察的功能 */
  3893. performanceObserver.init();
  3894.  
  3895. /* 注册选择器测量辅助功能 */
  3896. functionCall.initMeasureSelectorInterval();
  3897.  
  3898. /* 对Vue相关配置进行初始化 */
  3899. vueConfigInit(Vue, helper.config);
  3900.  
  3901. mixinRegister(Vue);
  3902. menuRegister(Vue);
  3903. hotKeyRegister();
  3904.  
  3905. inspect.init(Vue);
  3906.  
  3907. debug.log('vue debug helper register success');
  3908. registerStatus = 'success';
  3909. });
  3910.  
  3911. setTimeout(() => {
  3912. if (registerStatus !== 'success') {
  3913. menuRegister(null);
  3914. debug.warn('vue debug helper register failed, please check if vue is loaded .', win.location.href);
  3915. }
  3916. }, 1000 * 10);
  3917. }
  3918.  
  3919. let win$1 = null;
  3920. try {
  3921. win$1 = getPageWindowSync();
  3922. if (win$1) {
  3923. init(win$1);
  3924. debug.log('getPageWindowSync success');
  3925. }
  3926. } catch (e) {
  3927. debug.error('getPageWindowSync failed', e);
  3928. }
  3929. (async function () {
  3930. if (!win$1) {
  3931. win$1 = await getPageWindow();
  3932. init(win$1);
  3933. }
  3934. })();