Greasy Fork

来自缓存

Greasy Fork is available in English.

Web性能优化工具箱(主版-最终优化版)

SPA兼容懒加载+预连接+硬件加速+集成Core Web Vitals面板(空闲调度完美适配/Shadow DOM隔离/安全补丁)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Web性能优化工具箱(主版-最终优化版)
// @namespace    http://tampermonkey.net/
// @version      3.8.3-final
// @description  SPA兼容懒加载+预连接+硬件加速+集成Core Web Vitals面板(空闲调度完美适配/Shadow DOM隔离/安全补丁)
// @author       KiwiFruit
// @match        *://*/*
// @exclude      *://*.weibo.com/*
// @exclude      *://*.x.com/*
// @exclude      *://*.chat.z.ai*
// @exclude      *://*.doubao.com/*
// @grant        none
// @license      MIT
// @run-at       document-start
// ==/UserScript==
(function() {
'use strict';

// ========================
// 1. 环境检测与工具函数(增强版)
// ========================
const Env = {
    features: {
        nativeLazyLoad: 'loading' in HTMLImageElement.prototype,
        intersectionObserver: 'IntersectionObserver' in window,
        mutationObserver: 'MutationObserver' in window,
        performanceObserver: 'PerformanceObserver' in window,
        requestIdleCallback: 'requestIdleCallback' in window,
        contentVisibility: CSS.supports('content-visibility', 'hidden'),
        shadowDOM: 'attachShadow' in Element.prototype
    },
    performanceTier: (() => {
        if (navigator.hardwareConcurrency >= 4) return 2;
        if (window.devicePixelRatio <= 1.5) return 1;
        return 0;
    })(),
    networkType: navigator.connection?.effectiveType || 'unknown'
};

const Utils = {
    ric: (cb, timeout = 2000) => {
        if (Env.features.requestIdleCallback) {
            return requestIdleCallback(cb, { timeout });
        }
        return setTimeout(() => cb({ timeRemaining: () => 16, didTimeout: false }), 16);
    },
    cancelRic: (id) => {
        if (!id) return;
        if (Env.features.requestIdleCallback) {
            try { cancelIdleCallback(id); } catch (e) {}
        } else {
            clearTimeout(id);
        }
    },
    throttle: (fn, limit) => {
        let inThrottle;
        return function(...args) {
            if (!inThrottle) {
                fn.apply(this, args);
                inThrottle = true;
                setTimeout(() => { inThrottle = false; }, limit);
            }
        };
    }
};

// ========================
// 2. 统一配置
// ========================
const Config = {
    debug: false,
    ui: {
        enabled: true,
        zIndex: 2147483647,
        autoHideDelay: 3000,
        triggerDistance: 120,
        statsUpdateInterval: 3000,
        width: 320
    },
    lazyLoad: {
        enabled: true,
        selector: 'img[data-src]:not([data-src=""]), img[original-src], img[data-original], img.lazy, iframe[data-src]',
        preloadDistance: 120,
        useMutationObserver: true
    },
    hardwareAcceleration: {
        enabled: true,
        selector: 'header, nav, aside, .sticky, .fixed'
    },
    contentVisibility: {
        enabled: true,
        selector: '[data-perfopt-cv]'
    },
    preconnect: {
        enabled: true,
        dynamic: true,
        maxDomains: 6
    },
    blacklistedDomains: ['weibo.com', 'weibo.cn', 'x.com', 'chat.z.ai', 'doubao.com', 'kimi.com', 'qianwen.com']
};

const Logger = {
    info: (m, msg) => Config.debug && console.log(`[PerfOpt][${m}]`, msg),
    warn: (m, msg) => console.warn(`[PerfOpt][${m}]`, msg),
    error: (m, msg) => console.error(`[PerfOpt][${m}]`, msg)
};

// ========================
// 3. 基础模块类
// ========================
class BaseModule {
    constructor(name) {
        this.moduleName = name;
        this.initialized = false;
        this._handlers = [];
        this._timers = [];
        this._observers = [];
    }
    init() {
        if (this.initialized) return;
        this.initialized = true;
    }
    on(target, type, fn, options) {
        if (!target || typeof target.addEventListener !== 'function') return;
        target.addEventListener(type, fn, options);
        this._handlers.push({ target, type, fn });
    }
    setTimer(fn, delay) {
        const id = setTimeout(fn, delay);
        this._timers.push(id);
        return id;
    }
    clearTimer(id) {
        const idx = this._timers.indexOf(id);
        if (idx > -1) { clearTimeout(id); this._timers.splice(idx, 1); }
    }
    setInterval(fn, delay) {
        const id = setInterval(fn, delay);
        this._timers.push(id);
        return id;
    }
    observeMutations(callback, target, options) {
        if (!Env.features.mutationObserver || !target) return null;
        const mo = new MutationObserver(Utils.throttle(callback, 100));
        mo.observe(target, options || { childList: true, subtree: true });
        this._observers.push(mo);
        return mo;
    }
    destroy() {
        this._handlers.forEach(({ target, type, fn }) => {
            try { target.removeEventListener(type, fn); } catch (e) {}
        });
        this._handlers = [];
        this._timers.forEach(id => { try { clearTimeout(id); clearInterval(id); } catch (e) {} });
        this._timers = [];
        this._observers.forEach(obs => { try { obs.disconnect(); } catch (e) {} });
        this._observers = [];
        this.initialized = false;
    }
}

// ========================
// 4. 统一性能监控模块
// ========================
class UnifiedPerformanceMonitor extends BaseModule {
    constructor() {
        super('UnifiedPerformanceMonitor');
        this.metrics = { fcp: null, lcp: null, cls: 0, inp: null, ttfb: null, longTasks: [] };
        this._obsInstances = [];
    }
    init() {
        if (!Env.features.performanceObserver) {
            Logger.warn(this.moduleName, '浏览器不支持 PerformanceObserver');
            return;
        }
        super.init();
        this._readHistoricalData();
        this._setupObservers();
        this.on(document, 'visibilitychange', () => {
            if (document.visibilityState === 'visible') this._readHistoricalData();
        });
    }
    _readHistoricalData() {
        try {
            const navEntry = performance.getEntriesByType('navigation')[0];
            if (navEntry) this.metrics.ttfb = Math.round(navEntry.responseStart);

            const paints = performance.getEntriesByType('paint');
            paints.forEach(e => {
                if (e.name === 'first-contentful-paint' && !this.metrics.fcp) this.metrics.fcp = Math.round(e.startTime);
            });
            const lcps = performance.getEntriesByType('largest-contentful-paint');
            if (lcps.length > 0) this.metrics.lcp = Math.round(lcps[lcps.length - 1].startTime);

            let cls = 0;
            performance.getEntriesByType('layout-shift').forEach(e => { if (!e.hadRecentInput) cls += e.value; });
            this.metrics.cls = cls;

            this.metrics.longTasks = performance.getEntriesByType('longtask').map(t => ({ duration: t.duration, startTime: t.startTime }));
        } catch (e) { Logger.error(this.moduleName, '读取历史数据失败', e); }
    }
    _setupObservers() {
        const addObs = (type, cb) => {
            try {
                const obs = new PerformanceObserver(cb);
                // 修复:INP 明确支持 buffered,恢复以捕获早期交互;其他类型按需关闭避免警告
                obs.observe({ type, buffered: type === 'event' });
                this._obsInstances.push(obs);
            } catch (e) { Logger.warn(this.moduleName, `${type} 观察器失败`); }
        };

        addObs('paint', list => {
            list.getEntries().forEach(e => {
                if (e.name === 'first-contentful-paint' && !this.metrics.fcp) this.metrics.fcp = Math.round(e.startTime);
            });
        });
        addObs('largest-contentful-paint', list => {
            const entries = list.getEntries();
            if (entries.length > 0) this.metrics.lcp = Math.round(entries[entries.length - 1].startTime);
        });
        addObs('layout-shift', list => {
            list.getEntries().forEach(e => { if (!e.hadRecentInput) this.metrics.cls += e.value; });
        });
        addObs('event', list => {
            let maxDuration = 0;
            list.getEntries().forEach(entry => {
                if (entry.duration > maxDuration) {
                    maxDuration = entry.duration;
                    this.metrics.inp = Math.round(entry.duration);
                }
            });
        });
        addObs('longtask', list => {
            list.getEntries().forEach(entry => {
                this.metrics.longTasks.push({ duration: entry.duration, startTime: entry.startTime });
            });
        });
    }
    getMetrics() {
        try {
            const lcps = performance.getEntriesByType('largest-contentful-paint');
            if (lcps.length > 0) {
                const last = lcps[lcps.length - 1];
                if (last.startTime > (this.metrics.lcp || 0)) this.metrics.lcp = Math.round(last.startTime);
            }
        } catch (e) {}
        return { ...this.metrics, longTasks: this.metrics.longTasks.filter(t => t.duration > 200) };
    }
    destroy() {
        this._obsInstances.forEach(obs => { try { obs.disconnect(); } catch (e) {} });
        this._obsInstances = [];
        super.destroy();
    }
}

// ========================
// 5. 统一图片懒加载模块
// ========================
class UnifiedImageOptimizer extends BaseModule {
    constructor() {
        super('UnifiedImageOptimizer');
        this.io = null;
        this.mo = null;
        this._observedElements = new WeakSet();
    }
    init() {
        if (!Config.lazyLoad.enabled) return;
        super.init();
        if (document.readyState === 'loading') this.on(document, 'DOMContentLoaded', () => this._setup());
        else this._setup();
    }
    _setup() {
        if (Env.features.intersectionObserver) {
            this.io = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        this._loadImage(entry.target);
                        this.io.unobserve(entry.target);
                        this._observedElements.delete(entry.target);
                    }
                });
            }, { rootMargin: `${Config.lazyLoad.preloadDistance}px 0px`, threshold: 0.01 });

            document.querySelectorAll(Config.lazyLoad.selector).forEach(el => {
                if (!this._observedElements.has(el)) {
                    this._observedElements.add(el);
                    this.io.observe(el);
                }
            });
        }
        if (Config.lazyLoad.useMutationObserver && Env.features.mutationObserver) {
            this.mo = this.observeMutations((mutations) => {
                mutations.forEach(m => m.addedNodes.forEach(node => {
                    if (node.nodeType === 1) {
                        if (node.matches && node.matches(Config.lazyLoad.selector)) this._observeElement(node);
                        if (node.querySelectorAll) node.querySelectorAll(Config.lazyLoad.selector).forEach(this._observeElement.bind(this));
                    }
                }));
            }, document.body, { childList: true, subtree: true });
        }
    }
    _observeElement(el) {
        if (!this.io || this._observedElements.has(el)) return;
        this._observedElements.add(el);
        this.io.observe(el);
    }
    _loadImage(el) {
        if (!el.dataset.src) return;
        el.src = el.dataset.src;
        delete el.dataset.src;
        if (el.tagName !== 'IFRAME') el.decoding = 'async';
    }
    destroy() {
        this.io?.disconnect(); this.io = null;
        this.mo?.disconnect(); this.mo = null;
        super.destroy();
    }
}

