vue-debug-helper

Vue components debug helper

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