DevTools ẩn với Sources Browser + Pause Debug
// ==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);
})();