// ========================
// 6. 预连接优化模块
// ========================
class PreconnectOptimizer extends BaseModule {
    constructor() { super('PreconnectOptimizer'); this._appliedDomains = new Set(); }
    init() {
        if (!Config.preconnect.enabled) return;
        super.init();
        // 交给空闲调度器执行,绝对不阻塞首屏
        Utils.ric(() => this._analyzeAndApply(), 3000);
    }
    _analyzeAndApply() {
        const domains = new Set();
        ['link[rel="stylesheet"][href^="http"]', 'script[src^="http"]', 'img[src^="http"]', 'iframe[src^="http"]'].forEach(sel => {
            document.querySelectorAll(sel).forEach(el => {
                try {
                    const url = new URL(el.href || el.src);
                    if (url.hostname !== window.location.hostname) domains.add(url.hostname);
                } catch (e) {}
            });
        });
        Array.from(domains).slice(0, Config.preconnect.maxDomains).forEach(domain => {
            if (this._appliedDomains.has(domain) || document.querySelector(`link[rel*="preconnect"][href*="${domain}"]`)) return;
            const link = document.createElement('link');
            link.rel = 'preconnect'; link.href = `https://${domain}`; link.crossOrigin = 'anonymous';
            document.head.appendChild(link);
            this._appliedDomains.add(domain);
        });
    }
    destroy() { this._appliedDomains.clear(); super.destroy(); }
}

