Mini DevTools v3 - Secret Inspector

DevTools ẩn với Sources Browser + Pause Debug

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Mini DevTools v3 - Secret Inspector
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  DevTools ẩn với Sources Browser + Pause Debug
// @author       You
// @match        *://*/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // ========== CONFIG ==========
    const TOGGLE_KEY = '`';

    // ========== STATE ==========
    let isPaused = false;
    let pauseInterval = null;
    let currentTab = 'console';

    // ========== TẠO GIAO DIỆN ==========
    const panel = document.createElement('div');
    panel.id = 'secret-devtools-panel';
    panel.innerHTML = `
        <div id="sdt-header">
            <span>🔧 Mini DevTools</span>
            <div style="display:flex;align-items:center;gap:4px;">
                <button id="sdt-btn-network">🌐 Network</button>
                <button id="sdt-btn-console" class="active">📜 Console</button>
                <button id="sdt-btn-sources">📁 Sources</button>
                <button id="sdt-btn-storage">💾 Storage</button>
                <button id="sdt-btn-elements">📋 DOM</button>
                <button id="sdt-btn-iframe">🖼️ Iframe</button>
                <span style="color:#555;margin:0 8px;">|</span>
                <button id="sdt-btn-pause" style="background:#8b0000;color:#fff;">⏯️ Pause</button>
                <button id="sdt-btn-close">❌</button>
            </div>
        </div>
        <div id="sdt-body">
            <div id="sdt-main-area" style="display:flex;flex:1;overflow:hidden;">
                <div id="sdt-file-list" style="display:none;width:250px;background:#252526;overflow-y:auto;border-right:1px solid #3e3e3e;padding:5px;"></div>
                <div id="sdt-content-area" style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
                    <div id="sdt-log" style="flex:1;overflow-y:auto;padding:10px;background:#1e1e1e;font-size:11px;line-height:1.5;white-space:pre-wrap;word-break:break-all;"></div>
                    <div id="sdt-source-viewer" style="display:none;flex:1;overflow:auto;background:#1e1e1e;position:relative;">
                        <div id="sdt-source-header" style="background:#333;padding:4px 10px;color:#aaa;font-size:11px;border-bottom:1px solid #555;"></div>
                        <pre id="sdt-source-code" style="margin:0;padding:10px;color:#d4d4d4;font-size:11px;line-height:1.6;counter-reset:line;white-space:pre;tab-size:2;"></pre>
                    </div>
                    <textarea id="sdt-console-input" placeholder="Gõ lệnh JS rồi Enter (Shift+Enter xuống dòng)..."></textarea>
                </div>
            </div>
        </div>
    `;

    // ========== STYLE ==========
    const style = document.createElement('style');
    style.textContent = `
        #secret-devtools-panel {
            position:fixed;bottom:0;left:0;right:0;height:400px;background:#1e1e1e;
            color:#d4d4d4;font-family:'Consolas','Courier New',monospace;font-size:12px;
            z-index:999999;display:none;flex-direction:column;border-top:3px solid #007acc;
            resize:vertical;overflow:hidden;min-height:150px;
        }
        #secret-devtools-panel.show-flex { display:flex !important; }
        #sdt-header {
            display:flex;justify-content:space-between;align-items:center;
            padding:6px 10px;background:#252526;border-bottom:1px solid #3e3e3e;
            cursor:move;user-select:none;flex-shrink:0;
        }
        #sdt-header span { font-weight:bold;color:#007acc;font-size:13px; }
        #sdt-header button {
            background:#3e3e3e;color:#ccc;border:1px solid #555;
            padding:4px 10px;cursor:pointer;border-radius:3px;font-size:11px;
            white-space:nowrap;
        }
        #sdt-header button:hover { background:#555; }
        #sdt-header button.active { background:#007acc;color:white;border-color:#007acc; }
        #sdt-body { flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0; }
        #sdt-main-area { flex:1;overflow:hidden;min-height:0; }
        #sdt-console-input {
            width:100%;background:#2d2d2d;color:#4fc1ff;border:none;
            border-top:1px solid #3e3e3e;padding:8px 10px;
            font-family:'Consolas',monospace;font-size:12px;outline:none;
            box-sizing:border-box;flex-shrink:0;
        }
        #sdt-log .log-info { color:#4fc1ff; }
        #sdt-log .log-warn { color:#ffcc00; }
        #sdt-log .log-error { color:#ff6b6b; }
        #sdt-log .log-success { color:#4caf50; }
        #sdt-log .log-network { color:#ce9178; }
        #sdt-log .log-trace { color:#888; }
        #sdt-file-list .file-item {
            padding:4px 8px;cursor:pointer;font-size:11px;
            border-radius:2px;word-break:break-all;
        }
        #sdt-file-list .file-item:hover { background:#3e3e3e; }
        #sdt-file-list .file-item.selected { background:#007acc;color:white; }
        #sdt-file-list .file-folder {
            padding:4px 8px;cursor:pointer;font-size:11px;
            color:#e8ab53;font-weight:bold;
        }
        #sdt-source-code .line-number {
            display:inline-block;width:40px;color:#555;text-align:right;
            margin-right:12px;user-select:none;
        }
    `;

    // ========== APPEND TO DOM ==========
    function appendUI() {
        if (!document.head || !document.body) return false;
        if (document.getElementById('secret-devtools-panel')) return true;
        document.head.appendChild(style);
        document.body.appendChild(panel);
        return true;
    }
    if (!appendUI()) {
        document.addEventListener('DOMContentLoaded', appendUI);
        if (document.readyState !== 'loading') appendUI();
    }

    // ========== DOM REFS CACHE ==========
    function qs(id) { return document.getElementById(id); }

    function getLogEl() { return qs('sdt-log'); }
    function getInputEl() { return qs('sdt-console-input'); }
    function getFileListEl() { return qs('sdt-file-list'); }
    function getSourceViewerEl() { return qs('sdt-source-viewer'); }
    function getSourceCodeEl() { return qs('sdt-source-code'); }
    function getSourceHeaderEl() { return qs('sdt-source-header'); }
    function getPanelEl() { return qs('secret-devtools-panel'); }
    function getPauseBtn() { return qs('sdt-btn-pause'); }
    function getMainAreaEl() { return qs('sdt-main-area'); }
    function getContentAreaEl() { return qs('sdt-content-area'); }

    // ========== LOG FUNCTION ==========
    function dtLog(msg, type) {
        type = type || 'info';
        var logDiv = getLogEl();
        if (!logDiv) return;
        var line = document.createElement('div');
        line.className = 'log-' + type;
        var time = new Date().toLocaleTimeString();
        line.textContent = '[' + time + '] ' + msg;
        logDiv.appendChild(line);
        logDiv.scrollTop = logDiv.scrollHeight;
    }

    // ========== OVERRIDE CONSOLE ==========
    var origConsoleLog = console.log.bind(console);
    var origConsoleWarn = console.warn.bind(console);
    var origConsoleError = console.error.bind(console);

    console.log = function() {
        origConsoleLog.apply(console, arguments);
        var args = Array.prototype.slice.call(arguments);
        dtLog(args.map(function(a) {
            return typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a);
        }).join(' '), 'info');
    };
    console.warn = function() {
        origConsoleWarn.apply(console, arguments);
        dtLog(Array.prototype.slice.call(arguments).join(' '), 'warn');
    };
    console.error = function() {
        origConsoleError.apply(console, arguments);
        dtLog(Array.prototype.slice.call(arguments).join(' '), 'error');
    };

    // ========== NETWORK INTERCEPT ==========
    (function() {
        var origFetch = window.fetch;
        window.fetch = function() {
            var url = typeof arguments[0] === 'string' ? arguments[0] : arguments[0].url;
            var startTime = performance.now();
            dtLog('📡 FETCH → ' + url, 'network');
            return origFetch.apply(this, arguments).then(function(response) {
                var duration = (performance.now() - startTime).toFixed(1);
                dtLog('📡 FETCH ← ' + response.status + ' (' + duration + 'ms) ' + url, 'network');
                return response;
            });
        };

        var origXHROpen = XMLHttpRequest.prototype.open;
        var origXHRSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.open = function(method, url) {
            this._sdt_url = url;
            this._sdt_method = method;
            return origXHROpen.apply(this, arguments);
        };
        XMLHttpRequest.prototype.send = function() {
            var self = this;
            dtLog('📡 XHR ' + self._sdt_method + ' → ' + self._sdt_url, 'network');
            self.addEventListener('load', function() {
                dtLog('📡 XHR ← ' + self.status + ' ' + self._sdt_url, 'network');
            });
            return origXHRSend.apply(self, arguments);
        };

        var OrigWS = window.WebSocket;
        window.WebSocket = function(url) {
            var ws = new OrigWS(url);
            dtLog('🔌 WS CONNECT → ' + url, 'network');
            var origSend = ws.send;
            ws.send = function(data) {
                var preview = typeof data === 'string' ? data.substring(0, 200) : '[Binary]';
                dtLog('🔌 WS SEND → ' + preview, 'network');
                return origSend.apply(ws, arguments);
            };
            ws.addEventListener('message', function(e) {
                var preview = typeof e.data === 'string' ? e.data.substring(0, 200) : '[Binary]';
                dtLog('🔌 WS RECV ← ' + preview, 'network');
            });
            return ws;
        };
        window.WebSocket.prototype = OrigWS.prototype;
    })();

    // ========== IFRAME UTILS ==========
    function getGameIframe() {
        var iframes = document.querySelectorAll('iframe');
        for (var i = 0; i < iframes.length; i++) {
            try {
                if (iframes[i].contentWindow) return iframes[i];
            } catch(e) {}
        }
        return iframes[0] || null;
    }

    function getGameWindow() {
        var iframe = getGameIframe();
        if (iframe) {
            try {
                return iframe.contentWindow;
            } catch(e) {
                dtLog('⚠️ Không truy cập được iframe (cross-origin)', 'warn');
                return null;
            }
        }
        return window;
    }

    // ========== EXECUTE COMMAND ==========
    function executeCommand(cmd) {
        var gameWindow = getGameWindow();
        var ctx = gameWindow || window;
        try {
            var result = (function() { return eval(cmd); }).call(ctx);
            if (result !== undefined) {
                dtLog(String(result), 'success');
            }
        } catch(e) {
            dtLog('❌ ' + e.message, 'error');
        }
    }

    // ========== SOURCES TAB ==========
    var sourceFiles = {};

    function collectSourceFiles() {
        sourceFiles = {};
        var gameWindow = getGameWindow();
        var ctx = gameWindow || window;
        var doc = gameWindow ? (getGameIframe() ? getGameIframe().contentDocument : document) : document;

        // Thu thập script tags
        if (doc) {
            var scripts = doc.querySelectorAll('script[src]');
            scripts.forEach(function(s, i) {
                sourceFiles['scripts/' + (s.src.split('/').pop() || ('script_' + i + '.js'))] = {
                    url: s.src,
                    type: 'script'
                };
            });
            // Inline scripts
            var inlineScripts = doc.querySelectorAll('script:not([src])');
            inlineScripts.forEach(function(s, i) {
                if (s.textContent && s.textContent.trim().length > 0) {
                    sourceFiles['inline/inline_' + i + '.js'] = {
                        content: s.textContent,
                        type: 'inline'
                    };
                }
            });
        }

        // Performance entries (đã load)
        if (window.performance && window.performance.getEntriesByType) {
            var entries = window.performance.getEntriesByType('resource');
            entries.forEach(function(e) {
                if (e.initiatorType === 'script' || e.initiatorType === 'link' || e.initiatorType === 'xmlhttprequest') {
                    var name = e.name.split('/').pop() || e.name;
                    if (!sourceFiles[e.name]) {
                        sourceFiles[e.name] = { url: e.name, type: e.initiatorType };
                    }
                }
            });
        }
    }

    function fetchSourceContent(url, callback) {
        try {
            fetch(url).then(function(r) {
                return r.text();
            }).then(function(text) {
                callback(null, text);
            }).catch(function(err) {
                callback(err.message);
            });
        } catch(e) {
            callback(e.message);
        }
    }

    function showSourceContent(key, fileObj) {
        var viewer = getSourceViewerEl();
        var codeEl = getSourceCodeEl();
        var headerEl = getSourceHeaderEl();
        var logEl = getLogEl();
        var fileListEl = getFileListEl();

        if (!viewer || !codeEl) return;

        // Highlight selected
        if (fileListEl) {
            var items = fileListEl.querySelectorAll('.file-item');
            items.forEach(function(it) { it.classList.remove('selected'); });
            var targetItem = fileListEl.querySelector('[data-key="' + CSS.escape(key) + '"]');
            if (targetItem) targetItem.classList.add('selected');
        }

        // Show viewer, hide log
        viewer.style.display = 'block';
        if (logEl) logEl.style.display = 'none';

        if (headerEl) headerEl.textContent = '📄 ' + key;

        if (fileObj.content) {
            codeEl.innerHTML = renderSourceWithLineNumbers(fileObj.content);
        } else if (fileObj.url) {
            codeEl.textContent = 'Đang tải...';
            fetchSourceContent(fileObj.url, function(err, content) {
                if (err) {
                    codeEl.textContent = '// Lỗi tải file: ' + err;
                } else {
                    codeEl.innerHTML = renderSourceWithLineNumbers(content);
                    fileObj.content = content;
                }
            });
        }
    }

    function renderSourceWithLineNumbers(code) {
        var lines = code.split('\n');
        var html = '';
        for (var i = 0; i < lines.length; i++) {
            var lineNum = i + 1;
            html += '<span class="line-number">' + lineNum + '</span>' +
                    escapeHTML(lines[i]) + '\n';
        }
        return html;
    }

    function escapeHTML(str) {
        var div = document.createElement('div');
        div.textContent = str;
        return div.innerHTML;
    }

    function renderFileList() {
        var fileListEl = getFileListEl();
        if (!fileListEl) return;
        fileListEl.innerHTML = '';

        collectSourceFiles();

        var keys = Object.keys(sourceFiles).sort();
        if (keys.length === 0) {
            fileListEl.innerHTML = '<div style="padding:10px;color:#888;">Không tìm thấy file</div>';
            return;
        }

        // Nhóm theo thư mục
        var folders = {};
        keys.forEach(function(k) {
            var parts = k.split('/');
            var folder = parts.length > 1 ? parts[0] : '(root)';
            if (!folders[folder]) folders[folder] = [];
            folders[folder].push(k);
        });

        Object.keys(folders).sort().forEach(function(folder) {
            var folderDiv = document.createElement('div');
            folderDiv.className = 'file-folder';
            folderDiv.textContent = '📁 ' + folder;
            fileListEl.appendChild(folderDiv);

            folders[folder].forEach(function(key) {
                var item = document.createElement('div');
                item.className = 'file-item';
                item.setAttribute('data-key', key);
                var fileName = key.split('/').pop();
                item.textContent = '  📄 ' + fileName;
                item.addEventListener('click', function() {
                    showSourceContent(key, sourceFiles[key]);
                });
                fileListEl.appendChild(item);
            });
        });

        dtLog('📁 Đã thu thập ' + keys.length + ' file', 'success');
    }

    // ========== PAUSE/RESUME ==========
    function pauseExecution() {
        if (isPaused) return;
        isPaused = true;
        var btn = getPauseBtn();
        if (btn) {
            btn.textContent = '▶️ Resume';
            btn.style.background = '#006400';
        }
        dtLog('⏸️ ĐÃ PAUSE - Trang đang bị đóng băng', 'warn');

        // Sync loop giữ trang đóng băng
        pauseInterval = setInterval(function() {
            // Không làm gì, chỉ giữ CPU bận và ngăn event loop chạy tiếp
            var start = Date.now();
            while (Date.now() - start < 50) {
                // Busy wait 50ms
            }
        }, 0);

        // Chặn tất cả timer mới
        var origSetTimeout = window.setTimeout;
        var origSetInterval = window.setInterval;
        var origRequestAnimationFrame = window.requestAnimationFrame;

        window._sdt_origSetTimeout = origSetTimeout;
        window._sdt_origSetInterval = origSetInterval;
        window._sdt_origRAF = origRequestAnimationFrame;

        window.setTimeout = function() { return 0; };
        window.setInterval = function() { return 0; };
        window.requestAnimationFrame = function() { return 0; };
    }

    function resumeExecution() {
        if (!isPaused) return;
        isPaused = false;
        var btn = getPauseBtn();
        if (btn) {
            btn.textContent = '⏯️ Pause';
            btn.style.background = '#8b0000';
        }
        dtLog('▶️ ĐÃ RESUME - Trang tiếp tục chạy', 'success');

        if (pauseInterval) {
            clearInterval(pauseInterval);
            pauseInterval = null;
        }

        if (window._sdt_origSetTimeout) {
            window.setTimeout = window._sdt_origSetTimeout;
            window.setInterval = window._sdt_origSetInterval;
            window.requestAnimationFrame = window._sdt_origRAF;
        }
    }

    // ========== TAB SWITCHING ==========
    function switchTab(tab) {
        currentTab = tab;
        var logEl = getLogEl();
        var inputEl = getInputEl();
        var fileListEl = getFileListEl();
        var sourceViewerEl = getSourceViewerEl();

        // Reset view
        if (logEl) logEl.style.display = 'block';
        if (inputEl) inputEl.style.display = 'block';
        if (fileListEl) fileListEl.style.display = 'none';
        if (sourceViewerEl) sourceViewerEl.style.display = 'none';

        // Update buttons
        var buttons = ['network', 'console', 'sources', 'storage', 'elements', 'iframe'];
        buttons.forEach(function(b) {
            var btn = qs('sdt-btn-' + b);
            if (btn) btn.classList.remove('active');
        });
        var activeBtn = qs('sdt-btn-' + tab);
        if (activeBtn) activeBtn.classList.add('active');

        // Tab-specific logic
        switch(tab) {
            case 'network':
                if (logEl) logEl.innerHTML = '';
                dtLog('🌐 Đang theo dõi request...', 'info');
                break;
            case 'console':
                break;
            case 'sources':
                if (fileListEl) fileListEl.style.display = 'block';
                if (sourceViewerEl) sourceViewerEl.style.display = 'block';
                if (logEl) logEl.style.display = 'none';
                renderFileList();
                break;
            case 'storage':
                if (logEl) logEl.innerHTML = '';
                showStorage();
                break;
            case 'elements':
                if (logEl) logEl.innerHTML = '';
                showGlobalVars();
                break;
            case 'iframe':
                if (logEl) logEl.innerHTML = '';
                showIframeInfo();
                break;
        }
    }

    function showStorage() {
        dtLog('💾 === LOCAL STORAGE ===', 'info');
        var gameWin = getGameWindow();
        var ls = gameWin ? gameWin.localStorage : localStorage;
        for (var i = 0; i < ls.length; i++) {
            var key = ls.key(i);
            var val = ls.getItem(key);
            dtLog(key + ': ' + val.substring(0, 200) + (val.length > 200 ? '...' : ''), 'success');
        }
        dtLog('💾 === SESSION STORAGE ===', 'info');
        var ss = gameWin ? gameWin.sessionStorage : sessionStorage;
        for (var j = 0; j < ss.length; j++) {
            var skey = ss.key(j);
            var sval = ss.getItem(skey);
            dtLog(skey + ': ' + sval.substring(0, 200) + (sval.length > 200 ? '...' : ''), 'success');
        }
    }

    function showGlobalVars() {
        dtLog('📋 === GLOBAL VARIABLES (window/iframeWindow) ===', 'info');
        var gameWin = getGameWindow();
        var ctx = gameWin || window;
        var keys = Object.keys(ctx).filter(function(k) {
            return !k.startsWith('webkit') &&
                   !k.startsWith('on') &&
                   typeof ctx[k] !== 'function' &&
                   k !== 'undefined' &&
                   k !== 'NaN' &&
                   k !== 'Infinity';
        });
        dtLog('Tìm thấy ' + keys.length + ' biến global', 'info');
        keys.slice(0, 50).forEach(function(k) {
            var val = ctx[k];
            var type = typeof val;
            if (type === 'object' && val !== null) {
                var preview = Array.isArray(val) ? 'Array[' + val.length + ']' : 'Object{' + Object.keys(val).length + ' keys}';
                dtLog('📦 ' + k + ': ' + preview, 'success');
            } else {
                dtLog('📌 ' + k + ': ' + String(val).substring(0, 100), 'success');
            }
        });
        if (keys.length > 50) dtLog('...(còn ' + (keys.length - 50) + ' biến nữa, giới hạn hiển thị)', 'warn');
    }

    function showIframeInfo() {
        var iframe = getGameIframe();
        if (iframe) {
            dtLog('🖼️ Tìm thấy iframe: ' + (iframe.src || '(no src)'), 'success');
            try {
                var doc = iframe.contentDocument;
                if (doc) {
                    dtLog('   ✅ Same-origin - có thể truy cập nội dung', 'success');
                    dtLog('   Canvas: ' + doc.querySelectorAll('canvas').length, 'info');
                    dtLog('   Scripts: ' + doc.querySelectorAll('script').length, 'info');
                }
            } catch(e) {
                dtLog('   ⚠️ Cross-origin - không truy cập được', 'warn');
            }
        } else {
            dtLog('⚠️ Không tìm thấy iframe', 'warn');
        }
    }

    // ========== INIT EVENTS ==========
    function initEvents() {
        var closeBtn = qs('sdt-btn-close');
        var inputEl = getInputEl();
        var pauseBtn = getPauseBtn();

        // Close
        if (closeBtn) {
            closeBtn.addEventListener('click', function() {
                var p = getPanelEl();
                if (p) p.classList.remove('show-flex');
            });
        }

        // Console input
        if (inputEl) {
            inputEl.addEventListener('keydown', function(e) {
                if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault();
                    var cmd = this.value.trim();
                    if (cmd) {
                        dtLog('> ' + cmd, 'info');
                        executeCommand(cmd);
                        this.value = '';
                    }
                }
            });
        }

        // Pause/Resume
        if (pauseBtn) {
            pauseBtn.addEventListener('click', function() {
                if (isPaused) {
                    resumeExecution();
                } else {
                    pauseExecution();
                }
            });
        }

        // Tab buttons
        var tabs = ['network', 'console', 'sources', 'storage', 'elements', 'iframe'];
        tabs.forEach(function(tab) {
            var btn = qs('sdt-btn-' + tab);
            if (btn) {
                btn.addEventListener('click', function() {
                    switchTab(tab);
                });
            }
        });
    }

    // ========== KEYBOARD TOGGLE ==========
    document.addEventListener('keydown', function(e) {
        if (e.key === TOGGLE_KEY && !e.ctrlKey && !e.altKey && !e.metaKey) {
            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
                return;
            }
            e.preventDefault();
            e.stopPropagation();
            var p = getPanelEl();
            if (p) {
                if (p.classList.contains('show-flex')) {
                    p.classList.remove('show-flex');
                    if (isPaused) resumeExecution();
                } else {
                    p.classList.add('show-flex');
                }
            }
        }
    }, true);

    // ========== STARTUP ==========
    function startup() {
        if (!appendUI()) {
            setTimeout(startup, 50);
            return;
        }
        initEvents();
        dtLog('🔧 Mini DevTools v3 đã sẵn sàng', 'success');
        dtLog('📁 Tab Sources: xem & duyệt code game', 'info');
        dtLog('⏯️ Nút Pause: đóng băng game để debug', 'info');
        dtLog('💡 Bấm ` để bật/tắt', 'info');
    }

    setTimeout(startup, 100);

})();