// ==UserScript== // @name 网页调试 // @namespace https://greasyfork.org/zh-CN/scripts/475228 // @supportURL https://github.com/WhiteSevs/TamperMonkeyScript/issues // @version 2024.4.6 // @author WhiteSevs // @description 内置多种网页调试工具,包括:Eruda、vConsole、PageSpy、Chii,可在设置菜单中进行详细配置 // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAADbhJREFUeF7tnXmQHFUdx7+/noWkypiCgKVQIAuZ7R43UgGS6clRlkFEjAUIyiFHFYYjImIUEgiHAsoRiBAEjJyBSOQoghGilIligZaBnR4CVRGSfj0TKqUxeACW3Jrd/pnZzZLNZnfnve73Zrs3vX8mv/d7v9/395l39UXI/nZrBWh3yt5xpn+UaOt+6Az3B1n7gXj/7vyZtoDD19BibWHe4zUh1ry9u+gy4gEo5CfPCK3csQQ+DoAtU1gGXgGwnNlaVa12lGXapNVmRAJQKJQmhSGfBWAmAfk4xanDQExPUw7LfL+8No6vJLYdUQD0FD6cTaDZJsRm8D2WZd0zkkAYEQC054sTOi3MMVX4/jDVQSCyFgtRXmcCtGb6TD0AbW1TShaFD8rO7xrF/Rsxz/GrlRUafTbdVaoBsO3iUQR6uumq9emQwbODoHLvcMYQp+/UAuA47jlg3BcneW1tCZcJ4d2kzV8THaUSAKetOAdEtzVRp8ZdEZ8pROWhxobJskgdAIU291gm/CpZMvZEY4XWpA21jheTGNtgMaUKgHy+1J6zUAZ4TEJFfrMrpIm1WnlzQuPbJazUANDaOmP0qD3f+z2AaQkXd1Wu5Z0vr1+//n8Jj7M7vNQA4LS5N4IwPw2ignGTqHqXpSHWVACwfa+/BkAuDaIC6ArZmp6G6wipAMBpc5eDcFJKit8TJuNxUfVOTnrMiQeg0OaezoTUba+651fGGX7VezjJECQeAMdxnwNjapJFHCw2BjqCwEt07IkGIJ8vHZCz+K9pLH5vzGRZB/t+x6ak5pBoABzHPRmMx5IqnkxcxDzLr1aWytgOh02yAbCLiwG6YDiE0dUng5YGQXmWLn+6/SQdgBpA43Un3WR/m0TgHdzkPqW7kwagezgOMR+EQ7o3OaAaCI8IUf6xdG8Khu3t7WO6OseMjJszaY+xpm40jVsXKQBs272GgKsHqt82En4QBN41CrWVMs3nJ43PWbmalHHCjciC4/teoDtMHXVpCIDjlL4L5luHDJ7xPVH1rteZYKFQnMYh1U//Uv9HYXikX3vhWZ2JOI57HRhXDumTcIoQ3vKhbBoDYJfqV9/chsET5gvhLWxoJ2lg2+6JBKT6dqveVIlxul/1HpFMvaGZ0+beBMKlDQ0Za0XVmxwTAPcNAOMadtazMJgbBN4iGdtGNlIjTyMnCfl/As/zg8otOsIp2O4iBi6S9PVvEXhD1k5iBJAHoB4UMeb4Ve8OyQAzMwUFbNu9nYBvKzTRAYDkFNA3KuILhKjcqRBoZtpAAafNvROE85WE0jIFyCwCB4iKGOf7Ve9upYAz4wEVcBz3TrBi8buHYw2LwLqfobYbQ9WMQRcGQXlxVtfoChTs4k8Y9C1VD7Lb84ZrgN6Oo0IA5u+IauV21QQye8BxSreBeY6yFoyFoupJ3T0lDUC8kQAXB4E39FmCcpYju4Hiav9DMRi4Iwg8aWiUAIgDATFd4lfLN4/ssunJznGKPwLTvAje7hOBd55KO2UA4kCAFD9BoyJqHNvIN78SHhbCO0O170gAxIGAgCv9wLtBNdDdwd623esJuCJCrstF4J0SoV2828IjLwyJrhKifG2UgEdqG6et+EMQfT9CfpGL37NTjPkXGQJmrRdHYqYx/M2JZkQIIlbxtQDQMx0UbyDQ5RESyJpEVyB28bUB0ANB6WYCz42eT9ZSQQEtxdcKwPaFoerFCoWcM9PtCmgrvnYA6g4dx70LjG9k5TKigNbiGwGgB4LS/WBO7J2wRkpj3qn24hsDoBuCNvfnICgfTJjXMZU9GCm+UQB6ICg+g2jbm1RWyUjQzM+KauVII751nAMMFVgGgIayZQBoEDHNLjIA9FWvfpPEAN64/79tOx7V/pxD5CwyACJLt0tDEXhSR9+O7e4Chb4oFD1lACgKNoR5BsCu4kj9IqKWIGmLwAyADAAp4LMpIOpPvl+7bATQIGS2BtAg4nYX2RSQTQHZFNCPASlBov4GsykgqnJ92mVTgAYRsylgUBGzEWAAabJdgKYfXTYFaBAymwI0iJhNAdkUUFcg2wZm20CpNU+2BtA06mZrAA1CZmsADSJma4BsDZCtAQZmQGpOjPobzKaAqMplJ4EalNvVRbYLkNgFtLYettfolpbDdFSAia5O0m3haQWAmAe6l1G5RJ3IvVirld/q2/DDKaCQnzyDiS4C0fHKnlPSIJUA6NaWeSUx39r77uJuAAp2cS6DRvz7ezIAdtDU+0ZX6v7lW9YzukFLor8MgJ2rQhZPp4Lt3sLAxUksmO6YMgD6KUp0G9l26RcE/opusZPoLwOgX1Xq6wHHdutf5Ur8Fy51AJUBsIuKT1DBLi1gcCo+dBwXggyA/iMAFpJtT9qXkNsIYGxcgZPePgNgR4UY+AfRHm3d20DHmTwdbP0p6QWMG18GQB8FiSYKUV634yCo4O7DzHczk0vAgXHFTmL7DABsAvO6zhAXbtxY6f4kr+GLQe4qEI5JCgypBICxWlS9L5rS0CwAtvtLACeYCl7VbxoBINAKPyh/VTVXWXujANi2+zABp8kGY9oujQCA8ZCoemea0sYoAE6buwSEs00Fr+o3lQAAyt8AUNHFKAC27d5BwIUqAZm0TSMAql8AUdXPKACRP36gmoWkfRoBgML3fyRl2MnMLACOOx+MG6MEZqJNGgGQ/fpXVL0MA1A6CcxDfrw4auBR2qURAJlv/0XRoreNUQDa85MP77KsF+MEqLNtGgHoCmlCrVZer1OHvr6MApDPl8bmLP6PqeBV/aYRANmYVbVoyghQ78Sx3X8B2DdqgDrbyYqZlEfDGHglCLxP69Sgvy+jI0C9s4LtdjBQMpmErO+0AQDA2FvCmzYCJOl7QmkDwPQOoA6B8RHAtt3PEPBH2V+pSbv0AcBuEFQqJjUxDsD2dcDrAPYxmYiM71QBQPiLEN5BMnnFsWkSAKVlABu7oCErQKoAMHwNoGlrgO6FYFtpNhPfLVsoU3apAoBwihCe8UO0powAhcKUVg7DlwF8xFRxZfymCIDXR43+oHXdunXvyuQVx6YpANQDtO3SAwT+epxg47ZNEQBGLwH31bFpACThEbS0AEAhpvk17/m4wMu0bxoA23cDHoCiTGAmbNIAAINWBAZvAWv6SWDfDh2neCaYlpkorozPNAAQMh9drVaelslHh02TR4DiPwH6mI7Ao/hIAQDrROBNjJJb1DZNA8BximvBdETUQHW0SwEAz4jA+5yOXGV9NAUAp634ZBLePJICAEDg3/lB5QuyBYxrZxwAxy4uBuiCuIHqaJ8GALbnuUoE3kwdOTfyYRQA2y5eRqAFjYJo1v+nCAAw8Osg8I4zrY0xAIZ7xT+QcGkCYHv8T4jAO9EkBEYASMKhzwgBoH7B/nEhPGMv8NAOQD4/aXzOytXP/UebJDeK7xSOAN1pEvhRP6gYecROKwD5fH5UzhrnA2hVKVB9viPgfdOvqkkrAN1aGnpGUCsAjuM+B8ZUleIDtObd96yjN29+/n3HLi4DyNh9AwkBoPcSr/KwzsDPgsDTekFNGwAF213BgOKChXxQy1FCrNnSC43JncNwA0DAIj/w5tZzjfFyriUi8M5V+5ENbq0FAKfN/SkI31QM6gMrFxY3bHihvl7Y6c9xJh8PpkUAjVf0OaT58AFAa0DhdUJUVvUNMCoEDLo3CMqzdWgTGwDbLl1FUH+Zccj82Wq1MujNouPHFw9sabHmgXmOjkTrPoYBgLcYWDRx4kHXLV++vGugPKJCAEDLSBALgMi3ehGdIET5SZnCOk7pSDDPA/AlGfuhbJoMwJKukBbJPNYVFQIGLQ2C8qw4ukQGwHGmHA8OpYq4U4DMZ4tq5QHVoG27eB6B6iDYqm177ZsCAGM15XCr73urVeKMCgEBD/qBd5ZKX31tIwFg25NdC9ZKBj6u0jEDc4PAW6TSpq9t9zsNKXcOGF8DoPxNA6MAMFbDwnIhvCVR84sKQZwtojIA7YdM+mRXS24lAKXr1gS60Q/Kl0cVZ5eFYr50EohPAKF+0WScjF/tABD+gBBPdTE9JTPUy8QYFQIGPxpEOCxSAqC9vX3Prq1jVqq++s3kSdaECVPHdXZ2zkRIM0GYAmDQnYMGAOqHVS+BaJXVRU9tqHUYefQ9KgQAHhOBd6oMaL02SgAU7OICBim9V5iBjiDwFA+HVFLov4Wcvj/R1kPRRYeGhE8Q895E2HvbanysCLzPy3i27eJvAbxBwJsEvBWCNloWvdTZOe7lWu03/5XxEdcmKgQEXuAHlStk+5cG4FP5SUeElvUcQKNknQP4uwi8/RTsM9M+CkSFQOW+QmkAbLt4KoEeVanQ1s6uvV59dW1iXhChEntSbCNBQDhXdjEqDYDjuJeCcZOsMGRZB/t+xyZZ+8xucAVUIVB5rFwagELBPYZD7HSUOVjIVkhTN9TKHVlR9SmgAgExz/KrlaUyvUsDUH/fT4vFQaO9P4NOC4Ky0lQhE2hmI3sBid4hq3Oa76/9s4xm0gDUnTmOuxCMSwZzzODLg6CSmPcCygiQNptGIwGFdIxfK9d3MVJ/SgDUPQ7yjcHNILpaiPL9Ur1mRrEUsG33om3b0/qX3g7Y4Yi2gPhaIby7VJwrA1B3btvuRIv58JCo1QrDZzk3aq0Qa95W6TizjadA95TcwlPDEFPj1CASAPFCz1onSYH/A2cA4KVC+XrgAAAAAElFTkSuQmCC // @license MIT // @match *://*/* // @run-at document-start // @grant unsafeWindow // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_info // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_setClipboard // @grant GM_getResourceText // @resource Resource_erudaMonitor https://fastly.jsdelivr.net/npm/eruda-monitor // @resource Resource_erudaFeatures https://fastly.jsdelivr.net/npm/eruda-features // @resource Resource_erudaTiming https://fastly.jsdelivr.net/npm/eruda-timing // @resource Resource_erudaCode https://fastly.jsdelivr.net/npm/eruda-code // @resource Resource_erudaBenchmark https://fastly.jsdelivr.net/npm/eruda-benchmark // @resource Resource_Leaflet https://update.greasyfork.org/scripts/483765/1309677/Leaflet.js // @resource Resource_erudaGeolocation https://fastly.jsdelivr.net/gh/WhiteSevs/eruda-geolocation/eruda-geolocation.js // @resource Resource_erudaOrientation https://fastly.jsdelivr.net/gh/WhiteSevs/eruda-orientation/eruda-orientation.js // @resource Resource_erudaTouches https://fastly.jsdelivr.net/npm/eruda-touches // @resource Resource_erudaOutlinePlugin https://fastly.jsdelivr.net/npm/eruda-outline-plugin // @resource Resource_erudaPixel https://fastly.jsdelivr.net/npm/eruda-pixel // @resource Resource_vConsoleVueDevtools https://fastly.jsdelivr.net/npm/vue-vconsole-devtools@1.0.9/dist/vue_plugin.min.js // @require https://update.greasyfork.org/scripts/456485/1352602/pops.js // @require https://update.greasyfork.org/scripts/483694/1345961/Eruda-2.js // @require https://update.greasyfork.org/scripts/483695/1346076/vConsole-2.js // @require https://update.greasyfork.org/scripts/483696/1351860/PageSpy-2.js // @require https://update.greasyfork.org/scripts/455186/1355010/WhiteSevsUtils.js // @downloadURL none // ==/UserScript== (function () { if (typeof unsafeWindow === "undefined") { unsafeWindow = globalThis || window || self; } /** * @type {import("../库/pops")} */ const pops = window.pops; /** * @type {import("../库/Utils")} */ const utils = window.Utils.noConflict(); /** * 菜单对象 */ const GM_Menu = new utils.GM_Menu({ GM_getValue, GM_setValue, GM_registerMenuCommand, GM_unregisterMenuCommand, }); /** * @type {Window & typeof globalThis} */ let currentWin = unsafeWindow; let console = currentWin.console; /** * 配置面板 */ const PopsPanel = { /** * 本地存储的总键名 */ key: "GM_Panel", /** * 属性attributes的data-key */ attributeDataKey_Name: "data-key", /** * 属性attributes的data-default-value */ attributeDataDefaultValue_Name: "data-default-value", /** * 初始化菜单 */ initMenu() { this.initLocalDefaultValue(); if (!this.isTopWindow()) { return; } GM_Menu.add([ { key: "show_pops_panel_setting", text: "⚙ 设置", autoReload: false, isStoreValue: false, showText(text) { return text; }, callback() { PopsPanel.showPanel(); }, }, ]); }, isTopWindow() { return unsafeWindow.window.self === unsafeWindow.window.top; }, /** * 初始化本地设置默认的值 */ initLocalDefaultValue() { let content = this.getContent(); content.forEach((item) => { if (!item["forms"]) { return; } item.forms.forEach((__item__) => { if (__item__.forms) { __item__.forms.forEach((containerItem) => { if (!containerItem.attributes) { return; } let key = containerItem.attributes[this.attributeDataKey_Name]; let defaultValue = containerItem.attributes[this.attributeDataDefaultValue_Name]; if (this.getValue(key) == null) { this.setValue(key, defaultValue); } }); } else { } }); }); }, /** * 设置值 * @param {string} key 键 * @param {any} value 值 */ setValue(key, value) { let localValue = GM_getValue(this.key, {}); localValue[key] = value; GM_setValue(this.key, localValue); }, /** * 获取值 * @param {string} key 键 * @param {any} defaultValue 默认值 * @returns {any} */ getValue(key, defaultValue) { let localValue = GM_getValue(this.key, {}); return localValue[key] ?? defaultValue; }, /** * 删除值 * @param {string} key 键 */ deleteValue(key) { let localValue = GM_getValue(this.key, {}); delete localValue[key]; GM_setValue(this.key, localValue); }, /** * 显示设置面板 */ showPanel() { pops.panel({ title: { text: `${GM_info?.script?.name || "网页调试"}`, position: "center", }, content: this.getContent(), mask: { enable: true, clickEvent: { toClose: true, }, }, width: utils.isPhone() ? "92vw" : "650px", height: utils.isPhone() ? "80vh" : "500px", drag: true, only: true, zIndex: 200000000, style: ` aside.pops-panel-aside{ width: 20%; } `, }); }, /** * 获取按钮配置 * @param {string} text 文字 * @param {string} key 键 * @param {boolean} defaultValue 默认值 * @param {?(event:Event,value: boolean)=>boolean} _callback_ 点击回调 * @param {string|undefined} description 描述 */ getSwtichDetail(text, key, defaultValue, _callback_, description) { /** * @type {PopsPanelSwitchDetails} */ let result = { text: text, type: "switch", description: description, attributes: {}, getValue() { return Boolean(PopsPanel.getValue(key, defaultValue)); }, callback(event, value) { console.log(`${value ? "开启" : "关闭"} ${text}`); if (typeof _callback_ === "function") { if (_callback_(event, value)) { return; } } PopsPanel.setValue(key, Boolean(value)); }, }; result.attributes[this.attributeDataKey_Name] = key; result.attributes[this.attributeDataDefaultValue_Name] = Boolean(defaultValue); return result; }, /** * 获取输入框配置 * @param {string} text 文字 * @param {string} key 键 * @param {boolean} defaultValue 默认值 * @param {string} [placeholder=""] 提示 * @param {?(event:Event,value: string)=>boolean} _callback_ 输入回调 * @param {string|undefined} description 描述 * @returns {PopsPanelInputDetails} */ getInputDetail( text, key, defaultValue, placeholder = "", _callback_, description ) { return { text: text, type: "input", attributes: { "data-key": key, "data-default-value": defaultValue, }, description: description, getValue() { let localValue = PopsPanel.getValue(key, defaultValue); return localValue; }, callback(event, value) { if (typeof _callback_ === "function") { if (_callback_(event, value)) { return; } } PopsPanel.setValue(key, value); }, placeholder: placeholder, }; }, /** * 获取数字输入框配置 * @param {string} text 文字 * @param {string} key 键 * @param {boolean} defaultValue 默认值 * @param {string} [placeholder=""] 提示 * @param {?(event:Event,value: string)=>boolean} _callback_ 输入回调 * @param {string|undefined} description 描述 * @returns {PopsPanelInputDetails} */ getNumberInputDetail( text, key, defaultValue, placeholder = "", _callback_, description ) { let config = this.getInputDetail( text, key, defaultValue, (placeholder = ""), _callback_, description ); config.isNumber = true; config.getValue = function () { let localValue = PopsPanel.getValue(key, defaultValue); localValue = parseInt(localValue); if (isNaN(localValue)) { return defaultValue; } else { return localValue; } }; config.callback = function (event, value, valueAsNumber) { if (typeof _callback_ === "function") { if (_callback_(event, value)) { return; } } if (value === "") { PopsPanel.deleteValue(key); return; } if (isNaN(valueAsNumber)) { return; } PopsPanel.setValue(key, valueAsNumber); }; return config; }, /** * 获取下拉列表配置 * @param {string} text 文字 * @param {string} key 键 * @param {any} defaultValue 默认值 * @param {{ * value: any, * text: string, * disable?(value: any): boolean, * }[]} data 数据 * @param {string} description (可选)描述 * @param {(event:PointerEvent, isSelectedValue: any, isSelectedText:string)=>void} selectCallBack(可选)选择的回调 * @returns {PopsPanelSelectDetails} */ getSelectDetail( text, key, defaultValue, data, description, selectCallBack ) { return { text: text, type: "select", description: description, attributes: { "data-key": key, "data-default-value": defaultValue, }, getValue() { return PopsPanel.getValue(key, defaultValue); }, callback(event, isSelectedValue, isSelectedText) { PopsPanel.setValue(key, isSelectedValue); if (typeof selectCallBack === "function") { selectCallBack(event, isSelectedValue, isSelectedText); } }, data: data, }; }, /** * 获取配置内容 */ getContent() { /** * @type {PopsPanelContentConfig[]} */ let content = [ { id: "debug-panel-config-all", title: "总设置", headerTitle: "总设置", forms: [ { text: "功能", type: "forms", forms: [ this.getSelectDetail( "调试工具", "currentDebug", "eruda", [ { value: "eruda", text: "Eruda", }, { value: "vconsole", text: "VConsole", }, { value: "pagespy", text: "PageSpy", }, { value: "chii", text: "Chii", }, ], void 0, void 0 ), this.getSwtichDetail( "允许在iframe内加载", "allowRunInIframe", false, void 0, "如果指定本脚本的容器并没有在iframe内执行本脚本,那么该功能将不会生效" ), this.getSwtichDetail( "主动加载调试工具", "autoLoadDebugTool", true, void 0, "关闭后将会在脚本菜单注册按钮,有3种状态【加载并显示调试工具】、【隐藏调试工具】、【显示调试工具】" ), ], }, ], }, { id: "debug-panel-config-eruda", title: "Eruda", headerTitle: "Eruda设置", forms: [ { text: "功能", type: "forms", forms: [ { text: "版本", type: "button", attributes: { "data-key": "eruda-version", "data-default-value": GlobalDebug.erudaVersion, }, buttonType: "primary", buttonText: GlobalDebug.erudaVersion, callback(event) { window.open("https://github.com/liriliri/eruda", "_blank"); }, }, this.getSwtichDetail( "自动打开面板", "eruda-auto-open-panel", false, void 0, "加载完毕后自动显示面板内容" ), this.getSelectDetail( "默认展示的面板元素", "eruda-default-show-panel-name", "console", [ { text: "Console", value: "console", disable() { return !PopsPanel.getValue("eruda-panel-console"); }, }, { text: "Elements", value: "elements", disable() { return !PopsPanel.getValue("eruda-panel-elements"); }, }, { text: "Network", value: "network", disable() { return !PopsPanel.getValue("eruda-panel-network"); }, }, { text: "Resources", value: "resources", disable() { return !PopsPanel.getValue("eruda-panel-resources"); }, }, { text: "Sources", value: "sources", disable() { return !PopsPanel.getValue("eruda-panel-sources"); }, }, { text: "Info", value: "info", disable() { return !PopsPanel.getValue("eruda-panel-info"); }, }, { text: "Snippets", value: "snippets", disable() { return !PopsPanel.getValue("eruda-panel-snippets"); }, }, { text: "Monitor", value: "monitor", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaMonitor" ); }, }, { text: "Features", value: "features", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaFeatures" ); }, }, { text: "Timing", value: "timing", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaTiming" ); }, }, { text: "Code", value: "code", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaCode" ); }, }, { text: "Benchmark", value: "benchmark", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaBenchmark" ); }, }, { text: "Geolocation", value: "geolocation", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaGeolocation" ); }, }, { text: "Orientation", value: "orientation", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaOrientation" ); }, }, { text: "Touches", value: "touches", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaTouches" ); }, }, { text: "Outline", value: "outline", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaOutlinePlugin" ); }, }, { text: "Pixel", value: "ixel", disable() { return !PopsPanel.getValue( "eruda_plugin_Resource_erudaPixel" ); }, }, { text: "Settings", value: "settings", }, ], "开启【自动打开面板】才会生效", void 0 ), ], }, { text: "面板", type: "forms", forms: [ this.getSwtichDetail( "Console", "eruda-panel-console", true, void 0, "控制台" ), this.getSwtichDetail( "Elements", "eruda-panel-elements", true, void 0, "元素" ), this.getSwtichDetail( "Network", "eruda-panel-network", true, void 0, "网络" ), this.getSwtichDetail( "Resources", "eruda-panel-resources", true, void 0, "资源" ), this.getSwtichDetail( "Sources", "eruda-panel-sources", true, void 0, "源代码" ), this.getSwtichDetail( "Info", "eruda-panel-info", true, void 0, "信息" ), this.getSwtichDetail( "Snippets", "eruda-panel-snippets", true, void 0, "拓展" ), ], }, { text: "插件", type: "forms", forms: [ this.getSwtichDetail( "eruda-monitor", "eruda_plugin_Resource_erudaMonitor", false, void 0, "展示页面的 fps 和内存信息" ), this.getSwtichDetail( "eruda-features", "eruda_plugin_Resource_erudaFeatures", false, void 0, "浏览器特性检测" ), this.getSwtichDetail( "eruda-timing", "eruda_plugin_Resource_erudaTiming", false, void 0, "展示性能资源数据" ), this.getSwtichDetail( "eruda-code", "eruda_plugin_Resource_erudaCode", false, void 0, "运行 JavaScript 代码" ), this.getSwtichDetail( "eruda-benchmark", "eruda_plugin_Resource_erudaBenchmark", false, void 0, "运行 JavaScript 性能测试" ), this.getSwtichDetail( "eruda-geolocation", "eruda_plugin_Resource_erudaGeolocation", false, void 0, "测试地理位置接口" ), this.getSwtichDetail( "eruda-orientation", "eruda_plugin_Resource_erudaOrientation", false, void 0, "测试重力感应接口" ), this.getSwtichDetail( "eruda-touches", "eruda_plugin_Resource_erudaTouches", false, void 0, "(暂时无效)可视化屏幕 Touch 事件触发" ), this.getSwtichDetail( "eruda-outline-plugin", "eruda_plugin_Resource_erudaOutlinePlugin", false, void 0, "给页面的元素添加边框" ), this.getSwtichDetail( "eruda-pixel", "eruda_plugin_Resource_erudaPixel", false, void 0, "高精度的UI恢复辅助工具" ), ], }, ], }, { id: "debug-panel-config-vconsole", title: "vConsole", headerTitle: "vConsole设置", forms: [ { text: "功能", type: "forms", forms: [ { text: "版本", type: "button", attributes: { "data-key": "vconsole-version", "data-default-value": GlobalDebug.vConsoleVersion, }, buttonType: "primary", buttonText: GlobalDebug.vConsoleVersion, callback(event) { window.open( "https://github.com/Tencent/vConsole", "_blank" ); }, }, this.getSwtichDetail( "自动打开面板", "vconsole-auto-open-panel", false, void 0, "加载完毕后自动显示面板内容" ), this.getSelectDetail( "默认展示的面板元素", "vconsole-default-show-panel-name", "default", [ { text: "Log", value: "default", }, { text: "System", value: "system", disable() { return !PopsPanel.getValue("vConsole-panel-system"); }, }, { text: "Network", value: "network", disable() { return !PopsPanel.getValue("vConsole-panel-network"); }, }, { text: "Element", value: "element", disable() { return !PopsPanel.getValue("vConsole-panel-element"); }, }, { text: "Storage", value: "storage", disable() { return !PopsPanel.getValue("vConsole-panel-storage"); }, }, { text: "Stats", value: "stats", disable() { return !PopsPanel.getValue( "vConsole_plugin_Resource_vConsole_Stats" ); }, }, { text: "exportLog", value: "exportlog", disable() { return !PopsPanel.getValue( "vConsole_plugin_Resource_vConsole_ExportLog" ); }, }, { text: "Vue", value: "vue", disable() { return !PopsPanel.getValue( "vConsole_plugin_Resource_vConsoleVueDevtools" ); }, }, ], "开启【自动打开面板】才会生效", void 0 ), ], }, { text: "面板", type: "forms", forms: [ this.getSwtichDetail( "System", "vConsole-panel-system", true, void 0, "控制台" ), this.getSwtichDetail( "Network", "vConsole-panel-network", true, void 0, "元素" ), this.getSwtichDetail( "Element", "vConsole-panel-element", true, void 0, "网络" ), this.getSwtichDetail( "Storage", "vConsole-panel-storage", true, void 0, "资源" ), ], }, { text: "配置", type: "forms", forms: [ this.getSelectDetail( "主题", "vConsole-theme", "light", [ { value: "auto", text: "自动", }, { value: "light", text: "浅色主题", }, { value: "dark", text: "深色主题", }, ], void 0, void 0 ), this.getSwtichDetail( "禁止Log自动滚动", "vconsole-disableLogScrolling", false ), this.getSwtichDetail( "显示日志的输出时间", "vconsole-showTimestamps", false ), this.getNumberInputDetail( "日志的上限数量", "vconsole-maxLogNumber", 1000, "请输入数字" ), this.getNumberInputDetail( "请求记录的上限数量", "vconsole-maxNetworkNumber", 1000, "请输入数字" ), ], }, { text: "Storage配置", type: "forms", forms: [ this.getSwtichDetail( "Cookies", "vConsole-storage-defaultStorages-cookies", true, void 0, "显示Cookies" ), this.getSwtichDetail( "LocalStorage", "vConsole-storage-defaultStorages-localStorage", true, void 0, "显示LocalStorage" ), this.getSwtichDetail( "SessionStorage", "vConsole-storage-defaultStorages-sessionStorage", true, void 0, "显示SessionStorage" ), ], }, { text: "插件", type: "forms", forms: [ this.getSwtichDetail( "vconsole-stats-plugin", "vConsole_plugin_Resource_vConsole_Stats", false, void 0, "A vConsole plugin which can show Stats in front-end." ), this.getSwtichDetail( "vconsole-outputlog-plugin", "vConsole_plugin_Resource_vConsole_ExportLog", false, void 0, "使用该插件可以复制或下载console中打印的log" ), this.getSwtichDetail( "vconsole-vue-devtools-plugin", "vConsole_plugin_Resource_vConsoleVueDevtools", false, void 0, "Vue-vConsole-devtools 是一款vConsole插件,把Vue.js官方调试工具vue-devtools移植到移动端,可以直接在移动端查看调试Vue.js应用" ), ], }, ], }, { id: "debug-panel-config-pagespy", title: "PageSpy", headerTitle: "PageSpy设置", forms: [ { text: "功能", type: "forms", forms: [ { text: "注意!隐私保护!", type: "button", buttonType: "danger", buttonText: "了解详情", callback(event) { pops.confirm({ title: { text: "提示", }, content: { text: `下面默认配置的${GlobalDebug.pageSpyDefaultApi}是仅供测试使用的,其他人也可以看到你的调试信息,包括Cookie等信息,如果想用,请自己搭建一个调试端`, }, btn: { reverse: true, position: "end", ok: { text: "前往了解更多", callback() { window.open( "https://github.com/HuolalaTech/page-spy-web/wiki/%F0%9F%90%9E-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94#user-content-testjikejishucom-%E6%98%AF%E5%AE%98%E6%96%B9%E6%8F%90%E4%BE%9B%E7%9A%84%E5%9F%9F%E5%90%8D%E5%90%97%E4%B8%80%E7%9B%B4%E5%8F%AF%E4%BB%A5%E7%94%A8%E5%90%97", "_blank" ); }, }, }, mask: { enable: true, }, }); }, }, { text: "版本", type: "button", attributes: { "data-key": "pagespy-version", "data-default-value": GlobalDebug.pageSpyVersion, }, buttonType: "primary", buttonText: GlobalDebug.pageSpyVersion, callback(event) { window.open( "https://github.com/HuolalaTech/page-spy-web", "_blank" ); }, }, this.getSwtichDetail( "禁止在调试端运行", "pagespy-disable-run-in-debug-client", true, void 0, "调试端是下面配置的api/clientOrigin地址" ), ], }, { text: "配置", type: "forms", forms: [ this.getInputDetail( "api", "pagespy-api", GlobalDebug.pageSpyDefaultApi, "", function (event, value) { PopsPanel.setValue("pagespy-api", value.trim()); }, "服务器地址的 Host" ), this.getInputDetail( "clientOrigin", "pagespy-clientOrigin", GlobalDebug.pageSpyDefaultCliennOrigin, "", function (event, value) { PopsPanel.setValue("pagespy-clientOrigin", value.trim()); }, "服务器地址的 Origin" ), this.getInputDetail( "project", "pagespy-project", "default", void 0, void 0, "项目名称" ), this.getInputDetail( "title", "pagespy-title", "--", void 0, void 0, "自定义标题" ), this.getSwtichDetail( "autoRender", "pagespy-autoRender", true, void 0, "自动渲染「圆形白底带 Logo」" ), { text: "enableSSL", description: "是否https", type: "select", attributes: { "data-key": "pagespy-enableSSL", "data-default-value": true, }, getValue() { return PopsPanel.getValue( this.attributes["data-key"], this.attributes["data-default-value"] ); }, callback(event, isSelectedValue, isSelectedText) { PopsPanel.setValue( this.attributes["data-key"], isSelectedValue ); }, data: [ { value: null, text: "默认(自动分析)", }, { value: true, text: "开启", }, { value: false, text: "关闭", }, ], }, ], }, ], }, { id: "debug-panel-config-chii", title: "Chii", headerTitle: "Chii设置", forms: [ { text: "功能", type: "forms", forms: [ { text: "调试页面", type: "button", buttonType: "primary", buttonText: "前往", disable: Boolean( PopsPanel.getValue("chii-script-embedded", true) ), callback(event) { let url = PopsPanel.getValue( "chii-debug-url", GlobalDebug.chiiUrl ); window.open(url, "_blank"); }, }, ], }, { text: "配置", type: "forms", forms: [ this.getSwtichDetail( "本页展示", "chii-script-embedded", true, (event, value) => { let $shadowRoot = event.target.getRootNode(); let button = $shadowRoot.querySelector( "li.pops-panel-forms-container-item ul > li > .pops-panel-button button" ); if (value) { button.setAttribute("disabled", true); } else { button.removeAttribute("disabled"); } }, "将调试器展示在同一页面中" ), this.getSwtichDetail( "禁止在调试端运行", "chii-disable-run-in-debug-url", true, void 0, "调试端是下面配置的【调试页面Url】" ), this.getSwtichDetail( "检测script加载", "chii-check-script-load", true, void 0, "失败会有alert提示弹出" ), this.getInputDetail( "调试页面Url", "chii-debug-url", GlobalDebug.chiiUrl, "请输入链接Url", void 0, "配置【调试页面】的Url" ), this.getInputDetail( "来源js", "chii-target-js", GlobalDebug.chiiDetaultScriptJs, "请输入目标js文件", void 0, "用于注入页面来进行调试" ), ], }, { text: "本页展示的配置", type: "forms", forms: [ { text: "高度", type: "slider", description: "移动端不好拖拽,使用这个配置高度", attributes: { "data-key": ChiiHeight.$data.key, "data-default-value": ChiiHeight.$data.winHalfHeight, }, getValue() { return ChiiHeight.getLocalHeight(); }, callback(event, value) { ChiiHeight.setGMLocalHeight(value); ChiiHeight.setLocalHeight(value); let chiiContainer = Array.from( document.querySelectorAll(".__chobitsu-hide__") ).find((ele) => ele.querySelector("iframe")); if (chiiContainer) { chiiContainer.style.height = value + "px"; } }, getToolTipContent(value) { return value + "px"; }, min: 0, max: ChiiHeight.$data.winHeight, step: 1, }, ], }, ], }, ]; return content; }, }; const ChiiHeight = { $data: { key: "chii-embedded-height", winHeight: parseInt(globalThis.innerHeight), winHalfHeight: parseInt(globalThis.innerHeight / 2), }, init() { let height = this.$data.winHalfHeight; if (!this.isExistGMLocalHeight()) { /* GM未创建或不是数字,设置值到油猴数据管理器中 */ this.setGMLocalHeight(height); } else { height = this.getGMLocalHeight(); } this.setLocalHeight(height); }, isExistLocalHeight() { return typeof this.getLocalHeight() === "number"; }, /** * * @returns {number} */ getLocalHeight() { return globalThis.localStorage.getItem(this.$data.key); }, /** * * @param {number} value */ setLocalHeight(value) { if (typeof value !== "number") { console.log(value); throw new TypeError(`${this.$data.key}的值必须是number`); } globalThis.localStorage.setItem(this.$data.key, value); if (this.getLocalHeight() !== value) { globalThis.localStorage[this.$data.key] = value; } }, isExistGMLocalHeight() { return typeof this.getGMLocalHeight() === "number"; }, /** * * @returns {number} */ getGMLocalHeight() { return PopsPanel.getValue(this.$data.key); }, /** * * @param {number} value */ setGMLocalHeight(value) { if (typeof value !== "number") { console.log(value); throw new TypeError(`${this.$data.key}的值必须是number`); } PopsPanel.setValue(this.$data.key, value); }, }; const vConsolePlugin = { State(vConsole, VConsole) { const Stats = function () { var mode = 0; var localPositionStorageKey = "vConsole-Plugin-Stats-Position"; function getLocalPositionStorage() { return GM_getValue(localPositionStorageKey, { top: 0, left: 0, }); } function setLocalPositionStorage(left, top) { GM_setValue(localPositionStorageKey, { left: left, top: top, }); } var container = document.createElement("div"); let oldPosition = getLocalPositionStorage(); container.style.cssText = `position:fixed;top:${oldPosition.top}px;left:${oldPosition.left}px;cursor:pointer;opacity:0.9;z-index:10000`; container.addEventListener( "click", function (event) { event.preventDefault(); showPanel(++mode % container.children.length); }, { capture: true, } ); function addPanel(panel) { container.appendChild(panel.dom); return panel; } function showPanel(id) { for (var i = 0; i < container.children.length; i++) { container.children[i].style.display = i === id ? "block" : "none"; } mode = id; } function drag() { pops.config.Utils.drag(container, { dragElement: container, limit: true, extraDistance: 2, moveCallBack(moveElement, left, top) { setLocalPositionStorage(left, top); }, }); } var beginTime = (performance || Date).now(), prevTime = beginTime, frames = 0; var fpsPanel = addPanel(new Stats.Panel("FPS", "#0ff", "#002")); var msPanel = addPanel(new Stats.Panel("MS", "#0f0", "#020")); if (self.performance && self.performance.memory) { var memPanel = addPanel(new Stats.Panel("MB", "#f08", "#201")); } showPanel(0); drag(); return { REVISION: 16, dom: container, addPanel: addPanel, showPanel: showPanel, begin: function () { beginTime = (performance || Date).now(); }, end: function () { frames++; var time = (performance || Date).now(); msPanel.update(time - beginTime, 200); if (time >= prevTime + 1000) { fpsPanel.update((frames * 1000) / (time - prevTime), 100); prevTime = time; frames = 0; if (memPanel) { var memory = performance.memory; memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 ); } } return time; }, update: function () { beginTime = this.end(); }, // Backwards Compatibility domElement: container, setMode: showPanel, }; }; Stats.Panel = function (name, fg, bg) { var min = Infinity, max = 0, round = Math.round; var PR = round(window.devicePixelRatio || 1); var WIDTH = 80 * PR, HEIGHT = 48 * PR, TEXT_X = 3 * PR, TEXT_Y = 2 * PR, GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR, GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR; var canvas = document.createElement("canvas"); canvas.width = WIDTH; canvas.height = HEIGHT; canvas.style.cssText = "width:80px;height:48px"; var context = canvas.getContext("2d"); context.font = "bold " + 9 * PR + "px Helvetica,Arial,sans-serif"; context.textBaseline = "top"; context.fillStyle = bg; context.fillRect(0, 0, WIDTH, HEIGHT); context.fillStyle = fg; context.fillText(name, TEXT_X, TEXT_Y); context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); context.fillStyle = bg; context.globalAlpha = 0.9; context.fillRect(GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT); return { dom: canvas, update: function (value, maxValue) { min = Math.min(min, value); max = Math.max(max, value); context.fillStyle = bg; context.globalAlpha = 1; context.fillRect(0, 0, WIDTH, GRAPH_Y); context.fillStyle = fg; context.fillText( round(value) + " " + name + " (" + round(min) + "-" + round(max) + ")", TEXT_X, TEXT_Y ); context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT ); context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT ); context.fillStyle = bg; context.globalAlpha = 0.9; context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round((1 - value / maxValue) * GRAPH_HEIGHT) ); }, }; }; class VConsoleStatsPlugin { constructor(vConsole, VConsole) { this.vConsole = vConsole; this.VConsole = VConsole; this.dom = null; this.requestID = null; this.stats = null; return this.init(); } init() { this.addStyle(); const vConsoleStats = new this.VConsole.VConsolePlugin( "Stats", "Stats" ); vConsoleStats.on("ready", () => { document .querySelectorAll(".vc-stats-buttons") .forEach((statusButton) => { statusButton.addEventListener("click", (event) => { const currentType = event.target.dataset.type; if ( currentType.toString() === "2" && !(self.performance && self.performance.memory) ) { console.error( "浏览器不支持window.performance或者window.performance.memory" ); return; } this.changePanel(currentType); }); }); }); vConsoleStats.on("renderTab", (callback) => { const statsHTML = `
`; callback(statsHTML); }); vConsoleStats.on("addTool", (callback) => { const buttons = [ { name: "Show Stats", onClick: this.show, }, { name: "Close Stats", onClick: this.close, }, ]; callback(buttons); }); this.vConsole.addPlugin(vConsoleStats); return vConsoleStats; } addStyle = (target) => { if (target == null) { target = document.head || document.body || document.documentElement; } const cssNode = document.createElement("style"); cssNode.setAttribute("type", "text/css"); cssNode.innerHTML = ` .vc-stats-button{ margin: 10px 10px; background-color: #fbf9fe; padding: 2px 4px; cursor: pointer; border-radius: 4px; border: 1px solid; } .vc-button-container{ display: flex; align-items: center; } .vc-description{ display: flex; flex-direction: column; } .vc-description a.vc-link{ color: blue; }`; target.appendChild(cssNode); }; show = () => { if (!this.stats) { this.stats = new Stats(); this.stats.showPanel(1); // 0: fps, 1: ms, 2: mb, 3+: custom this.dom = this.stats.dom; document.body.appendChild(this.dom); this.requestID = requestAnimationFrame(this.loop); } }; changePanel = (type) => { if (!this.stats) { this.show(); } this.stats.setMode(Number(type)); }; loop = () => { this.stats.update(); this.requestID = requestAnimationFrame(this.loop); }; close = () => { if (this.requestID) { cancelAnimationFrame(this.requestID); } if (this.dom) { document.body.removeChild(this.dom); } this.stats = null; this.requestID = null; this.dom = null; }; } return new VConsoleStatsPlugin(vConsole, VConsole); }, exportLog(vConsole, VConsole) { class VConsoleOutputLogsPlugin { constructor(vConsole, VConsole, logItemSelector) { this.vConsole = vConsole; this.VConsole = VConsole; this.$ = vConsole.$; this.dom = null; this.logItemSelector = logItemSelector || ".vc-content #__vc_plug_default .vc-log-row"; return this.init(); } init() { const vConsoleExportLogs = new this.VConsole.VConsolePlugin( "exportLog", "exportLog" ); vConsoleExportLogs.on("ready", () => { console.log("[vConsole-exportlog-plugin] -- load"); }); vConsoleExportLogs.on("renderTab", (callback) => { const html = `