Mini DevTools Mobile v2

DevTools mobile có Pause thật (debugger) + overlay

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Mini DevTools Mobile v2
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  DevTools mobile có Pause thật (debugger) + overlay
// @author       You
// @match        *://*/*
// @include      *
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    function waitForBody(cb) {
        if (document.body) cb();
        else setTimeout(function() { waitForBody(cb); }, 50);
    }

    waitForBody(function() {

        // ========== PAUSE OVERLAY ==========
        var pauseOverlay = document.createElement('div');
        pauseOverlay.id = 'dtm-pause-overlay';
        pauseOverlay.innerHTML = 
            '<div style="text-align:center;background:rgba(0,0,0,0.9);padding:20px;border-radius:12px;border:2px solid #ff6b6b;">' +
                '<div style="font-size:40px;">⏸️</div>' +
                '<div style="font-size:18px;font-weight:bold;color:#ff6b6b;margin:10px 0;">PAUSED IN DEBUGGER</div>' +
                '<div id="dtm-pause-info" style="color:#aaa;font-size:12px;margin:5px 0;">Script paused</div>' +
                '<button id="dtm-resume-btn" style="margin-top:15px;padding:12px 30px;background:#4caf50;color:white;' +
                'border:none;border-radius:8px;font-size:16px;cursor:pointer;">▶️ RESUME</button>' +
                '<button id="dtm-step-btn" style="margin-left:8px;padding:12px 20px;background:#007acc;color:white;' +
                'border:none;border-radius:8px;font-size:16px;cursor:pointer;">⏭️ Step</button>' +
            '</div>';
        pauseOverlay.style.cssText = 
            'display:none;position:fixed;top:0;left:0;width:100%;height:100%;z-index:99999999;' +
            'background:rgba(0,0,0,0.7);justify-content:center;align-items:center;flex-direction:column;';
        document.body.appendChild(pauseOverlay);

        // ========== NÚT NỔI ==========
        var floatBtn = document.createElement('div');
        floatBtn.textContent = '🔧';
        floatBtn.style.cssText = 
            'position:fixed;top:15px;right:15px;z-index:9999999;width:45px;height:45px;' +
            'background:#007acc;color:white;border-radius:50%;text-align:center;' +
            'line-height:45px;font-size:22px;box-shadow:0 4px 12px rgba(0,0,0,0.5);' +
            'user-select:none;-webkit-user-select:none;' +
            '-webkit-tap-highlight-color:transparent;';

        var dragData = { dragging: false, sx:0, sy:0, sl:0, st:0 };
        floatBtn.addEventListener('touchstart', function(e) {
            var t = e.touches[0];
            dragData.sx = t.clientX; dragData.sy = t.clientY;
            dragData.sl = floatBtn.offsetLeft; dragData.st = floatBtn.offsetTop;
            dragData.dragging = false;
        });
        floatBtn.addEventListener('touchmove', function(e) {
            var t = e.touches[0];
            if (Math.abs(t.clientX - dragData.sx) > 5 || Math.abs(t.clientY - dragData.sy) > 5) {
                dragData.dragging = true;
                floatBtn.style.left = Math.max(0, Math.min(window.innerWidth - 45, dragData.sl + t.clientX - dragData.sx)) + 'px';
                floatBtn.style.top = Math.max(0, Math.min(window.innerHeight - 45, dragData.st + t.clientY - dragData.sy)) + 'px';
                floatBtn.style.right = 'auto';
            }
        });
        floatBtn.addEventListener('touchend', function() {
            if (!dragData.dragging) togglePanel();
        });
        document.body.appendChild(floatBtn);

        // ========== PANEL ==========
        var panel = document.createElement('div');
        panel.id = 'dtm-panel';
        panel.innerHTML = 
            '<div id="dtm-header">' +
                '<span>🔧 DevTools</span>' +
                '<div style="display:flex;gap:3px;flex-wrap:wrap;">' +
                    '<button id="dtm-tab-console" class="dtm-tab active">📜 JS</button>' +
                    '<button id="dtm-tab-network" class="dtm-tab">🌐 Net</button>' +
                    '<button id="dtm-tab-sources" class="dtm-tab">📁 Src</button>' +
                    '<button id="dtm-tab-storage" class="dtm-tab">💾 Stg</button>' +
                    '<button id="dtm-tab-iframe" class="dtm-tab">🖼️ Iframe</button>' +
                    '<button id="dtm-btn-pause">⏸️ Pause</button>' +
                    '<button id="dtm-btn-close">❌</button>' +
                '</div>' +
            '</div>' +
            '<div id="dtm-log"></div>' +
            '<textarea id="dtm-input" placeholder="Gõ lệnh JS rồi Enter..."></textarea>';
        panel.style.cssText = 
            'position:fixed;bottom:0;left:0;right:0;height:45vh;background:#1e1e1e;' +
            'color:#d4d4d4;font-family:monospace;font-size:11px;z-index:9999998;' +
            'display:none;flex-direction:column;border-top:3px solid #007acc;' +
            'border-radius:12px 12px 0 0;overflow:hidden;';
        document.body.appendChild(panel);

        // Style
        var styleEl = document.createElement('style');
        styleEl.textContent = 
            '#dtm-header{display:flex;justify-content:space-between;align-items:center;padding:8px;' +
            'background:#252526;border-bottom:1px solid #3e3e3e;flex-shrink:0;flex-wrap:wrap;gap:4px;}' +
            '#dtm-header span{font-weight:bold;color:#007acc;font-size:13px;}' +
            '.dtm-tab{background:#3e3e3e;color:#ccc;border:1px solid #555;padding:5px 8px;' +
            'border-radius:3px;font-size:10px;white-space:nowrap;}' +
            '.dtm-tab.active{background:#007acc;color:white;}' +
            '#dtm-log{flex:1;overflow-y:auto;padding:8px;font-size:10px;line-height:1.4;' +
            'white-space:pre-wrap;word-break:break-all;}' +
            '#dtm-input{width:100%;background:#2d2d2d;color:#4fc1ff;border:none;' +
            'border-top:1px solid #3e3e3e;padding:10px;font-family:monospace;font-size:13px;' +
            'outline:none;box-sizing:border-box;flex-shrink:0;}' +
            '.dtm-i{color:#4fc1ff;}.dtm-w{color:#ffcc00;}.dtm-e{color:#ff6b6b;}' +
            '.dtm-s{color:#4caf50;}.dtm-n{color:#ce9178;}';
        document.head.appendChild(styleEl);

        // ========== REFS ==========
        function G(id) { return document.getElementById(id); }
        function logEl() { return G('dtm-log'); }
        function inputEl() { return G('dtm-input'); }

        // ========== TOGGLE ==========
        function togglePanel() {
            var p = G('dtm-panel');
            if (p.style.display === 'none' || p.style.display === '') {
                p.style.display = 'flex';
                floatBtn.textContent = '▼';
                floatBtn.style.background = '#e74c3c';
            } else {
                p.style.display = 'none';
                floatBtn.textContent = '🔧';
                floatBtn.style.background = '#007acc';
            }
        }

        // ========== LOG ==========
        function dtLog(msg, cls) {
            cls = cls || 'i';
            var l = logEl();
            if (!l) return;
            var d = document.createElement('div');
            d.className = 'dtm-' + cls;
            d.textContent = '[' + new Date().toLocaleTimeString() + '] ' + msg;
            l.appendChild(d);
            l.scrollTop = l.scrollHeight;
        }

        // ========== OVERRIDE CONSOLE ==========
        var _cl = console.log.bind(console);
        var _cw = console.warn.bind(console);
        var _ce = console.error.bind(console);
        console.log = function() {
            _cl.apply(console, arguments);
            dtLog([].slice.call(arguments).map(function(a) {
                return typeof a === 'object' ? JSON.stringify(a) : String(a);
            }).join(' '), 'i');
        };
        console.warn = function() { _cw.apply(console, arguments); dtLog([].slice.call(arguments).join(' '), 'w'); };
        console.error = function() { _ce.apply(console, arguments); dtLog([].slice.call(arguments).join(' '), 'e'); };

        // ========== NETWORK ==========
        var of = window.fetch;
        window.fetch = function() {
            var url = typeof arguments[0] === 'string' ? arguments[0] : arguments[0].url;
            dtLog('📡 → ' + url, 'n');
            return of.apply(this, arguments).then(function(r) {
                dtLog('📡 ← ' + r.status + ' ' + url, 'n');
                return r;
            });
        };
        var oxo = XMLHttpRequest.prototype.open;
        var oxs = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.open = function(m, url) { this._u = url; this._m = m; return oxo.apply(this, arguments); };
        XMLHttpRequest.prototype.send = function() {
            var s = this;
            dtLog('📡 ' + s._m + ' → ' + s._u, 'n');
            s.addEventListener('load', function() { dtLog('📡 ← ' + s.status, 'n'); });
            return oxs.apply(s, arguments);
        };

        // ========== IFRAME ==========
        function getGameWindow() {
            var ifs = document.querySelectorAll('iframe');
            for (var i = 0; i < ifs.length; i++) {
                try { if (ifs[i].contentWindow) return ifs[i].contentWindow; } catch(e) {}
            }
            return window;
        }

        // ========== EXEC ==========
        function exec(cmd) {
            var ctx = getGameWindow();
            try {
                var r = (function() { return eval(cmd); }).call(ctx);
                if (r !== undefined) dtLog(String(r), 's');
            } catch(e) {
                dtLog('❌ ' + e.message, 'e');
            }
        }

        // ========== PAUSE THẬT (debugger) ==========
        var pauseActive = false;
        var pauseDebuggerInterval = null;

        function doPause() {
            if (pauseActive) return;
            pauseActive = true;

            // Hiện overlay
            G('dtm-pause-overlay').style.display = 'flex';
            G('dtm-pause-info').textContent = 'Game bị đóng băng - Debugger active';
            G('dtm-btn-pause').textContent = '▶️ Resume';
            G('dtm-btn-pause').style.background = '#006400';

            // Chạy debugger liên tục để giữ trạng thái pause
            // Mỗi lần engine gặp debugger; nó sẽ dừng
            pauseDebuggerInterval = setInterval(function() {
                debugger; // ⬅️ ĐÂY LÀ PAUSE THẬT
            }, 50);

            dtLog('⏸️ PAUSE - debugger activated', 'w');
        }

        function doResume() {
            if (!pauseActive) return;
            pauseActive = false;

            // Tắt overlay
            G('dtm-pause-overlay').style.display = 'none';
            G('dtm-btn-pause').textContent = '⏸️ Pause';
            G('dtm-btn-pause').style.background = '#8b0000';

            // Dừng debugger loop
            if (pauseDebuggerInterval) {
                clearInterval(pauseDebuggerInterval);
                pauseDebuggerInterval = null;
            }

            dtLog('▶️ RESUME', 's');
        }

        // ========== SOURCES TAB ==========
        function showSources() {
            var l = logEl();
            l.innerHTML = '';
            dtLog('📁 SCRIPTS:', 's');
            var ctx = getGameWindow();
            var doc;
            try { doc = ctx.document; } catch(e) { doc = document; }

            var scripts = doc.querySelectorAll('script[src]');
            scripts.forEach(function(s, i) {
                dtLog('  📄 ' + (s.src.split('/').pop() || 'script_' + i), 'i');
            });
            dtLog('📁 INLINE SCRIPTS: ' + doc.querySelectorAll('script:not([src])').length, 'i');
            dtLog('📁 CANVAS: ' + doc.querySelectorAll('canvas').length, 'i');

            // Thử lấy danh sách global vars
            var keys = Object.keys(ctx).filter(function(k) {
                return !k.startsWith('webkit') && !k.startsWith('on') && typeof ctx[k] !== 'function';
            });
            dtLog('📁 GLOBAL VARS: ' + keys.length, 'i');
            keys.slice(0, 30).forEach(function(k) {
                var v = ctx[k];
                var t = typeof v;
                if (t === 'object' && v !== null) {
                    dtLog('  📦 ' + k + ': ' + (Array.isArray(v) ? 'Array['+v.length+']' : 'Object{...}'), 'i');
                } else if (t !== 'function') {
                    dtLog('  📌 ' + k + ': ' + String(v).substring(0, 80), 'i');
                }
            });
        }

        // ========== STORAGE ==========
        function showStorage() {
            var l = logEl();
            l.innerHTML = '';
            try {
                var ls = getGameWindow().localStorage || localStorage;
                dtLog('💾 LOCAL STORAGE (' + ls.length + ' keys):', 's');
                for (var i = 0; i < ls.length; i++) {
                    var k = ls.key(i);
                    dtLog('  ' + k + ': ' + ls.getItem(k).substring(0, 150), 'i');
                }
            } catch(e) { dtLog('Lỗi: ' + e.message, 'e'); }
        }

        // ========== IFRAME INFO ==========
        function showIframe() {
            var l = logEl();
            l.innerHTML = '';
            var ifs = document.querySelectorAll('iframe');
            dtLog('🖼️ Iframes: ' + ifs.length, 'i');
            ifs.forEach(function(f, i) {
                dtLog('  #' + i + ': ' + (f.src || '(no src)'), 'i');
                try {
                    dtLog('    Canvas: ' + f.contentDocument.querySelectorAll('canvas').length, 's');
                } catch(e) {
                    dtLog('    ⚠️ Cross-origin', 'w');
                }
            });
        }

        // ========== TAB SWITCH ==========
        function switchTab(tab) {
            ['console','network','sources','storage','iframe'].forEach(function(t) {
                var b = G('dtm-tab-' + t);
                if (b) b.classList.remove('active');
            });
            var a = G('dtm-tab-' + tab);
            if (a) a.classList.add('active');
            if (logEl()) logEl().innerHTML = '';
            switch(tab) {
                case 'network': dtLog('🌐 Đang theo dõi...', 'i'); break;
                case 'sources': showSources(); break;
                case 'storage': showStorage(); break;
                case 'iframe': showIframe(); break;
                default: dtLog('📜 Sẵn sàng', 'i');
            }
        }

        // ========== EVENT BINDINGS ==========
        G('dtm-btn-close').addEventListener('click', togglePanel);
        G('dtm-btn-pause').addEventListener('click', function() {
            pauseActive ? doResume() : doPause();
        });

        // Resume từ overlay
        G('dtm-resume-btn').addEventListener('click', doResume);
        G('dtm-step-btn').addEventListener('click', function() {
            // Step: resume 1 nhịp rồi pause lại
            doResume();
            setTimeout(doPause, 100);
        });

        G('dtm-tab-console').addEventListener('click', function() { switchTab('console'); });
        G('dtm-tab-network').addEventListener('click', function() { switchTab('network'); });
        G('dtm-tab-sources').addEventListener('click', function() { switchTab('sources'); });
        G('dtm-tab-storage').addEventListener('click', function() { switchTab('storage'); });
        G('dtm-tab-iframe').addEventListener('click', function() { switchTab('iframe'); });

        G('dtm-input').addEventListener('keydown', function(e) {
            if (e.key === 'Enter') {
                e.preventDefault();
                var cmd = this.value.trim();
                if (cmd) {
                    dtLog('> ' + cmd, 'i');
                    exec(cmd);
                    this.value = '';
                }
            }
        });

        // ========== STARTUP ==========
        dtLog('✅ DevTools Mobile v2 sẵn sàng', 's');
        dtLog('👆 Bấm 🔧 để mở panel', 'i');
        dtLog('⏸️ Nút Pause = debugger thật', 'i');

    });
})();