Manifest V3 Chrome扩展:拦截下载链接后如何阻止Chrome原生下载?
Manifest V3 Chrome扩展:拦截下载链接后如何阻止Chrome原生下载?
我太懂你现在的烦躁了——明明已经把下载链接成功发送给Fetchify,也用尽了DOM拦截手段,Chrome却还是我行我素地启动原生下载,这搁谁都会头疼!别担心,咱们一步步拆解问题,把这个顽固的原生下载给按住。
问题根源分析
为什么e.preventDefault()拦不住?主要有两个核心原因:
- 事件触发顺序没抢赢:页面自身的事件监听器可能在你的脚本之前触发,或者你用的是事件冒泡阶段监听,晚了一步。
- 下载触发的场景不止点击:有些下载是通过JS动态生成、Enter键触发,甚至是页面自动发起的,这些情况DOM点击拦截根本覆盖不到,必须靠Chrome的扩展API从底层拦截。
解决方案:双重拦截(DOM层 + 扩展API层)
咱们用「前端事件抢跑拦截」+「后台API底层拦截」的组合拳,确保100%拦住原生下载。
第一步:优化Content Script的事件拦截(抢在页面之前触发)
修改autoDownloadScript.js,把事件监听切换到捕获模式(比冒泡阶段更早触发),同时覆盖更多触发场景(点击、鼠标按下、回车):
(function () { console.log("[Fetchify] Script injected and running..."); const downloadExtensions = /\.(zip|exe|mp4|mp3|pdf|iso|rar|7z|msi|deb|apk|tar\.gz|docx?|xlsx?|pptx?|csv)$/i; function sendToFetchify(url) { console.log(`[Fetchify] Sending to Fetchify API: ${url}`); fetch("http://localhost:12345/api/download", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url }) }) .then(res => { if (res.ok) { console.log("✅ Fetchify accepted the request."); // 通知后台:这个URL已经处理过了,别重复拦截 chrome.runtime.sendMessage({ type: 'DOWNLOAD_PROCESSED', url: url }).catch(err => { console.warn("[Fetchify] ⚠️ Failed to notify background:", err); }); } else { console.warn(`⚠️ Fetchify rejected it. Status: ${res.status}`); } }) .catch(err => { console.error("❌ Network error sending to Fetchify:", err); }); } function interceptDownloadTrigger(url, e) { console.log(`[Fetchify] ✅ Intercepted download trigger: ${url}`); // 彻底阻止所有默认行为和事件传播 e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); // 发送到Fetchify sendToFetchify(url); return false; } // 拦截点击事件(捕获模式) document.addEventListener('click', (e) => { const anchor = e.target.closest("a"); if (!anchor || !anchor.href || anchor.href === "javascript:void(0)") return; const url = new URL(anchor.href, document.baseURI).href; if (e.button === 0 && !e.ctrlKey && !e.metaKey && downloadExtensions.test(url)) { interceptDownloadTrigger(url, e); } }, { capture: true, passive: false }); // 拦截鼠标按下事件(有些页面会监听mousedown触发下载) document.addEventListener('mousedown', (e) => { const anchor = e.target.closest("a"); if (!anchor || !anchor.href || anchor.href === "javascript:void(0)") return; const url = new URL(anchor.href, document.baseURI).href; if (e.button === 0 && !e.ctrlKey && !e.metaKey && downloadExtensions.test(url)) { interceptDownloadTrigger(url, e); } }, { capture: true, passive: false }); // 拦截Enter键触发的链接下载 document.addEventListener('keydown', (e) => { if (e.key === 'Enter') { const activeEl = document.activeElement; if (activeEl.tagName === 'A' && activeEl.href) { const url = new URL(activeEl.href, document.baseURI).href; if (!e.ctrlKey && !e.metaKey && downloadExtensions.test(url)) { interceptDownloadTrigger(url, e); } } } }, { capture: true, passive: false }); })();
第二步:后台Service Worker用Downloads API底层拦截
修改background.js,添加下载拦截逻辑——不管下载是怎么触发的,只要是目标类型,直接取消原生下载,同时避免重复发送到Fetchify:
// 存储已处理的URL,避免重复操作(10秒后自动清理) const processedUrls = new Set(); const PROCESS_EXPIRY = 10000; // 监听Content Script的消息,标记URL为已处理 chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === 'DOWNLOAD_PROCESSED' && message.url) { processedUrls.add(message.url); setTimeout(() => processedUrls.delete(message.url), PROCESS_EXPIRY); console.log(`[Fetchify] ✅ Marked URL as processed: ${message.url}`); sendResponse({ status: 'ok' }); } return true; // 保持消息通道开放,支持异步响应 }); // 原生下载拦截核心逻辑 chrome.downloads.onCreated.addListener((downloadItem) => { const url = downloadItem.url || downloadItem.finalUrl; const downloadExtensions = /\.(zip|exe|mp4|mp3|pdf|iso|rar|7z|msi|deb|apk|tar\.gz|docx?|xlsx?|pptx?|csv)$/i; // 只拦截目标类型的下载 if (!url || !downloadExtensions.test(url)) return; // 如果已经处理过,直接取消下载 if (processedUrls.has(url)) { chrome.downloads.cancel(downloadItem.id, () => { if (chrome.runtime.lastError) { console.error("[Fetchify] ❌ Failed to cancel processed download:", chrome.runtime.lastError.message); } else { console.log(`[Fetchify] ✅ Cancelled native download (already processed): ${url}`); } }); return; } // 未处理的下载:取消原生下载 + 发送到Fetchify chrome.downloads.cancel(downloadItem.id, () => { if (chrome.runtime.lastError) { console.error("[Fetchify] ❌ Failed to cancel download:", chrome.runtime.lastError.message); return; } console.log(`[Fetchify] ✅ Cancelled native download, sending to Fetchify: ${url}`); sendToFetchify(url); // 标记为已处理 processedUrls.add(url); setTimeout(() => processedUrls.delete(url), PROCESS_EXPIRY); }); }); // 以下是原有的代码,保留不变 chrome.runtime.onInstalled.addListener(() => { chrome.contextMenus.create({ id: "send-to-fetchify", title: "Download with Fetchify", contexts: ["link"] }); console.log("[Fetchify] ✅ Context menu installed."); }); chrome.contextMenus.onClicked.addListener((info, tab) => { if (info.menuItemId === "send-to-fetchify" && info.linkUrl) { console.log(`[Fetchify] Context menu clicked for: ${info.linkUrl}`); sendToFetchify(info.linkUrl); } }); function sendToFetchify(url) { fetch("http://localhost:12345/api/download", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url }) }) .then((res) => { if (res.ok) { console.log("[Fetchify] ✅ Download sent successfully."); showNotification("✅ Sent to Fetchify", "The download was sent successfully."); } else { console.error("[Fetchify] ❌ Server rejected the download:", res.status); showNotification("❌ Fetchify Error", `Server responded with status ${res.status}`); } }) .catch((err) => { console.error("[Fetchify] ❌ Network error:", err); showNotification("❌ Fetchify Error", "Could not reach the Fetchify server."); }); } function showNotification(title, message) { chrome.notifications.create({ type: "basic", iconUrl: "icons/icon48.png", // 与manifest配置的图标路径保持一致 title: title, message: message }); } // 注释掉重复注入逻辑(content script已配置run_at: document_start,无需重复注入) /* chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (changeInfo.status === "complete" && /^https?:/.test(tab.url)) { chrome.scripting.executeScript({ target: { tabId: tabId }, files: ["autoDownloadScript.js"] }, () => { if (chrome.runtime.lastError) { console.warn("[Fetchify] ⚠️ Could not inject content script:", chrome.runtime.lastError.message); } else { console.log("[Fetchify] ✅ Content script injected on tab:", tab.url); } }); } }); */
第三步:Manifest.json的小调整
确保icons路径与代码中showNotification的iconUrl一致,避免通知图标加载失败(你的现有manifest配置已经满足要求,无需额外修改)。
验证步骤
- 打开Chrome扩展管理页面,重新加载你的扩展;
- 找一个测试下载链接(比如一个.zip文件);
- 点击链接,观察Chrome的下载栏——不会出现这个下载项;
- 检查Fetchify应用,确认已经收到了下载链接;
- 查看扩展的后台日志(扩展管理页面→Service Worker→Inspect),确认拦截日志正常输出。
额外注意事项
- 若遇到
blob:或data:协议的下载链接,可在后台onCreated事件中优先使用downloadItem.finalUrl判断; - 确保Fetchify应用处于运行状态,且
http://localhost:12345/api/download接口正常响应; - 可在
chrome://downloads/页面确认被拦截的下载未出现在记录中。
内容来源于stack exchange
相关产品推荐
相关产品推荐

