/** * 全局状态标记 */ let isProcessing = false; let fillButton = null; let clearButton = null; /** * 默认地址数据源 */ const DEFAULT_ADDRESSES = [{ name: "John Smith", firstName: "John", lastName: "Smith", address1: "69 Adams Street", address2: "", city: "Brooklyn", state: "New York", stateCode: "NY", postal: "11201", countryText: "United States", countryValue: "US" }, { name: "Michael Johnson", firstName: "Michael", lastName: "Johnson", address1: "3511 Carlisle Avenue", address2: "", city: "Covington", state: "Kentucky", stateCode: "KY", postal: "41015", countryText: "United States", countryValue: "US" }]; /** * 字段匹配同义词词典 * 用于启发式识别表单字段类型 */ const FIELD_SYNONYMS = { fullName: ["full name", "name", "cardholder name", "card name", "cc-name"], firstName: ["first name", "given-name"], lastName: ["last name", "family-name", "surname"], address1: ["address", "address line 1", "street", "addressline1", "address-line1", "address-line-1"], address2: ["address line 2", "apt", "apartment", "addressline2", "address-line2", "suite"], city: ["city", "locality", "address-level2"], state: ["state", "region", "province", "administrative area", "address-level1", "address level 1"], postal: ["postal", "zip", "postcode", "postal-code"], country: ["country", "country or region"] }; const CARD_FIELD_WORDS = ["card", "cvc", "cvv", "expiry", "expiration", "valid thru", "month", "year"]; // ========================================== // 核心工具函数 // ========================================== /** * 获取随机地址 * 优先从 storage 读取配置,支持 static/manual/auto 模式 */ async function getRandomAddress() { return new Promise(resolve => { chrome.storage.local.get(["customAddresses", "addressSource"], data => { const customList = data.customAddresses || []; const sourceMode = data.addressSource || "static"; let addressPool = []; switch (sourceMode) { case "static": addressPool = DEFAULT_ADDRESSES; break; case "manual": addressPool = customList.length > 0 ? customList : DEFAULT_ADDRESSES; break; case "auto": addressPool = DEFAULT_ADDRESSES; break; default: addressPool = DEFAULT_ADDRESSES; } if (addressPool.length === 0) { resolve(DEFAULT_ADDRESSES[0]); } else { const selected = addressPool[Math.floor(Math.random() * addressPool.length)]; console.log(`[cardbingenerator] Using ${sourceMode} address:`, selected.name); resolve(selected); } }); }); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function randomDelay(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } /** * 模拟人类输入文本 * @param {HTMLElement} element - 目标输入框 * @param {string} text - 要输入的文本 * @param {boolean} clearFirst - 是否先清空输入框 (默认 false) */ async function typeText(element, text, clearFirst = false) { if (!element || !text) return; element.focus(); element.scrollIntoView({ behavior: "smooth", block: "center" }); await sleep(randomDelay(150, 300)); if (clearFirst && text.length < 50) { // 模拟逐字删除或清空 element.value = ""; for (let i = 0; i < text.length; i++) { element.value += text[i]; element.dispatchEvent(new Event("input", { bubbles: true })); await sleep(randomDelay(30, 80)); // 模拟击键间隔 } element.dispatchEvent(new Event("change", { bubbles: true })); } else { // 直接赋值模式 element.value = text; element.dispatchEvent(new Event("input", { bubbles: true })); element.dispatchEvent(new Event("change", { bubbles: true })); } await sleep(randomDelay(100, 200)); element.blur(); await sleep(randomDelay(200, 400)); } /** * 收集页面所有根节点(包括 Shadow DOM) * 用于穿透 Shadow DOM 查找元素 */ function collectRoots() { const roots = [document]; const queue = [document.documentElement]; while (queue.length) { const node = queue.pop(); if (!node) continue; if (node.shadowRoot) { roots.push(node.shadowRoot); } const children = node.children || []; for (let i = 0; i < children.length; i++) { queue.push(children[i]); } } return roots; } /** * 检测元素是否可见且可交互 */ function isVisible(el) { if (!el) return false; const rect = el.getBoundingClientRect(); const style = window.getComputedStyle(el); if (style.visibility === "hidden" || style.display === "none") return false; if (el.disabled) return false; if (rect.width <= 0 || rect.height <= 0) return false; if (el.type === "hidden") return false; return true; } /** * 收集页面所有可见的表单元素 (input, select, textarea) */ function collectFormElements() { const elements = []; for (const root of collectRoots()) { const nodes = root.querySelectorAll("input, select, textarea"); nodes.forEach(node => { if (isVisible(node)) { elements.push(node); } }); } return elements; } /** * 使用原生 Setter 设置值并触发事件 * 绕过 React/Vue 等框架的状态绑定限制 */ async function setNativeValueAndDispatch(element, value, simulateTyping = false) { if (!element) return; try { element.focus(); element.scrollIntoView({ behavior: "smooth", block: "center" }); await sleep(randomDelay(150, 300)); const tagName = element.tagName; if (tagName === "INPUT" || tagName === "TEXTAREA") { const proto = tagName === "INPUT" ? window.HTMLInputElement.prototype : window.HTMLTextAreaElement.prototype; const nativeSetter = Object.getOwnPropertyDescriptor(proto, "value").set; if (simulateTyping && value && value.length < 30) { // 模拟打字效果 element.value = ""; for (let i = 0; i < value.length; i++) { nativeSetter.call(element, element.value + value[i]); element.dispatchEvent(new Event("input", { bubbles: true })); await sleep(randomDelay(50, 120)); } } else { // 快速填充 nativeSetter.call(element, value); element.dispatchEvent(new Event("input", { bubbles: true })); } element.dispatchEvent(new Event("change", { bubbles: true })); await sleep(randomDelay(150, 250)); element.blur(); } else if (tagName === "SELECT") { element.value = value; element.dispatchEvent(new Event("change", { bubbles: true })); await sleep(randomDelay(200, 350)); element.blur(); } await sleep(randomDelay(300, 500)); } catch (err) { // 降级处理:直接赋值 try { element.focus(); await sleep(randomDelay(150, 300)); element.value = value; element.dispatchEvent(new Event("input", { bubbles: true })); element.dispatchEvent(new Event("change", { bubbles: true })); await sleep(randomDelay(150, 250)); element.blur(); await sleep(randomDelay(300, 500)); } catch (ignored) {} } } /** * 处理 Select 下拉框的值选择 * 尝试匹配 value, textContent 或模糊匹配 */ function pickSelectValue(selectEl, valueOptions, textOptions) { if (!selectEl || selectEl.tagName !== "SELECT") return null; const options = Array.from(selectEl.options || []); const normalize = str => (str || "").toLowerCase().replace(/\s+/g, " ").trim(); const includesText = (text, query) => normalize(text).includes(normalize(query)); // 1. 尝试匹配 value for (const val of valueOptions || []) { const found = options.find(opt => normalize(opt.value) === normalize(val)); if (found) return found.value; } // 2. 尝试匹配 textContent (精确) for (const txt of textOptions || []) { const found = options.find(opt => normalize(opt.textContent) === normalize(txt)); if (found) return found.value; } // 3. 尝试匹配 textContent (包含) for (const txt of textOptions || []) { const found = options.find(opt => includesText(opt.textContent, txt)); if (found) return found.value; } return null; } // ========================================== // 字段识别逻辑 // ========================================== function getElementTextAttributes(el) { const attrs = [ el.getAttribute("name"), el.getAttribute("id"), el.getAttribute("placeholder"), el.getAttribute("aria-label"), el.getAttribute("autocomplete"), el.getAttribute("data-testid"), el.getAttribute("data-qa") ].filter(Boolean); return attrs.join(" ").toLowerCase(); } function matchesAny(text, keywords) { const lowerText = text.toLowerCase(); return keywords.some(kw => lowerText.includes(kw.toLowerCase())); } function isCardField(el) { const text = getElementTextAttributes(el); if (!text) return false; return matchesAny(text, CARD_FIELD_WORDS); } /** * 计算字段与特定类型的匹配分数 */ function scoreForSynonyms(el, synonyms) { const text = getElementTextAttributes(el); let score = 0; if (!text) return score; const autocomplete = (el.getAttribute("autocomplete") || "").toLowerCase(); // 权重最高:autocomplete 属性匹配 for (const syn of synonyms) { if (autocomplete.split(/\s+/).includes(syn.toLowerCase())) { score += 6; } } // 权重中等:name 或 id 包含关键词 const nameId = [(el.getAttribute("name") || "").toLowerCase(), (el.getAttribute("id") || "").toLowerCase()].join(" "); for (const syn of synonyms) { if (nameId.includes(syn.toLowerCase())) { score += 4; } } // 权重最低:placeholder 或 aria-label 包含关键词 const desc = [(el.getAttribute("placeholder") || "").toLowerCase(), (el.getAttribute("aria-label") || "").toLowerCase()].join(" "); for (const syn of synonyms) { if (desc.includes(syn.toLowerCase())) { score += 2; } } return score; } /** * 寻找最佳匹配字段 */ function findBestField(elements, synonyms, validator) { let bestEl = null; let bestScore = 0; for (const el of elements) { if (validator && !validator(el)) continue; const score = scoreForSynonyms(el, synonyms); if (score > bestScore) { bestEl = el; bestScore = score; } } return bestEl; } /** * 识别所有非卡号类的普通表单字段 (姓名、地址等) */ function detectAddressFields() { const formElements = collectFormElements().filter(el => !isCardField(el)); const fields = {}; fields.firstName = findBestField(formElements, FIELD_SYNONYMS.firstName, el => el.tagName !== "SELECT"); fields.lastName = findBestField(formElements, FIELD_SYNONYMS.lastName, el => el.tagName !== "SELECT"); fields.fullName = findBestField(formElements, FIELD_SYNONYMS.fullName, el => el.tagName !== "SELECT"); fields.address1 = findBestField(formElements, FIELD_SYNONYMS.address1, el => el.tagName !== "SELECT"); fields.address2 = findBestField(formElements, FIELD_SYNONYMS.address2, el => el.tagName !== "SELECT"); fields.city = findBestField(formElements, FIELD_SYNONYMS.city, el => el.tagName !== "SELECT"); fields.state = findBestField(formElements, FIELD_SYNONYMS.state, el => el.tagName !== "SELECT"); fields.postal = findBestField(formElements, FIELD_SYNONYMS.postal, el => el.tagName !== "SELECT"); fields.country = findBestField(formElements, FIELD_SYNONYMS.country, () => true); // Country 可以是 select // 逻辑修正:避免全名和名/姓重复匹配 if (fields.fullName) { if (fields.firstName === fields.fullName) fields.firstName = null; if (fields.lastName === fields.fullName) fields.lastName = null; } if (fields.firstName && fields.lastName && fields.firstName === fields.lastName) { fields.fullName = fields.firstName; fields.firstName = null; fields.lastName = null; } return fields; } /** * 专门识别信用卡相关字段 * 使用 CSS 选择器优先匹配 */ function detectCardFields() { const roots = collectRoots(); let numEl, expEl, cvcEl; const numSelectors = ["input[autocomplete=\"cc-number\"]", "input[name*=\"cardnumber\" i]", "input[id*=\"cardnumber\" i]", "input[name=\"cardNumber\"]", "input[placeholder*=\"1234\"]", "#cardNumber"]; const expSelectors = ["input[autocomplete=\"cc-exp\"]", "input[name*=\"exp\" i]", "input[id*=\"exp\" i]", "input[placeholder*=\"MM\"]", "input[name=\"cardExpiry\"]", "#cardExpiry"]; const cvcSelectors = ["input[autocomplete=\"cc-csc\"]", "input[name*=\"cvc\" i]", "input[name*=\"cvv\" i]", "input[id*=\"cvc\" i]", "input[placeholder*=\"CVC\"]", "input[name=\"cardCvc\"]", "#cardCvc"]; function findInRoots(selectors) { for (const root of roots) { for (const sel of selectors) { const el = root.querySelector(sel); if (el && isVisible(el)) return el; } } return null; } numEl = findInRoots(numSelectors); expEl = findInRoots(expSelectors); cvcEl = findInRoots(cvcSelectors); if (numEl || expEl || cvcEl) { return { number: numEl, exp: expEl, cvc: cvcEl }; } return null; } /** * 自动点击“手动输入地址”按钮(如果有的话) * 常见于 Google Places Autocomplete 覆盖了原生输入框的情况 */ function clickManualAddressIfPresent() { const keywords = ["enter address manually", "manually enter address", "ввести адрес вручную", "введите адрес вручную", "адрес вручную"]; try { const roots = collectRoots(); const tagSelectors = ["button", "[role=\"button\"]", "a", ".Button", ".Link", "span[role=\"button\"]", "div[role=\"button\"]"]; for (const root of roots) { for (const selector of tagSelectors) { const elements = root.querySelectorAll(selector); for (let i = 0; i < elements.length; i++) { const el = elements[i]; if (!isVisible(el)) continue; const text = [ el.textContent || "", el.getAttribute("aria-label") || "", el.getAttribute("title") || "", el.getAttribute("data-testid") || "" ].join(" ").toLowerCase(); if (!text) continue; if (keywords.some(kw => text.includes(kw.toLowerCase()))) { const clickable = el.closest("button, [role=\"button\"], a, [role=\"link\"]") || el; console.log("[cardbingenerator] Clicking manual address button:", el.textContent); clickable.click(); return true; } } } } } catch (err) {} return false; } // ========================================== // 主业务逻辑:自动填充流程 // ========================================== async function autofillAll() { if (isProcessing) return; isProcessing = true; try { showNotification("🔄 Starting auto-fill...", "info"); await sleep(randomDelay(500, 1000)); // 1. 生成新卡片 showNotification("🔄 Generating fresh cards...", "info"); const storage = await chrome.storage.local.get(["currentBin"]); const currentBin = storage.currentBin || "552461xxxxxxxxxx"; // 发送消息给 background script 生成卡片 const genResult = await new Promise(resolve => { chrome.runtime.sendMessage({ action: "generateCards", bin: currentBin, stripeTabId: null }, response => resolve(response)); }); if (!genResult || !genResult.success) { showNotification("❌ Failed to generate cards: " + (genResult?.error || "Unknown error"), "error"); isProcessing = false; return; } await sleep(2000); // 2. 获取生成的卡片数据 const cardStorage = await chrome.storage.local.get(["generatedCards"]); if (!cardStorage.generatedCards || cardStorage.generatedCards.length === 0) { showNotification("❌ No cards were generated", "error"); isProcessing = false; return; } const selectedCard = cardStorage.generatedCards[Math.floor(Math.random() * cardStorage.generatedCards.length)]; const addressData = await getRandomAddress(); showNotification("💳 Filling card details...", "info"); await sleep(randomDelay(400, 700)); // 3. 处理 Stripe 常见的折叠卡片按钮 const accordionBtn = document.querySelector("[data-testid=\"card-accordion-item-button\"]"); if (accordionBtn && isVisible(accordionBtn)) { console.log("[cardbingenerator] Clicking card button..."); accordionBtn.scrollIntoView({ behavior: "smooth", block: "center" }); await sleep(randomDelay(300, 500)); accordionBtn.click(); await sleep(randomDelay(800, 1200)); } // 4. 填充卡片信息 const cardFields = detectCardFields(); if (cardFields) { if (cardFields.number) { console.log("[cardbingenerator] Filling card number..."); await setNativeValueAndDispatch(cardFields.number, selectedCard.card_number); } if (cardFields.exp) { console.log("[cardbingenerator] Filling expiry date..."); const expStr = selectedCard.expiry_month + " / " + selectedCard.expiry_year.slice(-2); await setNativeValueAndDispatch(cardFields.exp, expStr); } if (cardFields.cvc) { console.log("[cardbingenerator] Filling CVC..."); await setNativeValueAndDispatch(cardFields.cvc, selectedCard.cvv); } } // 5. 填充地址信息 showNotification("📝 Filling address...", "info"); const addrFields = detectAddressFields(); // 优先处理国家选择,因为这可能会刷新表单格式 if (addrFields.country && addrFields.country.tagName === "SELECT") { console.log("[cardbingenerator] Filling country..."); const countryVal = pickSelectValue(addrFields.country, [addressData.countryValue], [addressData.countryText]); if (countryVal) { await setNativeValueAndDispatch(addrFields.country, countryVal); } await sleep(300); } // 检查是否有“手动输入地址”按钮并点击 const clickedManual = clickManualAddressIfPresent(); if (clickedManual) { console.log("[cardbingenerator] Manual address button clicked"); await sleep(randomDelay(800, 1200)); } // 填充详细地址字段 const fillAddressDetails = async () => { const currentFields = detectAddressFields(); // 重新检测,因为 DOM 可能已变化 // 名字处理逻辑 if (currentFields.firstName && currentFields.lastName && currentFields.firstName !== currentFields.lastName) { console.log("[cardbingenerator] Filling first name..."); await setNativeValueAndDispatch(currentFields.firstName, addressData.firstName, true); console.log("[cardbingenerator] Filling last name..."); await setNativeValueAndDispatch(currentFields.lastName, addressData.lastName, true); } else { const nameField = currentFields.fullName || currentFields.firstName || currentFields.lastName; if (nameField) { console.log("[cardbingenerator] Filling full name..."); await setNativeValueAndDispatch(nameField, addressData.name, true); } } if (currentFields.address1) { console.log("[cardbingenerator] Filling address line 1..."); await setNativeValueAndDispatch(currentFields.address1, addressData.address1, false); } if (currentFields.address2) { console.log("[cardbingenerator] Filling address line 2..."); await setNativeValueAndDispatch(currentFields.address2, addressData.address2, false); } if (currentFields.city) { console.log("[cardbingenerator] Filling city..."); await setNativeValueAndDispatch(currentFields.city, addressData.city, true); } if (currentFields.postal) { console.log("[cardbingenerator] Filling postal code..."); await setNativeValueAndDispatch(currentFields.postal, addressData.postal, false); } }; await fillAddressDetails(); // 延迟填充 State/Province,因为有些表单需要先填 Country/Zip 才会出现 State const fillState = async () => { await sleep(randomDelay(400, 600)); const fieldsNow = detectAddressFields(); if (!fieldsNow.state) return; if (fieldsNow.state.tagName === "SELECT") { console.log("📍 Filling state (select)..."); const stateVal = pickSelectValue(fieldsNow.state, [addressData.stateCode], [addressData.state]); if (stateVal) { await setNativeValueAndDispatch(fieldsNow.state, stateVal); } } else { console.log("📍 Filling state (input)..."); await setNativeValueAndDispatch(fieldsNow.state, addressData.stateCode || addressData.state, true); } }; await fillState(); await sleep(randomDelay(600, 1000)); // 清理已使用的卡片数据 chrome.storage.local.remove(["generatedCards"], () => { console.log("[cardbingenerator] Cleared used cards from storage"); }); console.log("[cardbingenerator] Auto-fill completed!"); showNotification("✅ All fields filled successfully!", "success"); } catch (err) { chrome.storage.local.remove(["generatedCards"]); showNotification("❌ Error: " + err.message, "error"); console.error("Autofill error:", err); } isProcessing = false; } // ========================================== // UI 辅助功能:通知和清理按钮 // ========================================== function showNotification(message, type = "info") { const existing = document.getElementById("auto-card-filler-notification"); if (existing) existing.remove(); const notif = document.createElement("div"); notif.id = "auto-card-filler-notification"; notif.textContent = message; const colors = { info: "#3498db", success: "#2ecc71", warning: "#f39c12", error: "#e74c3c" }; Object.assign(notif.style, { position: "fixed", top: "20px", right: "20px", background: colors[type] || colors.info, color: "white", padding: "15px 20px", borderRadius: "8px", boxShadow: "0 4px 12px rgba(0,0,0,0.3)", zIndex: "9999999", fontSize: "14px", fontWeight: "600", maxWidth: "300px", animation: "slideIn 0.3s ease-out" }); const styleEl = document.createElement("style"); styleEl.textContent = ` @keyframes slideIn { from { transform: translateX(400px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } `; if (!document.getElementById("autofill-notification-style")) { styleEl.id = "autofill-notification-style"; document.head.appendChild(styleEl); } document.body.appendChild(notif); setTimeout(() => { notif.style.transition = "all 0.3s ease-out"; notif.style.transform = "translateX(400px)"; notif.style.opacity = "0"; setTimeout(() => notif.remove(), 300); }, 5000); } function findPaymentMethodHeader() { const roots = collectRoots(); for (const root of roots) { const headers = root.querySelectorAll("h1, h2, h3, h4, .Header, [class*=\"header\"], [class*=\"title\"], [class*=\"Title\"]"); for (const h of headers) { const text = h.textContent.toLowerCase().trim(); if (["payment method", "payment", "метод оплаты", "способ оплаты"].includes(text) || text.includes("payment method")) { return h; } } } return null; } function createFillButton() { if (clearButton || document.getElementById("stripe-clear-btn")) return; const header = findPaymentMethodHeader(); clearButton = document.createElement("button"); clearButton.id = "stripe-clear-btn"; clearButton.innerHTML = "🗑️ Clear All Data"; // 注入按钮样式 clearButton.style.cssText = ` background: #dc3545; color: white; border: none; border-radius: 8px; padding: 8px 16px; font-size: 13px; font-weight: 600; cursor: pointer; box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3); transition: all 0.2s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin-left: 8px; vertical-align: middle; white-space: nowrap; `; clearButton.addEventListener("mouseenter", () => { clearButton.style.transform = "translateY(-1px)"; clearButton.style.boxShadow = "0 4px 12px rgba(220, 53, 69, 0.4)"; }); clearButton.addEventListener("mouseleave", () => { clearButton.style.transform = "translateY(0)"; clearButton.style.boxShadow = "0 2px 8px rgba(220, 53, 69, 0.3)"; }); clearButton.addEventListener("click", async () => { if (confirm(`⚠️ Clear all Stripe data? This will:\n\n• Delete all cookies\n• Clear localStorage\n• Clear sessionStorage\n• Clear cache\n• Reload the page\n\nContinue?`)) { clearButton.disabled = true; clearButton.innerHTML = "⏳ Clearing..."; await clearAllStripeData(); showNotification("✅ All data cleared! Reloading...", "success"); setTimeout(() => { location.reload(); }, 1000); } }); // 尝试将按钮插入到支付标题旁边,如果找不到标题则悬浮显示 if (header) { if (header.parentElement) { const container = document.createElement("div"); container.style.cssText = "display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;"; const headerClone = header.cloneNode(true); headerClone.style.margin = "0"; const btnGroup = document.createElement("div"); btnGroup.style.cssText = "display: flex; gap: 8px;"; btnGroup.appendChild(clearButton); container.appendChild(headerClone); container.appendChild(btnGroup); header.parentElement.replaceChild(container, header); } } else { clearButton.style.cssText += ` position: fixed; top: 20px; right: 20px; z-index: 999999; padding: 12px 20px; font-size: 14px; `; document.body.appendChild(clearButton); } } /** * 彻底清理浏览器数据 (Stripe 反指纹追踪) */ async function clearAllStripeData() { try { localStorage.clear(); sessionStorage.clear(); // 清理 IndexedDB if (window.indexedDB) { const dbs = await window.indexedDB.databases(); for (const db of dbs) { window.indexedDB.deleteDatabase(db.name); } } // 暴力清理 Cookies const cookies = document.cookie.split(";"); for (let cookie of cookies) { const eqPos = cookie.indexOf("="); const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim(); document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/"; document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=" + location.hostname; document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=." + location.hostname; } if ("caches" in window) { const keys = await caches.keys(); for (const key of keys) { await caches.delete(key); } } chrome.runtime.sendMessage({ action: "clearBrowsingData" }); console.log("[cardbingenerator] All Stripe data cleared"); } catch (err) { console.error("Error clearing data:", err); showNotification("⚠️ Partial clear - some data may remain", "warning"); } } function shouldShowButton() { const hasCardFields = detectCardFields(); const hasAddrFields = detectAddressFields(); return hasCardFields || hasAddrFields.fullName || hasAddrFields.address1; } function initButton() { if (shouldShowButton()) { createFillButton(); } } // ========================================== // 初始化与事件监听 // ========================================== const observer = new MutationObserver(() => { if (!fillButton && shouldShowButton()) { createFillButton(); } }); if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initButton); } else { initButton(); } // 多次尝试初始化,应对动态加载的 SPA setTimeout(initButton, 1000); setTimeout(initButton, 2000); setTimeout(initButton, 3000); chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === "fillForm") { autofillAll(); } }); window.addEventListener("beforeunload", () => { chrome.storage.local.remove(["generatedCards"]); console.log("[cardbingenerator] Cleared cards on page unload"); }); chrome.storage.local.remove(["generatedCards"], () => { console.log("[cardbingenerator] Cleared old cards on page load"); });