// ==UserScript==
// @name 仿M浏览器元素审查
// @namespace https://viayoo.com/81gzxv
// @version 4.9
// @description 利用AI模仿并生成M浏览器的元素审查(感谢M浏览器原生交互灵感),在脚本菜单开启元素审查,专注精准AD规则生成与编辑,支持DOM树浏览、实时编辑(文字/代码/删除/换图/撤销)、存储管理、JS终端等功能。
// @author Via && Gemini
// @match *://*/*
// @grant GM_addStyle
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-end
// @license MIT
// @icon data:image/png;base64,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
// ==/UserScript==
(function() {
'use strict';
let isDebugMode = false;
let isLogging = false;
let isPicking = false;
let isCollapsed = false;
let currentTarget = null;
let activePreviewStyle = null;
let adUpdateTimer = null;
let historyStack = [];
let searchResults = [];
let currentSearchIdx = -1;
const DEFAULT_SVG = ``;
const host = document.createElement('div');
host.id = 'mb-debug-host';
host.style.cssText = 'position:absolute;top:0;left:0;width:0;height:0;z-index:2147483647;';
document.documentElement.appendChild(host);
const shadow = host.attachShadow({ mode: 'open' });
const api = {
addStyle: (css) => {
const style = document.createElement('style');
style.textContent = css;
shadow.appendChild(style);
},
setClipboard: (text) => {
if (typeof GM_setClipboard !== 'undefined') {
GM_setClipboard(text);
alert('已复制');
} else {
navigator.clipboard.writeText(text).then(() => alert('已复制')).catch(() => {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('已复制');
});
}
},
registerMenu: (name, fn) => {
if (typeof GM_registerMenuCommand !== 'undefined') {
GM_registerMenuCommand(name, fn);
}
},
getValue: (key, def) => {
if (typeof GM_getValue !== 'undefined') {
return GM_getValue(key, def);
}
const val = localStorage.getItem(key);
return val === null ? def : JSON.parse(val);
},
setValue: (key, val) => {
if (typeof GM_setValue !== 'undefined') {
GM_setValue(key, val);
} else {
localStorage.setItem(key, JSON.stringify(val));
}
}
};
const formatAndHighlight = (code, lang) => {
if (!code) return "";
let fmt = code.replace(/\{/g, ' {\n ').replace(/\}/g, '\n}\n').replace(/;/g, ';\n ').replace(/\n\s*\n/g, '\n');
let level = 0;
let result = fmt.split('\n').map(line => {
line = line.trim();
if (line.includes('}')) level--;
let l = ' '.repeat(Math.max(0, level)) + line;
if (line.includes('{')) level++;
return l;
}).join('\n');
if (result.length > 15000) result = result.substring(0, 15000) + "\n...[此处代码过长已截断]";
const escapeHTML = (str) => str.replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":"'"}[m]));
if (lang === 'js') {
let safeJS = escapeHTML(result);
return safeJS
.replace(/(".*?"|'.*?'|`.*?`)/g, '$1')
.replace(/\b(var|let|const|function|if|else|return|for|while|new|try|catch|async|await|case|switch|break|default)\b/g, '$1');
} else {
let safeCSS = escapeHTML(result);
return safeCSS
.replace(/^(\s*)([^{}\n]+)(\s*\{)/gm, '$1$2$3')
.replace(/:(\s*)([^;#}\n]+)(;|\n)/g, ':$1$2$3');
}
};
api.addStyle(`
:host {
--mb-bg: #ffffff; --mb-text: #333; --mb-header-bg: #f1f1f1; --mb-border: #ddd; --mb-item-bg: #fdfdfd;
--mb-code-key: #881280; --mb-code-attr: #994500; --mb-code-val: #1a1aa6;
--mb-glass-bg: rgba(255, 255, 255, 0.7); --mb-glass-border: rgba(255, 255, 255, 0.5);
font-weight: 700 !important;
font-size: 14px !important; line-height: 1.4 !important; font-family: sans-serif !important; -webkit-text-size-adjust: 100% !important;
}
@media (prefers-color-scheme: dark) {
:host {
--mb-bg: #1e1e1e; --mb-text: #ccc; --mb-header-bg: #2d2d2d; --mb-border: #444; --mb-item-bg: #252525;
--mb-code-key: #d197d9; --mb-code-attr: #deb887; --mb-code-val: #7fb4ca;
--mb-glass-bg: rgba(45, 45, 45, 0.7); --mb-glass-border: rgba(255, 255, 255, 0.1);
}
}
#mb-debug-panel {
position: fixed; left: 0; bottom: 0; width: 100%; height: 50%;
background: var(--mb-bg) !important; z-index: 2147483647 !important;
display: none; flex-direction: column; box-shadow: 0 -2px 15px rgba(0,0,0,0.3);
border-top: 1px solid var(--mb-border);
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1); color: var(--mb-text);
}
#mb-debug-panel * { text-align: left; box-sizing: border-box; font-size: 14px; }
#mb-main-stage { display: flex; flex-wrap: nowrap; width: auto; height: calc(100% - 40px); transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
.mb-page { width: 100%; flex: 0 0 100%; height: 100%; display: flex; flex-direction: column; overflow: hidden; }
#mb-debug-header { display: flex; align-items: center; background: var(--mb-header-bg); height: 40px; border-bottom: 1px solid var(--mb-border); flex-shrink: 0; padding: 0; }
.mb-header-left, .mb-header-right { flex-shrink: 0; display: flex; align-items: center; padding: 0 12px; }
.mb-header-middle { flex: 1; display: flex; align-items: center; overflow-x: auto; white-space: nowrap; }
.mb-header-middle::-webkit-scrollbar { display: none; }
.mb-tool-btn { margin-right: 18px; cursor: pointer; color: var(--mb-text); font-size: 14px; user-select: none; flex-shrink: 0; }
.mb-tool-btn.active { color: #ff4757 !important; font-weight: bold; }
#mb-btn-close { margin-right: 0; font-size: 18px; }
#mb-debug-content, #mb-ad-content, #mb-data-content, #mb-icon-content { flex: 1; overflow: auto; padding: 10px; background: var(--mb-bg) !important; }
.ad-rule-item, .data-group-box, .icon-config-card { background: var(--mb-item-bg); border: 1px solid var(--mb-border); border-radius: 6px; padding: 12px; margin-bottom: 15px; }
.ad-rule-display, .data-row-display { display: block; word-break: break-all; font-weight: bold; margin-bottom: 10px; font-size: 14px; line-height: 1.4; font-family: monospace; }
.hl-domain { color: #ff8c00; } .hl-sep { color: #007bff; } .hl-selector { color: #808080; } .hl-url { color: #ff0000; }
.ad-action-bar, .data-action-bar { display: flex; flex-wrap: wrap; gap: 8px; }
.ad-mini-btn { padding: 5px 12px; font-size: 12px !important; border: 1px solid var(--mb-border); background: var(--mb-bg); cursor: pointer; border-radius: 4px; color: var(--mb-text); }
.icon-config-row { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; font-size: 14px; }
.icon-config-row span { font-size: 14px; }
.icon-input { width: 80px; padding: 4px; border: 1px solid var(--mb-border); background: var(--mb-bg); color: var(--mb-text); border-radius: 4px; font-size: 14px; }
.icon-area { width: 100%; height: 80px; margin-top: 8px; font-family: monospace; font-size: 12px; padding: 6px; border: 1px solid var(--mb-border); background: var(--mb-bg); color: var(--mb-text); resize: none; }
.data-item-card { border-top: 1px solid var(--mb-border); padding: 8px 0; margin-top: 8px; }
.data-key-label { color: var(--mb-code-attr); font-weight: bold; font-size: 13px; }
.node-wrapper { margin-left: 14px; border-left: 1px solid var(--mb-border); font-family: monospace; font-size: 13px; }
.node-row { padding: 2px 4px; cursor: pointer; white-space: pre-wrap; word-break: break-all; display: flex; color: var(--mb-text); font-size: 13px; }
.node-row.selected { background: rgba(30, 144, 255, 0.2); outline: 1px solid #1e90ff; }
.node-row span { font-size: 13px; }
.toggle-btn { width: 18px; flex-shrink: 0; text-align: center; font-size: 10px; color: #999; cursor: pointer; }
#mb-debug-trigger { position: fixed; right: 16px; width: 32px; height: 32px; background: var(--mb-glass-bg); backdrop-filter: blur(15px) saturate(160%); -webkit-backdrop-filter: blur(15px) saturate(160%); border-radius: 14px; border: 1.5px solid var(--mb-glass-border); box-shadow: 0 6px 16px rgba(0,0,0,0.12), inset 0 0 2px rgba(255,255,255,0.8); cursor: pointer; z-index: 2147483646; display: none; align-items: center; justify-content: center; transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1); user-select: none; -webkit-tap-highlight-color: transparent; }
#mb-debug-trigger svg { filter: drop-shadow(0 1px 1.5px rgba(0,0,0,0.15)); }
#mb-js-content {height: 100%;display: flex;flex-direction: column; padding: 10px; box-sizing: border-box;background: var(--mb-bg) !important;}
#mb-js-log {flex: 1; margin-top: 10px;overflow-y: auto !important;font-family: monospace;font-size: 11px;border-top: 1px solid var(--mb-border);padding-top: 5px;-webkit-overflow-scrolling: touch;}
#mb-js-input {flex-shrink: 0; height: 100px; background: var(--mb-bg); color: var(--mb-text); border: 1px solid var(--mb-border); padding: 8px; font-size: 13px;}
.log-item { border-bottom: 0.5px solid var(--mb-border); padding: 4px 0; white-space: pre-wrap; font-size: 11px;}
.log-warn { color: #f1c40f; }
.log-error { color: #ff4757; background: rgba(255, 71, 87, 0.05); }
.log-result { color: #2ecc71; }
#mb-debug-content { display: flex; flex-direction: column; height: 100%; padding: 0 !important; margin: 0; }
#mb-dom-tree { flex: 1; overflow-y: auto; padding: 10px; }
#mb-node-actions { display: none; gap: 8px; padding: 10px; background: var(--mb-header-bg); border-top: 1px solid var(--mb-border); flex-wrap: wrap; flex-shrink: 0; }
.edit-btn { padding: 6px 12px; border-radius: 4px; border: 1px solid var(--mb-border); background: var(--mb-bg); color: var(--mb-text); cursor: pointer; font-size: 12px; font-weight: bold; }
.edit-btn.active { background: #ff4757; color: #fff; border-color: #ff4757; }
.html-edit-area { background: #1e1e1e !important; color: #fab1a0 !important; width: calc(100% - 20px); min-height: 80px; outline: none; padding: 8px; margin: 5px 0 5px 18px; font-family: monospace; font-size: 12px; display: none; white-space: pre-wrap; word-break: break-all; border: 1px dashed #ff4757; position: relative; z-index: 10; }
body.mb-editing-text a, body.mb-editing-text button { pointer-events: none !important; }
@media (min-width: 768px) {
#mb-js-content { flex-direction: row; gap: 12px; }
#mb-js-input { flex: 1; height: auto !important; }
#mb-js-log { flex: 1; margin-top: 0; border-top: none; border-left: 1px solid var(--mb-border); padding-left: 10px; }
#mb-ad-content, #mb-data-content, #mb-icon-content { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; align-content: start; }
}
`);
const globalStyle = document.createElement('style');
globalStyle.textContent = `
.mb-inspect-hl { outline: 2px dashed #ff4757 !important; outline-offset: 2px !important; background: rgba(255, 71, 87, 0.1) !important; }
body.mb-picking-mode { cursor: crosshair !important; }
body.mb-picking-mode a, body.mb-picking-mode button, body.mb-picking-mode [onclick], body.mb-picking-mode input { cursor: crosshair !important; pointer-events: auto !important; }
`;
document.head.appendChild(globalStyle);
const panel = document.createElement('div');
panel.id = 'mb-debug-panel';
panel.innerHTML = `
`;
shadow.appendChild(panel);
const trigger = document.createElement('div');
trigger.id = 'mb-debug-trigger';
shadow.appendChild(trigger);
const domContent = shadow.getElementById('mb-debug-content');
const adContent = shadow.getElementById('mb-ad-content');
const dataContent = shadow.getElementById('mb-data-content');
const iconContent = shadow.getElementById('mb-icon-content');
const stage = shadow.getElementById('mb-main-stage');
const btnPick = shadow.getElementById('mb-btn-pick');
const btnFold = shadow.getElementById('mb-btn-fold');
const jsLog = shadow.getElementById('mb-js-log');
const jsInput = shadow.getElementById('mb-js-input');
const btnLogSwitch = shadow.getElementById('btn-log-switch');
function addLog(args, type = '') {
const isManualRun = (type === 'log-result' || type === 'log-error');
if (!isManualRun && !isLogging && !type.startsWith('script')) return;
if (jsLog.childNodes.length >= 300) jsLog.lastChild.remove();
const div = document.createElement('div');
div.className = `log-item ${type}`;
div.style.cssText = "word-break:break-all;line-height:1.4;margin-bottom:2px;font-size:12px;";
const time = `[${new Date().toLocaleTimeString(undefined, {hour12: false})}] `;
const ArrayArgs = Array.isArray(args) ? args : [args];
if (type === 'log-table' || (ArrayArgs.length === 1 && ArrayArgs[0] && typeof ArrayArgs[0] === 'object' && !ArrayArgs[0].nodeType)) {
try {
const data = ArrayArgs[0];
const table = document.createElement('table');
table.style.cssText = "border-collapse:collapse;width:100%;margin:5px 0;border:1px solid var(--mb-border);font-size:10px;";
const isArr = Array.isArray(data);
const keys = isArr ? (data[0] && typeof data[0] === 'object' ? Object.keys(data[0]) : ['Value']) : Object.keys(data);
const head = table.insertRow();
['(idx)', ...keys].forEach(k => {
const th = document.createElement('th');
th.style.cssText = "border:1px solid var(--mb-border);padding:2px;background:var(--mb-header-bg);";
th.innerText = k;
head.appendChild(th);
});
const rows = isArr ? data : [data];
rows.forEach((row, i) => {
const tr = table.insertRow();
tr.insertCell().innerText = i;
keys.forEach(k => {
const td = tr.insertCell();
td.style.border = "1px solid var(--mb-border)";
const val = (isArr && typeof row !== 'object') ? row : row[k];
td.innerText = val !== undefined ? String(val) : '';
});
});
div.innerText = time;
div.appendChild(table);
} catch (e) { div.innerText = time + "[Table解析失败] " + String(ArrayArgs[0]); }
}
else if (typeof ArrayArgs[0] === 'string' && ArrayArgs[0].includes('%c')) {
div.innerText = time;
let parts = ArrayArgs[0].split('%c'), styles = ArrayArgs.slice(1);
if (parts[0]) div.appendChild(document.createTextNode(parts[0]));
for (let i = 1; i < parts.length; i++) {
const span = document.createElement('span');
span.innerText = parts[i];
if (styles[i-1]) span.style.cssText = styles[i-1];
div.appendChild(span);
}
}
else {
const out = ArrayArgs.map(arg => {
if (arg === null) return 'null';
if (typeof arg === 'object') {
try { return JSON.stringify(arg, (k,v) => typeof v === 'bigint' ? v.toString() : v, 2); }
catch(e) { return Object.prototype.toString.call(arg); }
}
return String(arg);
}).join(' ');
div.innerText = time + (out.length > 5000 ? out.substring(0, 5000) + "..." : out);
}
jsLog.prepend(div);
}
(function initLogHook() {
const levels = { log:'', error:'log-error', warn:'log-warn', info:'log-result', table:'log-table' };
for (const key in levels) {
const original = console[key];
console[key] = function(...args) {
original.apply(console, args);
const isStackIdx = new Error().stack?.includes('eval') || new Error().stack?.includes('anonymous');
if (isLogging || isStackIdx || key === 'error') {
addLog(args, levels[key]);
}
};
}
})();
function updateLogBtnUI() {
if (isLogging) {
btnLogSwitch.style.background = '#f1c40f';
btnLogSwitch.style.color = '#000';
btnLogSwitch.innerText = '停止监听';
} else {
btnLogSwitch.style.background = 'var(--mb-bg)';
btnLogSwitch.style.color = 'var(--mb-text)';
btnLogSwitch.innerText = '监听网页日志';
}
}
btnLogSwitch.onclick = () => {
isLogging = !isLogging;
updateLogBtnUI();
isLogging ? addLog("已开启网页日志监听", "log-result") : addLog("已停止网页日志监听", "log-warn");
};
function switchToPage(index) {
stage.style.transform = `translateX(-${index * 100}%)`;
const btnBack = shadow.getElementById('mb-btn-back');
if (btnBack) btnBack.style.display = (index === 0 || index === 5) ? 'none' : 'inline';
}
function updateFoldState() {
if (isCollapsed) {
panel.style.height = '40px';
btnFold.innerText = '▲展开';
} else {
const adaptiveHeight = window.innerHeight < 600 ? '40%' : '50%';
panel.style.height = adaptiveHeight;
document.body.style.paddingBottom = adaptiveHeight.replace('%', 'vh');
btnFold.innerText = '▼收起';
}
}
function startPicking() {
isPicking = true;
btnPick.classList.add('active');
document.body.classList.add('mb-picking-mode');
window.onbeforeunload = () => { if (isPicking) return "正在审查元素"; };
}
function stopPicking() {
isPicking = false;
btnPick.classList.remove('active');
document.body.classList.remove('mb-picking-mode');
window.onbeforeunload = null;
}
function togglePanel(show) {
isDebugMode = show;
panel.style.display = show ? 'flex' : 'none';
if (show) {
document.body.style.paddingBottom = '50vh';
isCollapsed = false;
updateFoldState();
startPicking();
} else {
document.body.style.removeProperty('padding-bottom');
stopPicking();
if (currentTarget) currentTarget.classList.remove('mb-inspect-hl');
}
}
function highlightAdRule(rule) {
const match = rule.match(/^(.*?)(###?)(.*)$/);
if (!match) return `${rule}`;
let rest = match[3].replace(/("(.*?)")/g, '"$2"');
return `${match[1]}${match[2]}${rest}`;
}
/* 参考了[轻量规则](https://raw.githubusercontent.com/damengzhu/abpmerge/main/abpmerge.txt) (Abpmerge)
[混合规则](https://raw.githubusercontent.com/lingeringsound/adblock_auto/main/Rules/adblock_auto_lite.txt)
!! 感谢 !!
*/
function generateSmartRules(el) {
const domain = window.location.hostname;
const tagName = el.tagName.toLowerCase();
let rules = [];
const isInvalid = (str) => !str || /^[:\d]/.test(str) || str.includes(':') || str.includes('(') || str.includes(')');
const adKeywords = /(?:(?:^|[-_ \b])(?:ad|popup|modal|gg|google|float|fixed|sticky|overlay|iframe|script)(?:$|[-_ \b]))|(?:ads|adv|banner|sponsor|推广|广告|棋牌|葡京|威尼斯|太阳城|新葡京|约炮|直播|成人|抖阴|黄播|博彩|体育|下注|开奖|娱乐城|美高梅|金沙|银河|皇冠|开元|利记|沙巴|亚星|迷情|春药)/i;
const getAttr = (node, name) => node.getAttribute(name) || "";
const hasAttr = (node, name) => node.hasAttribute(name);
const getBestSubSelector = (node) => {
if (node.id && !isInvalid(node.id)) return '#' + node.id;
const classes = Array.from(node.classList).filter(c => !isInvalid(c) && c !== 'mb-inspect-hl' && !/\d{5,}/.test(c));
if (classes.length > 0) return node.tagName.toLowerCase() + '.' + classes[0];
const style = getAttr(node, 'style');
if (style) {
const parts = style.split(';').map(s => s.trim()).filter(s => s.includes(':') && !s.includes('url'));
if (parts.length > 0) return node.tagName.toLowerCase() + '[style*="' + parts[0].split(':')[0] + '"]';
}
return node.tagName.toLowerCase();
};
if (el.id && !isInvalid(el.id)) {
rules.push(`${domain}###${el.id}`);
if (adKeywords.test(el.id)) rules.push(`###${el.id}`);
}
const classList = Array.from(el.classList).filter(c =>
!/\d{5,}/.test(c) && c.length < 35 && c !== 'mb-inspect-hl' && !isInvalid(c)
);
if (classList.length > 0) {
classList.forEach(c => {
rules.push(`${domain}##.${c}`);
if (adKeywords.test(c)) {
rules.push(`##.${c}`);
rules.push(`${domain}##${tagName}.${c}`);
}
});
if (classList.length >= 2) {
const pair = classList.slice(0, 2).join('.');
rules.push(`${domain}##.${pair}`);
if (adKeywords.test(pair)) rules.push(`##.${pair}`);
}
}
if (el.previousElementSibling) {
const prev = el.previousElementSibling;
const prevS = getBestSubSelector(prev);
const currS = getBestSubSelector(el);
if (prevS && currS) rules.push(`${domain}##${prevS} + ${currS}`);
}
let parentStr = "";
if (el.parentElement && el.parentElement.id && el.parentElement.tagName !== 'BODY') {
const currS = getBestSubSelector(el);
parentStr = `#${el.parentElement.id} > `;
rules.push(`${domain}##${parentStr}${currS}`);
}
let attrRules = [];
let sizeBundle = "";
let smartAttrCount = 0;
for (let attr of el.attributes) {
let val = attr.value;
const attrName = attr.name.toLowerCase();
if (!val || ['id', 'class'].includes(attrName)) continue;
if (['srcid', 'tpl', 'data-module'].includes(attrName)) {
rules.push(`${domain}##${tagName}[${attrName}="${val}"]`);
}
if (['width', 'height'].includes(attrName)) {
sizeBundle += `[${attrName}="${val}"]`;
continue;
}
if (attrName === 'style') {
const isFixed = /fixed|sticky|absolute/.test(val);
const isHighZ = /z-index\s*:\s*(99\d+|2147483647)/.test(val);
if (isFixed || isHighZ) {
if (isFixed) rules.push(`${domain}##${tagName}[style*="fixed"]`);
if (isHighZ) rules.push(`${domain}##${tagName}[style*="z-index"]`);
}
continue;
}
if (val.startsWith('data:')) {
const b64 = val.match(/^data:[^;]+;base64,[A-Za-z0-9+/=]{20,50}/);
if (b64) attrRules.push(`${tagName}[${attrName}^="${b64[0]}"]`);
continue;
}
const isStandard = ['src', 'href', 'title', 'alt', 'ref', 'rel', 'onclick', 'aria-label'].includes(attrName);
if (!isStandard && !attrName.startsWith('data-') && smartAttrCount < 3 && val.length < 30) {
const currentSub = getBestSubSelector(el);
rules.push(`${domain}##${parentStr}${currentSub}[${attrName}="${val}"]`);
if (val.length > 3) {
rules.push(`${domain}##${tagName}[${attrName}*="${val.substring(0, Math.floor(val.length/2))}"]`);
}
smartAttrCount++;
}
if (attrName.startsWith('data-') || isStandard) {
if (adKeywords.test(attrName) || adKeywords.test(val)) {
rules.push(`${domain}##${tagName}[${attrName}]`);
if (val.length > 0 && val.length < 50) {
const subVal = val.split('-')[0].split(' ')[0].substring(0, 20);
rules.push(`${domain}##${tagName}[${attrName}*="${subVal}"]`);
}
}
if (/^(https?:|)\/\//.test(val)) {
const m = val.match(/^((?:https?:|)\/\/[^\/]+\/)/);
if (m) attrRules.push(`${tagName}[${attrName}^="${m[1]}"]`);
} else if (val.length > 0 && val.length < 100) {
attrRules.push(`${tagName}[${attrName}*="${val}"]`);
}
}
}
if (tagName === 'a' || (tagName === 'img' && el.closest('a'))) {
const anchor = tagName === 'a' ? el : el.closest('a');
const img = anchor.querySelector('img');
const aLabel = getAttr(anchor, 'aria-label') || getAttr(anchor, 'title');
const imgAlt = img ? (getAttr(img, 'alt') || getAttr(img, 'aria-label')) : "";
const keyLabel = (aLabel || imgAlt || "").split('-')[0].substring(0, 10);
const aOnclick = hasAttr(anchor, 'onclick');
const aRef = getAttr(anchor, 'ref') || getAttr(anchor, 'rel');
if (aOnclick || adKeywords.test(aRef)) {
let base = aOnclick ? `a[onclick]` : `a[ref*="sponsored"]`;
if (keyLabel) {
rules.push(`${domain}##${base}[aria-label*="${keyLabel}"]`);
if (img) rules.push(`${domain}##${base} > img[alt*="${keyLabel}"]`);
if (img) rules.push(`${domain}##a[href] img[alt*="${keyLabel}"]`);
}
if (img && hasAttr(img, 'data-src')) rules.push(`${domain}##${base} img[data-src]`);
}
}
if (sizeBundle) rules.push(`${domain}##${tagName}${sizeBundle}`);
attrRules.forEach(r => rules.push(`${domain}##${r}`));
const adTags = ['iframe', 'embed', 'ins', 'object'];
if (adTags.includes(tagName)) rules.push(`${domain}##${tagName}`);
rules = [...new Set(rules)];
rules.sort((a, b) => {
const getWeight = (s) => {
if (s.includes(' + ')) return 0;
const hasDomain = s.includes(domain);
if (adKeywords.test(s)) return hasDomain ? 1 : 2;
if (s.includes('[onclick]') || s.includes('[srcid') || s.includes('[tpl') || s.includes('[ref*=')) return 2;
if (s.includes('###') || s.includes('##.')) return hasDomain ? 3 : 4;
if (s.includes('[width')) return 5;
if (s.includes(' > ')) return 6;
return 7;
};
const wa = getWeight(a), wb = getWeight(b);
return wa !== wb ? wa - wb : a.length - b.length;
});
const genericTags = ['div', 'span', 'p', 'li', 'ul', 'ins', 'section', 'article'];
return rules.filter(r => {
const selector = r.split(/###?/)[1];
const hasSmartAttr = /\[(?!(?:style|width|height|class|id)\b)[^\]]+[\*^]?=/.test(selector);
if (selector.includes('[srcid') || selector.includes('[tpl') || r.includes(' + ') || hasSmartAttr) return true;
return !genericTags.includes(selector);
});
}
function renderAdPage() {
if (!adContent) return;
adContent.innerHTML = '';
if (!currentTarget) {
adContent.innerHTML = '请先在网页上点击选取一个元素...
';
return;
}
const rules = generateSmartRules(currentTarget);
// 优先调用window.via.record
// Via广告标记模拟函数
window.viaApiInject = function(rule) {
if (!rule) return;
let host = location.hostname;
let filter = rule.trim();
if (filter.includes('##')) {
const parts = filter.split('##');
host = parts[0].trim() || location.host;
filter = parts[1].trim();
}
if (typeof window.via === "object" && typeof window.via.record === "function") {
try {
window.via.record(host, filter);
window.via.toast(`添加一条自定义规则${host}${filter}`);
return;
} catch (e) {
console.error(e);
}
}
if (!window.via?.cmd) return;
let target = filter;
const _s = JSON.stringify;
JSON.stringify = function(a) {
if (target && a && typeof a === 'object') {
if (a.action === 102 || a.action === 103) {
a.host = host;
a.filter = target;
return _s.apply(this, [a]);
}
}
return _s.apply(this, arguments);
};
via.cmd(517);
setTimeout(() => {
const el = document.createElement('via-proxy');
el.style.cssText = "position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none;";
document.body.appendChild(el);
el.click();
if (window.__getMarkerFilter) window.__getMarkerFilter();
setTimeout(() => {
el.remove();
target = "";
JSON.stringify = _s;
if (window.__setMarkerEnabled) window.__setMarkerEnabled(false);
}, 300);
}, 100);
};
if (rules.length === 0) {
adContent.innerHTML = '该元素特征不足,未生成自动规则。
';
return;
}
rules.forEach(ruleText => {
let currentRule = ruleText;
const originalRule = ruleText;
const item = document.createElement('div');
item.className = 'ad-rule-item';
const updateUI = (isEditing = false) => {
const isVia = !!(window.via && window.via.cmd);
// 调用X浏览器Api添加广告规则
const isX = !!(window.mbrowser && window.mbrowser.addCustomAdRule);
let btnText = isVia ? '添加到Via' : (isX ? '添加到Xbrowser' : '复制');
item.innerHTML = `
${isEditing ? currentRule : highlightAdRule(currentRule)}
${isEditing ? `
` : `
`}
`;
if (isEditing) {
const display = item.querySelector('.ad-rule-display');
setTimeout(() => display.focus(), 10);
item.querySelector('.btn-save').onclick = () => { currentRule = display.innerText.trim(); updateUI(false); };
item.querySelector('.btn-undo').onclick = () => { currentRule = originalRule; updateUI(false); };
} else {
item.querySelector('.btn-copy').onclick = () => {
if (isX) window.mbrowser.addCustomAdRule(currentRule);
else if (isVia) window.viaApiInject(currentRule);
else api.setClipboard(currentRule);
};
item.querySelector('.btn-edit').onclick = () => updateUI(true);
item.querySelector('.btn-pre').onclick = () => {
if (activePreviewStyle) activePreviewStyle.remove();
activePreviewStyle = document.createElement('style');
try {
const isIdRule = currentRule.includes('###');
let selector = currentRule.split(/###?/)[1];
if (isIdRule && !selector.startsWith('#')) selector = '#' + selector;
activePreviewStyle.innerHTML = `${selector} { display: none !important; }`;
document.head.appendChild(activePreviewStyle);
} catch (e) { alert("语法错误"); }
};
item.querySelector('.btn-res').onclick = () => { if (activePreviewStyle) activePreviewStyle.remove(); };
}
};
updateUI();
adContent.appendChild(item);
});
}
function renderDataPage() {
dataContent.innerHTML = '';
const getPaths = () => {
const p = window.location.pathname;
const s = p.split('/').filter(Boolean);
const r = ['/', p];
let c = '';
s.forEach(seg => { c += '/' + seg; r.push(c); });
return [...new Set(r)];
};
const getDoms = () => {
const d = window.location.hostname;
const r = [d, '.' + d];
const p = d.split('.');
if (p.length > 2) {
const root = p.slice(-2).join('.');
r.push(root, '.' + root, 'www.' + root, '.www.' + root);
}
return [...new Set(r)];
};
const configs = [
{
label: 'Cookies', emoji: '🍪',
get: () => document.cookie.split('; ').reduce((acc, c) => { const [k, v] = c.split('='); if (k) acc[k] = decodeURIComponent(v); return acc; }, {}),
set: (k, v) => { document.cookie = `${encodeURIComponent(k)}=${encodeURIComponent(v)}; path=/`; },
del: (k) => {
const ps = getPaths();
const ds = getDoms();
ps.forEach(p => {
ds.forEach(d => {
document.cookie = `${encodeURIComponent(k)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${p}; domain=${d}`;
document.cookie = `${encodeURIComponent(k)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${p}; domain=${d}; SameSite=None; Secure`;
document.cookie = `${encodeURIComponent(k)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${p}`;
});
});
}
},
{ label: 'SessionStorage', emoji: '💬', get: () => ({ ...sessionStorage }), set: (k, v) => sessionStorage.setItem(k, v), del: (k) => sessionStorage.removeItem(k) },
{ label: 'LocalStorage', emoji: '🌐', get: () => ({ ...localStorage }), set: (k, v) => localStorage.setItem(k, v), del: (k) => localStorage.removeItem(k) }
];
configs.forEach(conf => {
const data = conf.get();
const box = document.createElement('div');
box.className = 'data-group-box';
const keys = Object.keys(data);
box.innerHTML = `
${conf.emoji} ${conf.label} (${keys.length})
`;
const list = box.querySelector('.data-items-list');
box.onclick = (e) => { if (e.target === box || e.target.parentElement === box) list.style.display = list.style.display === 'none' ? 'block' : 'none'; };
box.querySelector('.btn-copy-all').onclick = (e) => { e.stopPropagation(); api.setClipboard(JSON.stringify(data, null, 2)); };
box.querySelector('.btn-clear-all').onclick = (e) => { e.stopPropagation(); if (confirm('确定清理全部?')) { keys.forEach(k => conf.del(k)); renderDataPage(); } };
keys.forEach(k => {
const item = document.createElement('div');
item.className = 'data-item-card';
let currentVal = data[k];
const updateItemUI = (isEditing = false) => {
item.innerHTML = `
${k} :
${currentVal}
${isEditing ? `` : ``}
`;
if (isEditing) {
const vDom = item.querySelector('.data-val-text');
setTimeout(() => vDom.focus(), 10);
item.querySelector('.btn-save').onclick = () => { conf.set(k, vDom.innerText.trim()); currentVal = vDom.innerText.trim(); updateItemUI(false); };
item.querySelector('.btn-cancel').onclick = () => updateItemUI(false);
} else {
item.querySelector('.btn-copy').onclick = () => api.setClipboard(currentVal);
item.querySelector('.btn-edit').onclick = () => updateItemUI(true);
item.querySelector('.btn-del').onclick = () => { conf.del(k); item.remove(); };
}
};
updateItemUI();
list.appendChild(item);
});
dataContent.appendChild(box);
});
}
function renderIconPage() {
iconContent.innerHTML = `
自定义 SVG 代码
`;
shadow.getElementById('btn-icon-save').onclick = () => {
api.setValue('mb_icon_size', parseInt(shadow.getElementById('in-icon-size').value));
api.setValue('mb_icon_bottom', parseInt(shadow.getElementById('in-icon-bottom').value));
api.setValue('mb_icon_right', parseInt(shadow.getElementById('in-icon-right').value));
api.setValue('mb_icon_svg', shadow.getElementById('in-icon-svg').value);
updateIconStyle();
alert('保存成功');
};
shadow.getElementById('btn-icon-reset').onclick = () => {
if(confirm('确定恢复默认图标设置吗?')){
api.setValue('mb_icon_size', 32);
api.setValue('mb_icon_bottom', 95);
api.setValue('mb_icon_right', 16);
api.setValue('mb_icon_svg', DEFAULT_SVG);
renderIconPage();
updateIconStyle();
}
};
shadow.getElementById('btn-icon-temp-hide').onclick = () => {
if (trigger) trigger.style.display = 'none';
alert('图标已临时隐藏,刷新页面即可恢复。');
};
}
function renderSearchUI(bar) {
bar.innerHTML = `
`;
const input = bar.querySelector('#mb-search-input');
const countLab = bar.querySelector('#mb-search-count');
const stopProp = (e) => e.stopPropagation();
const evTypes = ['keydown', 'keyup', 'keypress', 'input', 'touchstart', 'mousedown', 'click'];
const updateSelection = () => {
if (searchResults.length > 0) {
currentSearchIdx = (currentSearchIdx + searchResults.length) % searchResults.length;
const target = searchResults[currentSearchIdx];
if (currentTarget) currentTarget.classList.remove('mb-inspect-hl');
currentTarget = target;
currentTarget.classList.add('mb-inspect-hl');
const treeContainer = shadow.getElementById('mb-dom-tree');
if (treeContainer) {
treeContainer.innerHTML = '';
const parent = currentTarget.parentElement || currentTarget;
treeContainer.appendChild(buildTree(parent, true));
setTimeout(() => {
const targetLine = treeContainer.querySelector('.mb-tree-line.active');
if (targetLine) {
targetLine.scrollIntoView({ behavior: 'auto', block: 'center' });
}
}, 100);
}
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
countLab.innerText = `结果: ${currentSearchIdx + 1} / ${searchResults.length}`;
} else {
countLab.innerText = '未找到匹配';
}
};
const doSearch = () => {
const val = input.value.trim().toLowerCase();
if (!val) { countLab.innerText = '请输入内容'; return; }
input.blur();
searchResults = [];
currentSearchIdx = 0;
countLab.innerText = '搜索中...';
setTimeout(() => {
try {
const selectorMatches = document.querySelectorAll(val);
selectorMatches.forEach(el => {
if (!host.contains(el) && el !== document.documentElement && el !== document.body) searchResults.push(el);
});
} catch (e) {}
const allElements = document.querySelectorAll('*');
allElements.forEach(el => {
if (host.contains(el) || searchResults.includes(el)) return;
const matchTag = el.tagName.toLowerCase().includes(val);
const matchText = Array.from(el.childNodes).some(n => n.nodeType === 3 && n.textContent.toLowerCase().includes(val));
const matchAttr = Array.from(el.attributes).some(a => a.name.toLowerCase().includes(val) || a.value.toLowerCase().includes(val));
if (matchTag || matchText || matchAttr) searchResults.push(el);
});
searchResults = [...new Set(searchResults)];
updateSelection();
}, 100);
};
evTypes.forEach(type => input.addEventListener(type, stopProp, { capture: true }));
bar.querySelector('#btn-search-go').onclick = (e) => { e.stopPropagation(); doSearch(); };
bar.querySelector('#btn-search-prev').onclick = (e) => { e.stopPropagation(); if (searchResults.length) { currentSearchIdx--; updateSelection(); } };
bar.querySelector('#btn-search-next').onclick = (e) => { e.stopPropagation(); if (searchResults.length) { currentSearchIdx++; updateSelection(); } };
bar.querySelector('#btn-search-exit').onclick = (e) => { e.stopPropagation(); evTypes.forEach(type => input.removeEventListener(type, stopProp, { capture: true })); searchResults = []; currentSearchIdx = -1; bar.removeAttribute('data-mode'); renderDOM(); if (currentTarget) highlight(currentTarget); };
setTimeout(() => input.focus(), 100);
}
const clearAllHighlights = () => {
const highlighters = shadow.querySelectorAll('[id^="mb-highlighter"]');
highlighters.forEach(el => el.remove());
if (currentTarget) {
currentTarget.classList.remove('mb-inspect-hl');
currentTarget.contentEditable = 'false';
currentTarget.style.outline = '';
currentTarget.style.backgroundColor = '';
}
};
const preventInteraction = (e) => {
if (host.contains(e.target)) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
return false;
};
function updateNodeActions() {
const actionsBar = shadow.getElementById('mb-node-actions');
if (!currentTarget || !actionsBar) return;
actionsBar.style.display = 'flex';
const isSearching = actionsBar.getAttribute('data-mode') === 'search';
if (isSearching) {
renderSearchUI(actionsBar);
return;
}
actionsBar.innerHTML = '';
const btnHtml = document.createElement('button'); btnHtml.className = 'edit-btn'; btnHtml.id = 'btn-edit-html'; actionsBar.appendChild(btnHtml);
const btnEdit = document.createElement('button'); btnEdit.className = 'edit-btn'; btnEdit.id = 'btn-edit-node'; actionsBar.appendChild(btnEdit);
const btnImg = document.createElement('button'); btnImg.className = 'edit-btn'; btnImg.id = 'btn-edit-img'; actionsBar.appendChild(btnImg);
const btnDel = document.createElement('button'); btnDel.className = 'edit-btn'; btnDel.id = 'btn-del-node'; btnDel.style.color = '#e74c3c'; btnDel.innerText = '✂️删除'; actionsBar.appendChild(btnDel);
const btnHide = document.createElement('button'); btnHide.className = 'edit-btn'; btnHide.id = 'btn-hide-inspect'; btnHide.style.cssText = 'color:#95a5a6; border-color:#95a5a6;'; btnHide.innerText = '🚫隐藏选取'; actionsBar.appendChild(btnHide);
const btnSearch = document.createElement('button'); btnSearch.className = 'edit-btn'; btnSearch.id = 'btn-search-node'; btnSearch.style.cssText = 'color:#3498db; border-color:#3498db;'; btnSearch.innerText = '🔍搜索元素'; actionsBar.appendChild(btnSearch);
const btnUndo = document.createElement('button'); btnUndo.className = 'edit-btn'; btnUndo.id = 'btn-undo-node'; btnUndo.style.cssText = 'color:#2ecc71; border-color:#2ecc71;'; btnUndo.innerText = '↩️撤销'; actionsBar.appendChild(btnUndo);
const selectedRow = shadow.querySelector('.node-row.selected');
const editArea = selectedRow ? selectedRow.querySelector('.html-edit-area') : null;
const isHtmlEditing = editArea && editArea.style.display === 'block';
btnHtml.innerText = isHtmlEditing ? '✅同步代码' : '🏗️代码模式';
btnHtml.classList.toggle('active', isHtmlEditing);
btnEdit.innerText = (currentTarget.contentEditable === 'true' || currentTarget.getAttribute('contenteditable') === 'true') ? '✅完成文字' : '📝文字模式';
btnEdit.classList.toggle('active', (currentTarget.contentEditable === 'true' || currentTarget.getAttribute('contenteditable') === 'true'));
btnUndo.style.display = historyStack.length > 0 ? 'block' : 'none';
const isImg = currentTarget.tagName === 'IMG';
const hasBg = window.getComputedStyle(currentTarget).backgroundImage !== 'none';
btnImg.style.display = (isImg || hasBg) ? 'block' : 'none';
btnImg.innerText = '🖼️换图';
const saveHistory = () => {
const parent = currentTarget.parentElement;
if (!parent) return;
const index = Array.from(parent.children).indexOf(currentTarget);
historyStack.push({ parent, index, outerHTML: currentTarget.outerHTML });
};
const finishTextEdit = () => {
currentTarget.setAttribute('contenteditable', 'false');
currentTarget.style.outline = ''; currentTarget.style.backgroundColor = '';
currentTarget.onblur = null; currentTarget.onkeydown = null;
['click', 'mousedown', 'mouseup', 'submit'].forEach(evName => { document.removeEventListener(evName, preventInteraction, { capture: true }); });
isCollapsed = false; updateFoldState(); startPicking(); clearAllHighlights(); renderDOM(); highlight(currentTarget); updateNodeActions();
};
btnSearch.onclick = (e) => { e.stopPropagation(); actionsBar.setAttribute('data-mode', 'search'); updateNodeActions(); };
btnHide.onclick = (e) => { e.stopPropagation(); clearAllHighlights(); currentTarget = null; renderDOM(); };
btnHtml.onclick = (e) => {
e.stopPropagation(); if (!editArea) return;
if (editArea.style.display !== 'block') {
saveHistory(); editArea.style.display = 'block';
const contentSpan = selectedRow.querySelector('.node-content');
if (contentSpan) contentSpan.style.display = 'none';
editArea.setAttribute('contenteditable', 'true'); editArea.innerText = currentTarget.innerHTML;
setTimeout(() => editArea.focus(), 50);
} else {
currentTarget.innerHTML = editArea.innerText;
clearAllHighlights(); renderDOM(); highlight(currentTarget);
}
updateNodeActions();
};
btnEdit.onclick = (e) => {
e.stopPropagation();
if (currentTarget.contentEditable !== 'true' && currentTarget.getAttribute('contenteditable') !== 'true') {
saveHistory(); stopPicking();
['click', 'mousedown', 'mouseup', 'submit'].forEach(evName => { document.addEventListener(evName, preventInteraction, { capture: true });});
isCollapsed = true; updateFoldState();
currentTarget.setAttribute('contenteditable', 'true');
currentTarget.style.outline = '2px dashed #ff4757';
setTimeout(() => {
currentTarget.focus();
try {
const range = document.createRange(); const sel = window.getSelection();
range.selectNodeContents(currentTarget); range.collapse(false);
sel.removeAllRanges(); sel.addRange(range);
} catch (err) {}
}, 0);
currentTarget.onblur = () => finishTextEdit();
currentTarget.onkeydown = (ev) => { if (ev.key === 'Enter' && !ev.shiftKey) { ev.preventDefault(); currentTarget.blur(); } };
} else {
finishTextEdit();
}
updateNodeActions();
};
btnDel.onclick = (e) => {
e.stopPropagation();
if (confirm('确定删除该元素?')) {
saveHistory(); clearAllHighlights();
const p = currentTarget.parentElement;
const nextTarget = currentTarget.nextElementSibling || currentTarget.previousElementSibling || p;
currentTarget.remove();
currentTarget = (nextTarget && nextTarget !== document.documentElement) ? nextTarget : null;
renderDOM(); if (currentTarget) highlight(currentTarget);
}
};
btnImg.onclick = (e) => {
e.stopPropagation();
const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*';
input.onchange = ev => {
const reader = new FileReader();
reader.onload = (rev) => { saveHistory(); clearAllHighlights(); if (currentTarget.tagName === 'IMG') currentTarget.src = rev.target.result; else currentTarget.style.backgroundImage = `url(${rev.target.result})`; renderDOM(); highlight(currentTarget); };
reader.readAsDataURL(ev.target.files[0]);
};
input.click();
};
btnUndo.onclick = (e) => {
e.stopPropagation(); const last = historyStack.pop(); if (!last || !last.parent) return;
if (currentTarget) {
currentTarget.setAttribute('contenteditable', 'false'); currentTarget.onblur = null; currentTarget.onkeydown = null; currentTarget.style.outline = '';
['click', 'mousedown', 'mouseup', 'submit'].forEach(evName => { document.removeEventListener(evName, preventInteraction, { capture: true }); });
}
clearAllHighlights();
const temp = document.createElement('div'); temp.innerHTML = last.outerHTML;
const restoredNode = temp.firstElementChild;
const existingNode = last.parent.children[last.index];
if (existingNode) existingNode.replaceWith(restoredNode); else last.parent.appendChild(restoredNode);
currentTarget = restoredNode; isCollapsed = false; updateFoldState(); startPicking(); renderDOM(); highlight(currentTarget); updateNodeActions();
};
}
function updateIconStyle() {
if (!trigger) return;
const size = api.getValue('mb_icon_size', 32);
const bottom = api.getValue('mb_icon_bottom', 95);
const right = api.getValue('mb_icon_right', 16);
const svgCode = api.getValue('mb_icon_svg', DEFAULT_SVG);
trigger.style.width = size + 'px';
trigger.style.height = size + 'px';
trigger.style.bottom = bottom + 'px';
trigger.style.right = right + 'px';
trigger.innerHTML = svgCode;
const svgEl = trigger.querySelector('svg');
if (svgEl) {
svgEl.style.width = (size * 0.7) + 'px';
svgEl.style.height = (size * 0.7) + 'px';
}
}
function buildTree(el, isRoot = false) {
if (!el) return null;
if (el.nodeType === 3) {
const text = el.textContent.trim();
if (!text) return null;
const textDiv = document.createElement('div');
textDiv.className = 'node-row';
textDiv.style.cssText = "margin-left: 18px; white-space: pre-wrap; cursor: default;";
textDiv.innerText = text.length > 8000 ? text.substring(0, 8000) + "..." : text;
return textDiv;
}
if (el.nodeType !== 1) return null;
const wrapper = document.createElement('div');
wrapper.className = 'node-wrapper';
const row = document.createElement('div');
const isSelected = el === currentTarget;
row.className = 'node-row' + (isSelected ? ' selected' : '');
const hasChildren = el.childNodes.length > 0;
const arrow = document.createElement('span');
arrow.className = 'toggle-btn';
arrow.innerText = (hasChildren && (isRoot || isSelected)) ? '▼' : (hasChildren ? '▶' : ' ');
row.appendChild(arrow);
let html = `<${el.tagName.toLowerCase()}`;
for (let attr of el.attributes) {
let val = attr.value;
if (attr.name === 'class') { val = val.replace('mb-inspect-hl', '').trim(); if (!val) continue; }
html += ` ${attr.name}="${val}"`;
}
html += `>`;
const label = document.createElement('span');
label.className = 'node-content';
label.innerHTML = html;
row.appendChild(label);
if (isSelected) {
const editArea = document.createElement('div');
editArea.className = 'html-edit-area';
editArea.innerText = el.innerHTML;
editArea.onclick = (e) => e.stopPropagation();
editArea.onkeydown = (e) => e.stopPropagation();
row.appendChild(editArea);
if (el.contentEditable === 'true') {
el.style.outline = '2px dashed #ff4757';
el.style.backgroundColor = 'rgba(255,71,87,0.1)';
}
}
const isInternalScript = el.tagName === 'SCRIPT' && !el.hasAttribute('src') && el.textContent.trim().length > 0;
const isInternalStyle = el.tagName === 'STYLE' && !el.hasAttribute('href') && el.textContent.trim().length > 0;
if (isInternalScript || isInternalStyle) {
const viewBtn = document.createElement('span');
viewBtn.innerText = ' [查看代码]';
viewBtn.style.cssText = "color:#007aff; cursor:pointer; font-weight:bold; margin-left:8px;";
viewBtn.onclick = (e) => {
e.stopPropagation();
const display = shadow.getElementById('mb-code-display');
const title = shadow.getElementById('mb-code-title');
const isJS = el.tagName === 'SCRIPT';
title.innerText = isJS ? 'JavaScript 格式化查看' : 'CSS 格式化查看';
display.innerHTML = formatAndHighlight(el.textContent, isJS ? 'js' : 'css');
shadow.getElementById('mb-btn-code-copy').onclick = () => api.setClipboard(el.textContent);
switchToPage(5);
};
row.appendChild(viewBtn);
}
wrapper.appendChild(row);
const cBox = document.createElement('div');
if (hasChildren && (isRoot || isSelected)) {
cBox.style.display = 'block';
Array.from(el.childNodes).forEach(c => { const childNode = buildTree(c, false); if (childNode) cBox.appendChild(childNode); });
} else { cBox.style.display = 'none'; }
wrapper.appendChild(cBox);
arrow.onclick = (e) => {
e.stopPropagation();
if (cBox.style.display === 'none') {
if (cBox.innerHTML === '') {
Array.from(el.childNodes).forEach(c => { const childNode = buildTree(c, false); if (childNode) cBox.appendChild(childNode); });
}
cBox.style.display = 'block'; arrow.innerText = '▼';
} else { cBox.style.display = 'none'; arrow.innerText = '▶'; }
};
row.onclick = (e) => { e.stopPropagation(); highlight(el); renderDOM(); };
return wrapper;
}
function renderDOM() {
const treeContainer = shadow.getElementById('mb-dom-tree');
if (!treeContainer) return;
treeContainer.innerHTML = '';
if (!currentTarget) {
const bar = shadow.getElementById('mb-node-actions');
if (bar) bar.style.display = 'none';
return;
}
const parent = currentTarget.parentElement || currentTarget;
treeContainer.appendChild(buildTree(parent, true));
updateNodeActions();
setTimeout(() => {
const selected = treeContainer.querySelector('.node-row.selected');
if (selected) selected.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 50);
}
function highlight(el) {
if (currentTarget) currentTarget.classList.remove('mb-inspect-hl');
currentTarget = el;
currentTarget.classList.add('mb-inspect-hl');
}
function updateTriggerVisibility() {
if (trigger) {
trigger.style.display = api.getValue('mb_icon_visible', true) ? 'flex' : 'none';
}
}
function toggleIconVisible() {
const currentState = api.getValue('mb_icon_visible', true);
api.setValue('mb_icon_visible', !currentState);
updateTriggerVisibility();
}
function createDebugTrigger() {
updateIconStyle();
updateTriggerVisibility();
}
btnPick.onclick = (e) => { e.stopPropagation(); isPicking ? stopPicking() : startPicking(); };
btnFold.onclick = (e) => { e.stopPropagation(); isCollapsed = !isCollapsed; updateFoldState(); };
shadow.getElementById('mb-btn-to-js').onclick = () => switchToPage(1);
shadow.getElementById('mb-btn-to-ad').onclick = () => { if (!currentTarget) return; renderAdPage(); switchToPage(2); };
shadow.getElementById('mb-btn-to-data').onclick = () => { renderDataPage(); switchToPage(3); };
shadow.getElementById('mb-btn-to-icon').onclick = () => { renderIconPage(); switchToPage(4); };
shadow.getElementById('mb-btn-back').onclick = () => switchToPage(0);
shadow.getElementById('mb-btn-code-back').onclick = () => switchToPage(0);
shadow.getElementById('mb-btn-close').onclick = () => togglePanel(false);
shadow.getElementById('mb-btn-copy-html').onclick = () => { if (currentTarget) {const clone = currentTarget.cloneNode(true);clone.classList.remove('mb-inspect-hl');if (clone.classList.length === 0) clone.removeAttribute('class');api.setClipboard(clone.outerHTML);}};
shadow.getElementById('mb-btn-parent').onclick = () => { if (currentTarget && currentTarget.parentElement) { highlight(currentTarget.parentElement); renderDOM(); } };
shadow.getElementById('mb-btn-restore').onclick = () => { if (activePreviewStyle) { activePreviewStyle.remove(); activePreviewStyle = null; } };
shadow.getElementById('btn-js-clear').onclick = () => { jsLog.innerHTML = ''; };
shadow.getElementById('btn-js-run').onclick = () => { const code = jsInput.value.trim(); if (!code) return; try { const result = window.eval(code); if (result !== undefined) addLog(result, 'log-result'); } catch (e) { addLog(e.stack || e.message, 'log-error');}};
shadow.getElementById('btn-js-copy-all').onclick = () => { const logs = Array.from(jsLog.querySelectorAll('.log-item')); if (logs.length === 0) { alert('没有可复制的日志'); return;} const text = logs.map(el => el.innerText).reverse().join('\n'); api.setClipboard(text);};
api.registerMenu("开启/关闭审查面板", () => togglePanel(!isDebugMode));
api.registerMenu("显示/隐藏悬浮图标", () => toggleIconVisible());
if (document.readyState === 'complete') createDebugTrigger();
else window.addEventListener('load', createDebugTrigger);
trigger.onclick = () => togglePanel(!isDebugMode);
let startX, startY;
const handler = (e) => {
if (!isDebugMode || !isPicking || host.contains(e.target)) return;
if (e.type === 'mousedown' || e.type === 'touchstart' || e.type === 'pointerdown') {
const touch = e.touches ? e.touches[0] : e; startX = touch.clientX; startY = touch.clientY;
return;
}
if (e.type === 'click' || e.type === 'pointerup' || e.type === 'touchend') {
const touch = e.changedTouches ? e.changedTouches[0] : e; const diffX = Math.abs(touch.clientX - startX); const diffY = Math.abs(touch.clientY - startY);
if (diffX < 10 && diffY < 10) {
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); highlight(e.target);
if (e.target.tagName === 'SCRIPT' || e.target.tagName === 'STYLE') {
if (window.mbFormatTimer) clearTimeout(window.mbFormatTimer);
window.mbFormatTimer = setTimeout(() => { if (currentTarget === e.target) renderDOM(); }, 600);
}
renderDOM();
const transform = stage.style.transform;
const isAtAdPage = transform.includes('translateX(-200%)');
if (isAtAdPage) {
if (adUpdateTimer) clearTimeout(adUpdateTimer);
adUpdateTimer = setTimeout(() => {
renderAdPage();
}, 500);
}
if (isCollapsed) { isCollapsed = false; updateFoldState(); }
return false;
}
}
};
const events = ['mousedown', 'touchstart', 'pointerdown', 'click', 'pointerup', 'touchend'];
events.forEach(type => { window.addEventListener(type, handler, { capture: true, passive: false });
});
})();