You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Manifest V3 Chrome扩展:拦截下载链接后如何阻止Chrome原生下载?

Manifest V3 Chrome扩展:拦截下载链接后如何阻止Chrome原生下载?

我太懂你现在的烦躁了——明明已经把下载链接成功发送给Fetchify,也用尽了DOM拦截手段,Chrome却还是我行我素地启动原生下载,这搁谁都会头疼!别担心,咱们一步步拆解问题,把这个顽固的原生下载给按住。

问题根源分析

为什么e.preventDefault()拦不住?主要有两个核心原因:

  1. 事件触发顺序没抢赢:页面自身的事件监听器可能在你的脚本之前触发,或者你用的是事件冒泡阶段监听,晚了一步。
  2. 下载触发的场景不止点击:有些下载是通过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配置已经满足要求,无需额外修改)。

验证步骤

  1. 打开Chrome扩展管理页面,重新加载你的扩展;
  2. 找一个测试下载链接(比如一个.zip文件);
  3. 点击链接,观察Chrome的下载栏——不会出现这个下载项;
  4. 检查Fetchify应用,确认已经收到了下载链接;
  5. 查看扩展的后台日志(扩展管理页面→Service Worker→Inspect),确认拦截日志正常输出。

额外注意事项

  • 若遇到blob:或data:协议的下载链接,可在后台onCreated事件中优先使用downloadItem.finalUrl判断;
  • 确保Fetchify应用处于运行状态,且http://localhost:12345/api/download接口正常响应;
  • 可在chrome://downloads/页面确认被拦截的下载未出现在记录中。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 09:47:59