// ========================
// 7. 统一滚动优化模块 (安全版)
// ========================
class UnifiedScrollOptimizer extends BaseModule {
    constructor() { super('UnifiedScrollOptimizer'); this._originalAddEventListener = null; }
    init() {
        super.init();
        this._optimizePassiveEvents();
        this._applyHardwareAcceleration();
    }
    _optimizePassiveEvents() {
        this._originalAddEventListener = EventTarget.prototype.addEventListener;
        const self = this;
        const passiveEvents = ['wheel', 'mousewheel', 'touchstart', 'touchmove', 'scroll'];
        EventTarget.prototype.addEventListener = function(type, handler, options) {
            let safeOptions = options;
            if (passiveEvents.includes(type)) {
                const isRoot = this === window || this === document || this === document.body;
                if (isRoot) {
                    if (typeof safeOptions === 'boolean') {
                        safeOptions = { capture: safeOptions, passive: true };
                    } else if (typeof safeOptions === 'object') {
                        // 🔑 安全补丁:仅当未显式声明 passive: false 时才强制开启,兼容 preventDefault
                        if (safeOptions.passive !== false) safeOptions = { ...safeOptions, passive: true };
                    } else {
                        safeOptions = { passive: true };
                    }
                }
            }
            return self._originalAddEventListener.call(this, type, handler, safeOptions);
        };
    }
    _applyHardwareAcceleration() {
        if (!Config.hardwareAcceleration.enabled) return; // 🔑 移除错误的 WebGPU 判断
        document.querySelectorAll(Config.hardwareAcceleration.selector).forEach(el => {
            if (!el.style.willChange) {
                el.style.willChange = 'transform';
                el.style.transform = 'translateZ(0)';
            }
        });
    }
    destroy() {
        if (this._originalAddEventListener) {
            EventTarget.prototype.addEventListener = this._originalAddEventListener;
            this._originalAddEventListener = null;
        }
        super.destroy();
    }
}

