console.log("Content script starting..."); (function () { // 检查是否为 HTML 文档 if (document.documentElement?.nodeName.toLowerCase() !== 'html') { console.log("Not an HTML document, content script will not run"); return; } // 代理操作类型常量 const ProxyActionType = { SET_PROXY_CONFIG: "SET_PROXY_CONFIG", CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG", GET_PROXY_STATUS: "GET_PROXY_STATUS", GET_PROXY_CONFIGS: "GET_PROXY_CONFIGS", UPDATE_PROXY_CONFIG: "UPDATE_PROXY_CONFIG", }; // 在文件顶部添加常量声明 const YAK_ICON_URL = chrome.runtime.getURL("/images/yak.svg"); // 添加一个通用的消息发送函数 async function sendMessageWithRetry(message, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await chrome.runtime.sendMessage(message); } catch (error) { console.warn(`Attempt ${i + 1} failed:`, error); if (i === maxRetries - 1) { throw error; } // 等待一小段时间后重试 await new Promise((resolve) => setTimeout(resolve, 500)); } } } // 获取当前代理状态 async function getCurrentProxy() { try { const response = await sendMessageWithRetry({ action: ProxyActionType.GET_PROXY_STATUS, }); if (!response || !response.success) { console.log("No valid response from background script"); return {enable: false, proxy: "", currentMode: "direct"}; } const status = response.data; console.log("Proxy status from background:", status); // 返回当前模式 return { enable: status.enabled, proxy: status.mode === "system" ? "system" : "", currentMode: status.mode || "direct", // 使用 mode 来判断当前激活的代理 }; } catch (error) { console.error("Error getting proxy status:", error); return {enable: false, proxy: "", currentMode: "direct"}; } } // 获取所有代理配置 async function getProxyConfigs() { try { const response = await sendMessageWithRetry({ action: ProxyActionType.GET_PROXY_CONFIGS, }); if (!response) { console.log("No response from background script"); return []; } if (!response.success) { console.error("Error in response:", response.error); return []; } return response.data || []; } catch (error) { console.error("Error getting proxy configs:", error); return []; } } // 切换代理 async function switchProxy(config) { try { console.log("Switching proxy:", config); // 根据配置类型构建正确的配置对象 let proxyConfig; if (config.proxyType === "system") { proxyConfig = { id: "system", name: "[系统代理]", proxyType: "system", enabled: true, }; } else if (config.proxyType === "direct") { proxyConfig = { id: "direct", name: "[直接连接]", proxyType: "direct", enabled: false, }; } else { proxyConfig = { ...config, enabled: true, }; } // 发送配置更改消息 await sendMessageWithRetry({ action: ProxyActionType.SET_PROXY_CONFIG, config: proxyConfig, }); await PanelManager.updatePanel(); } catch (error) { console.error("Error switching proxy:", error); } } // 清除代理 async function clearProxy() { try { const response = await sendMessageWithRetry({ action: ProxyActionType.SET_PROXY_CONFIG, config: { id: "direct", name: "[直接连接]", proxyType: "direct", enabled: false, }, }); console.log("Clear proxy response:", response); // 使用 PanelManager 的 updatePanel 方法 await PanelManager.updatePanel(); } catch (error) { console.error("Error clearing proxy:", error); } } // 修改 PanelManager const PanelManager = { panel: null, messageListener: null, _updating: false, _updateQueue: Promise.resolve(), _currentState: null, // 用于跟踪当前状态 _lastUpdate: null, // 添加最后更新时间戳 _lastState: null, _pollingInterval: null, isDragging: false, startY: 0, currentY: 0, init() { // 确保 document.body 存在 if (!document.body) { console.log("Body not ready, waiting..."); this.waitForBody(); return; } if (this.panel) { console.log("Panel already exists, updating..."); this.updatePanel(); return; } console.log("Creating new panel..."); this.createPanel(); }, // 添加等待 body 的方法 waitForBody() { if (document.body) { this.init(); return; } console.log("Setting up MutationObserver for body"); const observer = new MutationObserver((mutations, obs) => { if (document.body) { console.log("Body found via observer"); obs.disconnect(); this.init(); } }); observer.observe(document.documentElement, { childList: true, subtree: true, }); }, createPanel() { if (!document.body) { console.log("Body not available during panel creation"); return; } // 创建容器 const container = document.createElement("div"); container.id = "yakit-proxy-panel"; // 创建 shadow DOM const shadow = container.attachShadow({mode: "open"}); // 添加样式 const style = document.createElement("style"); style.textContent = ` .floating-panel { position: fixed; top: 30%; right: 0; transform: translateY(-30%); background: white; z-index: 2147483647; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; width: 50px; height: 40px; overflow: hidden; transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1), height 0.2s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.2s ease; } /* 吸附状态 */ .floating-panel:not(.expanded):not(.dragging) { border-radius: 50px 0 0 50px; box-shadow: -4px 0 20px rgba(0,0,0,0.15); border: 1px solid #eee; border-right: none; } /* 拖动状态 */ .floating-panel.dragging { cursor: grabbing !important; user-select: none; opacity: 0.95; transition: none; } .floating-panel.dragging * { cursor: grabbing !important; } .floating-panel:active { cursor: grabbing; } .floating-panel .panel-header { cursor: grab; } .floating-panel .panel-header:active { cursor: grabbing; } /* 吸附状态悬浮时 */ .floating-panel:not(.expanded):hover { width: 120px; background: #fff7e6; border-color: #ffd591; } /* 展开状态 - 立即应用圆角变化 */ .floating-panel.expanded { width: 180px; height: auto; max-height: 400px; border-radius: 8px 0 0 8px; box-shadow: -2px 0 10px rgba(0,0,0,0.1); border: 1px solid #eee; border-right: none; /* 展开时立即应用新的圆角 */ transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1), height 0.2s cubic-bezier(0.4, 0, 0.2, 1), border-radius 0s, background-color 0.2s ease; } .panel-header { height: 40px; min-height: 40px; display: flex; align-items: center; padding: 0 8px; cursor: pointer; user-select: none; } /* 吸附状态的头部 */ .floating-panel:not(.expanded) .panel-header { background: transparent; } /* 展开状态的头部 */ .floating-panel.expanded .panel-header { background: #f8f9fa; border-bottom: 1px solid #eee; } .header-content { display: flex; align-items: center; flex: 1; overflow: hidden; } /* 优化图标大小过渡 */ .yak-icon { width: 36px; height: 36px; min-width: 36px; object-fit: contain; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1)); transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .floating-panel.expanded .yak-icon { width: 24px; height: 24px; min-width: 24px; } .active-proxy-info { display: flex; align-items: center; margin-left: 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #ff6b00; font-size: 13px; font-weight: 500; } .active-proxy-info span:first-child { margin-right: 6px; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1)); } .panel-content { display: none; background: white; overflow-y: auto; max-height: 360px; opacity: 0; transition: opacity 0.2s ease; } .floating-panel.expanded .panel-content { display: block; opacity: 1; } .panel-content::-webkit-scrollbar { width: 4px; } .panel-content::-webkit-scrollbar-track { background: #f5f5f5; } .panel-content::-webkit-scrollbar-thumb { background: #ddd; border-radius: 4px; } .panel-content::-webkit-scrollbar-thumb:hover { background: #ccc; } .proxy-item { display: flex; align-items: center; padding: 8px 12px; cursor: pointer; transition: all 0.2s; white-space: nowrap; position: relative; } .proxy-item:hover { background: #fff7e6; } .proxy-item.active { background: #fff7e6; color: #ff6b00; } .proxy-item span:first-child { margin-right: 8px; font-size: 16px; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1)); } .proxy-status { position: absolute; right: 12px; width: 6px; height: 6px; border-radius: 50%; background: #52c41a; box-shadow: 0 0 4px rgba(82,196,26,0.3); } .proxy-item.active .proxy-status { background: #ff6b00; box-shadow: 0 0 4px rgba(255,107,0,0.3); } .divider { height: 1px; background: #f0f0f0; margin: 4px 0; } .action-button { display: flex; align-items: center; padding: 8px 12px; cursor: pointer; transition: all 0.2s; white-space: nowrap; color: #666; } .action-button:hover { background: #fff7e6; color: #ff6b00; } .action-button span:first-child { margin-right: 8px; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.1)); } `; // 创建面板内容 const panel = document.createElement("div"); panel.className = "floating-panel"; panel.innerHTML = `
Yak
`; // 将样式和面板添加到 shadow DOM shadow.appendChild(style); shadow.appendChild(panel); // 确保安全地添加到 body try { document.body.appendChild(container); this.panel = container; const floatingPanel = shadow.querySelector(".floating-panel"); const header = shadow.querySelector(".panel-header"); // 添加拖拽功能 this._initDragFeature(header, floatingPanel); // 添加自动收起功能 this._initAutoCollapse(floatingPanel); // 添加点击展开/收起功能 header.addEventListener("click", (e) => { if (!this.isDragging) { floatingPanel.classList.toggle("expanded"); } }); // 设置消息监听和开始轮询 this.setupMessageListener(); // 初始更新面板 this.updatePanel(); // 添加页面卸载时的清理 window.addEventListener("unload", () => { if (this._pollingInterval) { clearInterval(this._pollingInterval); } if (this.messageListener) { chrome.runtime.onMessage.removeListener(this.messageListener); } }); } catch (error) { console.error("Error creating panel:", error); } }, setupMessageListener() { if (this.messageListener) { chrome.runtime.onMessage.removeListener(this.messageListener); } this.messageListener = async (message, sender) => { // 只检查消息是否来自同一个扩展 if (sender.id !== chrome.runtime.id) { return; } // 处理状态更新消息 if ( message.action === "PROXY_STATUS_CHANGED" || message.action === "PROXY_CONFIGS_UPDATED" ) { console.log("Received update message:", message, "from:", sender); await this.updatePanel(); } }; chrome.runtime.onMessage.addListener(this.messageListener); }, async updatePanel() { if (!this.panel || !document.body.contains(this.panel)) { console.log("Panel not in document, recreating..."); this.createPanel(); return; } const panel = this.panel.shadowRoot?.querySelector(".panel-content"); if (!panel) return; // 使用更新队列确保更新按顺序执行 this._updateQueue = this._updateQueue .then(async () => { if (this._updating) { console.log("Update already in progress, skipping..."); return; } try { this._updating = true; // 获取最新状态 const [currentProxy, configs] = await Promise.all([ getCurrentProxy(), getProxyConfigs(), ]); // 状态没有变化时不更新 const newState = JSON.stringify({currentProxy, configs}); if (this._currentState === newState) { console.log("State unchanged, skipping update"); return; } this._currentState = newState; // 再次检查面板状态 if (!this.panel || !document.body.contains(this.panel)) { console.log("Panel was removed during data fetch"); return; } console.log("Updating panel with:", {currentProxy, configs}); if (!Array.isArray(configs)) { console.error("Invalid configs:", configs); return; } // 保存当前激活的项 const currentActiveId = panel.querySelector(".proxy-item.active")?.dataset.id; // 构建新的 HTML const newHtml = this._buildPanelHtml(currentProxy, configs); // 创建一个临时容器来比较内容 const temp = document.createElement("div"); temp.innerHTML = newHtml; // 只在内容真正改变时更新 if (panel.innerHTML !== temp.innerHTML) { requestAnimationFrame(() => { panel.innerHTML = newHtml; this._bindEventListeners(panel, configs, currentProxy); // 验证更新后的状态 const newActiveId = panel.querySelector(".proxy-item.active")?.dataset.id; if (currentActiveId !== newActiveId) { console.log("Active state changed:", { from: currentActiveId, to: newActiveId, }); } }); } // 更新当前代理信息显示 const activeProxyInfo = this.panel.shadowRoot?.querySelector(".active-proxy-info"); if (activeProxyInfo) { let proxyIcon = "🟢"; let proxyName = "直接连接"; if (currentProxy.currentMode === "system") { proxyIcon = "⚙️"; proxyName = "系统代理"; } else if (currentProxy.currentMode === "fixed_servers") { const activeConfig = configs.find((c) => c.enabled); if (activeConfig) { proxyIcon = activeConfig.proxyType === "pac_script" ? "📜" : "🌐"; proxyName = activeConfig.name || "未命名代理"; } } activeProxyInfo.innerHTML = ` ${proxyIcon} ${proxyName} `; } } catch (error) { console.error("Error updating panel:", error); } finally { this._updating = false; } }) .catch((error) => { console.error("Error in update queue:", error); this._updating = false; }); return this._updateQueue; }, // 将 HTML 构建逻辑抽离成单独的方法 _buildPanelHtml(currentProxy, configs) { let html = `
🟢 直接连接 ${ currentProxy.currentMode === "direct" ? '
' : "" }
⚙️ 系统代理 ${ currentProxy.currentMode === "system" ? '
' : "" }
`; // 添加自定义代理配置 configs.forEach((config) => { if (config.proxyType !== "direct" && config.proxyType !== "system") { const isActive = currentProxy.currentMode === "fixed_servers" && config.enabled; const proxyIcon = config.proxyType === "pac_script" ? "📜" : "🌐"; // 构建 title 提示信息 let tooltipText; if (config.proxyType === "pac_script") { tooltipText = "PAC Script"; } else { const scheme = config.scheme ? `${config.scheme.toUpperCase()} ` : ""; tooltipText = `${scheme}${config.host}:${config.port}`; } html += `
${proxyIcon} ${config.name || "未命名代理"} ${isActive ? '
' : ""}
`; } }); html += `
添加代理
⚙️ 设置
`; return html; }, _bindEventListeners(panel, configs, currentProxy) { // 代理项点击事件 panel.querySelectorAll(".proxy-item").forEach((item) => { const id = item.dataset.id; // 使用事件委托来提高性能 const clickHandler = async (e) => { e.stopPropagation(); if (this._updating) { console.log("Panel is updating, ignoring click"); return; } // 添加点击反馈 const originalOpacity = item.style.opacity; item.style.opacity = "0.7"; try { // 立即更新 UI 状态,不等待响应 panel.querySelectorAll(".proxy-item").forEach((i) => { i.classList.remove("active"); i.querySelector("span").style.color = "#666"; }); item.classList.add("active"); item.querySelector("span").style.color = "#ff6b00"; if (id === "direct") { await clearProxy(); } else if (id === "system") { await switchProxy({ id: "system", name: "[系统代理]", proxyType: "system", }); } else { const config = configs.find((c) => c.id === id); if (config) { await switchProxy(config); } } } catch (error) { console.error("Error handling proxy item click:", error); // 发生错误时恢复原状 await this.updatePanel(); } finally { item.style.opacity = originalOpacity; } }; // 使用 { once: true } 确保事件监听器不会重复 item.addEventListener("click", clickHandler, {once: true}); }); // 添加代理按钮 panel.querySelector(".add-proxy")?.addEventListener("click", async () => { try { await sendMessageWithRetry({ action: "OPEN_OPTIONS_PAGE", triggerAdd: true, }); } catch (error) { console.error("Error handling add proxy:", error); } }); // 设置按钮 panel.querySelector(".settings")?.addEventListener("click", async () => { try { await sendMessageWithRetry({ action: "OPEN_OPTIONS_PAGE", }); } catch (error) { console.error("Error opening options page:", error); } }); }, _initDragFeature(header, panel) { let isDragging = false; let startY = 0; let startTop = 0; let rafId = null; const updatePosition = (e) => { if (!isDragging) return; const deltaY = e.clientY - startY; const newTop = startTop + deltaY; // 限制拖动范围在视窗内 const maxTop = window.innerHeight - panel.offsetHeight; const boundedTop = Math.max(0, Math.min(newTop, maxTop)); // 保持水平位置不变,只改变垂直位置 panel.style.top = `${boundedTop}px`; panel.style.transform = "translateY(0)"; // 移除默认的 translateY(-50%) }; const onMouseDown = (e) => { // 如果点击时是展开状态,则处理展开/收起 if (panel.classList.contains("expanded")) { panel.classList.remove("expanded"); return; } if (e.button !== 0) return; // 只响应左键 isDragging = true; startY = e.clientY; // 获取当前实际位置 const rect = panel.getBoundingClientRect(); startTop = rect.top; // 开始拖动时固定当前位置 panel.style.top = `${startTop}px`; panel.style.transform = "translateY(0)"; // 添加拖动时的视觉反馈 panel.style.transition = "none"; panel.classList.add("dragging"); // 防止文本选择 e.preventDefault(); }; const onMouseMove = (e) => { if (!isDragging) return; // 使用 requestAnimationFrame 优化性能 if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => updatePosition(e)); }; const onMouseUp = () => { if (!isDragging) return; isDragging = false; // 清理 if (rafId) { cancelAnimationFrame(rafId); rafId = null; } panel.classList.remove("dragging"); panel.style.transition = ""; // 保存位置 const top = panel.getBoundingClientRect().top; const viewportHeight = window.innerHeight; const percentage = (top / viewportHeight) * 100; localStorage.setItem("yakitProxyPanelPosition", percentage.toString()); }; // 修改事件监听 header.addEventListener("mousedown", onMouseDown); document.addEventListener("mousemove", onMouseMove, {passive: true}); document.addEventListener("mouseup", onMouseUp); // 恢复保存的位置 const savedPosition = localStorage.getItem("yakitProxyPanelPosition"); if (savedPosition) { const top = (parseFloat(savedPosition) / 100) * window.innerHeight; panel.style.top = `${top}px`; panel.style.transform = "translateY(0)"; } }, _initAutoCollapse(panel) { let leaveTimer = null; const onMouseLeave = () => { if (panel.classList.contains("expanded")) { leaveTimer = setTimeout(() => { panel.classList.remove("expanded"); }, 300); // 300ms 延迟,避免意外触发 } }; const onMouseEnter = () => { if (leaveTimer) { clearTimeout(leaveTimer); leaveTimer = null; } }; panel.addEventListener("mouseleave", onMouseLeave); panel.addEventListener("mouseenter", onMouseEnter); }, }; // 修改初始化调用 console.log("Setting up initialization..."); // 根据文档状态决定初始化方式 if (document.readyState === "loading") { console.log("Document still loading, waiting for DOMContentLoaded"); document.addEventListener("DOMContentLoaded", () => { console.log("DOMContentLoaded fired"); PanelManager.init(); }); } else { console.log("Document already loaded, initializing immediately"); PanelManager.init(); } // 保留 load 事件作为备份 window.addEventListener("load", () => { console.log("Window load triggered"); if (!PanelManager.panel) { PanelManager.init(); } }); // 添加更详细的日志 console.log("Document readyState:", document.readyState); console.log("Document body exists:", !!document.body); console.log("Document documentElement exists:", !!document.documentElement); })();