Websites Base64 Helper

Base64编解码工具 for all websites

// ==UserScript==
// @name         Websites Base64 Helper
// @icon         https://raw.githubusercontent.com/XavierBar/Discourse-Base64-Helper/refs/heads/main/discourse.svg
// @namespace    http://tampermonkey.net/
// @version      1.4.60
// @description  Base64编解码工具 for all websites
// @author       Xavier
// @match        *://*/*
// @grant        GM_notification
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_addValueChangeListener
// @run-at       document-idle
// @noframes     true
// ==/UserScript==

(function () {
	('use strict');

	// 常量定义
	const Z_INDEX = 2147483647;
	const STORAGE_KEYS = {
		BUTTON_POSITION: 'btnPosition',
		SHOW_NOTIFICATION: 'showNotification',
		HIDE_BUTTON: 'hideButton',
		AUTO_DECODE: 'autoDecode',
	};

	// 存储管理器
	const storageManager = {
		get: (key, defaultValue) => {
			try {
				// 优先从 GM 存储获取
				const value = GM_getValue(`base64helper_${key}`);
				if (value !== undefined) {
					return value;
				}

				// 尝试从 localStorage 迁移数据(兼容旧版本)
				const localValue = localStorage.getItem(`base64helper_${key}`);
				if (localValue !== null) {
					const parsedValue = JSON.parse(localValue);
					// 迁移数据到 GM 存储
					GM_setValue(`base64helper_${key}`, parsedValue);
					// 清理 localStorage 中的旧数据
					localStorage.removeItem(`base64helper_${key}`);
					return parsedValue;
				}

				return defaultValue;
			} catch (e) {
				console.error('Error getting value from storage:', e);
				return defaultValue;
			}
		},
		set: (key, value) => {
			try {
				// 存储到 GM 存储
				GM_setValue(`base64helper_${key}`, value);
				return true;
			} catch (e) {
				console.error('Error setting value to storage:', e);
				return false;
			}
		},
		// 添加删除方法
		remove: (key) => {
			try {
				GM_deleteValue(`base64helper_${key}`);
				return true;
			} catch (e) {
				console.error('Error removing value from storage:', e);
				return false;
			}
		},
		// 添加监听方法
		addChangeListener: (key, callback) => {
			return GM_addValueChangeListener(`base64helper_${key}`,
				(_, oldValue, newValue, remote) => {
					callback(newValue, oldValue, remote);
				}
			);
		},
		// 移除监听方法
		removeChangeListener: (listenerId) => {
			if (listenerId) {
				GM_removeValueChangeListener(listenerId);
			}
		}
	};
	const BASE64_REGEX = /([A-Za-z0-9+/]+={0,2})(?!\w)/g;
	// 样式常量
	const STYLES = {
		GLOBAL: `
            /* 基础内容样式 */
            .decoded-text {
                cursor: pointer;
                transition: all 0.2s;
                padding: 1px 3px;
                border-radius: 3px;
                background-color: #fff3cd !important;
                color: #664d03 !important;
            }
            .decoded-text:hover {
                background-color: #ffe69c !important;
            }
            /* 通知动画 */
            @keyframes slideIn {
                from {
                    transform: translateY(-20px);
                    opacity: 0;
                }
                to {
                    transform: translateY(0);
                    opacity: 1;
                }
            }
            @keyframes fadeOut {
                from { opacity: 1; }
                to { opacity: 0; }
            }
            /* 暗色模式全局样式 */
            @media (prefers-color-scheme: dark) {
                .decoded-text {
                    background-color: #332100 !important;
                    color: #ffd54f !important;
                }
                .decoded-text:hover {
                    background-color: #664d03 !important;
                }
            }
        `,
		NOTIFICATION: `
            @keyframes slideUpOut {
                0% {
                    transform: translateY(0) scale(1);
                    opacity: 1;
                }
                100% {
                    transform: translateY(-30px) scale(0.95);
                    opacity: 0;
                }
            }
            .base64-notifications-container {
                position: fixed;
                top: 20px;
                left: 50%;
                transform: translateX(-50%);
                z-index: ${Z_INDEX};
                display: flex;
                flex-direction: column;
                gap: 0;
                pointer-events: none;
                align-items: center;
                width: fit-content;
            }
            .base64-notification {
                transform-origin: top center;
                white-space: nowrap;
                padding: 12px 24px;
                border-radius: 8px;
                margin-bottom: 10px;
                animation: slideIn 0.3s ease forwards;
                font-family: system-ui, -apple-system, sans-serif;
                backdrop-filter: blur(4px);
                border: 1px solid rgba(255, 255, 255, 0.1);
                text-align: center;
                line-height: 1.5;
                background: rgba(255, 255, 255, 0.95);
                color: #2d3748;
                box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
                opacity: 1;
                transform: translateY(0);
                transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
                will-change: transform, opacity;
                position: relative;
                height: auto;
                max-height: 100px;
            }
            .base64-notification.fade-out {
                animation: slideUpOut 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
                margin-bottom: 0 !important;
                max-height: 0 !important;
                padding-top: 0 !important;
                padding-bottom: 0 !important;
                border-width: 0 !important;
            }
            .base64-notification[data-type="success"] {
                background: rgba(72, 187, 120, 0.95) !important;
                color: #f7fafc !important;
            }
            .base64-notification[data-type="error"] {
                background: rgba(245, 101, 101, 0.95) !important;
                color: #f8fafc !important;
            }
            .base64-notification[data-type="info"] {
                background: rgba(66, 153, 225, 0.95) !important;
                color: #f7fafc !important;
            }
            @media (prefers-color-scheme: dark) {
                .base64-notification {
                    background: rgba(26, 32, 44, 0.95) !important;
                    color: #e2e8f0 !important;
                    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
                    border-color: rgba(255, 255, 255, 0.05);
                }
                .base64-notification[data-type="success"] {
                    background: rgba(22, 101, 52, 0.95) !important;
                }
                .base64-notification[data-type="error"] {
                    background: rgba(155, 28, 28, 0.95) !important;
                }
                .base64-notification[data-type="info"] {
                    background: rgba(29, 78, 216, 0.95) !important;
                }
            }
        `,
		SHADOW_DOM: `
            :host {
                all: initial !important;
                position: fixed !important;
                z-index: ${Z_INDEX} !important;
                pointer-events: none !important;
            }
            .base64-helper {
                position: fixed;
                z-index: ${Z_INDEX} !important;
                transform: translateZ(100px);
                cursor: drag;
                font-family: system-ui, -apple-system, sans-serif;
                opacity: 0.5;
                transition: opacity 0.3s ease, transform 0.2s;
                pointer-events: auto !important;
                will-change: transform;
            }
            .base64-helper.dragging {
                cursor: grabbing;
            }
						.base64-helper:hover {
              opacity: 1 !important;
            }
            .main-btn {
                background: #ffffff;
                color: #000000 !important;
                padding: 8px 16px;
                border-radius: 6px;
                box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
                font-weight: 500;
                user-select: none;
                transition: all 0.2s;
                font-size: 14px;
                cursor: drag;
                border: none !important;
            }
            .main-btn.dragging {
                cursor: grabbing;
            }
            .menu {
                position: absolute;
                background: #ffffff;
                border-radius: 6px;
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
                display: none;
                min-width: auto !important;
                width: max-content !important;
                overflow: hidden;
            }

            /* 菜单弹出方向 */
            .menu.popup-top {
                bottom: calc(100% + 5px);
            }
            .menu.popup-bottom {
                top: calc(100% + 5px);
            }

            /* 新增: 左对齐样式 */
            .menu.align-left {
                left: 0;
            }
            .menu.align-left .menu-item {
                text-align: left;
            }

            /* 新增: 右对齐样式 */
            .menu.align-right {
                right: 0;
            }
            .menu.align-right .menu-item {
                text-align: right;
            }
            .menu-item {
                padding: 8px 12px !important;
                color: #333 !important;
                transition: all 0.2s;
                font-size: 13px;
                cursor: pointer;
                position: relative;
                border-radius: 0 !important;
                isolation: isolate;
                white-space: nowrap !important;
                // 新增以下样式防止文本被选中
                user-select: none;
                -webkit-user-select: none;
                -moz-user-select: none;
                -ms-user-select: none;
            }
            .menu-item:hover::before {
                content: '';
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: currentColor;
                opacity: 0.1;
                z-index: -1;
            }
            @media (prefers-color-scheme: dark) {
                .main-btn {
                    background: #2d2d2d;
                    color: #fff !important;
                    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
                }
                .menu {
                    background: #1a1a1a;
                    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
                }
                .menu-item {
                    color: #e0e0e0 !important;
                }
                .menu-item:hover::before {
                    opacity: 0.08;
                }
            }
        `,
	};

	// 样式初始化
	const initStyles = () => {
		GM_addStyle(STYLES.GLOBAL + STYLES.NOTIFICATION);
	};

	// 全局变量存储所有菜单命令ID
	let menuIds = {
		decode: null,
		encode: null,
		reset: null,
		notification: null,
		hideButton: null,
		autoDecode: null
	};

	// 更新菜单命令
	const updateMenuCommands = () => {
		// 取消注册所有菜单命令
		Object.values(menuIds).forEach(id => {
			if (id !== null) {
				try {
					GM_unregisterMenuCommand(id);
				} catch (e) {
					console.error('Failed to unregister menu command:', e);
				}
			}
		});

		// 重置菜单ID对象
		menuIds = {
			decode: null,
			encode: null,
			reset: null,
			notification: null,
			hideButton: null,
			autoDecode: null
		};

		// 检查当前状态,决定解析菜单文本
		const hasDecodedContent = document.querySelectorAll('.decoded-text').length > 0;
		const decodeMenuText = hasDecodedContent ? '恢复本页 Base64' : '解析本页 Base64';

		// 注册解析菜单命令 - 放在第一位
		try {
			menuIds.decode = GM_registerMenuCommand(decodeMenuText, () => {
				if (window.__base64HelperInstance) {
					// 直接调用实例方法
					window.__base64HelperInstance.handleDecode();
					// 操作完成后更新菜单命令
					setTimeout(updateMenuCommands, 100);
				}
			});
			console.log('Registered decode menu command with ID:', menuIds.decode);
		} catch (e) {
			console.error('Failed to register decode menu command:', e);
		}

		// 文本转 Base64
		try {
			menuIds.encode = GM_registerMenuCommand('文本转 Base64', () => {
				if (window.__base64HelperInstance) window.__base64HelperInstance.handleEncode();
			});
			console.log('Registered encode menu command with ID:', menuIds.encode);
		} catch (e) {
			console.error('Failed to register encode menu command:', e);
		}

		// 重置按钮位置
		try {
			menuIds.reset = GM_registerMenuCommand('重置按钮位置', () => {
				if (window.__base64HelperInstance) {
					// 使用 storageManager 存储按钮位置
					storageManager.set(STORAGE_KEYS.BUTTON_POSITION, {
						x: window.innerWidth - 120,
						y: window.innerHeight - 80,
					});
					window.__base64HelperInstance.initPosition();
					window.__base64HelperInstance.showNotification('按钮位置已重置', 'success');
				}
			});
			console.log('Registered reset menu command with ID:', menuIds.reset);
		} catch (e) {
			console.error('Failed to register reset menu command:', e);
		}

		// 显示解析通知开关
		const showNotificationEnabled = storageManager.get(STORAGE_KEYS.SHOW_NOTIFICATION, true);
		try {
			menuIds.notification = GM_registerMenuCommand(`${showNotificationEnabled ? '✅' : '❌'} 显示通知`, () => {
				const newValue = !showNotificationEnabled;
				storageManager.set(STORAGE_KEYS.SHOW_NOTIFICATION, newValue);
				// 使用通知提示用户设置已更改
				if (window.__base64HelperInstance) {
					window.__base64HelperInstance.showNotification(
						`显示通知已${newValue ? '开启' : '关闭'}`,
						'success'
					);
				}
				// 更新菜单文本
				setTimeout(updateMenuCommands, 100);
			});
			console.log('Registered notification menu command with ID:', menuIds.notification);
		} catch (e) {
			console.error('Failed to register notification menu command:', e);
		}

		// 隐藏按钮开关
		const hideButtonEnabled = storageManager.get(STORAGE_KEYS.HIDE_BUTTON, false);
		try {
			menuIds.hideButton = GM_registerMenuCommand(`${hideButtonEnabled ? '✅' : '❌'} 隐藏按钮`, () => {
				const newValue = !hideButtonEnabled;
				storageManager.set(STORAGE_KEYS.HIDE_BUTTON, newValue);
				// 使用通知提示用户设置已更改
				if (window.__base64HelperInstance) {
					window.__base64HelperInstance.showNotification(
						`按钮已${newValue ? '隐藏' : '显示'}`,
						'success'
					);
				}
				// 更新菜单文本
				setTimeout(updateMenuCommands, 100);
			});
			console.log('Registered hideButton menu command with ID:', menuIds.hideButton);
		} catch (e) {
			console.error('Failed to register hideButton menu command:', e);
		}

		// 自动解码开关
		const autoDecodeEnabled = storageManager.get(STORAGE_KEYS.AUTO_DECODE, false);
		try {
			menuIds.autoDecode = GM_registerMenuCommand(`${autoDecodeEnabled ? '✅' : '❌'} 自动解码`, () => {
				const newValue = !autoDecodeEnabled;
				storageManager.set(STORAGE_KEYS.AUTO_DECODE, newValue);

				// 如果启用了自动解码,立即解析页面
				if (newValue) {
					// 检查是否是通过菜单命令触发的变更
					// 如果是通过菜单命令触发,则不再显示确认对话框
					// 因为菜单命令处理程序中已经处理了这些确认

					// 立即解析页面
					this.hasAutoDecodedOnLoad = true; // 标记已执行过自动解码
					setTimeout(() => {
						this.handleDecode();
						// 同步按钮和菜单状态
						setTimeout(() => {
							this.syncButtonAndMenuState();
							// 更新油猴菜单命令
							updateMenuCommands();
						}, 200);
					}, 100);
				} else {
					// 如果关闭了自动解码,也需要同步状态
					setTimeout(() => {
						this.syncButtonAndMenuState();
						// 更新油猴菜单命令
						updateMenuCommands();
					}, 200);
				}
			});
			console.log('Registered autoDecode menu command with ID:', menuIds.autoDecode);
		} catch (e) {
			console.error('Failed to register autoDecode menu command:', e);
		}
	};

	// 菜单命令注册
	const registerMenuCommands = () => {
		// 注册所有菜单命令
		updateMenuCommands();

		// 添加 DOMContentLoaded 事件监听器,确保在页面加载完成后注册菜单命令
		document.addEventListener('DOMContentLoaded', () => {
			console.log('DOMContentLoaded 事件触发,更新菜单命令');
			updateMenuCommands();
		});
	};

	class Base64Helper {
		/**
		 * Base64 Helper 类的构造函数
		 * @description 初始化所有必要的状态和UI组件,仅在主窗口中创建实例
		 * @throws {Error} 当在非主窗口中实例化时抛出错误
		 */
		constructor() {
			// 确保只在主文档中创建实例
			if (window.top !== window.self) {
				throw new Error(
					'Base64Helper can only be instantiated in the main window'
				);
			}

			// 初始化配置
			this.config = {
				showNotification: storageManager.get(STORAGE_KEYS.SHOW_NOTIFICATION, true),
				hideButton: storageManager.get(STORAGE_KEYS.HIDE_BUTTON, false),
				autoDecode: storageManager.get(STORAGE_KEYS.AUTO_DECODE, false)
			};

			this.originalContents = new Map();
			this.isDragging = false;
			this.hasMoved = false;
			this.startX = 0;
			this.startY = 0;
			this.initialX = 0;
			this.initialY = 0;
			this.startTime = 0;
			this.menuVisible = false;
			this.resizeTimer = null;
			this.notifications = [];
			this.notificationContainer = null;
			this.notificationEventListeners = [];
			this.eventListeners = new Map(); // 使用 Map 替代数组,便于管理

			// 添加缓存对象
			this.base64Cache = new Map();
			this.MAX_CACHE_SIZE = 1000; // 最大缓存条目数
			this.MAX_TEXT_LENGTH = 10000; // 最大文本长度限制
			this.cacheHits = 0;
			this.cacheMisses = 0;

			// 添加DOM节点处理跟踪
			this.processedNodes = new WeakSet(); // 使用WeakSet跟踪已处理的节点,避免内存泄漏
			this.decodedTextNodes = new WeakMap(); // 存储节点及其解码状态
			this.processedMutations = new Set(); // 跟踪已处理的mutation记录
			this.nodeReferences = new WeakMap(); // 存储节点引用

			// 初始化配置监听器
			this.configListeners = {
				showNotification: null,
				hideButton: null,
				autoDecode: null,
				buttonPosition: null
			};

			// 添加初始化标志
			this.isInitialLoad = true;
			this.lastDecodeTime = 0;
			this.lastNavigationTime = 0; // 添加前进后退时间记录
			this.isShowingNotification = false; // 添加通知显示标志
			this.hasAutoDecodedOnLoad = false; // 添加标志,跟踪是否已在页面加载时执行过自动解码
			this.isPageRefresh = true; // 添加页面刷新标志,初始加载视为刷新
			this.pageRefreshCompleted = false; // 添加页面刷新完成标志
			this.isRestoringContent = false; // 添加内容恢复标志
			this.isDecodingContent = false; // 添加内容解码标志
			this.lastPageUrl = window.location.href; // 记录当前页面URL
			this.currentMutations = []; // 存储当前的DOM变化记录
			const MIN_DECODE_INTERVAL = 1000; // 最小解码间隔(毫秒)

			// 初始化统一的页面稳定性跟踪器
			this.pageStabilityTracker = {
				// 状态管理
				lastChangeTime: Date.now(),
				changeCount: 0,
				isStable: false,
				pendingDecode: false,
				lastRouteChange: 0,
				lastDomChange: 0,
				stabilityTimer: null,
				decodePendingTimer: null,
				stabilityThreshold: 800, // 降低稳定性阈值,从2000ms降低到800ms
				maxChangeCount: 5,

				// 检查稳定性
				checkStability() {
					const currentTime = Date.now();
					return (currentTime - this.lastRouteChange > this.stabilityThreshold) &&
						   (currentTime - this.lastDomChange > this.stabilityThreshold);
				},

				// 记录变化
				recordChange(type) {
					const currentTime = Date.now();
					this.lastChangeTime = currentTime;

					// 更新对应类型的最后变化时间
					if (type === 'Route') {
						this.lastRouteChange = currentTime;
					} else if (type === 'Dom') {
						this.lastDomChange = currentTime;
					}

					this.isStable = false;
					this.changeCount++;

					if (this.changeCount > this.maxChangeCount) {
						this.changeCount = 1;
					}

					// 清除之前的定时器
					clearTimeout(this.stabilityTimer);
					clearTimeout(this.decodePendingTimer);

					console.log(`记录${type}变化,重置稳定性定时器`);
				},

				// 重置状态
				reset() {
					this.changeCount = 0;
					this.isStable = false;
					this.pendingDecode = false;
					clearTimeout(this.stabilityTimer);
					clearTimeout(this.decodePendingTimer);
				}
			};

			// 添加配置监听
			this.setupConfigListeners();

			// 初始化UI
			this.initUI();
			this.initEventListeners();
			this.addRouteListeners();

			// 优化自动解码的初始化逻辑
			// 在构造函数中不直接执行自动解码,而是通过 resetState 方法处理
			if (this.config.autoDecode) {
				const currentTime = Date.now();
				// 确保足够的时间间隔
				if (currentTime - this.lastDecodeTime > MIN_DECODE_INTERVAL) {
					this.lastDecodeTime = currentTime;
					console.log('构造函数中准备执行 resetState');
					// 使用 requestIdleCallback 在浏览器空闲时执行
					if (window.requestIdleCallback) {
						requestIdleCallback(() => this.resetState(), { timeout: 2000 });
					} else {
						// 降级使用 setTimeout
						setTimeout(() => this.resetState(), 800);
					}
				}
			}

			// 添加DOM加载完成后的一次性解析
			const handleDOMReady = () => {
				// 重置标志
				this.isInitialLoad = false;
				this.isPageRefresh = false; // 重置页面刷新标志
				this.pageRefreshCompleted = true; // 设置页面刷新完成标志

				// 检查页面上是否已有解码内容
				const hasDecodedContent = document.querySelectorAll('.decoded-text').length > 0;

				// 如果页面上已有解码内容,更新菜单状态
				if (hasDecodedContent) {
					console.log('页面上已有解码内容,更新菜单状态');
					if (this.decodeBtn) {
						this.decodeBtn.textContent = '恢复本页 Base64';
						this.decodeBtn.dataset.mode = 'restore';
					}
					setTimeout(updateMenuCommands, 100);
				}
				// 注意:我们在这里不执行解码,而是依赖resetState中的解码逻辑
				// 这样可以避免刷新页面时解码两次导致的文本抖动
			};

			// 如果文档已经加载完成,直接执行
			if (document.readyState === 'complete') {
				console.log('文档已加载完成,直接执行解析');
				handleDOMReady();
			} else {
				// 否则等待文档加载完成
				console.log('等待文档加载完成后执行解析');
				window.addEventListener('load', handleDOMReady, { once: true });
			}

			// 添加防抖相关的变量
			this.decodeDebounceTimer = null;
			this.notificationDebounceTimer = null;
			this.lastDecodeTime = 0;
			this.lastNotificationTime = 0;
			this.DECODE_DEBOUNCE_DELAY = 2000; // 解码防抖延迟时间(毫秒)
			this.NOTIFICATION_DEBOUNCE_DELAY = 3000; // 通知防抖延迟时间(毫秒)
		}

		/**
		 * 设置配置监听器
		 * @description 为各个配置项添加监听器,实现配置变更的实时响应
		 */
		setupConfigListeners() {
			// 清理现有监听器
			Object.values(this.configListeners).forEach(listenerId => {
				if (listenerId) {
					storageManager.removeChangeListener(listenerId);
				}
			});

			// 监听显示通知设置变更
			this.configListeners.showNotification = storageManager.addChangeListener(
				STORAGE_KEYS.SHOW_NOTIFICATION,
				(newValue) => {
					console.log('显示通知设置已更改:', newValue);
					this.config.showNotification = newValue;
				}
			);

			// 监听隐藏按钮设置变更
			this.configListeners.hideButton = storageManager.addChangeListener(
				STORAGE_KEYS.HIDE_BUTTON,
				(newValue) => {
					console.log('隐藏按钮设置已更改:', newValue);
					this.config.hideButton = newValue;

					// 实时更新UI显示状态
					const ui = this.shadowRoot?.querySelector('.base64-helper');
					if (ui) {
						ui.style.display = newValue ? 'none' : 'block';
					}
				}
			);

			// 监听自动解码设置变更
			this.configListeners.autoDecode = storageManager.addChangeListener(
				STORAGE_KEYS.AUTO_DECODE,
				(newValue) => {
					console.log('自动解码设置已更改:', newValue);
					this.config.autoDecode = newValue;

					// 如果启用了自动解码,立即解析页面
					if (newValue) {
						// 检查是否是通过菜单命令触发的变更
						// 如果是通过菜单命令触发,则不再显示确认对话框
						// 因为菜单命令处理程序中已经处理了这些确认

						// 立即解析页面
						this.hasAutoDecodedOnLoad = true; // 标记已执行过自动解码
						setTimeout(() => {
							this.handleDecode();
							// 同步按钮和菜单状态
							setTimeout(() => {
								this.syncButtonAndMenuState();
								// 更新油猴菜单命令
								updateMenuCommands();
							}, 200);
						}, 100);
					} else {
						// 如果关闭了自动解码,也需要同步状态
						setTimeout(() => {
							this.syncButtonAndMenuState();
							// 更新油猴菜单命令
							updateMenuCommands();
						}, 200);
					}
				}
			);

			// 监听按钮位置变更
			this.configListeners.buttonPosition = storageManager.addChangeListener(
				STORAGE_KEYS.BUTTON_POSITION,
				(newValue) => {
					console.log('按钮位置已更改:', newValue);
					// 更新按钮位置
					this.initPosition();
				}
			);
		}

		// 添加正则常量
		static URL_PATTERNS = {
			URL: /^(?:(?:https?|ftp):\/\/)?(?:(?:[\w-]+\.)+[a-z]{2,}|localhost)(?::\d+)?(?:\/[^\s]*)?$/i,
			EMAIL: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
			DOMAIN_PATTERNS: {
				POPULAR_SITES:
					/(?:google|youtube|facebook|twitter|instagram|linkedin|github|gitlab|bitbucket|stackoverflow|reddit|discord|twitch|tiktok|snapchat|pinterest|netflix|amazon|microsoft|apple|adobe)/i,
				VIDEO_SITES:
					/(?:bilibili|youku|iqiyi|douyin|kuaishou|nicovideo|vimeo|dailymotion)/i,
				CN_SITES:
					/(?:baidu|weibo|zhihu|taobao|tmall|jd|qq|163|sina|sohu|csdn|aliyun|tencent)/i,
				TLD: /\.(?:com|net|org|edu|gov|mil|biz|info|io|cn|me|tv|cc|uk|jp|ru|eu|au|de|fr)(?:\/|\?|#|$)/i,
			},
		};

		// UI 初始化
		initUI() {
			if (
				window.top !== window.self ||
				document.getElementById('base64-helper-root')
			) {
				return;
			}

			this.container = document.createElement('div');
			this.container.id = 'base64-helper-root';
			document.body.append(this.container);

			this.shadowRoot = this.container.attachShadow({ mode: 'open' });
			this.shadowRoot.appendChild(this.createShadowStyles());

			// 创建 UI 容器
			const uiContainer = document.createElement('div');
			uiContainer.className = 'base64-helper';
			uiContainer.style.cursor = 'grab';

			// 创建按钮和菜单
			this.mainBtn = this.createButton('Base64', 'main-btn');
			this.menu = this.createMenu();
			this.decodeBtn = this.menu.querySelector('[data-mode="decode"]');
			this.encodeBtn = this.menu.querySelector('.menu-item:not([data-mode])');

			// 添加到 UI 容器
			uiContainer.append(this.mainBtn, this.menu);
			this.shadowRoot.appendChild(uiContainer);

			// 初始化位置
			this.initPosition();

			// 如果配置为隐藏按钮,则设置为不可见
			if (this.config.hideButton) {
				uiContainer.style.display = 'none';
			}
		}

		createShadowStyles() {
			const style = document.createElement('style');
			style.textContent = STYLES.SHADOW_DOM;
			return style;
		}

		// 不再需要 createMainUI 方法,因为我们直接在 initUI 中创建 UI

		createButton(text, className) {
			const btn = document.createElement('button');
			btn.className = className;
			btn.textContent = text;
			return btn;
		}

		createMenu() {
			const menu = document.createElement('div');
			menu.className = 'menu';

			this.decodeBtn = this.createMenuItem('解析本页 Base64', 'decode');
			this.encodeBtn = this.createMenuItem('文本转 Base64');

			menu.append(this.decodeBtn, this.encodeBtn);
			return menu;
		}

		createMenuItem(text, mode) {
			const item = document.createElement('div');
			item.className = 'menu-item';
			item.textContent = text;
			if (mode) item.dataset.mode = mode;
			return item;
		}

		// 位置管理
		initPosition() {
			const pos = this.positionManager.get() || {
				x: window.innerWidth - 120,
				y: window.innerHeight - 80,
			};

			const ui = this.shadowRoot.querySelector('.base64-helper');
			ui.style.left = `${pos.x}px`;
			ui.style.top = `${pos.y}px`;

			// 新增: 初始化时更新菜单对齐
			this.updateMenuAlignment();
		}
		updateMenuAlignment() {
			const ui = this.shadowRoot.querySelector('.base64-helper');
			const menu = this.menu;
			const windowWidth = window.innerWidth;
			const windowHeight = window.innerHeight;
			const uiRect = ui.getBoundingClientRect();
			const centerX = uiRect.left + uiRect.width / 2;
			const centerY = uiRect.top + uiRect.height / 2;

			// 判断按钮是在页面左半边还是右半边
			if (centerX < windowWidth / 2) {
				// 左对齐
				menu.classList.remove('align-right');
				menu.classList.add('align-left');
			} else {
				// 右对齐
				menu.classList.remove('align-left');
				menu.classList.add('align-right');
			}

			// 判断按钮是在页面上半部分还是下半部分
			if (centerY < windowHeight / 2) {
				// 在页面上方,菜单向下弹出
				menu.classList.remove('popup-top');
				menu.classList.add('popup-bottom');
			} else {
				// 在页面下方,菜单向上弹出
				menu.classList.remove('popup-bottom');
				menu.classList.add('popup-top');
			}
		}
		get positionManager() {
			return {
				get: () => {
					// 使用 storageManager 获取按钮位置
					const saved = storageManager.get(STORAGE_KEYS.BUTTON_POSITION, null);
					if (!saved) return null;

					const ui = this.shadowRoot.querySelector('.base64-helper');
					const maxX = window.innerWidth - ui.offsetWidth - 20;
					const maxY = window.innerHeight - ui.offsetHeight - 20;

					return {
						x: Math.min(Math.max(saved.x, 20), maxX),
						y: Math.min(Math.max(saved.y, 20), maxY),
					};
				},
				set: (x, y) => {
					const ui = this.shadowRoot.querySelector('.base64-helper');
					const pos = {
						x: Math.max(
							20,
							Math.min(x, window.innerWidth - ui.offsetWidth - 20)
						),
						y: Math.max(
							20,
							Math.min(y, window.innerHeight - ui.offsetHeight - 20)
						),
					};

					// 使用 storageManager 存储按钮位置
					storageManager.set(STORAGE_KEYS.BUTTON_POSITION, pos);
					return pos;
				},
			};
		}

		// 初始化事件监听器
		initEventListeners() {
			this.addUnifiedEventListeners();
			this.addGlobalClickListeners();

			// 核心编解码事件监听
			const commonListeners = [
				{
					element: this.decodeBtn,
					events: [
						{
							name: 'click',
							handler: (e) => {
								e.preventDefault();
								e.stopPropagation();
								this.handleDecode();
							},
						},
					],
				},
				{
					element: this.encodeBtn,
					events: [
						{
							name: 'click',
							handler: (e) => {
								e.preventDefault();
								e.stopPropagation();
								this.handleEncode();
							},
						},
					],
				},
			];

			commonListeners.forEach(({ element, events }) => {
				events.forEach(({ name, handler }) => {
					element.addEventListener(name, handler, { passive: false });
					this.eventListeners.set(name, { element, event: name, handler });
				});
			});
		}

		addUnifiedEventListeners() {
			const ui = this.shadowRoot.querySelector('.base64-helper');
			const btn = this.mainBtn;

			// 统一的开始事件处理
			const startHandler = (e) => {
				e.preventDefault();
				e.stopPropagation();
				const point = e.touches ? e.touches[0] : e;
				this.isDragging = true;
				this.hasMoved = false;
				this.startX = point.clientX;
				this.startY = point.clientY;
				const rect = ui.getBoundingClientRect();
				this.initialX = rect.left;
				this.initialY = rect.top;
				this.startTime = Date.now();
				ui.style.transition = 'none';
				ui.classList.add('dragging');
				btn.style.cursor = 'grabbing';
			};

			// 统一的移动事件处理
			const moveHandler = (e) => {
				if (!this.isDragging) return;
				e.preventDefault();
				e.stopPropagation();

				const point = e.touches ? e.touches[0] : e;
				const moveX = Math.abs(point.clientX - this.startX);
				const moveY = Math.abs(point.clientY - this.startY);

				if (moveX > 5 || moveY > 5) {
					this.hasMoved = true;
					const dx = point.clientX - this.startX;
					const dy = point.clientY - this.startY;
					const newX = Math.min(
						Math.max(20, this.initialX + dx),
						window.innerWidth - ui.offsetWidth - 20
					);
					const newY = Math.min(
						Math.max(20, this.initialY + dy),
						window.innerHeight - ui.offsetHeight - 20
					);
					ui.style.left = `${newX}px`;
					ui.style.top = `${newY}px`;
				}
			};

			// 统一的结束事件处理
			const endHandler = (e) => {
				if (!this.isDragging) return;
				e.preventDefault();
				e.stopPropagation();

				this.isDragging = false;
				ui.classList.remove('dragging');
				btn.style.cursor = 'grab';
				ui.style.transition = 'opacity 0.3s ease';

				const duration = Date.now() - this.startTime;
				if (duration < 200 && !this.hasMoved) {
					this.toggleMenu(e);
				} else if (this.hasMoved) {
					const rect = ui.getBoundingClientRect();
					const pos = this.positionManager.set(rect.left, rect.top);
					ui.style.left = `${pos.x}px`;
					ui.style.top = `${pos.y}px`;
					// 新增: 拖动结束后更新菜单对齐
					this.updateMenuAlignment();
				}
			};

			// 统一收集所有事件监听器
			const listeners = [
				{
					element: ui,
					event: 'touchstart',
					handler: startHandler,
					options: { passive: false },
				},
				{
					element: ui,
					event: 'touchmove',
					handler: moveHandler,
					options: { passive: false },
				},
				{
					element: ui,
					event: 'touchend',
					handler: endHandler,
					options: { passive: false },
				},
				{ element: ui, event: 'mousedown', handler: startHandler },
				{ element: document, event: 'mousemove', handler: moveHandler },
				{ element: document, event: 'mouseup', handler: endHandler },
				{
					element: this.menu,
					event: 'touchstart',
					handler: (e) => e.stopPropagation(),
					options: { passive: false },
				},
				{
					element: this.menu,
					event: 'mousedown',
					handler: (e) => e.stopPropagation(),
				},
				{
					element: window,
					event: 'resize',
					handler: () => this.handleResize(),
				},
			];

			// 注册事件并保存引用
			listeners.forEach(({ element, event, handler, options }) => {
				element.addEventListener(event, handler, options);
				this.eventListeners.set(event, { element, event, handler, options });
			});
		}

		toggleMenu(e) {
			e?.preventDefault();
			e?.stopPropagation();

			// 如果正在拖动或已移动,不处理菜单切换
			if (this.isDragging || this.hasMoved) return;

			this.menuVisible = !this.menuVisible;
			if (this.menuVisible) {
				// 在显示菜单前更新位置
				this.updateMenuAlignment();
			}
			this.menu.style.display = this.menuVisible ? 'block' : 'none';

			// 重置状态
			this.hasMoved = false;
		}

		addGlobalClickListeners() {
			const handleOutsideClick = (e) => {
				const ui = this.shadowRoot.querySelector('.base64-helper');
				const path = e.composedPath();
				if (!path.includes(ui) && this.menuVisible) {
					this.menuVisible = false;
					this.menu.style.display = 'none';
				}
			};

			// 将全局点击事件添加到 eventListeners 数组
			const globalListeners = [
				{
					element: document,
					event: 'click',
					handler: handleOutsideClick,
					options: true,
				},
				{
					element: document,
					event: 'touchstart',
					handler: handleOutsideClick,
					options: { passive: false },
				},
			];

			globalListeners.forEach(({ element, event, handler, options }) => {
				element.addEventListener(event, handler, options);
				this.eventListeners.set(event, { element, event, handler, options });
			});
		}

		// 路由监听
		addRouteListeners() {
			// 统一的路由变化处理函数
			this.handleRouteChange = () => {
				console.log('路由变化被检测到');
				// 使用防抖,避免短时间内多次触发
				clearTimeout(this.routeTimer);

				// 添加时间检查,避免短时间内多次触发
				const currentTime = Date.now();
				if (currentTime - this.lastDecodeTime < 1000) {
					console.log('距离上次解码时间太短,跳过这次路由变化');
					return;
				}

				// 如果启用了自动解码,直接执行全页解码
				if (this.config.autoDecode) {
					console.log('路由变化,执行全页解码');
					// 确保没有正在进行的处理
					if (!this.isProcessing && !this.isDecodingContent && !this.isRestoringContent) {
						// 使用延时确保页面内容已更新
						setTimeout(() => {
							this.handleAutoDecode(true, true);
						}, 500);
					}
				}
			};

			// 添加路由相关事件到 eventListeners 数组
			const routeListeners = [
				{
					element: window,
					event: 'popstate',
					handler: (e) => {
						console.log('检测到popstate前进后退事件');
						// 重置页面状态标志
						this.isInitialLoad = false;
						this.isPageRefresh = false;
						this.pageRefreshCompleted = true;

						// 创建一个临时的MutationObserver来监听页面内容变化
						const tempObserver = new MutationObserver((mutations) => {
							// 检查是否有显著的DOM变化
							const hasSignificantChanges = mutations.some(mutation => {
								// 忽略文本节点的变化
								if (mutation.type === 'characterData') return false;

								// 忽略样式相关的属性变化
								if (mutation.type === 'attributes' &&
									(mutation.attributeName === 'style' ||
									 mutation.attributeName === 'class')) {
									return false;
								}

								// 检查是否是重要的DOM变化
								const isImportantNode = (node) => {
									return node.nodeType === 1 && // 元素节点
										(node.tagName === 'DIV' ||
										 node.tagName === 'ARTICLE' ||
										 node.tagName === 'SECTION' ||
										 node.tagName === 'MAIN');
								};

								return Array.from(mutation.addedNodes).some(isImportantNode) ||
									   Array.from(mutation.removedNodes).some(isImportantNode);
							});

							if (hasSignificantChanges) {
								console.log('检测到新页面内容加载完成');
								// 停止观察
								tempObserver.disconnect();
								// 执行解码
								if (this.config.autoDecode) {
									setTimeout(() => {
										this.handleAutoDecode(true, true);
									}, 500);
								}
							}
						});

						// 开始观察页面变化
						tempObserver.observe(document.body, {
							childList: true,
							subtree: true,
							attributes: false,
							characterData: false
						});

						// 设置超时,防止页面变化检测失败
						setTimeout(() => {
							tempObserver.disconnect();
							if (this.config.autoDecode) {
								this.handleAutoDecode(true, true);
							}
						}, 3000);
					}
				},
				{
					element: window,
					event: 'hashchange',
					handler: this.handleRouteChange,
				},
				{
					element: window,
					event: 'DOMContentLoaded',
					handler: this.handleRouteChange,
				},
				{
					element: document,
					event: 'readystatechange',
					handler: () => {
						if (document.readyState === 'complete') {
							this.handleRouteChange();
						}
					},
				},
				{
					element: window,
					event: 'pageshow',
					handler: (e) => {
						console.log('检测到pageshow事件');
						// 重置页面状态标志
						this.isInitialLoad = false;
						this.isPageRefresh = false;
						this.pageRefreshCompleted = true;

						// 创建一个临时的MutationObserver来监听页面内容变化
						const tempObserver = new MutationObserver((mutations) => {
							// 检查是否有显著的DOM变化
							const hasSignificantChanges = mutations.some(mutation => {
								// 忽略文本节点的变化
								if (mutation.type === 'characterData') return false;

								// 忽略样式相关的属性变化
								if (mutation.type === 'attributes' &&
									(mutation.attributeName === 'style' ||
									 mutation.attributeName === 'class')) {
									return false;
								}

								// 检查是否是重要的DOM变化
								const isImportantNode = (node) => {
									return node.nodeType === 1 && // 元素节点
										(node.tagName === 'DIV' ||
										 node.tagName === 'ARTICLE' ||
										 node.tagName === 'SECTION' ||
										 node.tagName === 'MAIN');
								};

								return Array.from(mutation.addedNodes).some(isImportantNode) ||
									   Array.from(mutation.removedNodes).some(isImportantNode);
							});

							if (hasSignificantChanges) {
								console.log('检测到新页面内容加载完成');
								// 停止观察
								tempObserver.disconnect();
								// 执行解码
								if (this.config.autoDecode) {
									setTimeout(() => {
										this.handleAutoDecode(true, true);
									}, 500);
								}
							}
						});

						// 开始观察页面变化
						tempObserver.observe(document.body, {
							childList: true,
							subtree: true,
							attributes: false,
							characterData: false
						});

						// 设置超时,防止页面变化检测失败
						setTimeout(() => {
							tempObserver.disconnect();
							if (this.config.autoDecode) {
								this.handleAutoDecode(true, true);
							}
						}, 3000);
					},
				},
				{
					element: window,
					event: 'pagehide',
					handler: this.handleRouteChange,
				},
			];

			// 确保在页面加载完成后添加事件监听器
			if (document.readyState === 'complete') {
				routeListeners.forEach(({ element, event, handler }) => {
					element.addEventListener(event, handler);
					this.eventListeners.set(event, { element, event, handler });
				});
			} else {
				window.addEventListener('load', () => {
					routeListeners.forEach(({ element, event, handler }) => {
							element.addEventListener(event, handler);
							this.eventListeners.set(event, { element, event, handler });
					});
				}, { once: true });
			}

			// 修改 history 方法
			this.originalPushState = history.pushState;
			this.originalReplaceState = history.replaceState;
			history.pushState = (...args) => {
				this.originalPushState.apply(history, args);
				console.log('history.pushState 被调用');
				this.handleRouteChange();
			};
			history.replaceState = (...args) => {
				this.originalReplaceState.apply(history, args);
				console.log('history.replaceState 被调用');
				this.handleRouteChange();
			};

			// 优化 MutationObserver 配置
			this.observer = new MutationObserver((mutations) => {
				// 如果正在处理中或正在显示通知,跳过这次变化
				if (this.isProcessing || this.isShowingNotification || this.isDecodingContent || this.isRestoringContent) {
					console.log('正在处理中或显示通知,跳过 DOM 变化检测');
					return;
				}

				// 添加防止短时间内重复触发的防抖
				const currentTime = Date.now();
				if (currentTime - this.lastDecodeTime < 1500) {
					console.log('距离上次解码时间太短,跳过这次 DOM 变化检测');
					return;
				}

				// 存储mutations供后续使用
				this.currentMutations = mutations;

				// 检查是否有显著的 DOM 变化
				const significantChanges = mutations.some(mutation => {
					// 忽略文本节点的变化
					if (mutation.type === 'characterData') return false;

					// 忽略样式相关的属性变化
					if (mutation.type === 'attributes' &&
						(mutation.attributeName === 'style' ||
						 mutation.attributeName === 'class')) {
						return false;
					}

					// 排除通知容器的变化
					if (mutation.target &&
						(mutation.target.classList?.contains('base64-notifications-container') ||
						 mutation.target.classList?.contains('base64-notification'))) {
						return false;
					}

					// 检查添加的节点是否与通知相关
					const isNotificationNode = (node) => {
						if (node.nodeType !== 1) return false; // 非元素节点
						return node.classList?.contains('base64-notifications-container') ||
							   node.classList?.contains('base64-notification') ||
							   node.closest('.base64-notifications-container') !== null;
					};

					// 如果添加的节点是通知相关的,则忽略
					if (Array.from(mutation.addedNodes).some(isNotificationNode)) {
						return false;
					}

					// 如果有大量节点添加或删除,可能是路由变化
					if (mutation.addedNodes.length > 5 || mutation.removedNodes.length > 5) {
						return true;
					}

					// 检查是否是重要的 DOM 变化
					const isImportantNode = (node) => {
						return node.nodeType === 1 && // 元素节点
							(node.tagName === 'DIV' ||
							 node.tagName === 'ARTICLE' ||
							 node.tagName === 'SECTION');
					};

					return Array.from(mutation.addedNodes).some(isImportantNode) ||
						   Array.from(mutation.removedNodes).some(isImportantNode);
				});

				if (significantChanges && this.config.autoDecode) {
					console.log('检测到显著的 DOM 变化,可能是路由变化');
					this.handleRouteChange();
				}
			});

			// 优化 MutationObserver 观察选项
			this.observer.observe(document.body, {
				childList: true,
				subtree: true,
				attributes: false, // 不观察属性变化
				characterData: false // 不观察文本变化
			});

			// 监听路由变化
			const observer = new MutationObserver((mutations) => {
				mutations.forEach((mutation) => {
					if (mutation.type === 'childList' || mutation.type === 'subtree') {
						// 检查是否是路由变化
						if (window.location.href !== this.lastUrl) {
							this.lastUrl = window.location.href;
							console.log('检测到路由变化,准备执行解码');

							// 重置状态
							this.resetState();

							// 如果启用了自动解码,等待页面稳定后执行
							if (this.config.autoDecode) {
								setTimeout(() => {
									const { nodesToReplace, validDecodedCount } = this.processTextNodes();
									if (validDecodedCount > 0) {
										this.replaceNodes(nodesToReplace);
										setTimeout(() => {
											this.addClickListenersToDecodedText();
											this.debouncedShowNotification(`解码成功,共找到 ${validDecodedCount} 个 Base64 内容`, 'success');
											this.syncButtonAndMenuState();
										}, 100);
									}
								}, 500);
							}
						}
					}
				});
			});
		}

		/**
		 * 处理自动解码
		 * @description 根据不同场景选择全页解码或增量解码
		 * @param {boolean} [forceFullDecode=false] - 是否强制全页面解码
		 * @param {boolean} [showNotification=false] - 是否显示通知
		 */
		handleAutoDecode(forceFullDecode = false, showNotification = false) {
			// 防止重复处理
			if (this.isProcessing || this.isShowingNotification || this.isDecodingContent || this.isRestoringContent) {
				console.log('正在处理中,跳过自动解码请求');
				return;
			}

			// 控制通知显示
			this.suppressNotification = !showNotification;

			// 更新最后解码时间
			this.lastDecodeTime = Date.now();

			console.log('执行全页面解码');
			// 使用processTextNodes方法进行解码
			const { nodesToReplace, validDecodedCount } = this.processTextNodes();

			if (validDecodedCount > 0) {
				// 分批处理节点替换
				const BATCH_SIZE = 50;
				const processNodesBatch = (startIndex) => {
					const endIndex = Math.min(startIndex + BATCH_SIZE, nodesToReplace.length);
					const batch = nodesToReplace.slice(startIndex, endIndex);

					this.replaceNodes(batch);

					if (endIndex < nodesToReplace.length) {
						setTimeout(() => processNodesBatch(endIndex), 0);
					} else {
						setTimeout(() => {
							this.addClickListenersToDecodedText();
							if (showNotification) {
								this.showNotification(`解码成功,共找到 ${validDecodedCount} 个 Base64 内容`, 'success');
							}
							// 同步按钮和菜单状态
							this.syncButtonAndMenuState();
							// 更新油猴菜单命令
							updateMenuCommands();
						}, 100);
					}
				};

				processNodesBatch(0);
			}

			// 同步按钮和菜单状态
			setTimeout(() => {
				this.syncButtonAndMenuState();
				// 更新油猴菜单命令
				updateMenuCommands();
			}, 200);
		}

		/**
		 * 处理页面中的Base64解码操作
		 * @description 根据当前模式执行解码或恢复操作
		 * 如果当前模式是restore则恢复原始内容,否则查找并解码页面中的Base64内容
		 * @fires showNotification 显示操作结果通知
		 */
		handleDecode() {
			// 检查当前模式
			const hasDecodedContent = document.querySelectorAll('.decoded-text').length > 0;
			const currentMode = this.decodeBtn?.dataset.mode === 'restore' || hasDecodedContent ? 'restore' : 'decode';

			// 如果是恢复模式
			if (currentMode === 'restore') {
				this.restoreContent();
				return;
			}

			// 防止重复处理或在显示通知时触发
			if (this.isProcessing || this.isShowingNotification || this.isDecodingContent || this.isRestoringContent) {
				console.log('正在处理中,跳过解码请求');
				return;
			}

			try {
				// 隐藏菜单
				if (this.menu && this.menu.style.display !== 'none') {
					this.menu.style.display = 'none';
					this.menuVisible = false;
				}

				// 执行解码
				this.isDecodingContent = true;
				const { nodesToReplace, validDecodedCount } = this.processTextNodes();

				if (validDecodedCount === 0) {
					this.showNotification('本页未发现有效 Base64 内容', 'info');
					this.menuVisible = false;
					this.menu.style.display = 'none';
					// 重置处理标志
					this.isProcessing = false;
					this.isDecodingContent = false;
					// 更新最后解码时间
					this.lastDecodeTime = Date.now();
					return;
				}

				// 分批处理节点替换,避免大量 DOM 操作导致界面冻结
				const BATCH_SIZE = 50;
				const processNodesBatch = (startIndex) => {
					const endIndex = Math.min(startIndex + BATCH_SIZE, nodesToReplace.length);
					const batch = nodesToReplace.slice(startIndex, endIndex);

					this.replaceNodes(batch);

					if (endIndex < nodesToReplace.length) {
						// 还有更多节点需要处理,安排下一批
						setTimeout(() => processNodesBatch(endIndex), 0);
					} else {
						// 所有节点处理完成,添加点击监听器
						setTimeout(() => {
							this.addClickListenersToDecodedText();
						}, 100);

						// 更新按钮状态
						if (this.decodeBtn) {
							this.decodeBtn.textContent = '恢复本页 Base64';
							this.decodeBtn.dataset.mode = 'restore';
						}

						// 显示通知,除非被抑制
						if (!this.suppressNotification) {
							this.showNotification(
								`解码成功,共找到 ${validDecodedCount} 个 Base64 内容`,
								'success'
							);
						}

						// 操作完成后同步按钮和菜单状态
						this.syncButtonAndMenuState();
						// 更新油猴菜单命令
						updateMenuCommands();

						// 重置处理标志
						this.isProcessing = false;
						this.isDecodingContent = false;

						// 更新最后解码时间
						this.lastDecodeTime = Date.now();
					}
				};

				// 开始分批处理
				processNodesBatch(0);
			} catch (e) {
				console.error('Base64 decode error:', e);
				// 显示错误通知
				this.showNotification(`解析失败: ${e.message}`, 'error');
				this.menuVisible = false;
				this.menu.style.display = 'none';
				// 重置处理标志
				this.isProcessing = false;
				this.isDecodingContent = false;
				// 更新最后解码时间
				this.lastDecodeTime = Date.now();
			}
		}

		/**
		 * 处理文本节点中的Base64内容
		 * @description 遍历文档中的文本节点,查找并处理其中的Base64内容
		 * 注意: 此方法包含性能优化措施,如超时检测和节点过滤
		 * @returns {Object} 处理结果
		 * @property {Array} nodesToReplace - 需要替换的节点数组
		 * @property {number} validDecodedCount - 有效的Base64解码数量
		 */
		processTextNodes() {
			const startTime = Date.now();
			const TIMEOUT = 5000;

			const excludeTags = new Set([
				'script',
				'style',
				'noscript',
				'iframe',
				'img',
				'input',
				'textarea',
				'svg',
				'canvas',
				'template',
				'pre',
				'code',
				'button',
				'meta',
				'link',
				'head',
				'title',
				'select',
				'form',
				'object',
				'embed',
				'video',
				'audio',
				'source',
				'track',
				'map',
				'area',
				'math',
				'figure',
				'picture',
				'portal',
				'slot',
				'data',
				'a',
				'base', // 包含href属性的base标签
				'param', // object的参数
				'applet', // 旧版Java小程序
				'frame', // 框架
				'frameset', // 框架集
				'marquee', // 滚动文本
				'time', // 时间标签
				'wbr', // 可能的换行符
				'bdo', // 文字方向
				'dialog', // 对话框
				'details', // 详情
				'summary', // 摘要
				'menu', // 菜单
				'menuitem', // 菜单项
				'[hidden]', // 隐藏元素
				'[aria-hidden="true"]', // 可访问性隐藏
				'.base64', // 自定义class
				'.encoded', // 自定义class
			]);

			const excludeAttrs = new Set([
				'src',
				'data-src',
				'href',
				'data-url',
				'content',
				'background',
				'poster',
				'data-image',
				'srcset',
				'data-background', // 背景图片
				'data-thumbnail', // 缩略图
				'data-original', // 原始图片
				'data-lazy', // 懒加载
				'data-defer', // 延迟加载
				'data-fallback', // 后备图片
				'data-preview', // 预览图
				'data-avatar', // 头像
				'data-icon', // 图标
				'data-base64', // 显式标记的base64
				'style', // 内联样式可能包含base64
				'integrity', // SRI完整性校验
				'crossorigin', // 跨域属性
				'rel', // 关系属性
				'alt', // 替代文本
				'title', // 标题属性
			]);

			const walker = document.createTreeWalker(
				document.body,
				NodeFilter.SHOW_TEXT,
				{
					acceptNode: (node) => {
						const isExcludedTag = (parent) => {
							const tagName = parent.tagName?.toLowerCase();
							return excludeTags.has(tagName);
						};

						const isHiddenElement = (parent) => {
							if (!(parent instanceof HTMLElement)) return false;
							const style = window.getComputedStyle(parent);
							return (
								style.display === 'none' ||
								style.visibility === 'hidden' ||
								style.opacity === '0' ||
								style.clipPath === 'inset(100%)' ||
								(style.height === '0px' && style.overflow === 'hidden')
							);
						};

						const isOutOfViewport = (parent) => {
							if (!(parent instanceof HTMLElement)) return false;
							const rect = parent.getBoundingClientRect();
							return rect.width === 0 || rect.height === 0;
						};

						const hasBase64Attributes = (parent) => {
							if (!parent.hasAttributes()) return false;
							for (const attr of parent.attributes) {
								if (excludeAttrs.has(attr.name)) {
									const value = attr.value.toLowerCase();
									if (
										value.includes('base64') ||
										value.match(/^[a-z0-9+/=]+$/i)
									) {
										return true;
									}
								}
							}
							return false;
						};

						let parent = node.parentNode;
						while (parent && parent !== document.body) {
							if (
								isExcludedTag(parent) ||
								isHiddenElement(parent) ||
								isOutOfViewport(parent) ||
								hasBase64Attributes(parent)
							) {
								return NodeFilter.FILTER_REJECT;
							}
							parent = parent.parentNode;
						}

						const text = node.textContent?.trim();
						if (!text) {
							return NodeFilter.FILTER_SKIP;
						}

						return /[A-Za-z0-9+/]+/.exec(text)
							? NodeFilter.FILTER_ACCEPT
							: NodeFilter.FILTER_SKIP;
					},
				},
				false
			);

			let nodesToReplace = [];
			let processedMatches = new Set();
			let validDecodedCount = 0;

			while (walker.nextNode()) {
				if (Date.now() - startTime > TIMEOUT) {
					console.warn('Base64 processing timeout');
					break;
				}

				const node = walker.currentNode;
				const { modified, newHtml, count } = this.processMatches(
					node.nodeValue,
					processedMatches
				);
				if (modified) {
					nodesToReplace.push({ node, newHtml });
					validDecodedCount += count;
				}
			}

			return { nodesToReplace, validDecodedCount };
		}

		/**
		 * 收集变化的节点
		 * @description 从变化记录中收集需要处理的节点
		 * @param {MutationRecord[]} mutations - 变化记录数组
		 * @returns {Node[]} 需要处理的节点数组
		 */
		collectChangedNodes(mutations) {
			const changedNodes = [];
			const excludeTags = new Set([
				'script', 'style', 'noscript', 'iframe', 'img', 'input', 'textarea',
				'svg', 'canvas', 'template', 'pre', 'code', 'button', 'meta', 'link'
			]);

			// 遍历所有变化记录
			for (const mutation of mutations) {
				// 跳过通知相关的变化
				if (mutation.target && (
					mutation.target.classList?.contains('base64-notifications-container') ||
					mutation.target.classList?.contains('base64-notification') ||
					mutation.target.closest?.('.base64-notifications-container')
				)) {
					continue;
				}

				// 处理新添加的节点
				if (mutation.addedNodes.length > 0) {
					for (const node of mutation.addedNodes) {
						// 跳过已处理过的节点
						if (this.processedNodes.has(node)) {
							continue;
						}

						// 跳过非元素节点和排除的标签
						if (node.nodeType === 1 && excludeTags.has(node.tagName.toLowerCase())) {
							continue;
						}

						// 跳过已解码的文本节点
						if (node.classList?.contains('decoded-text')) {
							continue;
						}

						// 添加到待处理节点列表
						changedNodes.push(node);
						// 标记为已处理
						this.processedNodes.add(node);
					}
				}

				// 处理变化的目标节点
				if (mutation.type === 'childList' && !this.processedNodes.has(mutation.target)) {
					// 跳过非元素节点和排除的标签
					if (mutation.target.nodeType === 1 &&
						!excludeTags.has(mutation.target.tagName?.toLowerCase()) &&
						!mutation.target.classList?.contains('decoded-text')) {
						changedNodes.push(mutation.target);
						this.processedNodes.add(mutation.target);
					}
				}
			}

			return changedNodes;
		}

		/**
		 * 处理增量解码
		 * @description 只对变化的节点进行解码处理
		 * @param {Node[]} changedNodes - 需要处理的节点数组
		 */
		async handleIncrementalDecode(changedNodes) {
			console.log(`开始增量解码,处理 ${changedNodes.length} 个变化节点`);

			// 设置处理标志
			this.isProcessing = true;
			this.isDecodingContent = true;

			try {
				// 处理每个变化节点
				let validDecodedCount = 0;
				const nodesToReplace = [];
				const processedMatches = new Set();

				// 递归处理节点及其子节点
				const processNode = (node) => {
					// 如果是文本节点,处理其内容
					if (node.nodeType === 3 && node.nodeValue?.trim()) {
						const { modified, newHtml, count } = this.processMatches(node.nodeValue, processedMatches);
						if (modified) {
							nodesToReplace.push({ node, newHtml });
							validDecodedCount += count;
						}
					} else if (node.nodeType === 1) {
						// 如果是元素节点,递归处理其子节点
						const excludeTags = new Set([
							'script', 'style', 'noscript', 'iframe', 'img', 'input', 'textarea',
							'svg', 'canvas', 'template', 'pre', 'code', 'button', 'meta', 'link'
						]);

						// 跳过排除的标签和已解码的元素
						if (excludeTags.has(node.tagName.toLowerCase()) ||
							node.classList?.contains('decoded-text') ||
							node.closest?.('.decoded-text')) {
							return;
						}

						// 递归处理子节点
						for (const child of node.childNodes) {
							processNode(child);
						}
					}
				};

				// 处理所有变化节点
				for (const node of changedNodes) {
					processNode(node);
				}

				// 如果没有找到有效的解码内容
				if (validDecodedCount === 0) {
					console.log('增量解码未发现有效 Base64 内容');
					// 重置处理标志
					this.isProcessing = false;
					this.isDecodingContent = false;
					return;
				}

				// 分批处理节点替换,避免大量 DOM 操作导致界面冻结
				const BATCH_SIZE = 50;
				const processNodesBatch = async (startIndex) => {
					const endIndex = Math.min(startIndex + BATCH_SIZE, nodesToReplace.length);
					const batch = nodesToReplace.slice(startIndex, endIndex);

					this.replaceNodes(batch);

					if (endIndex < nodesToReplace.length) {
						// 还有更多节点需要处理,安排下一批
						setTimeout(() => processNodesBatch(endIndex), 0);
					} else {
						// 所有节点处理完成,添加点击监听器
						await this.addClickListenersToDecodedText();

						// 更新按钮状态
						if (this.decodeBtn) {
							this.decodeBtn.textContent = '恢复本页 Base64';
							this.decodeBtn.dataset.mode = 'restore';
						}

						// 显示通知,除非被抑制
						if (!this.suppressNotification) {
							this.showNotification(
								`解码成功,共找到 ${validDecodedCount} 个 Base64 内容`,
								'success'
							);
						}

						// 操作完成后同步按钮和菜单状态
						this.syncButtonAndMenuState();

						// 重置处理标志
						this.isProcessing = false;
						this.isDecodingContent = false;

						// 更新最后解码时间
						this.lastDecodeTime = Date.now();
					}
				};

				// 开始分批处理
				await processNodesBatch(0);
			} catch (e) {
				console.error('增量解码处理错误:', e);
				// 检查是否有成功解码的内容
				const hasDecodedContent = document.querySelectorAll('.decoded-text').length > 0;
				// 更新按钮状态
				if (hasDecodedContent) {
					// 如果有成功解码的内容,更新按钮状态但不显示通知
					// 在自动解码模式下,静默处理部分解码失败的情况
					if (this.decodeBtn) {
						this.decodeBtn.textContent = '恢复本页 Base64';
						this.decodeBtn.dataset.mode = 'restore';
					}
					// 操作完成后同步按钮和菜单状态
					this.syncButtonAndMenuState();
				} else {
					// 如果没有成功解码的内容,不显示失败通知
					// 在自动解码模式下,静默处理解码失败的情况
					console.log('自动解码未发现有效内容,静默处理');
				}
				// 重置处理标志
				this.isProcessing = false;
				this.isDecodingContent = false;
				// 更新最后解码时间
				this.lastDecodeTime = Date.now();
			}
		}

		/**
		 * 处理文本中的Base64匹配项
		 * @description 查找并处理文本中的Base64编码内容
		 * @param {string} text - 要处理的文本内容
		 * @param {Set} processedMatches - 已处理过的匹配项集合
		 * @returns {Object} 处理结果
		 * @property {boolean} modified - 文本是否被修改
		 * @property {string} newHtml - 处理后的HTML内容
		 * @property {number} count - 处理的Base64数量
		 */
		processMatches(text, processedMatches) {
			const matches = Array.from(text.matchAll(BASE64_REGEX));
			if (!matches.length) return { modified: false, newHtml: text, count: 0 };

			let modified = false;
			let newHtml = text;
			let count = 0;

			for (const match of matches.reverse()) {
				const original = match[0];

				// 使用 validateBase64 进行验证
				if (!this.validateBase64(original)) {
					console.log('Skipped: invalid Base64 string');
					continue;
				}

				try {
					const decoded = this.decodeBase64(original);
					console.log('Decoded:', decoded);

					if (!decoded) {
						console.log('Skipped: decode failed');
						continue;
					}

					// 将原始Base64和位置信息添加到已处理集合中,防止重复处理
					const matchKey = `${original}-${match.index}`;
					processedMatches.add(matchKey);

					// 创建解码文本节点
					const span = document.createElement('span');
					span.className = 'decoded-text';
					span.title = '点击复制';
					span.dataset.original = original;
					span.textContent = decoded;

					// 直接添加点击事件监听器
					span.addEventListener('click', async (e) => {
						e.preventDefault();
						e.stopPropagation();
						const success = await this.copyToClipboard(decoded);
						this.debouncedShowNotification(
							success ? '已复制文本内容' : '复制失败,请手动复制',
							success ? 'success' : 'error'
						);
					});

					// 构建新的HTML内容
					const beforeMatch = newHtml.substring(0, match.index);
					const afterMatch = newHtml.substring(match.index + original.length);
					newHtml = beforeMatch + span.outerHTML + afterMatch;

					// 标记内容已被修改
					modified = true;
					// 增加成功解码计数
					count++;

					// 记录日志
					console.log('成功解码: 发现有意义的文本或中文字符');
				} catch (e) {
					console.error('Error processing:', e);
					continue;
				}
			}

			return { modified, newHtml, count };
		}

		/**
		 * 判断文本是否有意义
		 * @description 通过一系列规则判断解码后的文本是否具有实际意义
		 * @param {string} text - 要验证的文本
		 * @returns {boolean} 如果文本有意义返回true,否则返回false
		 */
		isMeaningfulText(text) {
			// 1. 基本字符检查
			if (!text || typeof text !== 'string') return false;

			// 2. 长度检查
			if (text.length < 2 || text.length > 10000) return false;

			// 3. 文本质量检查
			const stats = {
				printable: 0, // 可打印字符
				control: 0, // 控制字符
				chinese: 0, // 中文字符
				letters: 0, // 英文字母
				numbers: 0, // 数字
				punctuation: 0, // 标点符号
				spaces: 0, // 空格
				other: 0, // 其他字符
			};

			// 统计字符分布
			for (let i = 0; i < text.length; i++) {
				const char = text.charAt(i);
				const code = text.charCodeAt(i);

				if (/[\u4E00-\u9FFF]/.test(char)) {
					stats.chinese++;
					stats.printable++;
				} else if (/[a-zA-Z]/.test(char)) {
					stats.letters++;
					stats.printable++;
				} else if (/[0-9]/.test(char)) {
					stats.numbers++;
					stats.printable++;
				} else if (/[\s]/.test(char)) {
					stats.spaces++;
					stats.printable++;
				} else if (/[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/.test(char)) {
					stats.punctuation++;
					stats.printable++;
				} else if (code < 32 || code === 127) {
					stats.control++;
				} else {
					stats.other++;
				}
			}

			// 4. 质量评估规则
			const totalChars = text.length;
			const printableRatio = stats.printable / totalChars;
			const controlRatio = stats.control / totalChars;
			const meaningfulRatio =
				(stats.chinese + stats.letters + stats.numbers) / totalChars;

			// 判断条件:
			// 1. 可打印字符比例必须大于90%
			// 2. 控制字符比例必须小于5%
			// 3. 有意义字符(中文、英文、数字)比例必须大于30%
			// 4. 空格比例不能过高(小于50%)
			// 5. 其他字符比例必须很低(小于10%)
			return (
				printableRatio > 0.9 &&
				controlRatio < 0.05 &&
				meaningfulRatio > 0.3 &&
				stats.spaces / totalChars < 0.5 &&
				stats.other / totalChars < 0.1
			);
		}

		/**
		 * 替换页面中的节点
		 * @description 使用新的HTML内容替换原有节点
		 * @param {Array} nodesToReplace - 需要替换的节点数组
		 * @param {Node} nodesToReplace[].node - 原始节点
		 * @param {string} nodesToReplace[].newHtml - 新的HTML内容
		 */
		replaceNodes(nodesToReplace) {
			nodesToReplace.forEach(({ node, newHtml }) => {
				if (node && node.parentNode) {
					// 创建临时容器
					const temp = document.createElement('div');
					temp.innerHTML = newHtml;

					// 替换节点
					while (temp.firstChild) {
						node.parentNode.insertBefore(temp.firstChild, node);
					}
					node.parentNode.removeChild(node);
				}
			});
		}

		/**
		 * 为解码后的文本添加点击复制功能
		 * @description 为所有解码后的文本元素添加点击事件监听器
		 * @fires copyToClipboard 点击时触发复制操作
		 * @fires showNotification 显示复制结果通知
		 */
		async addClickListenersToDecodedText() {
			// 等待 DOM 更新完成
			await this.waitForDOMUpdate();

			// 获取所有解码文本节点
			const decodedTextNodes = document.querySelectorAll('.decoded-text');
			console.log('找到解码文本节点数量:', decodedTextNodes.length);

			decodedTextNodes.forEach((el) => {
				// 检查是否已经有事件监听器
				if (!el.hasAttribute('data-has-listener')) {
					// 添加新的事件监听器
					el.addEventListener('click', async (e) => {
						e.preventDefault();
						e.stopPropagation();
						const success = await this.copyToClipboard(e.target.textContent);
						this.debouncedShowNotification(
							success ? '已复制文本内容' : '复制失败,请手动复制',
							success ? 'success' : 'error'
						);
					});

					// 标记节点已添加事件监听器
					el.setAttribute('data-has-listener', 'true');
					console.log('已为节点添加点击事件监听器');
				}
			});
		}

		/**
		 * 处理文本编码为Base64
		 * @description 提示用户输入文本并转换为Base64格式
		 * @async
		 * @fires showNotification 显示编码结果通知
		 * @fires copyToClipboard 复制编码结果到剪贴板
		 */
		async handleEncode() {
			// 隐藏菜单
			if (this.menu && this.menu.style.display !== 'none') {
				this.menu.style.display = 'none';
				this.menuVisible = false;
			}

			const text = prompt('请输入要编码的文本:');
			if (text === null) return; // 用户点击取消

			// 添加空输入检查
			if (!text.trim()) {
				this.debouncedShowNotification('请输入有效的文本内容', 'error');
				return;
			}

			try {
				// 处理输入文本:去除首尾空格和多余的换行符
				const processedText = text.trim().replace(/[\r\n]+/g, '\n');
				const encoded = this.encodeBase64(processedText);
				const success = await this.copyToClipboard(encoded);
				this.debouncedShowNotification(
					success
						? 'Base64 已复制'
						: '编码成功但复制失败,请手动复制:' + encoded,
					success ? 'success' : 'info'
				);
			} catch (e) {
				this.debouncedShowNotification('编码失败: ' + e.message, 'error');
			}
		}

		/**
		 * 验证Base64字符串
		 * @description 检查字符串是否为有效的Base64格式
		 * @param {string} str - 要验证的字符串
		 * @returns {boolean} 如果是有效的Base64返回true,否则返回false
		 * @example
		 * validateBase64('SGVsbG8gV29ybGQ=') // returns true
		 * validateBase64('Invalid-Base64') // returns false
		 */
		validateBase64(str) {
			if (!str) return false;

			// 使用缓存避免重复验证
			if (this.base64Cache.has(str)) {
				return this.base64Cache.get(str);
			}

			// 检查缓存大小并在必要时清理
			if (this.base64Cache.size >= this.MAX_CACHE_SIZE) {
				// 删除最早添加的缓存项
				const oldestKey = this.base64Cache.keys().next().value;
				this.base64Cache.delete(oldestKey);
			}

			// 1. 基本格式检查
			// - 长度必须是4的倍数
			// - 只允许包含合法的Base64字符
			// - =号只能出现在末尾,且最多2个
			if (
				!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(
					str
				)
			) {
				this.base64Cache.set(str, false);
				return false;
			}

			// 2. 长度检查
			// 过滤掉太短的字符串(至少8个字符)和过长的字符串(最多10000个字符)
			if (str.length < 8 || str.length > 10000) {
				this.base64Cache.set(str, false);
				return false;
			}

			// 3. 特征检查
			// 过滤掉可能是图片、视频等二进制数据的Base64
			if (/^(?:data:|iVBOR|R0lGO|\/9j\/4|PD94bW|JVBER)/.test(str)) {
				this.base64Cache.set(str, false);
				return false;
			}

			// 添加到 validateBase64 方法中
			const commonPatterns = {
				// 常见的二进制数据头部特征
				binaryHeaders:
					/^(?:data:|iVBOR|R0lGO|\/9j\/4|PD94bW|JVBER|UEsDB|H4sIA|77u\/|0M8R4)/,

				// 常见的文件类型标识
				fileSignatures: /^(?:UEs|PK|%PDF|GIF8|RIFF|OggS|ID3|ÿØÿ|8BPS)/,

				// 常见的编码标识
				encodingMarkers:
					/^(?:utf-8|utf-16|base64|quoted-printable|7bit|8bit|binary)/i,

				// 可疑的URL模式
				urlPatterns: /^(?:https?:|ftp:|data:|blob:|file:|ws:|wss:)/i,

				// 常见的压缩文件头部
				compressedHeaders: /^(?:eJw|H4s|Qk1Q|UEsD|N3q8|KLUv)/,
			};

			// 在验证时使用这些模式
			if (
				commonPatterns.binaryHeaders.test(str) ||
				commonPatterns.fileSignatures.test(str) ||
				commonPatterns.encodingMarkers.test(str) ||
				commonPatterns.urlPatterns.test(str) ||
				commonPatterns.compressedHeaders.test(str)
			) {
				this.base64Cache.set(str, false);
				return false;
			}

			try {
				const decoded = this.decodeBase64(str);
				if (!decoded) {
					this.base64Cache.set(str, false);
					return false;
				}

				// 4. 解码后的文本验证
				// 检查解码后的文本是否有意义
				if (!this.isMeaningfulText(decoded)) {
					this.base64Cache.set(str, false);
					return false;
				}

				this.base64Cache.set(str, true);
				return true;
			} catch (e) {
				console.error('Base64 validation error:', e);
				this.base64Cache.set(str, false);
				return false;
			}
		}

		/**
		 * Base64解码
		 * @description 将Base64字符串解码为普通文本
		 * @param {string} str - 要解码的Base64字符串
		 * @returns {string|null} 解码后的文本,解码失败时返回null
		 * @example
		 * decodeBase64('SGVsbG8gV29ybGQ=') // returns 'Hello World'
		 */
		decodeBase64(str) {
			try {
				// 优化解码过程
				const binaryStr = atob(str);
				const bytes = new Uint8Array(binaryStr.length);
				for (let i = 0; i < binaryStr.length; i++) {
					bytes[i] = binaryStr.charCodeAt(i);
				}
				return new TextDecoder().decode(bytes);
			} catch (e) {
				console.error('Base64 decode error:', e);
				return null;
			}
		}

		/**
		 * Base64编码
		 * @description 将普通文本编码为Base64格式
		 * @param {string} str - 要编码的文本
		 * @returns {string|null} Base64编码后的字符串,编码失败时返回null
		 * @example
		 * encodeBase64('Hello World') // returns 'SGVsbG8gV29ybGQ='
		 */
		encodeBase64(str) {
			try {
				// 优化编码过程
				const bytes = new TextEncoder().encode(str);
				let binaryStr = '';
				for (let i = 0; i < bytes.length; i++) {
					binaryStr += String.fromCharCode(bytes[i]);
				}
				return btoa(binaryStr);
			} catch (e) {
				console.error('Base64 encode error:', e);
				return null;
			}
		}

		/**
		 * 复制文本到剪贴板
		 * @description 尝试使用现代API或降级方案将文本复制到剪贴板
		 * @param {string} text - 要复制的文本
		 * @returns {Promise<boolean>} 复制是否成功
		 * @example
		 * await copyToClipboard('Hello World') // returns true
		 */
		async copyToClipboard(text) {
			if (navigator.clipboard && window.isSecureContext) {
				try {
					await navigator.clipboard.writeText(text);
					return true;
				} catch (e) {
					return this.fallbackCopy(text);
				}
			}

			return this.fallbackCopy(text);
		}

		/**
		 * 降级复制方案
		 * @description 当现代复制API不可用时的备选复制方案
		 * @param {string} text - 要复制的文本
		 * @returns {boolean} 复制是否成功
		 * @private
		 */
		fallbackCopy(text) {
			if (typeof GM_setClipboard !== 'undefined') {
				try {
					GM_setClipboard(text);
					return true;
				} catch (e) {
					console.debug('GM_setClipboard failed:', e);
				}
			}

			try {
				// 注意: execCommand 已经被废弃,但作为降级方案仍然有用
				const textarea = document.createElement('textarea');
				textarea.value = text;
				textarea.style.cssText = 'position:fixed;opacity:0;';
				document.body.appendChild(textarea);

				if (navigator.userAgent.match(/ipad|iphone/i)) {
					textarea.contentEditable = true;
					textarea.readOnly = false;

					const range = document.createRange();
					range.selectNodeContents(textarea);

					const selection = window.getSelection();
					selection.removeAllRanges();
					selection.addRange(range);
					textarea.setSelectionRange(0, 999999);
				} else {
					textarea.select();
				}

				// 使用 try-catch 包裹 execCommand 调用,以防将来完全移除
				let success = false;
				try {
					// @ts-ignore - 忽略废弃警告
					success = document.execCommand('copy');
				} catch (copyError) {
					console.debug('execCommand copy operation failed:', copyError);
				}

				document.body.removeChild(textarea);
				return success;
			} catch (e) {
				console.debug('Fallback copy method failed:', e);
				return false;
			}
		}

		/**
		 * 恢复原始内容
		 * @description 将所有解码后的内容恢复为原始的Base64格式
		 * @fires showNotification 显示恢复结果通知
		 */
		restoreContent() {
			// 设置恢复内容标志,防止重复处理
			if (this.isRestoringContent) {
				console.log('已经在恢复内容中,避免重复操作');
				return;
			}

			this.isRestoringContent = true;

			try {
				// 获取所有需要恢复的元素
				const elementsToRestore = Array.from(document.querySelectorAll('.decoded-text'));
				if (elementsToRestore.length === 0) {
					this.showNotification('没有需要恢复的内容', 'info');
					this.isRestoringContent = false;
					return;
				}

				// 分批处理节点替换,避免大量 DOM 操作导致界面冻结
				const BATCH_SIZE = 50;
				const processBatch = (startIndex) => {
					const endIndex = Math.min(startIndex + BATCH_SIZE, elementsToRestore.length);
					const batch = elementsToRestore.slice(startIndex, endIndex);

					batch.forEach((el) => {
						if (el && el.parentNode && el.dataset.original) {
							const textNode = document.createTextNode(el.dataset.original);
							el.parentNode.replaceChild(textNode, el);
						}
					});

					if (endIndex < elementsToRestore.length) {
						// 还有更多元素需要处理
						setTimeout(() => processBatch(endIndex), 0);
					} else {
						// 所有元素处理完成
						this.originalContents.clear();

						// 如果按钮存在,更新按钮状态
						if (this.decodeBtn) {
							this.decodeBtn.textContent = '解析本页 Base64';
							this.decodeBtn.dataset.mode = 'decode';
						}

						// 获取已恢复元素的数量
						const restoredCount = elementsToRestore.length;
						// 显示通知,除非被抑制
						if (!this.suppressNotification) {
							this.showNotification(`已恢复 ${restoredCount} 个 Base64 内容`, 'success');
						}

						// 只有当按钮可见时才隐藏菜单
						if (!this.config.hideButton && this.menu) {
							this.menu.style.display = 'none';
						}

						// 操作完成后同步按钮和菜单状态
						this.syncButtonAndMenuState();
						// 更新油猴菜单命令
						updateMenuCommands();

						// 重置恢复内容标志
						this.isRestoringContent = false;
					}
				};

				// 开始处理第一批
				processBatch(0);
			} catch (e) {
				console.error('恢复内容时出错:', e);
				this.showNotification(`恢复失败: ${e.message}`, 'error');
				this.isRestoringContent = false;
			}
		}

		/**
		 * 同步按钮和菜单状态
		 * @description 根据页面上是否有解码内容,同步按钮和菜单状态
		 */
		syncButtonAndMenuState() {
			// 检查页面上是否有解码内容
			const hasDecodedContent = document.querySelectorAll('.decoded-text').length > 0;

			// 同步按钮状态
			if (this.decodeBtn) {
				if (hasDecodedContent) {
					this.decodeBtn.textContent = '恢复本页 Base64';
					this.decodeBtn.dataset.mode = 'restore';
				} else {
					this.decodeBtn.textContent = '解析本页 Base64';
					this.decodeBtn.dataset.mode = 'decode';
				}
			}

			// 更新菜单命令
			setTimeout(updateMenuCommands, 100);
		}

		/**
		 * 重置插件状态
		 * @description 重置所有状态变量并在必要时恢复原始内容
		 * 如果启用了自动解码,则在路由变化后自动解析页面
		 * @fires restoreContent 如果当前处于restore模式则触发内容恢复
		 * @fires handleDecode 如果启用了自动解码则触发自动解码
		 */
		resetState() {
			console.log('执行 resetState,自动解码状态:', this.config.autoDecode);

			// 如果正在处理中,跳过这次重置
			if (this.isProcessing || this.isDecodingContent || this.isRestoringContent) {
				console.log('正在处理中,跳过这次状态重置');
				return;
			}

			// 检查URL是否变化,如果变化了,可能是新页面
			const currentUrl = window.location.href;
			const urlChanged = currentUrl !== this.lastPageUrl;
			// 检查是否是前进后退事件
			const isNavigationEvent = this.lastNavigationTime && (Date.now() - this.lastNavigationTime < 500);

			if (urlChanged || isNavigationEvent) {
				console.log('URL已变化或检测到前进后退事件,从', this.lastPageUrl, '到', currentUrl);
				this.lastPageUrl = currentUrl;
				// URL变化或前进后退时重置自动解码标志
				this.hasAutoDecodedOnLoad = false;
			}

			// 页面刷新时的特殊处理
			if (this.isPageRefresh && this.config.autoDecode) {
				console.log('页面刷新且自动解码已启用');

				// 如果页面刷新尚未完成,不执行任何操作,等待页面完全加载
				if (!this.pageRefreshCompleted) {
					console.log('页面刷新尚未完成,等待页面加载完成后再处理');
					return;
				}

				// 检查是否已经执行过解码,避免重复解码
				if (this.hasAutoDecodedOnLoad || document.querySelectorAll('.decoded-text').length > 0) {
					console.log('页面已经执行过解码或已有解码内容,跳过重复解码');
					return;
				}

				// 页面上没有已解码内容,执行自动解码
				console.log('页面刷新时未发现已解码内容,执行自动解码');
				this.hasAutoDecodedOnLoad = true;
				// 增加延时,确保页面内容已完全加载
				setTimeout(() => {
					if (!this.isProcessing && !this.isDecodingContent && !this.isRestoringContent) {
						// 使用processTextNodes方法进行解码
						const { nodesToReplace, validDecodedCount } = this.processTextNodes();

						if (validDecodedCount > 0) {
							// 分批处理节点替换
							const BATCH_SIZE = 50;
							const processNodesBatch = (startIndex) => {
								const endIndex = Math.min(startIndex + BATCH_SIZE, nodesToReplace.length);
								const batch = nodesToReplace.slice(startIndex, endIndex);

								this.replaceNodes(batch);

								if (endIndex < nodesToReplace.length) {
									setTimeout(() => processNodesBatch(endIndex), 0);
								} else {
									setTimeout(() => {
										this.addClickListenersToDecodedText();
										this.debouncedShowNotification(`解码成功,共找到 ${validDecodedCount} 个 Base64 内容`, 'success');
										this.syncButtonAndMenuState();
									}, 100);
								}
							};

							processNodesBatch(0);
						}
					}
				}, 1000);
				return;
			}

			// 如果启用了自动解码,且尚未在页面加载时执行过,则在路由变化后自动解析页面
			if (this.config.autoDecode && !this.hasAutoDecodedOnLoad) {
				console.log('自动解码已启用,准备解析页面');
				// 标记已执行过自动解码
				this.hasAutoDecodedOnLoad = true;

				// 使用统一的页面稳定性跟踪器
				const tracker = this.pageStabilityTracker;

				// 记录路由变化
				tracker.recordChange('Route');

				// 标记有待处理的解码请求
				if (this.config.autoDecode) {
					tracker.pendingDecode = true;
				}

				// 设置页面稳定性定时器
				tracker.stabilityTimer = setTimeout(() => {
					// 检查页面是否真正稳定(路由和DOM都稳定)
					if (tracker.checkStability()) {
						console.log('页面已稳定(路由和DOM都稳定),准备在 resetState 中执行自动解码');

						// 标记页面已稳定
						tracker.isStable = true;

						// 如果有待处理的解码请求,则执行解码
						if (tracker.pendingDecode && this.config.autoDecode) {
							// 使用延时确保页面内容已更新
							tracker.decodePendingTimer = setTimeout(() => {
								// 重置待处理标志
								tracker.pendingDecode = false;

								console.log('resetState 中执行自动解码');
								if (
									!this.isProcessing &&
									!this.isDecodingContent &&
									!this.isRestoringContent
								) {
									// 使用自动解码方法,强制全页面解码并显示通知
									console.log('在resetState中强制执行全页面解码');
									this.handleAutoDecode(true, true);
									// 同步按钮和菜单状态
									setTimeout(() => this.syncButtonAndMenuState(), 200);
								}

							}, 500); // 页面稳定后再等待500毫秒再执行解码
						}
					} else {
						console.log('页面尚未完全稳定,继续等待');
					}
				}, tracker.stabilityThreshold); // 等待页面稳定的时间
			}
		}

		/**
		 * 为通知添加动画效果
		 * @param {HTMLElement} notification - 通知元素
		 */
		animateNotification(notification) {
			const currentTransform = getComputedStyle(notification).transform;
			notification.style.transform = currentTransform;
			notification.style.transition = 'all 0.3s ease-out';
			notification.style.transform = 'translateY(-100%)';
		}

		/**
		 * 处理通知淡出效果
		 * @description 为通知添加淡出效果并处理相关动画
		 * @param {HTMLElement} notification - 要处理的通知元素
		 * @fires animateNotification 触发其他通知的位置调整动画
		 */
		handleNotificationFadeOut(notification) {
			notification.classList.add('fade-out');
			const index = this.notifications.indexOf(notification);

			this.notifications.slice(0, index).forEach((prev) => {
				if (prev.parentNode) {
					prev.style.transform = 'translateY(-100%)';
				}
			});
		}

		/**
		 * 清理通知容器
		 * @description 移除所有通知元素和相关事件监听器
		 * @fires removeEventListener 移除所有通知相关的事件监听器
		 */
		cleanupNotificationContainer() {
			// 清理通知相关的事件监听器
			this.notificationEventListeners.forEach(({ element, event, handler }) => {
				element.removeEventListener(event, handler);
			});
			this.notificationEventListeners = [];

			// 移除所有通知元素
			while (this.notificationContainer.firstChild) {
				this.notificationContainer.firstChild.remove();
			}

			this.notificationContainer.remove();
			this.notificationContainer = null;
		}

		/**
		 * 处理通知过渡结束事件
		 * @description 处理通知元素的过渡动画结束后的清理工作
		 * @param {TransitionEvent} e - 过渡事件对象
		 * @fires animateNotification 触发其他通知的位置调整
		 */
		handleNotificationTransitionEnd(e) {
			if (
				e.propertyName === 'opacity' &&
				e.target.classList.contains('fade-out')
			) {
				const notification = e.target;
				const index = this.notifications.indexOf(notification);

				this.notifications.forEach((notif, i) => {
					if (i > index && notif.parentNode) {
						this.animateNotification(notif);
					}
				});

				if (index > -1) {
					this.notifications.splice(index, 1);
					notification.remove();
				}

				if (this.notifications.length === 0) {
					this.cleanupNotificationContainer();
				}
			}
		}

		/**
		 * 显示通知消息
		 * @description 创建并显示一个通知消息,包含自动消失功能
		 * @param {string} text - 通知文本内容
		 * @param {string} type - 通知类型 ('success'|'error'|'info')
		 * @fires handleNotificationFadeOut 触发通知淡出效果
		 * @example
		 * showNotification('操作成功', 'success')
		 */
		showNotification(text, type) {
			// 如果禁用了通知,则不显示
			if (this.config && !this.config.showNotification) {
				console.log(`[Base64 Helper] ${type}: ${text}`);
				return;
			}

			// 设置通知显示标志,防止 MutationObserver 触发自动解码
			this.isShowingNotification = true;

			if (!this.notificationContainer) {
				this.notificationContainer = document.createElement('div');
				this.notificationContainer.className = 'base64-notifications-container';
				document.body.appendChild(this.notificationContainer);

				const handler = (e) => this.handleNotificationTransitionEnd(e);
				this.notificationContainer.addEventListener('transitionend', handler);
				this.notificationEventListeners.push({
					element: this.notificationContainer,
					event: 'transitionend',
					handler,
				});
			}

			const notification = document.createElement('div');
			notification.className = 'base64-notification';
			notification.setAttribute('data-type', type);
			notification.textContent = text;

			this.notifications.push(notification);
			this.notificationContainer.appendChild(notification);

			// 使用延时来清除通知标志,确保 DOM 变化已完成
			setTimeout(() => {
				this.isShowingNotification = false;
			}, 100);

			setTimeout(() => {
				if (notification.parentNode) {
					this.handleNotificationFadeOut(notification);
				}
			}, 2000);
		}

		/**
		 * 销毁插件实例
		 * @description 清理所有资源,移除事件监听器,恢复原始状态
		 * @fires restoreContent 如果需要则恢复原始内容
		 * @fires removeEventListener 移除所有事件监听器
		 */
		destroy() {
			// 清理所有事件监听器
			this.eventListeners.forEach(({ element, event, handler, options }) => {
				element.removeEventListener(event, handler, options);
			});
			this.eventListeners = [];

			// 清理配置监听器
			if (this.configListeners) {
				Object.values(this.configListeners).forEach(listenerId => {
					if (listenerId) {
						storageManager.removeChangeListener(listenerId);
					}
				});
				// 重置配置监听器
				this.configListeners = {
					showNotification: null,
					hideButton: null,
					autoDecode: null,
					buttonPosition: null
				};
			}

			// 清理定时器
			if (this.resizeTimer) clearTimeout(this.resizeTimer);
			if (this.routeTimer) clearTimeout(this.routeTimer);
			if (this.domChangeTimer) clearTimeout(this.domChangeTimer);

			// 清理 MutationObserver
			if (this.observer) {
				this.observer.disconnect();
				this.observer = null;
			}

			// 清理通知相关资源
			if (this.notificationContainer) {
				this.cleanupNotificationContainer();
			}
			this.notifications = [];

			// 恢复原始的 history 方法
			if (this.originalPushState) history.pushState = this.originalPushState;
			if (this.originalReplaceState)
				history.replaceState = this.originalReplaceState;

			// 恢复原始状态
			if (this.decodeBtn?.dataset.mode === 'restore') {
				this.restoreContent();
			}

			// 移除 DOM 元素
			if (this.container) {
				this.container.remove();
			}

			// 清理缓存
			if (this.base64Cache) {
				this.base64Cache.clear();
			}

			// 清理节点跟踪相关资源
			this.processedNodes = null;
			this.decodedTextNodes = null;
			this.processedMutations = null;
			this.currentMutations = null;

			// 清理引用
			this.shadowRoot = null;
			this.mainBtn = null;
			this.menu = null;
			this.decodeBtn = null;
			this.encodeBtn = null;
			this.container = null;
			this.originalContents.clear();
			this.originalContents = null;
			this.isDragging = false;
			this.hasMoved = false;
			this.menuVisible = false;
			this.base64Cache = null;
			this.configListeners = null;
		}

		/**
		 * 防抖处理解码
		 * @param {boolean} [forceFullDecode=false] - 是否强制全页面解码
		 * @param {boolean} [showNotification=false] - 是否显示通知
		 */
		debouncedHandleAutoDecode(forceFullDecode = false, showNotification = false) {
			// 清除之前的定时器
			clearTimeout(this.decodeDebounceTimer);

			// 检查距离上次解码的时间
			const currentTime = Date.now();
			if (currentTime - this.lastDecodeTime < this.DECODE_DEBOUNCE_DELAY) {
				console.log('距离上次解码时间太短,跳过这次解码');
				return;
			}

			// 设置新的定时器
			this.decodeDebounceTimer = setTimeout(() => {
				this.handleAutoDecode(forceFullDecode, showNotification);
				this.lastDecodeTime = Date.now();
			}, 500); // 添加500ms的延迟,确保页面内容已更新
		}

		/**
		 * 防抖处理通知
		 * @param {string} text - 通知文本
		 * @param {string} type - 通知类型
		 */
		debouncedShowNotification(text, type) {
			this.showNotification(text, type);
		}

		/**
		 * 等待 DOM 更新完成后再添加事件监听器
		 * @description 使用 MutationObserver 监听 DOM 变化,确保在节点真正被添加到 DOM 后再添加事件监听器
		 * @private
		 */
		waitForDOMUpdate() {
			return new Promise((resolve) => {
				// 先检查是否已经有解码文本节点
				const existingNodes = document.querySelectorAll('.decoded-text');
				if (existingNodes.length > 0) {
					resolve();
					return;
				}

				const observer = new MutationObserver((mutations) => {
					// 检查是否有新的 decoded-text 节点
					const hasNewNodes = document.querySelectorAll('.decoded-text').length > 0;
					if (hasNewNodes) {
						observer.disconnect();
						resolve();
					}
				});

				observer.observe(document.body, {
					childList: true,
					subtree: true
				});

				// 设置超时,防止无限等待
				setTimeout(() => {
					observer.disconnect();
					resolve();
				}, 1000);
			});
		}

		/**
		 * 添加事件监听器
		 * @param {HTMLElement} element - 目标元素
		 * @param {string} event - 事件名称
		 * @param {Function} handler - 事件处理函数
		 * @param {Object} options - 事件选项
		 * @returns {string} - 监听器ID
		 */
		addEventListener(element, event, handler, options = {}) {
			const listenerId = `${element.id || 'global'}_${event}_${Date.now()}`;
			element.addEventListener(event, handler, options);
			this.eventListeners.set(listenerId, { element, event, handler, options });
			return listenerId;
		}

		/**
		 * 移除事件监听器
		 * @param {string} listenerId - 监听器ID
		 */
		removeEventListener(listenerId) {
			const listener = this.eventListeners.get(listenerId);
			if (listener) {
				const { element, event, handler, options } = listener;
				element.removeEventListener(event, handler, options);
				this.eventListeners.delete(listenerId);
			}
		}

		/**
		 * 清理所有事件监听器
		 */
		cleanupEventListeners() {
			for (const [listenerId, listener] of this.eventListeners) {
				const { element, event, handler, options } = listener;
				element.removeEventListener(event, handler, options);
			}
			this.eventListeners.clear();
		}

		/**
		 * 添加缓存项
		 * @param {string} key - 缓存键
		 * @param {string} value - 缓存值
		 */
		addToCache(key, value) {
			// 检查缓存大小,如果超过限制则清理最旧的条目
			if (this.base64Cache.size >= this.MAX_CACHE_SIZE) {
				const oldestKey = this.base64Cache.keys().next().value;
				this.base64Cache.delete(oldestKey);
			}

			// 只缓存长度在限制范围内的文本
			if (value.length <= this.MAX_TEXT_LENGTH) {
				this.base64Cache.set(key, value);
			}
		}

		/**
		 * 从缓存中获取值
		 * @param {string} key - 缓存键
		 * @returns {string|undefined} - 缓存值或undefined
		 */
		getFromCache(key) {
			const value = this.base64Cache.get(key);
			if (value !== undefined) {
				this.cacheHits++;
				return value;
			}
			this.cacheMisses++;
			return undefined;
		}

		/**
		 * 清理缓存
		 */
		clearCache() {
			this.base64Cache.clear();
			this.cacheHits = 0;
			this.cacheMisses = 0;
		}

		/**
		 * 获取缓存统计信息
		 * @returns {Object} - 缓存统计信息
		 */
		getCacheStats() {
			return {
				size: this.base64Cache.size,
				hits: this.cacheHits,
				misses: this.cacheMisses,
				hitRate: this.cacheHits / (this.cacheHits + this.cacheMisses) || 0
			};
		}

		/**
		 * 处理文本节点
		 * @param {Text} node - 文本节点
		 * @returns {boolean} - 是否处理成功
		 */
		processTextNode(node) {
			if (this.processedNodes.has(node)) {
				return false;
			}

			// 检查节点是否已经被处理过
			const cachedResult = this.decodedTextNodes.get(node);
			if (cachedResult !== undefined) {
				return cachedResult;
			}

			// 处理节点
			const result = this.processNodeContent(node);
			this.processedNodes.add(node);
			this.decodedTextNodes.set(node, result);

			// 存储节点引用
			this.nodeReferences.set(node, {
				parent: node.parentNode,
				nextSibling: node.nextSibling
			});

			return result;
		}

		/**
		 * 清理节点引用
		 * @param {Text} node - 要清理的节点
		 */
		cleanupNodeReferences(node) {
			this.processedNodes.delete(node);
			this.decodedTextNodes.delete(node);
			this.nodeReferences.delete(node);
		}

		/**
		 * 批量清理节点引用
		 * @param {Array<Text>} nodes - 要清理的节点数组
		 */
		cleanupNodeReferencesBatch(nodes) {
			nodes.forEach(node => this.cleanupNodeReferences(node));
		}

		/**
		 * 恢复节点状态
		 * @param {Text} node - 要恢复的节点
		 */
		restoreNodeState(node) {
			const reference = this.nodeReferences.get(node);
			if (reference) {
				const { parent, nextSibling } = reference;
				if (parent && nextSibling) {
					parent.insertBefore(node, nextSibling);
				}
				this.cleanupNodeReferences(node);
			}
		}
	}

	// 确保只初始化一次
	if (window.__base64HelperInstance) {
		return;
	}

	// 只在主窗口中初始化
	if (window.top === window.self) {

		initStyles();

		window.__base64HelperInstance = new Base64Helper();

		// 注册油猴菜单命令
		registerMenuCommands();

		// 确保在页面完全加载后更新菜单命令
		window.addEventListener('load', () => {
			console.log('页面加载完成,更新菜单命令');
			updateMenuCommands();
		});
	}

	// 使用 { once: true } 确保事件监听器只添加一次
	window.addEventListener(
		'unload',
		() => {
			if (window.__base64HelperInstance) {
				window.__base64HelperInstance.destroy();
				delete window.__base64HelperInstance;
			}
		},
		{ once: true }
	);
})();