// ========================
// 8. 内容可见性优化模块
// ========================
class ContentVisibilityOptimizer extends BaseModule {
    init() {
        if (!Config.contentVisibility.enabled || !Env.features.contentVisibility) return;
        super.init();
        this._apply();
        if (Config.lazyLoad.useMutationObserver) {
            this.observeMutations(() => this._apply(), document.body, { childList: true, subtree: true });
        }
    }
    _apply() {
        document.querySelectorAll(Config.contentVisibility.selector).forEach(el => {
            if (!el.style.contentVisibility) {
                el.style.contentVisibility = 'auto';
                el.style.containIntrinsicSize = '0 500px';
            }
        });
    }
    destroy() { super.destroy(); }
}

// ========================
// 9. 统一空闲任务调度(完美适配版)
// ========================
class UnifiedIdleScheduler extends BaseModule {
    constructor() {
        super('UnifiedIdleScheduler');
        this.tasks = [];
        this._isRunning = false;
        this._pendingId = null;
    }
    init() {
        super.init();
        // 页面完全加载后自动触发一次调度,确保非关键任务在资源空闲后执行
        const trigger = () => this.schedule();
        if (document.readyState === 'complete') Utils.ric(trigger, 1000);
        else window.addEventListener('load', () => Utils.ric(trigger, 1000), { once: true });
    }
    /**
     * 添加空闲任务
     * @param {Function} fn 任务函数
     * @param {string} priority 优先级 high | normal | low
     * @param {string} [id] 可选任务标识,用于后续取消
     */
    add(fn, priority = 'normal', id = null) {
        if (typeof fn !== 'function') return;
        // 过滤重复ID
        if (id && this.tasks.some(t => t.id === id)) return;
        this.tasks.push({ fn, priority, id });
        this._sortTasks();
        if (!this._isRunning) this.schedule();
    }
    cancel(id) {
        this.tasks = this.tasks.filter(t => t.id !== id);
    }
    _sortTasks() {
        const p = { high: 0, normal: 1, low: 2 };
        this.tasks.sort((a, b) => (p[a.priority] || 1) - (p[b.priority] || 1));
    }
    schedule() {
        if (this.tasks.length === 0) {
            this._isRunning = false;
            this._pendingId = null;
            return;
        }
        this._isRunning = true;
        // 🔑 核心优化:严格非阻塞检查 + 超时兜底
        this._pendingId = Utils.ric((deadline) => {
            // 条件1:剩余时间充足(>50ms) 或 浏览器已强制超时(didTimeout=true) 则执行
            // 条件2:否则主动让出主线程,等待下一次空闲
            const canRun = deadline.timeRemaining() > 50 || deadline.didTimeout;

            if (canRun && this.tasks.length > 0) {
                while (this.tasks.length > 0 && (deadline.timeRemaining() > 50 || deadline.didTimeout)) {
                    const task = this.tasks.shift();
                    try { task.fn(); } catch (e) { Logger.error(this.moduleName, e); }
                }
            }

            // 队列仍有任务,继续调度;否则标记空闲
            if (this.tasks.length > 0) this.schedule();
            else { this._isRunning = false; this._pendingId = null; }
        }, { timeout: 2000 }); // 2秒超时保证任务最终被执行
    }
    destroy() {
        if (this._pendingId) {
            Utils.cancelRic(this._pendingId);
            this._pendingId = null;
        }
        this.tasks = [];
        this._isRunning = false;
        super.destroy();
    }
}

