Vue調試分析助手

Vue組件探測、統計、分析輔助腳本

目前為 2022-04-27 提交的版本,檢視 最新版本

  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.3
  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_addStyle
  21. // @grant GM_setValue
  22. // @grant GM_getValue
  23. // @grant GM_deleteValue
  24. // @grant GM_listValues
  25. // @grant GM_addValueChangeListener
  26. // @grant GM_removeValueChangeListener
  27. // @grant GM_registerMenuCommand
  28. // @grant GM_unregisterMenuCommand
  29. // @grant GM_getTab
  30. // @grant GM_saveTab
  31. // @grant GM_getTabs
  32. // @grant GM_openInTab
  33. // @grant GM_download
  34. // @grant GM_xmlhttpRequest
  35. // @run-at document-start
  36. // @connect 127.0.0.1
  37. // @license GPL
  38. // ==/UserScript==
  39. (function (w) { if (w) { w._vueDebugHelper_ = 'https://github.com/xxxily/vue-debug-helper'; } })();
  40.  
  41. class Debug {
  42. constructor (msg) {
  43. const t = this;
  44. msg = msg || 'debug message:';
  45. t.log = t.createDebugMethod('log', null, msg);
  46. t.error = t.createDebugMethod('error', null, msg);
  47. t.info = t.createDebugMethod('info', null, msg);
  48. t.warn = t.createDebugMethod('warn', null, msg);
  49. }
  50.  
  51. create (msg) {
  52. return new Debug(msg)
  53. }
  54.  
  55. createDebugMethod (name, color, tipsMsg) {
  56. name = name || 'info';
  57.  
  58. const bgColorMap = {
  59. info: '#2274A5',
  60. log: '#95B46A',
  61. error: '#D33F49'
  62. };
  63.  
  64. return function () {
  65. if (!window._debugMode_) {
  66. return false
  67. }
  68.  
  69. const curTime = new Date();
  70. const H = curTime.getHours();
  71. const M = curTime.getMinutes();
  72. const S = curTime.getSeconds();
  73. const msg = tipsMsg || 'debug message:';
  74.  
  75. const arg = Array.from(arguments);
  76. arg.unshift(`color: white; background-color: ${color || bgColorMap[name] || '#95B46A'}`);
  77. arg.unshift(`%c [${H}:${M}:${S}] ${msg} `);
  78. window.console[name].apply(window.console, arg);
  79. }
  80. }
  81.  
  82. isDebugMode () {
  83. return Boolean(window._debugMode_)
  84. }
  85. }
  86.  
  87. var Debug$1 = new Debug();
  88.  
  89. var debug = Debug$1.create('vue-debug-helper message:');
  90.  
  91. /**
  92. * 对特定数据结构的对象进行排序
  93. * @param {object} obj 一个对象,其结构应该类似于:{key1: [], key2: []}
  94. * @param {boolean} reverse -可选 是否反转、降序排列,默认为false
  95. * @param {object} opts -可选 指定数组的配置项,默认为{key: 'key', value: 'value'}
  96. * @param {object} opts.key -可选 指定对象键名的别名,默认为'key'
  97. * @param {object} opts.value -可选 指定对象值的别名,默认为'value'
  98. * @returns {array} 返回一个数组,其结构应该类似于:[{key: key1, value: []}, {key: key2, value: []}]
  99. */
  100. const objSort = (obj, reverse, opts = { key: 'key', value: 'value' }) => {
  101. const arr = [];
  102. for (const key in obj) {
  103. if (Object.prototype.hasOwnProperty.call(obj, key) && Array.isArray(obj[key])) {
  104. const tmpObj = {};
  105. tmpObj[opts.key] = key;
  106. tmpObj[opts.value] = obj[key];
  107. arr.push(tmpObj);
  108. }
  109. }
  110.  
  111. arr.sort((a, b) => {
  112. return a[opts.value].length - b[opts.value].length
  113. });
  114.  
  115. reverse && arr.reverse();
  116. return arr
  117. };
  118.  
  119. /**
  120. * 根据指定长度创建空白数据
  121. * @param {number} size -可选 指str的重复次数,默认为1024次,如果str为单个单字节字符,则意味着默认产生1Mb的空白数据
  122. * @param {string|number|any} str - 可选 指定数据的字符串,默认为'd'
  123. */
  124. function createEmptyData (count = 1024, str = 'd') {
  125. const arr = [];
  126. arr.length = count + 1;
  127. return arr.join(str)
  128. }
  129.  
  130. window.vueDebugHelper = {
  131. /* 存储全部未被销毁的组件对象 */
  132. components: {},
  133. /* 存储全部创建过的组件的概要信息,即使销毁了概要信息依然存在 */
  134. componentsSummary: {},
  135. /* 基于componentsSummary的组件情况统计 */
  136. componentsSummaryStatistics: {},
  137. /* 已销毁的组件概要信息列表 */
  138. destroyList: [],
  139. /* 基于destroyList的组件情况统计 */
  140. destroyStatistics: {},
  141. /* 给组件注入空白数据的配置信息 */
  142. ddConfig: {
  143. enabled: false,
  144. filters: [],
  145. size: 1024
  146. }
  147. };
  148.  
  149. const helper = window.vueDebugHelper;
  150.  
  151. const methods = {
  152. objSort,
  153. createEmptyData,
  154. /* 清除全部helper的全部记录数据,以便重新统计 */
  155. clearAll () {
  156. helper.components = {};
  157. helper.componentsSummary = {};
  158. helper.componentsSummaryStatistics = {};
  159. helper.destroyList = [];
  160. helper.destroyStatistics = {};
  161. },
  162.  
  163. /**
  164. * 对当前的helper.components进行统计与排序
  165. * 如果一直没运行过清理函数,则表示统计页面创建至今依然存活的组件对象
  166. * 运行过清理函数,则表示统计清理后新创建且至今依然存活的组件对象
  167. */
  168. componentsStatistics (reverse = true) {
  169. const tmpObj = {};
  170.  
  171. Object.keys(helper.components).forEach(key => {
  172. const component = helper.components[key];
  173.  
  174. tmpObj[component._componentName]
  175. ? tmpObj[component._componentName].push(component)
  176. : (tmpObj[component._componentName] = [component]);
  177. });
  178.  
  179. return objSort(tmpObj, reverse, {
  180. key: 'componentName',
  181. value: 'componentInstance'
  182. })
  183. },
  184.  
  185. /**
  186. * 对componentsSummaryStatistics进行排序输出,以便可以直观查看组件的创建情况
  187. */
  188. componentsSummaryStatisticsSort (reverse = true) {
  189. return objSort(helper.componentsSummaryStatistics, reverse, {
  190. key: 'componentName',
  191. value: 'componentsSummary'
  192. })
  193. },
  194.  
  195. /**
  196. * 对destroyList进行排序输出,以便可以直观查看组件的销毁情况
  197. */
  198. destroyStatisticsSort (reverse = true) {
  199. return objSort(helper.destroyStatistics, reverse, {
  200. key: 'componentName',
  201. value: 'destroyList'
  202. })
  203. },
  204.  
  205. /**
  206. * 对destroyList进行排序输出,以便可以直观查看组件的销毁情况
  207. */
  208. getDestroyByDuration (duration = 1000) {
  209. const destroyList = helper.destroyList;
  210. const destroyListLength = destroyList.length;
  211. const destroyListDuration = destroyList.map(item => item.duration).sort();
  212. const maxDuration = Math.max(...destroyListDuration);
  213. const minDuration = Math.min(...destroyListDuration);
  214. const avgDuration =
  215. destroyListDuration.reduce((a, b) => a + b, 0) / destroyListLength;
  216. const durationRange = maxDuration - minDuration;
  217. const durationRangePercent = (duration - minDuration) / durationRange;
  218.  
  219. return {
  220. destroyList,
  221. destroyListLength,
  222. destroyListDuration,
  223. maxDuration,
  224. minDuration,
  225. avgDuration,
  226. durationRange,
  227. durationRangePercent
  228. }
  229. },
  230.  
  231. /**
  232. * 获取组件的调用链信息
  233. */
  234. getComponentChain (component, moreDetail = false) {
  235. const result = [];
  236. let current = component;
  237. let deep = 0;
  238.  
  239. while (current && deep < 50) {
  240. deep++;
  241.  
  242. if (moreDetail) {
  243. result.push({
  244. name: current._componentName,
  245. componentsSummary: helper.componentsSummary[current._uid] || null
  246. });
  247. } else {
  248. result.push(current._componentName);
  249. }
  250.  
  251. current = current.$parent;
  252. }
  253.  
  254. if (moreDetail) {
  255. return result
  256. } else {
  257. return result.join(' -> ')
  258. }
  259. },
  260.  
  261. /**
  262. * 给指定组件注入大量空数据,以便观察组件的内存泄露情况
  263. * @param {Array|string} filter -必选 指定组件的名称,如果为空则表示注入所有组件
  264. * @param {number} size -可选 指定注入空数据的大小,单位Kb,默认为1024Kb,即1Mb
  265. * @returns
  266. */
  267. dd (filter, size = 1024) {
  268. filter = filter || [];
  269.  
  270. /* 如果是字符串,则支持通过, | 两个符号来指定多个组件名称的过滤器 */
  271. if (typeof filter === 'string') {
  272. /* 移除前后的, |分隔符,防止出现空字符的过滤规则 */
  273. filter.replace(/^(,|\|)/, '').replace(/(,|\|)$/, '');
  274.  
  275. if (/\|/.test(filter)) {
  276. filter = filter.split('|');
  277. } else {
  278. filter = filter.split(',');
  279. }
  280. }
  281.  
  282. helper.ddConfig = {
  283. enabled: true,
  284. filters: filter,
  285. size
  286. };
  287. },
  288. /* 禁止给组件注入空数据 */
  289. undd () {
  290. helper.ddConfig = {
  291. enabled: false,
  292. filters: [],
  293. size: 1024
  294. };
  295.  
  296. /* 删除之前注入的数据 */
  297. Object.keys(helper.components).forEach(key => {
  298. const component = helper.components[key];
  299. component.$data && delete component.$data.__dd__;
  300. });
  301. }
  302. };
  303.  
  304. helper.methods = methods;
  305.  
  306. function mixinRegister (Vue) {
  307. if (!Vue || !Vue.mixin) {
  308. debug.error('未检查到VUE对象,请检查是否引入了VUE,且将VUE对象挂载到全局变量window.Vue上');
  309. return false
  310. }
  311.  
  312. Vue.mixin({
  313. beforeCreate: function () {
  314. const tag = this.$options?._componentTag || this.$vnode?.tag || this._uid;
  315. const chain = helper.methods.getComponentChain(this);
  316. this._componentTag = tag;
  317. this._componentChain = chain;
  318. this._componentName = isNaN(Number(tag)) ? tag.replace(/^vue\-component\-\d+\-/, '') : 'anonymous-component';
  319. this._createdTime = Date.now();
  320.  
  321. /* 判断是否为函数式组件,函数式组件无状态 (没有响应式数据),也没有实例,也没生命周期概念 */
  322. if (this._componentName === 'anonymous-component' && !this.$parent && !this.$vnode) {
  323. this._componentName = 'functional-component';
  324. }
  325.  
  326. helper.components[this._uid] = this;
  327.  
  328. /**
  329. * 收集所有创建过的组件信息,此处只存储组件的基础信息,没销毁的组件会包含组件实例
  330. * 严禁对组件内其它对象进行引用,否则会导致组件实列无法被正常回收
  331. */
  332. const componentSummary = {
  333. uid: this._uid,
  334. name: this._componentName,
  335. tag: this._componentTag,
  336. createdTime: this._createdTime,
  337. // 0 表示还没被销毁
  338. destroyTime: 0,
  339. // 0 表示还没被销毁,duration可持续当当前查看时间
  340. duration: 0,
  341. component: this,
  342. chain
  343. };
  344. helper.componentsSummary[this._uid] = componentSummary;
  345.  
  346. /* 添加到componentsSummaryStatistics里,生成统计信息 */
  347. Array.isArray(helper.componentsSummaryStatistics[this._componentName])
  348. ? helper.componentsSummaryStatistics[this._componentName].push(componentSummary)
  349. : (helper.componentsSummaryStatistics[this._componentName] = [componentSummary]);
  350. },
  351. created: function () {
  352. /* 增加空白数据,方便观察内存泄露情况 */
  353. if (helper.ddConfig.enabled) {
  354. let needDd = false;
  355.  
  356. if (helper.ddConfig.filters.length === 0) {
  357. needDd = true;
  358. } else {
  359. for (let index = 0; index < helper.ddConfig.filters.length; index++) {
  360. const filter = helper.ddConfig.filters[index];
  361. if (filter === this._componentName || String(this._componentName).endsWith(filter)) {
  362. needDd = true;
  363. break
  364. }
  365. }
  366. }
  367.  
  368. if (needDd) {
  369. const size = helper.ddConfig.size * 1024;
  370. const componentInfo = `tag: ${this._componentTag}, uid: ${this._uid}, createdTime: ${this._createdTime}`;
  371. /* 此处必须使用JSON.stringify对产生的字符串进行消费,否则没法将内存占用上去 */
  372. this.$data.__dd__ = JSON.stringify(componentInfo + ' ' + helper.methods.createEmptyData(size, this._uid));
  373. console.log(`[dd success] ${componentInfo} componentChain: ${this._componentChain}`);
  374. }
  375. }
  376. },
  377. destroyed: function () {
  378. if (this._componentTag) {
  379. const uid = this._uid;
  380. const name = this._componentName;
  381. const destroyTime = Date.now();
  382.  
  383. /* helper里的componentSummary有可能通过调用clear函数而被清除掉,所以需进行判断再更新赋值 */
  384. const componentSummary = helper.componentsSummary[this._uid];
  385. if (componentSummary) {
  386. /* 补充/更新组件信息 */
  387. componentSummary.destroyTime = destroyTime;
  388. componentSummary.duration = destroyTime - this._createdTime;
  389.  
  390. helper.destroyList.push(componentSummary);
  391.  
  392. /* 统计被销毁的组件信息 */
  393. Array.isArray(helper.destroyStatistics[name])
  394. ? helper.destroyStatistics[name].push(componentSummary)
  395. : (helper.destroyStatistics[name] = [componentSummary]);
  396.  
  397. /* 删除已销毁的组件实例 */
  398. delete componentSummary.component;
  399. }
  400.  
  401. // 解除引用关系
  402. delete this._componentTag;
  403. delete this._componentChain;
  404. delete this._componentName;
  405. delete this._createdTime;
  406. delete this.$data.__dd__;
  407. delete helper.components[uid];
  408. } else {
  409. console.error('存在未被正常标记的组件,请检查组件采集逻辑是否需完善', this);
  410. }
  411. }
  412. });
  413. }
  414.  
  415. /*!
  416. * @name menuCommand.js
  417. * @version 0.0.1
  418. * @author Blaze
  419. * @date 2019/9/21 14:22
  420. */
  421.  
  422. const monkeyMenu = {
  423. on (title, fn, accessKey) {
  424. return window.GM_registerMenuCommand && window.GM_registerMenuCommand(title, fn, accessKey)
  425. },
  426. off (id) {
  427. return window.GM_unregisterMenuCommand && window.GM_unregisterMenuCommand(id)
  428. },
  429. /* 切换类型的菜单功能 */
  430. switch (title, fn, defVal) {
  431. const t = this;
  432. t.on(title, fn);
  433. }
  434. };
  435.  
  436. /**
  437. * 简单的i18n库
  438. */
  439.  
  440. class I18n {
  441. constructor (config) {
  442. this._languages = {};
  443. this._locale = this.getClientLang();
  444. this._defaultLanguage = '';
  445. this.init(config);
  446. }
  447.  
  448. init (config) {
  449. if (!config) return false
  450.  
  451. const t = this;
  452. t._locale = config.locale || t._locale;
  453. /* 指定当前要是使用的语言环境,默认无需指定,会自动读取 */
  454. t._languages = config.languages || t._languages;
  455. t._defaultLanguage = config.defaultLanguage || t._defaultLanguage;
  456. }
  457.  
  458. use () {}
  459.  
  460. t (path) {
  461. const t = this;
  462. let result = t.getValByPath(t._languages[t._locale] || {}, path);
  463.  
  464. /* 版本回退 */
  465. if (!result && t._locale !== t._defaultLanguage) {
  466. result = t.getValByPath(t._languages[t._defaultLanguage] || {}, path);
  467. }
  468.  
  469. return result || ''
  470. }
  471.  
  472. /* 当前语言值 */
  473. language () {
  474. return this._locale
  475. }
  476.  
  477. languages () {
  478. return this._languages
  479. }
  480.  
  481. changeLanguage (locale) {
  482. if (this._languages[locale]) {
  483. this._languages = locale;
  484. return locale
  485. } else {
  486. return false
  487. }
  488. }
  489.  
  490. /**
  491. * 根据文本路径获取对象里面的值
  492. * @param obj {Object} -必选 要操作的对象
  493. * @param path {String} -必选 路径信息
  494. * @returns {*}
  495. */
  496. getValByPath (obj, path) {
  497. path = path || '';
  498. const pathArr = path.split('.');
  499. let result = obj;
  500.  
  501. /* 递归提取结果值 */
  502. for (let i = 0; i < pathArr.length; i++) {
  503. if (!result) break
  504. result = result[pathArr[i]];
  505. }
  506.  
  507. return result
  508. }
  509.  
  510. /* 获取客户端当前的语言环境 */
  511. getClientLang () {
  512. return navigator.languages ? navigator.languages[0] : navigator.language
  513. }
  514. }
  515.  
  516. var zhCN = {
  517. about: '关于',
  518. issues: '反馈',
  519. setting: '设置',
  520. hotkeys: '快捷键',
  521. donate: '赞赏',
  522. };
  523.  
  524. var enUS = {
  525. about: 'about',
  526. issues: 'issues',
  527. setting: 'setting',
  528. hotkeys: 'hotkeys',
  529. donate: 'donate'
  530. };
  531.  
  532. var zhTW = {
  533. about: '關於',
  534. issues: '反饋',
  535. setting: '設置',
  536. hotkeys: '快捷鍵',
  537. donate: '讚賞',
  538. };
  539.  
  540. const messages = {
  541. 'zh-CN': zhCN,
  542. zh: zhCN,
  543. 'zh-HK': zhTW,
  544. 'zh-TW': zhTW,
  545. 'en-US': enUS,
  546. en: enUS,
  547. };
  548.  
  549. /*!
  550. * @name i18n.js
  551. * @description vue-debug-helper的国际化配置
  552. * @version 0.0.1
  553. * @author xxxily
  554. * @date 2022/04/26 14:56
  555. * @github https://github.com/xxxily
  556. */
  557.  
  558. const i18n = new I18n({
  559. defaultLanguage: 'en',
  560. /* 指定当前要是使用的语言环境,默认无需指定,会自动读取 */
  561. // locale: 'zh-TW',
  562. languages: messages
  563. });
  564.  
  565. /*!
  566. * @name menu.js
  567. * @description vue-debug-helper的菜单配置
  568. * @version 0.0.1
  569. * @author xxxily
  570. * @date 2022/04/25 22:28
  571. * @github https://github.com/xxxily
  572. */
  573.  
  574. function menuRegister (Vue) {
  575. if (!Vue) {
  576. monkeyMenu.on('not detected' + i18n.t('issues'), () => {
  577. window.GM_openInTab('https://github.com/xxxily/vue-debug-helper/issues', {
  578. active: true,
  579. insert: true,
  580. setParent: true
  581. });
  582. });
  583. return false
  584. }
  585.  
  586. monkeyMenu.on('查看vueDebugHelper对象', () => {
  587. debug.log('vueDebugHelper对象', helper);
  588. });
  589.  
  590. monkeyMenu.on('当前存活组件统计', () => {
  591. debug.log('当前存活组件统计', helper.methods.componentsStatistics());
  592. });
  593.  
  594. monkeyMenu.on('已销毁组件统计', () => {
  595. debug.log('已销毁组件统计', helper.methods.destroyStatisticsSort());
  596. });
  597.  
  598. monkeyMenu.on('全部组件混合统计', () => {
  599. debug.log('全部组件混合统计', helper.methods.componentsSummaryStatisticsSort());
  600. });
  601.  
  602. monkeyMenu.on('组件存活时间信息', () => {
  603. debug.log('组件存活时间信息', helper.methods.getDestroyByDuration());
  604. });
  605.  
  606. monkeyMenu.on('清空统计信息', () => {
  607. helper.methods.clearAll();
  608. debug.log('清空统计信息');
  609. });
  610.  
  611. monkeyMenu.on('数据注入(dd)', () => {
  612. const filter = window.prompt('组件过滤器(如果为空,则对所有组件注入)', localStorage.getItem('vueDebugHelper_dd_filter') || '');
  613. const count = window.prompt('指定注入数据的重复次数(默认1024)', localStorage.getItem('vueDebugHelper_dd_count') || 1024);
  614. filter && localStorage.setItem('vueDebugHelper_dd_filter', filter);
  615. count && localStorage.setItem('vueDebugHelper_dd_count', count);
  616. debug.log('数据注入(dd)');
  617. helper.methods.dd(filter, Number(count));
  618. });
  619.  
  620. monkeyMenu.on('取消数据注入(undd)', () => {
  621. debug.log('取消数据注入(undd)');
  622. helper.methods.undd();
  623. });
  624.  
  625. // monkeyMenu.on('i18n.t('setting')', () => {
  626. // window.alert('功能开发中,敬请期待...')
  627. // })
  628.  
  629. monkeyMenu.on(i18n.t('issues'), () => {
  630. window.GM_openInTab('https://github.com/xxxily/vue-debug-helper/issues', {
  631. active: true,
  632. insert: true,
  633. setParent: true
  634. });
  635. });
  636.  
  637. // monkeyMenu.on(i18n.t('donate'), () => {
  638. // window.GM_openInTab('https://cdn.jsdelivr.net/gh/xxxily/vue-debug-helper@main/donate.png', {
  639. // active: true,
  640. // insert: true,
  641. // setParent: true
  642. // })
  643. // })
  644. }
  645.  
  646. const isff = typeof navigator !== 'undefined' ? navigator.userAgent.toLowerCase().indexOf('firefox') > 0 : false;
  647.  
  648. // 绑定事件
  649. function addEvent (object, event, method) {
  650. if (object.addEventListener) {
  651. object.addEventListener(event, method, false);
  652. } else if (object.attachEvent) {
  653. object.attachEvent(`on${event}`, () => { method(window.event); });
  654. }
  655. }
  656.  
  657. // 修饰键转换成对应的键码
  658. function getMods (modifier, key) {
  659. const mods = key.slice(0, key.length - 1);
  660. for (let i = 0; i < mods.length; i++) mods[i] = modifier[mods[i].toLowerCase()];
  661. return mods
  662. }
  663.  
  664. // 处理传的key字符串转换成数组
  665. function getKeys (key) {
  666. if (typeof key !== 'string') key = '';
  667. key = key.replace(/\s/g, ''); // 匹配任何空白字符,包括空格、制表符、换页符等等
  668. const keys = key.split(','); // 同时设置多个快捷键,以','分割
  669. let index = keys.lastIndexOf('');
  670.  
  671. // 快捷键可能包含',',需特殊处理
  672. for (; index >= 0;) {
  673. keys[index - 1] += ',';
  674. keys.splice(index, 1);
  675. index = keys.lastIndexOf('');
  676. }
  677.  
  678. return keys
  679. }
  680.  
  681. // 比较修饰键的数组
  682. function compareArray (a1, a2) {
  683. const arr1 = a1.length >= a2.length ? a1 : a2;
  684. const arr2 = a1.length >= a2.length ? a2 : a1;
  685. let isIndex = true;
  686.  
  687. for (let i = 0; i < arr1.length; i++) {
  688. if (arr2.indexOf(arr1[i]) === -1) isIndex = false;
  689. }
  690. return isIndex
  691. }
  692.  
  693. // Special Keys
  694. const _keyMap = {
  695. backspace: 8,
  696. tab: 9,
  697. clear: 12,
  698. enter: 13,
  699. return: 13,
  700. esc: 27,
  701. escape: 27,
  702. space: 32,
  703. left: 37,
  704. up: 38,
  705. right: 39,
  706. down: 40,
  707. del: 46,
  708. delete: 46,
  709. ins: 45,
  710. insert: 45,
  711. home: 36,
  712. end: 35,
  713. pageup: 33,
  714. pagedown: 34,
  715. capslock: 20,
  716. num_0: 96,
  717. num_1: 97,
  718. num_2: 98,
  719. num_3: 99,
  720. num_4: 100,
  721. num_5: 101,
  722. num_6: 102,
  723. num_7: 103,
  724. num_8: 104,
  725. num_9: 105,
  726. num_multiply: 106,
  727. num_add: 107,
  728. num_enter: 108,
  729. num_subtract: 109,
  730. num_decimal: 110,
  731. num_divide: 111,
  732. '⇪': 20,
  733. ',': 188,
  734. '.': 190,
  735. '/': 191,
  736. '`': 192,
  737. '-': isff ? 173 : 189,
  738. '=': isff ? 61 : 187,
  739. ';': isff ? 59 : 186,
  740. '\'': 222,
  741. '[': 219,
  742. ']': 221,
  743. '\\': 220
  744. };
  745.  
  746. // Modifier Keys
  747. const _modifier = {
  748. // shiftKey
  749. '⇧': 16,
  750. shift: 16,
  751. // altKey
  752. '⌥': 18,
  753. alt: 18,
  754. option: 18,
  755. // ctrlKey
  756. '⌃': 17,
  757. ctrl: 17,
  758. control: 17,
  759. // metaKey
  760. '⌘': 91,
  761. cmd: 91,
  762. command: 91
  763. };
  764. const modifierMap = {
  765. 16: 'shiftKey',
  766. 18: 'altKey',
  767. 17: 'ctrlKey',
  768. 91: 'metaKey',
  769.  
  770. shiftKey: 16,
  771. ctrlKey: 17,
  772. altKey: 18,
  773. metaKey: 91
  774. };
  775. const _mods = {
  776. 16: false,
  777. 18: false,
  778. 17: false,
  779. 91: false
  780. };
  781. const _handlers = {};
  782.  
  783. // F1~F12 special key
  784. for (let k = 1; k < 20; k++) {
  785. _keyMap[`f${k}`] = 111 + k;
  786. }
  787.  
  788. // https://github.com/jaywcjlove/hotkeys
  789.  
  790. let _downKeys = []; // 记录摁下的绑定键
  791. let winListendFocus = false; // window是否已经监听了focus事件
  792. let _scope = 'all'; // 默认热键范围
  793. const elementHasBindEvent = []; // 已绑定事件的节点记录
  794.  
  795. // 返回键码
  796. const code = (x) => _keyMap[x.toLowerCase()] ||
  797. _modifier[x.toLowerCase()] ||
  798. x.toUpperCase().charCodeAt(0);
  799.  
  800. // 设置获取当前范围(默认为'所有')
  801. function setScope (scope) {
  802. _scope = scope || 'all';
  803. }
  804. // 获取当前范围
  805. function getScope () {
  806. return _scope || 'all'
  807. }
  808. // 获取摁下绑定键的键值
  809. function getPressedKeyCodes () {
  810. return _downKeys.slice(0)
  811. }
  812.  
  813. // 表单控件控件判断 返回 Boolean
  814. // hotkey is effective only when filter return true
  815. function filter (event) {
  816. const target = event.target || event.srcElement;
  817. const { tagName } = target;
  818. let flag = true;
  819. // ignore: isContentEditable === 'true', <input> and <textarea> when readOnly state is false, <select>
  820. if (
  821. target.isContentEditable ||
  822. ((tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT') && !target.readOnly)
  823. ) {
  824. flag = false;
  825. }
  826. return flag
  827. }
  828.  
  829. // 判断摁下的键是否为某个键,返回true或者false
  830. function isPressed (keyCode) {
  831. if (typeof keyCode === 'string') {
  832. keyCode = code(keyCode); // 转换成键码
  833. }
  834. return _downKeys.indexOf(keyCode) !== -1
  835. }
  836.  
  837. // 循环删除handlers中的所有 scope(范围)
  838. function deleteScope (scope, newScope) {
  839. let handlers;
  840. let i;
  841.  
  842. // 没有指定scope,获取scope
  843. if (!scope) scope = getScope();
  844.  
  845. for (const key in _handlers) {
  846. if (Object.prototype.hasOwnProperty.call(_handlers, key)) {
  847. handlers = _handlers[key];
  848. for (i = 0; i < handlers.length;) {
  849. if (handlers[i].scope === scope) handlers.splice(i, 1);
  850. else i++;
  851. }
  852. }
  853. }
  854.  
  855. // 如果scope被删除,将scope重置为all
  856. if (getScope() === scope) setScope(newScope || 'all');
  857. }
  858.  
  859. // 清除修饰键
  860. function clearModifier (event) {
  861. let key = event.keyCode || event.which || event.charCode;
  862. const i = _downKeys.indexOf(key);
  863.  
  864. // 从列表中清除按压过的键
  865. if (i >= 0) {
  866. _downKeys.splice(i, 1);
  867. }
  868. // 特殊处理 cmmand 键,在 cmmand 组合快捷键 keyup 只执行一次的问题
  869. if (event.key && event.key.toLowerCase() === 'meta') {
  870. _downKeys.splice(0, _downKeys.length);
  871. }
  872.  
  873. // 修饰键 shiftKey altKey ctrlKey (command||metaKey) 清除
  874. if (key === 93 || key === 224) key = 91;
  875. if (key in _mods) {
  876. _mods[key] = false;
  877.  
  878. // 将修饰键重置为false
  879. for (const k in _modifier) if (_modifier[k] === key) hotkeys[k] = false;
  880. }
  881. }
  882.  
  883. function unbind (keysInfo, ...args) {
  884. // unbind(), unbind all keys
  885. if (!keysInfo) {
  886. Object.keys(_handlers).forEach((key) => delete _handlers[key]);
  887. } else if (Array.isArray(keysInfo)) {
  888. // support like : unbind([{key: 'ctrl+a', scope: 's1'}, {key: 'ctrl-a', scope: 's2', splitKey: '-'}])
  889. keysInfo.forEach((info) => {
  890. if (info.key) eachUnbind(info);
  891. });
  892. } else if (typeof keysInfo === 'object') {
  893. // support like unbind({key: 'ctrl+a, ctrl+b', scope:'abc'})
  894. if (keysInfo.key) eachUnbind(keysInfo);
  895. } else if (typeof keysInfo === 'string') {
  896. // support old method
  897. // eslint-disable-line
  898. let [scope, method] = args;
  899. if (typeof scope === 'function') {
  900. method = scope;
  901. scope = '';
  902. }
  903. eachUnbind({
  904. key: keysInfo,
  905. scope,
  906. method,
  907. splitKey: '+'
  908. });
  909. }
  910. }
  911.  
  912. // 解除绑定某个范围的快捷键
  913. const eachUnbind = ({
  914. key, scope, method, splitKey = '+'
  915. }) => {
  916. const multipleKeys = getKeys(key);
  917. multipleKeys.forEach((originKey) => {
  918. const unbindKeys = originKey.split(splitKey);
  919. const len = unbindKeys.length;
  920. const lastKey = unbindKeys[len - 1];
  921. const keyCode = lastKey === '*' ? '*' : code(lastKey);
  922. if (!_handlers[keyCode]) return
  923. // 判断是否传入范围,没有就获取范围
  924. if (!scope) scope = getScope();
  925. const mods = len > 1 ? getMods(_modifier, unbindKeys) : [];
  926. _handlers[keyCode] = _handlers[keyCode].filter((record) => {
  927. // 通过函数判断,是否解除绑定,函数相等直接返回
  928. const isMatchingMethod = method ? record.method === method : true;
  929. return !(
  930. isMatchingMethod &&
  931. record.scope === scope &&
  932. compareArray(record.mods, mods)
  933. )
  934. });
  935. });
  936. };
  937.  
  938. // 对监听对应快捷键的回调函数进行处理
  939. function eventHandler (event, handler, scope, element) {
  940. if (handler.element !== element) {
  941. return
  942. }
  943. let modifiersMatch;
  944.  
  945. // 看它是否在当前范围
  946. if (handler.scope === scope || handler.scope === 'all') {
  947. // 检查是否匹配修饰符(如果有返回true)
  948. modifiersMatch = handler.mods.length > 0;
  949.  
  950. for (const y in _mods) {
  951. if (Object.prototype.hasOwnProperty.call(_mods, y)) {
  952. if (
  953. (!_mods[y] && handler.mods.indexOf(+y) > -1) ||
  954. (_mods[y] && handler.mods.indexOf(+y) === -1)
  955. ) {
  956. modifiersMatch = false;
  957. }
  958. }
  959. }
  960.  
  961. // 调用处理程序,如果是修饰键不做处理
  962. if (
  963. (handler.mods.length === 0 &&
  964. !_mods[16] &&
  965. !_mods[18] &&
  966. !_mods[17] &&
  967. !_mods[91]) ||
  968. modifiersMatch ||
  969. handler.shortcut === '*'
  970. ) {
  971. if (handler.method(event, handler) === false) {
  972. if (event.preventDefault) event.preventDefault();
  973. else event.returnValue = false;
  974. if (event.stopPropagation) event.stopPropagation();
  975. if (event.cancelBubble) event.cancelBubble = true;
  976. }
  977. }
  978. }
  979. }
  980.  
  981. // 处理keydown事件
  982. function dispatch (event, element) {
  983. const asterisk = _handlers['*'];
  984. let key = event.keyCode || event.which || event.charCode;
  985.  
  986. // 表单控件过滤 默认表单控件不触发快捷键
  987. if (!hotkeys.filter.call(this, event)) return
  988.  
  989. // Gecko(Firefox)的command键值224,在Webkit(Chrome)中保持一致
  990. // Webkit左右 command 键值不一样
  991. if (key === 93 || key === 224) key = 91;
  992.  
  993. /**
  994. * Collect bound keys
  995. * If an Input Method Editor is processing key input and the event is keydown, return 229.
  996. * https://stackoverflow.com/questions/25043934/is-it-ok-to-ignore-keydown-events-with-keycode-229
  997. * http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html
  998. */
  999. if (_downKeys.indexOf(key) === -1 && key !== 229) _downKeys.push(key);
  1000. /**
  1001. * Jest test cases are required.
  1002. * ===============================
  1003. */
  1004. ['ctrlKey', 'altKey', 'shiftKey', 'metaKey'].forEach((keyName) => {
  1005. const keyNum = modifierMap[keyName];
  1006. if (event[keyName] && _downKeys.indexOf(keyNum) === -1) {
  1007. _downKeys.push(keyNum);
  1008. } else if (!event[keyName] && _downKeys.indexOf(keyNum) > -1) {
  1009. _downKeys.splice(_downKeys.indexOf(keyNum), 1);
  1010. } else if (keyName === 'metaKey' && event[keyName] && _downKeys.length === 3) {
  1011. /**
  1012. * Fix if Command is pressed:
  1013. * ===============================
  1014. */
  1015. if (!(event.ctrlKey || event.shiftKey || event.altKey)) {
  1016. _downKeys = _downKeys.slice(_downKeys.indexOf(keyNum));
  1017. }
  1018. }
  1019. });
  1020. /**
  1021. * -------------------------------
  1022. */
  1023.  
  1024. if (key in _mods) {
  1025. _mods[key] = true;
  1026.  
  1027. // 将特殊字符的key注册到 hotkeys 上
  1028. for (const k in _modifier) {
  1029. if (_modifier[k] === key) hotkeys[k] = true;
  1030. }
  1031.  
  1032. if (!asterisk) return
  1033. }
  1034.  
  1035. // 将 modifierMap 里面的修饰键绑定到 event 中
  1036. for (const e in _mods) {
  1037. if (Object.prototype.hasOwnProperty.call(_mods, e)) {
  1038. _mods[e] = event[modifierMap[e]];
  1039. }
  1040. }
  1041. /**
  1042. * https://github.com/jaywcjlove/hotkeys/pull/129
  1043. * This solves the issue in Firefox on Windows where hotkeys corresponding to special characters would not trigger.
  1044. * An example of this is ctrl+alt+m on a Swedish keyboard which is used to type μ.
  1045. * Browser support: https://caniuse.com/#feat=keyboardevent-getmodifierstate
  1046. */
  1047. if (event.getModifierState && (!(event.altKey && !event.ctrlKey) && event.getModifierState('AltGraph'))) {
  1048. if (_downKeys.indexOf(17) === -1) {
  1049. _downKeys.push(17);
  1050. }
  1051.  
  1052. if (_downKeys.indexOf(18) === -1) {
  1053. _downKeys.push(18);
  1054. }
  1055.  
  1056. _mods[17] = true;
  1057. _mods[18] = true;
  1058. }
  1059.  
  1060. // 获取范围 默认为 `all`
  1061. const scope = getScope();
  1062. // 对任何快捷键都需要做的处理
  1063. if (asterisk) {
  1064. for (let i = 0; i < asterisk.length; i++) {
  1065. if (
  1066. asterisk[i].scope === scope &&
  1067. ((event.type === 'keydown' && asterisk[i].keydown) ||
  1068. (event.type === 'keyup' && asterisk[i].keyup))
  1069. ) {
  1070. eventHandler(event, asterisk[i], scope, element);
  1071. }
  1072. }
  1073. }
  1074. // key 不在 _handlers 中返回
  1075. if (!(key in _handlers)) return
  1076.  
  1077. for (let i = 0; i < _handlers[key].length; i++) {
  1078. if (
  1079. (event.type === 'keydown' && _handlers[key][i].keydown) ||
  1080. (event.type === 'keyup' && _handlers[key][i].keyup)
  1081. ) {
  1082. if (_handlers[key][i].key) {
  1083. const record = _handlers[key][i];
  1084. const { splitKey } = record;
  1085. const keyShortcut = record.key.split(splitKey);
  1086. const _downKeysCurrent = []; // 记录当前按键键值
  1087. for (let a = 0; a < keyShortcut.length; a++) {
  1088. _downKeysCurrent.push(code(keyShortcut[a]));
  1089. }
  1090. if (_downKeysCurrent.sort().join('') === _downKeys.sort().join('')) {
  1091. // 找到处理内容
  1092. eventHandler(event, record, scope, element);
  1093. }
  1094. }
  1095. }
  1096. }
  1097. }
  1098.  
  1099. // 判断 element 是否已经绑定事件
  1100. function isElementBind (element) {
  1101. return elementHasBindEvent.indexOf(element) > -1
  1102. }
  1103.  
  1104. function hotkeys (key, option, method) {
  1105. _downKeys = [];
  1106. const keys = getKeys(key); // 需要处理的快捷键列表
  1107. let mods = [];
  1108. let scope = 'all'; // scope默认为all,所有范围都有效
  1109. let element = document; // 快捷键事件绑定节点
  1110. let i = 0;
  1111. let keyup = false;
  1112. let keydown = true;
  1113. let splitKey = '+';
  1114.  
  1115. // 对为设定范围的判断
  1116. if (method === undefined && typeof option === 'function') {
  1117. method = option;
  1118. }
  1119.  
  1120. if (Object.prototype.toString.call(option) === '[object Object]') {
  1121. if (option.scope) scope = option.scope; // eslint-disable-line
  1122. if (option.element) element = option.element; // eslint-disable-line
  1123. if (option.keyup) keyup = option.keyup; // eslint-disable-line
  1124. if (option.keydown !== undefined) keydown = option.keydown; // eslint-disable-line
  1125. if (typeof option.splitKey === 'string') splitKey = option.splitKey; // eslint-disable-line
  1126. }
  1127.  
  1128. if (typeof option === 'string') scope = option;
  1129.  
  1130. // 对于每个快捷键进行处理
  1131. for (; i < keys.length; i++) {
  1132. key = keys[i].split(splitKey); // 按键列表
  1133. mods = [];
  1134.  
  1135. // 如果是组合快捷键取得组合快捷键
  1136. if (key.length > 1) mods = getMods(_modifier, key);
  1137.  
  1138. // 将非修饰键转化为键码
  1139. key = key[key.length - 1];
  1140. key = key === '*' ? '*' : code(key); // *表示匹配所有快捷键
  1141.  
  1142. // 判断key是否在_handlers中,不在就赋一个空数组
  1143. if (!(key in _handlers)) _handlers[key] = [];
  1144. _handlers[key].push({
  1145. keyup,
  1146. keydown,
  1147. scope,
  1148. mods,
  1149. shortcut: keys[i],
  1150. method,
  1151. key: keys[i],
  1152. splitKey,
  1153. element
  1154. });
  1155. }
  1156. // 在全局document上设置快捷键
  1157. if (typeof element !== 'undefined' && !isElementBind(element) && window) {
  1158. elementHasBindEvent.push(element);
  1159. addEvent(element, 'keydown', (e) => {
  1160. dispatch(e, element);
  1161. });
  1162. if (!winListendFocus) {
  1163. winListendFocus = true;
  1164. addEvent(window, 'focus', () => {
  1165. _downKeys = [];
  1166. });
  1167. }
  1168. addEvent(element, 'keyup', (e) => {
  1169. dispatch(e, element);
  1170. clearModifier(e);
  1171. });
  1172. }
  1173. }
  1174.  
  1175. function trigger (shortcut, scope = 'all') {
  1176. Object.keys(_handlers).forEach((key) => {
  1177. const data = _handlers[key].find((item) => item.scope === scope && item.shortcut === shortcut);
  1178. if (data && data.method) {
  1179. data.method();
  1180. }
  1181. });
  1182. }
  1183.  
  1184. const _api = {
  1185. setScope,
  1186. getScope,
  1187. deleteScope,
  1188. getPressedKeyCodes,
  1189. isPressed,
  1190. filter,
  1191. trigger,
  1192. unbind,
  1193. keyMap: _keyMap,
  1194. modifier: _modifier,
  1195. modifierMap
  1196. };
  1197. for (const a in _api) {
  1198. if (Object.prototype.hasOwnProperty.call(_api, a)) {
  1199. hotkeys[a] = _api[a];
  1200. }
  1201. }
  1202.  
  1203. if (typeof window !== 'undefined') {
  1204. const _hotkeys = window.hotkeys;
  1205. hotkeys.noConflict = (deep) => {
  1206. if (deep && window.hotkeys === hotkeys) {
  1207. window.hotkeys = _hotkeys;
  1208. }
  1209. return hotkeys
  1210. };
  1211. window.hotkeys = hotkeys;
  1212. }
  1213.  
  1214. /*!
  1215. * @name hotKeyRegister.js
  1216. * @description vue-debug-helper的快捷键配置
  1217. * @version 0.0.1
  1218. * @author xxxily
  1219. * @date 2022/04/26 14:37
  1220. * @github https://github.com/xxxily
  1221. */
  1222.  
  1223. function hotKeyRegister () {
  1224. const hotKeyMap = {
  1225. 'shift+alt+a,shift+alt+ctrl+a': function (event, handler) {
  1226. debug.log('全部组件混合统计', helper.methods.componentsSummaryStatisticsSort());
  1227. },
  1228. 'shift+alt+l': function (event, handler) {
  1229. debug.log('当前存活组件统计', helper.methods.componentsStatistics());
  1230. },
  1231. 'shift+alt+d': function (event, handler) {
  1232. debug.log('已销毁组件统计', helper.methods.destroyStatisticsSort());
  1233. },
  1234. 'shift+alt+c': function (event, handler) {
  1235. helper.methods.clearAll();
  1236. debug.log('清空统计信息');
  1237. },
  1238. 'shift+alt+e': function (event, handler) {
  1239. if (helper.ddConfig.enabled) {
  1240. debug.log('取消数据注入(undd)');
  1241. helper.methods.undd();
  1242. } else {
  1243. const filter = window.prompt('组件过滤器(如果为空,则对所有组件注入)', localStorage.getItem('vueDebugHelper_dd_filter') || '');
  1244. const count = window.prompt('指定注入数据的重复次数(默认1024)', localStorage.getItem('vueDebugHelper_dd_count') || 1024);
  1245. filter && localStorage.setItem('vueDebugHelper_dd_filter', filter);
  1246. count && localStorage.setItem('vueDebugHelper_dd_count', count);
  1247. debug.log('数据注入(dd)');
  1248. helper.methods.dd(filter, Number(count));
  1249. }
  1250. }
  1251. };
  1252.  
  1253. Object.keys(hotKeyMap).forEach(key => {
  1254. hotkeys(key, hotKeyMap[key]);
  1255. });
  1256. }
  1257.  
  1258. /*!
  1259. * @name vueDetector.js
  1260. * @description 检测页面是否存在Vue对象
  1261. * @version 0.0.1
  1262. * @author xxxily
  1263. * @date 2022/04/27 11:43
  1264. * @github https://github.com/xxxily
  1265. */
  1266.  
  1267. /**
  1268. * 检测页面是否存在Vue对象,方法参考:https://github.com/vuejs/devtools/blob/main/packages/shell-chrome/src/detector.js
  1269. * @param {window} win windwod对象
  1270. * @param {function} callback 检测到Vue对象后的回调函数
  1271. */
  1272. function vueDetect (win, callback) {
  1273. let delay = 1000;
  1274. let detectRemainingTries = 10;
  1275.  
  1276. function runDetect () {
  1277. // Method 1: use defineProperty to detect Vue, has BUG, so use Method 2
  1278. // 使用下面方式会导致 'Vue' in window 为 true,从而引发其他问题
  1279. // Object.defineProperty(win, 'Vue', {
  1280. // enumerable: true,
  1281. // configurable: true,
  1282. // get () {
  1283. // return win.__originalVue__
  1284. // },
  1285. // set (value) {
  1286. // win.__originalVue__ = value
  1287.  
  1288. // if (value && value.mixin) {
  1289. // callback(value)
  1290. // }
  1291. // }
  1292. // })
  1293.  
  1294. // Method 2: Check Vue 3
  1295. const vueDetected = !!(window.__VUE__);
  1296. if (vueDetected) {
  1297. callback(window.__VUE__);
  1298. return
  1299. }
  1300.  
  1301. // Method 3: Scan all elements inside document
  1302. const all = document.querySelectorAll('*');
  1303. let el;
  1304. for (let i = 0; i < all.length; i++) {
  1305. if (all[i].__vue__) {
  1306. el = all[i];
  1307. break
  1308. }
  1309. }
  1310. if (el) {
  1311. let Vue = Object.getPrototypeOf(el.__vue__).constructor;
  1312. while (Vue.super) {
  1313. Vue = Vue.super;
  1314. }
  1315. callback(Vue);
  1316. return
  1317. }
  1318.  
  1319. if (detectRemainingTries > 0) {
  1320. detectRemainingTries--;
  1321. setTimeout(() => {
  1322. runDetect();
  1323. }, delay);
  1324. delay *= 5;
  1325. }
  1326. }
  1327.  
  1328. setTimeout(() => {
  1329. runDetect();
  1330. }, 100);
  1331. }
  1332.  
  1333. /**
  1334. * 判断是否处于Iframe中
  1335. * @returns {boolean}
  1336. */
  1337. function isInIframe () {
  1338. return window !== window.top
  1339. }
  1340.  
  1341. /**
  1342. * 由于tampermonkey对window对象进行了封装,我们实际访问到的window并非页面真实的window
  1343. * 这就导致了如果我们需要将某些对象挂载到页面的window进行调试的时候就无法挂载了
  1344. * 所以必须使用特殊手段才能访问到页面真实的window对象,于是就有了下面这个函数
  1345. * @returns {Promise<void>}
  1346. */
  1347. async function getPageWindow () {
  1348. return new Promise(function (resolve, reject) {
  1349. if (window._pageWindow) {
  1350. return resolve(window._pageWindow)
  1351. }
  1352.  
  1353. const listenEventList = ['load', 'mousemove', 'scroll', 'get-page-window-event'];
  1354.  
  1355. function getWin (event) {
  1356. window._pageWindow = this;
  1357. // debug.log('getPageWindow succeed', event)
  1358. listenEventList.forEach(eventType => {
  1359. window.removeEventListener(eventType, getWin, true);
  1360. });
  1361. resolve(window._pageWindow);
  1362. }
  1363.  
  1364. listenEventList.forEach(eventType => {
  1365. window.addEventListener(eventType, getWin, true);
  1366. });
  1367.  
  1368. /* 自行派发事件以便用最短的时候获得pageWindow对象 */
  1369. window.dispatchEvent(new window.Event('get-page-window-event'));
  1370. })
  1371. }
  1372.  
  1373. let registerStatus = 'init';
  1374. window._debugMode_ = true
  1375.  
  1376. ;(async function () {
  1377. if (isInIframe()) {
  1378. debug.log('running in iframe, skip init', window.location.href);
  1379. return false
  1380. }
  1381.  
  1382. debug.log('init');
  1383.  
  1384. const win = await getPageWindow();
  1385. vueDetect(win, function (Vue) {
  1386. mixinRegister(Vue);
  1387. menuRegister(Vue);
  1388. hotKeyRegister();
  1389.  
  1390. debug.log('vue debug helper register success');
  1391. registerStatus = 'success';
  1392. });
  1393.  
  1394. setTimeout(() => {
  1395. if (registerStatus !== 'success') {
  1396. debug.warn('vue debug helper register failed, please check if vue is loaded .', win.location.href);
  1397. }
  1398. menuRegister(null);
  1399. }, 1000 * 10);
  1400. })();