Via Css隐藏规则日志

格式化CSS规则,检测哪些规则生效,并输出匹配日志。

目前为 2025-03-12 提交的版本。查看 最新版本

// ==UserScript==
// @name         Via Css隐藏规则日志
// @namespace    https://viayoo.com/
// @version      2.3.2
// @license      MIT
// @description  格式化CSS规则,检测哪些规则生效,并输出匹配日志。
// @author       Copilot
// @run-at       document-start
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdn.jsdelivr.net/npm/[email protected]/js/lib/beautify-css.js
// ==/UserScript==

(function() {
    'use strict';

    // 获取CSS文件URL
    function getCssFileUrl() {
        const currentHost = window.location.hostname;
        return `https://${currentHost}/via_inject_blocker.css`;
    }

    // 格式化CSS内容
    function formatCss(rawCss) {
        try {
            return css_beautify(rawCss, {
                indent_size: 2,
                selector_separator_newline: true
            });
        } catch (error) {
            console.error(`CSS格式化失败:${error.message}`);
            return null;
        }
    }

    // 拆分选择器,保护属性值中的逗号
    function splitSelectors(selectorText) {
        const selectors = [];
        let currentSelector = '';
        let inAttribute = false;
        let quote = null;
        let i = 0;

        while (i < selectorText.length) {
            const char = selectorText[i];

            if (inAttribute) {
                // 如果在属性值中,检查是否遇到结束引号
                if (char === quote) {
                    inAttribute = false;
                    quote = null;
                }
                currentSelector += char;
            } else if (char === '"' || char === "'") {
                // 进入属性值
                inAttribute = true;
                quote = char;
                currentSelector += char;
            } else if (char === ',' && !inAttribute) {
                // 遇到选择器分隔符,且不在属性值中
                if (currentSelector.trim()) {
                    selectors.push(currentSelector.trim());
                    currentSelector = '';
                }
            } else {
                currentSelector += char;
            }

            i++;
        }

        // 添加最后一个选择器
        if (currentSelector.trim()) {
            selectors.push(currentSelector.trim());
        }

        return selectors;
    }

    // 提取有效选择器
    function extractValidSelectors(rule) {
        if (!rule.selectorText) return [];

        // 使用 splitSelectors 函数拆分选择器
        const selectors = splitSelectors(rule.selectorText).map(selector => selector.trim());

        return selectors.filter(selector => {
            try {
                document.querySelector(selector);
                return true;
            } catch {
                return false;
            }
        });
    }

    // 检测生效的CSS规则
    function checkActiveRules(sheet) {
        const activeRules = [];
        if (!sheet || !sheet.cssRules) return activeRules;

        for (const rule of sheet.cssRules) {
            if (rule.selectorText) {
                const validSelectors = extractValidSelectors(rule);
                validSelectors.forEach(selector => {
                    const elements = document.querySelectorAll(selector);
                    if (elements.length > 0) {
                        activeRules.push({
                            selector,
                            count: elements.length
                        });
                    }
                });
            }
        }

        return activeRules;
    }

    // 检查CSS文件
    async function checkCssFile() {
        const cssFileUrl = getCssFileUrl();

        try {
            const response = await fetch(cssFileUrl);
            if (!response.ok) {
                alert(`无法加载CSS文件: ${cssFileUrl} (状态码: ${response.status})`);
                return;
            }

            const rawCss = await response.text();
            if (!rawCss.trim()) {
                alert("CSS文件为空!");
                return;
            }

            // 格式化CSS
            const formattedCss = formatCss(rawCss);
            if (!formattedCss) {
                alert("CSS格式化失败!");
                return;
            }

            // 创建临时样式表并检测规则
            const styleElement = document.createElement('style');
            styleElement.textContent = formattedCss;
            document.head.appendChild(styleElement);

            const activeRules = checkActiveRules(styleElement.sheet);
            document.head.removeChild(styleElement); // 移除临时样式表

            if (activeRules.length > 0) {
                let resultMessage = `检测完成!共有 ${activeRules.length} 条规则生效:\n\n`;
                activeRules.forEach((rule, index) => {
                    resultMessage += `${index + 1}. 匹配规则: ##${rule.selector}\n`;
                    resultMessage += `匹配到的元素数: ${rule.count}\n\n`;
                });
                alert(resultMessage);
            } else {
                alert("没有发现生效的CSS规则!");
            }
        } catch (error) {
            console.error("获取CSS文件失败:", error);
            alert("获取CSS文件失败,请检查网络连接或CSS文件!");
        }
    }

    // 确保按钮存在
    function ensureButtonExists() {
        const existingButton = document.querySelector("div[style*='CSS日志']");
        if (!existingButton) {
            console.log("悬浮按钮不存在,正在创建...");
            createFloatingButtonForCssCheck();
        } else {
            console.log("悬浮按钮已存在");
        }
    }

    // 创建悬浮按钮功能(附带位置记忆)
    function createFloatingButtonForCssCheck() {
        // 如果是iframe,不创建按钮
        if (window.self !== window.top) {
            console.warn("当前页面是iframe,按钮可能不可见");
            return;
        }

        console.log("正在创建悬浮按钮...");
        const button = document.createElement("div");
        button.textContent = "CSS日志";
        button.style.position = "fixed";
        button.style.zIndex = "10000"; // 提高 z-index
        button.style.width = "70px";
        button.style.height = "35px";
        button.style.backgroundColor = "#2d89ef";
        button.style.color = "white";
        button.style.borderRadius = "5px";
        button.style.textAlign = "center";
        button.style.lineHeight = "35px";
        button.style.fontSize = "14px";
        button.style.boxShadow = "0px 4px 6px rgba(0,0,0,0.1)";
        button.style.cursor = "pointer";
        button.style.opacity = "0.9";
        button.style.transition = "opacity 0.3s, transform 0.3s";

        // 读取按钮上次保存的位置(默认在右下角)
        const savedLeft = GM_getValue("floatingButtonLeft", window.innerWidth - 100);
        const savedTop = GM_getValue("floatingButtonTop", window.innerHeight - 100);
        button.style.left = `${savedLeft}px`;
        button.style.top = `${savedTop}px`;

        document.body.appendChild(button);
        console.log("悬浮按钮已创建");

        // 触控移动功能
        let startX, startY, startLeft, startTop;

        button.addEventListener("touchstart", (e) => {
            const touch = e.touches[0];
            startX = touch.clientX;
            startY = touch.clientY;
            startLeft = parseInt(button.style.left, 10);
            startTop = parseInt(button.style.top, 10);
        });

        button.addEventListener("touchmove", (e) => {
            const touch = e.touches[0];
            const deltaX = touch.clientX - startX;
            const deltaY = touch.clientY - startY;

            button.style.left = `${startLeft + deltaX}px`;
            button.style.top = `${startTop + deltaY}px`;
        });

        button.addEventListener("touchend", () => {
            // 保存按钮位置
            GM_setValue("floatingButtonLeft", parseInt(button.style.left, 10));
            GM_setValue("floatingButtonTop", parseInt(button.style.top, 10));

            // 自动贴边隐藏
            const rect = button.getBoundingClientRect();
            if (rect.left + rect.width / 2 < window.innerWidth / 2) {
                button.style.left = "0px"; // 靠左
                GM_setValue("floatingButtonLeft", 0);
            } else {
                button.style.left = `${window.innerWidth - rect.width}px`; // 靠右
                GM_setValue("floatingButtonLeft", window.innerWidth - rect.width);
            }
        });

        // 点击事件:执行检查功能
        button.addEventListener("click", () => {
            if (typeof checkCssFile === "function") {
                checkCssFile();
            } else {
                console.error("checkCssFile 函数未定义!");
            }
        });
    }

    // 初始化脚本
    function initializeFloatingButtonScript() {
        const isButtonEnabled = GM_getValue("floatingButtonEnabled", false);

        // 添加菜单命令:用于切换悬浮按钮开关
        GM_registerMenuCommand(isButtonEnabled ? "关闭悬浮按钮" : "开启悬浮按钮", () => {
            GM_setValue("floatingButtonEnabled", !isButtonEnabled);
            alert(`悬浮按钮已${isButtonEnabled ? "关闭" : "开启"}!`);
            location.reload();
        });

        if (isButtonEnabled) {
            // 确保在 DOM 加载完成后创建按钮
            if (document.readyState === "complete" || document.readyState === "interactive") {
                ensureButtonExists();
            } else {
                document.addEventListener("DOMContentLoaded", ensureButtonExists);
            }
        }
    }

    // 注册菜单命令
    GM_registerMenuCommand("检测CSS隐藏规则", checkCssFile);

    // 执行初始化
    initializeFloatingButtonScript();
})();