import { configureLoggerDiagnostics, logger } from '../utils/logger'; import type { PlatformAdapter, PlatformName } from '../types/platform'; import { FloatingButton } from './components/floating-button'; import { AliyunAdapter } from '../adapters/aliyun/aliyun-adapter'; import { BaiduAdapter } from '../adapters/baidu/baidu-adapter'; import { QuarkAdapter } from '../adapters/quark/quark'; import { UCAdapter } from '../adapters/uc/uc-adapter'; import { Drive115Adapter } from '../adapters/115/adapter'; import { Drive123Adapter } from '../adapters/123/adapter'; import { CMCCAdapter } from '../adapters/cmcc/adapter'; import { EsurfingAdapter } from '../adapters/esurfing/adapter'; import { XunleiAdapter } from '../adapters/xunlei/adapter'; import { WoozoooAdapter } from '../adapters/woozooo/adapter'; import { storage } from '../utils/storage'; import { STORAGE_KEYS } from '../types/stats'; import { I18nService } from '../utils/i18n'; import type { LanguageChangeMessage } from '../types/i18n'; import { detectPlatformFromUrl, isQuarkShareLink, isAliyunShareLink } from '../utils/platform-detector'; import { applyAppearanceToElement, getAppearanceMode, watchSystemColorScheme } from '../utils/appearance'; import { APPEARANCE_STORAGE_KEY, DEFAULT_APPEARANCE_MODE, isAppearanceMode, type AppearanceMode } from '../types/appearance'; import { RUNTIME_MESSAGE_TYPES } from '../types/runtime-message'; /** * Content Script * 注入到网盘页面的脚本 * * 新架构: * - 使用悬浮按钮替代页面内按钮注入 * - 使用 iframe 对话框替代直接的 Web Components * - 完全独立于页面结构 * - 支持按平台控制悬浮按钮显示/隐藏 */ // 🔍 VERSION IDENTIFIER - 用于诊断代码版本 // 全局初始化标志位(使用window对象实现跨执行上下文共享) const INIT_FLAG = '__cloudDriveRenamerInitialized'; // 全局实例 let floatingButton: FloatingButton | null = null; let fileSelectorPanel: (HTMLElement & { open: boolean; adapter: PlatformAdapter }) | null = null; let platformAdapter: PlatformAdapter | null = null; let currentAppearanceMode: AppearanceMode = DEFAULT_APPEARANCE_MODE; let stopWatchSystemColorScheme: (() => void) | null = null; // 初始化重试计数器(防止无限递归) let initRetryCount = 0; const MAX_INIT_RETRIES = 3; configureLoggerDiagnostics({ source: 'content', transport: async (entry) => { await chrome.runtime.sendMessage({ type: RUNTIME_MESSAGE_TYPES.APPEND_DIAGNOSTIC_LOG, entry, }); }, }); // 检测当前平台 // 使用统一的平台检测工具函数 export function detectPlatform(): PlatformName | null { const url = window.location.href; const pathname = window.location.pathname; // 使用统一的检测逻辑 const platform = detectPlatformFromUrl(url, pathname); // 如果是分享链接,记录日志 (提升日志级别为 warn,因为这是需要用户关注的情况) if (url.includes('pan.quark.cn') && isQuarkShareLink(pathname)) { logger.warn('Quark share link detected, extension disabled for share pages'); } if ((url.includes('www.aliyundrive.com') || url.includes('www.alipan.com')) && isAliyunShareLink(pathname)) { logger.warn('Aliyun share link detected, extension disabled for share pages'); } return platform; } // 初始化 function init() { // 增强的初始化保护:检查标志位和UI实际状态 if ((window as any)[INIT_FLAG]) { // 验证UI是否真的存在(检查Shadow DOM宿主元素) const existingShadowHost = document.querySelector('#cloud-drive-renamer-shadow-host'); if (existingShadowHost) { logger.warn('Already initialized with UI present, skipping duplicate init() call'); return; } else { // 标志位已设置但UI缺失,可能是异步mount正在进行中 // 等待一小段时间(200ms)后重新检查DOM,避免误判异步竞态条件 logger.warn('Initialization flag set but UI missing, checking if mount is in progress...'); setTimeout(() => { const recheckShadowHost = document.querySelector('#cloud-drive-renamer-shadow-host'); if (!recheckShadowHost) { // UI仍然缺失,检查重试次数 initRetryCount++; if (initRetryCount < MAX_INIT_RETRIES) { logger.warn(`UI still missing after delay (retry ${initRetryCount}/${MAX_INIT_RETRIES}), allowing re-initialization`); delete (window as any)[INIT_FLAG]; init(); } else { logger.error(`Maximum initialization retries (${MAX_INIT_RETRIES}) exceeded, giving up`); delete (window as any)[INIT_FLAG]; // 释放标志位,允许用户手动刷新页面重试 } } else { // UI已出现,说明异步mount正在进行中,不需要重新初始化 logger.info('UI appeared after delay, initialization was in progress'); initRetryCount = 0; // 成功检测到UI,重置计数器 } }, 200); // 等待异步mount操作完成 return; // 立即返回,不要继续执行初始化流程 } } // 设置全局标志位(在window对象上,所有执行上下文共享) (window as any)[INIT_FLAG] = true; const platform = detectPlatform(); if (!platform) { logger.warn('Unsupported platform'); // 不支持的平台也重置标志位,允许后续重试 delete (window as any)[INIT_FLAG]; return; } // 等待DOM加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { injectUI(platform).catch((error) => { logger.error('Failed to inject UI on DOMContentLoaded:', error instanceof Error ? error : new Error(String(error))); }); }); } else { injectUI(platform).catch((error) => { logger.error('Failed to inject UI:', error instanceof Error ? error : new Error(String(error))); }); } // 页面卸载时清理标志位(允许页面导航后重新初始化) window.addEventListener('beforeunload', () => { delete (window as any)[INIT_FLAG]; }); } /** * 清理旧的UI实例 * 在重新初始化前调用,确保没有残留的DOM和事件监听器 */ function cleanupOldInstances(): void { // 清理旧的悬浮按钮实例 if (floatingButton) { try { floatingButton.unmount(); } catch (error) { logger.error('Failed to unmount old floating button:', error instanceof Error ? error : new Error(String(error))); } floatingButton = null; } // 清理旧的文件选择面板实例 if (fileSelectorPanel) { try { fileSelectorPanel.remove(); } catch (error) { logger.error('Failed to close old file selector panel:', error instanceof Error ? error : new Error(String(error))); } fileSelectorPanel = null; } // 清理外观模式监听器(避免重复初始化导致泄漏) if (stopWatchSystemColorScheme) { stopWatchSystemColorScheme(); stopWatchSystemColorScheme = null; } // 额外保护:手动移除可能残留的DOM元素(Shadow DOM宿主) const existingShadowHost = document.querySelector('#cloud-drive-renamer-shadow-host'); if (existingShadowHost) { existingShadowHost.remove(); } // 额外保护:移除可能残留的面板元素(避免变量丢失导致的泄漏) const existingPanel = document.querySelector('file-selector-panel'); if (existingPanel) { existingPanel.remove(); } } /** * 动态注入 page-script 到 MAIN world * * 背景: @crxjs/vite-plugin 不支持在 manifest.json 中直接配置 world: "MAIN" 的 TypeScript 文件 * 解决方案: 在 content script (ISOLATED world) 中动态创建