// ========================
// 10. UI控制器 (Shadow DOM 隔离版)
// ========================
class UIController extends BaseModule {
    constructor() {
        super('UIController');
        this.panelVisible = false;
        this.button = this.panel = this.host = this.shadow = null;
        this.monitor = null;
        this.statsTimer = this._hideTimer = this._throttledMouseMove = null;
        this.isInTriggerZone = false;
    }
    setPerformanceMonitor(monitor) { this.monitor = monitor; }
    init() {
        if (!Config.ui.enabled) return;
        super.init();
        if (document.readyState === 'loading') this.on(document, 'DOMContentLoaded', () => this._createUI());
        else this._createUI();
    }
    _createUI() {
        if (!document.body) return setTimeout(() => this._createUI(), 100);
        if (!Env.features.shadowDOM) {
            Logger.warn('UIController', '不支持 Shadow DOM,UI 降级隐藏');
            return;
        }
        this.host = document.createElement('div');
        this.host.style.cssText = `position:fixed;inset:0;pointer-events:none;z-index:${Config.ui.zIndex};`;
        document.body.appendChild(this.host);
        this.shadow = this.host.attachShadow({ mode: 'open' });

        const style = document.createElement('style');
        style.textContent = `
            :host { all: initial; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
            .perfopt-btn {
                position: absolute; bottom: 20px; right: 20px; width: 48px; height: 48px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 50%;
                box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center;
                cursor: pointer; font-size: 20px; transition: all 0.3s ease; pointer-events: auto; user-select: none;
            }
            .perfopt-btn:hover { transform: scale(1.1); }
            .perfopt-btn.hidden { right: -40px; opacity: 0.3; pointer-events: none; }
            .perfopt-panel {
                position: absolute; bottom: 80px; right: 20px; width: ${Config.ui.width}px; max-height: 85vh;
                background: rgba(255,255,255,0.98); backdrop-filter: blur(10px); border-radius: 12px;
                box-shadow: 0 8px 32px rgba(0,0,0,0.15); padding: 20px; font-size: 13px;
                display: none; opacity: 0; transform: translateY(10px); transition: all 0.3s ease;
                overflow-y: auto; box-sizing: border-box; pointer-events: auto;
            }
            .perfopt-panel.visible { display: block; opacity: 1; transform: translateY(0); }
            .perfopt-header { font-weight: 600; margin-bottom: 16px; padding-bottom: 10px; border-bottom: 1px solid rgba(0,0,0,0.1); font-size: 15px; color: #333; display: flex; align-items: center; gap: 6px; }
            .perfopt-row { display: flex; justify-content: space-between; align-items: center; margin: 10px 0; line-height: 1.5; }
            .perfopt-label { color: #666; }
            .perfopt-value { font-family: "SF Mono", Monaco, monospace; font-weight: 600; font-variant-numeric: tabular-nums; }
            .perfopt-good { color: #22c55e; } .perfopt-warn { color: #f59e0b; } .perfopt-bad { color: #ef4444; }
            .perfopt-footer { margin-top: 16px; padding-top: 12px; border-top: 1px solid rgba(0,0,0,0.1); font-size: 11px; color: #999; }
        `;
        this.shadow.appendChild(style);

        this.button = document.createElement('div');
        this.button.className = 'perfopt-btn hidden';
        this.button.innerHTML = '⚡'; this.button.title = '性能监控 (靠近显示)';
        this.shadow.appendChild(this.button);

        this.panel = document.createElement('div');
        this.panel.className = 'perfopt-panel';
        this.panel.innerHTML = `
            <div class="perfopt-header">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
                Core Web Vitals
            </div>
            <div class="perfopt-row"><span class="perfopt-label">FCP (首次绘制)</span><span id="po-fcp" class="perfopt-value">--</span></div>
            <div class="perfopt-row"><span class="perfopt-label">LCP (最大内容)</span><span id="po-lcp" class="perfopt-value">--</span></div>
            <div class="perfopt-row"><span class="perfopt-label">CLS (布局偏移)</span><span id="po-cls" class="perfopt-value">--</span></div>
            <div class="perfopt-row"><span class="perfopt-label">INP (交互延迟)</span><span id="po-inp" class="perfopt-value" title="需用户首次交互后计算">等待交互</span></div>
            <div class="perfopt-row"><span class="perfopt-label">TTFB (首字节)</span><span id="po-ttfb" class="perfopt-value">--</span></div>
            <div class="perfopt-row"><span class="perfopt-label">Long Tasks (卡顿)</span><span id="po-longtasks" class="perfopt-value">--</span></div>
            <div class="perfopt-footer">网络: ${Env.networkType} · 设备: ${Env.performanceTier === 2 ? '高性能' : Env.performanceTier === 1 ? '中性能' : '低性能'}</div>
        `;
        this.shadow.appendChild(this.panel);
        this._setupEvents();
        this._startHideTimer();
    }
    _setupEvents() {
        this._throttledMouseMove = Utils.throttle((e) => {
            if (this.panelVisible) return;
            const rect = this.button.getBoundingClientRect();
            const dist = Math.hypot(e.clientX - (rect.left + rect.width/2), e.clientY - (rect.top + rect.height/2));
            if (dist <= Config.ui.triggerDistance) {
                if (!this.isInTriggerZone) { this.isInTriggerZone = true; this._showButton(); this._clearHideTimer(); }
            } else if (this.isInTriggerZone) {
                this.isInTriggerZone = false; this._startHideTimer();
            }
        }, 50);
        this.on(document, 'mousemove', this._throttledMouseMove);
        this.on(document, 'click', (e) => {
            if (this.panelVisible && !this.button.contains(e.target) && !this.panel.contains(e.target)) this._closePanel();
        });
        this.on(this.button, 'click', (e) => {
            e.stopPropagation();
            this.panelVisible ? this._closePanel() : this._openPanel();
        });
        this.on(this.button, 'mouseenter', () => this._clearHideTimer());
        this.on(this.button, 'mouseleave', () => !this.panelVisible && this._startHideTimer());
    }
    _showButton() { this.button.classList.remove('hidden'); }
    _hideButton() { if (!this.panelVisible) this.button.classList.add('hidden'); }
    _openPanel() {
        this.panelVisible = true; this.panel.classList.add('visible'); this._showButton(); this._updateStats();
        this.statsTimer = this.setInterval(() => this._updateStats(), Config.ui.statsUpdateInterval);
    }
    _closePanel() {
        this.panelVisible = false; this.panel.classList.remove('visible'); this._startHideTimer();
        if (this.statsTimer) { clearInterval(this.statsTimer); this.statsTimer = null; }
    }
    _startHideTimer() { this._clearHideTimer(); this._hideTimer = this.setTimer(() => this._hideButton(), Config.ui.autoHideDelay); }
    _clearHideTimer() { if (this._hideTimer) { this.clearTimer(this._hideTimer); this._hideTimer = null; } }
    _updateStats() {
        if (!this.monitor) return;
        const m = this.monitor.getMetrics();
        const setVal = (id, val, good, bad, unit = '') => {
            const el = this.shadow.getElementById(id);
            if (!el) return;
            if (val === null || val === undefined) {
                el.textContent = id === 'po-inp' ? '等待交互' : '--';
                el.className = 'perfopt-value';
                return;
            }
            const num = typeof val === 'number' ? val : parseFloat(val);
            el.textContent = unit ? `${Math.round(num)}${unit}` : num.toFixed(3);
            let cls = 'perfopt-value ';
            cls += num < good ? 'perfopt-good' : num < bad ? 'perfopt-warn' : 'perfopt-bad';
            el.className = cls;
        };
        setVal('po-fcp', m.fcp, 1800, 3000, 'ms');
        setVal('po-lcp', m.lcp, 2500, 4000, 'ms');
        setVal('po-cls', m.cls, 0.1, 0.25);
        setVal('po-inp', m.inp, 200, 500, 'ms');
        setVal('po-ttfb', m.ttfb, 600, 1000, 'ms');
        setVal('po-longtasks', m.longTasks.length, 0, 3, '次');
    }
    destroy() {
        if (this.statsTimer) { clearInterval(this.statsTimer); this.statsTimer = null; }
        this.host?.remove(); this.host = this.shadow = this.button = this.panel = null;
        super.destroy();
    }
}

// ========================
// 11. 主应用控制器
// ========================
class UnifiedAppController {
    constructor() { this.modules = {}; }
    init() {
        const hostname = window.location.hostname;
        if (Config.blacklistedDomains.some(d => hostname.includes(d))) return;

        this.modules.monitor = new UnifiedPerformanceMonitor();
        this.modules.monitor.init();

        this.modules.preconnect = new PreconnectOptimizer();
        this.modules.preconnect.init();

        this.modules.idleScheduler = new UnifiedIdleScheduler();
        this.modules.idleScheduler.init();

        this.modules.scrollOptimizer = new UnifiedScrollOptimizer();
        this.modules.scrollOptimizer.init();

        this.modules.cvOptimizer = new ContentVisibilityOptimizer();
        this.modules.cvOptimizer.init();

        const initDOM = () => {
            this.modules.images = new UnifiedImageOptimizer();
            this.modules.images.init();

            this.modules.ui = new UIController();
            this.modules.ui.setPerformanceMonitor(this.modules.monitor);
            this.modules.ui.init();
        };

        // 🔑 修复:使用原生事件绑定,避免 this.on 报错
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initDOM);
        } else {
            initDOM();
        }

        const cleanup = () => this.destroy();
        window.addEventListener('beforeunload', cleanup);
        window.addEventListener('pagehide', cleanup);
    }
    destroy() {
        Object.values(this.modules).forEach(m => m?.destroy?.());
        this.modules = {};
    }
}

// ========================
// 12. 启动脚本
// ========================
try {
    const app = new UnifiedAppController();
    app.init();
    window.UnifiedPerfOptimizer = app;
    if (Config.debug) console.log('[PerfOpt] 脚本加载成功 v4.0.0-final');
} catch (error) {
    console.error('[PerfOpt] 致命错误:', error);
}
})();