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

Chrome扩展GET请求拦截功能失效,请求排查问题

排查Chrome扩展GET请求拦截失效问题

当前开发的Chrome扩展(Manifest V3)本应拦截GET请求,但存在以下问题:

  • 页面控制台和服务工作者控制台均看不到content.js开头的console.log("Content script running")输出,尽管资源列表显示content.js已加载
  • 服务工作者未收到content.js发送的任何消息,拦截代码完全未被触发
  • 已确认valid-domains.txt和intercept-file.txt文件存在且内容有效

相关代码

manifest.json

{
  "manifest_version": 3,
  "name": "GET Request Interceptor",
  "version": "1.0",
  "permissions": [
    "webRequest",
    "activeTab"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "host_permissions": [
    "<all_urls>"
  ],
  "icons": {
        "16": "icons/icon.svg",
        "48": "icons/icon.svg",
        "128": "icons/icon.svg"
  },
  "action": {
    "default_icon": "icons/icon.svg"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"],
      "run_at": "document_start"
    }
  ]  
}

background.js

let validDomains = [];
let interceptRegex = null;

// Load valid domains and intercept regex at startup
fetch(chrome.runtime.getURL('valid-domains.txt'))
  .then((response) => response.text())
  .then((text) => {
    validDomains = text.split('\n').map(domain => domain.trim()).filter(domain => domain);
    console.log('Valid domains loaded:', validDomains);
  })
  .catch((error) => console.error('Failed to load valid domains:', error));

// Load the regex for path matching from intercept-file.txt
fetch(chrome.runtime.getURL('intercept-file.txt'))
  .then((response) => response.text())
  .then((regexText) => {
    try {
      // Create a regular expression object from the file content
      interceptRegex = new RegExp(regexText.trim());
      console.log('Intercept regex loaded:', interceptRegex);
    } catch (error) {
      console.error('Invalid regex in intercept-file.txt:', error);
    }
  })
  .catch((error) => console.error('Failed to load intercept regex:', error));

// Helper function to extract the root domain from a hostname
function getRootDomain(hostname) {
  const parts = hostname.split('.').reverse();
  if (parts.length >= 2) {
    return `${parts[1]}.${parts[0]}`;
  }
  return hostname; // Fallback for localhost or unusual domains
}

// Transform function to modify the original content
function transformContent(originalContent) {
  return originalContent; // Just return original content for now
}

// Initialize message listeners
function setupMessageListeners() {
  const messageListener = (message, sender, sendResponse) => {
    if (message.type === 'intercept-fetch' || message.type === 'intercept-xmlhttp') {
      const { url, method } = message;

      // Fetch the original content
      fetch(url, { method: method || 'GET' })
        .then((response) => response.text().then((body) => ({ response, body })))
        .then(({ response, body }) => {
          const parsedUrl = new URL(url);
          const domain = getRootDomain(parsedUrl.hostname);
          const path = parsedUrl.pathname;

          console.log("Intercepted GET request: ", domain, path);

          // Check if the domain is valid
          if (validDomains.includes(domain) && interceptRegex.test(path)) {
            // Transform content if regex matches
            const transformedContent = transformContent(body);

            sendResponse({
              status: 200,
              statusText: response.statusText,
              headers: Object.fromEntries(response.headers.entries()),
              body: transformedContent
            });
          } else {
            // Return original content if no match
            sendResponse({
              status: response.status,
              statusText: response.statusText,
              headers: Object.fromEntries(response.headers.entries()),
              body
            });
          }
        })
        .catch((error) => {
          console.error('Error fetching original content:', error);
          sendResponse({ status: 404, statusText: 'Cannot find content', body: '' });
        });

      return true; // Keep the message channel open for async response
    }
  };

  // Register the message listener
  chrome.runtime.onMessage.addListener(messageListener);
}

// Initialize listeners when the worker starts
setupMessageListeners();

content.js

console.log("Content script running");

// Intercept fetch API
const originalFetch = window.fetch;
window.fetch = async (...args) => {
    const [resource, config] = args;

    // Send the request details to the service worker
    const message = {
        type: 'intercept-fetch',
        url: resource,
        method: config?.method || 'GET',
        headers: config?.headers,
        body: config?.body
    };

    // Send message to service worker and wait for response
    const response = await new Promise((resolve) => {
        chrome.runtime.sendMessage(message, resolve);
    });

    if (response && response.status) {
        // Construct a Response object with the data from the service worker
        return new Response(response.body, {
            status: response.status,
            statusText: response.statusText,
            headers: new Headers(response.headers)
        });
    }

    // Fallback to original fetch if no valid response received
    return originalFetch(...args);
};

// Intercept XMLHttpRequest
const originalXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url, async = true, user, password) {
    this._intercepted = { method, url };
    return originalXHROpen.apply(this, arguments);
};

const originalXHRSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function (body) {
    if (this._intercepted) {
        const { method, url } = this._intercepted;

        // Send the request details to the service worker
        chrome.runtime.sendMessage(
            {
                type: 'intercept-xmlhttp',
                url,
                method,
                body
            },
            (response) => {
                if (response && response.status) {
                    // Replace the XHR response
                    Object.defineProperty(this, 'responseText', {
                        get: () => response.body,
                    });

                    this.status = response.status;
                    this.statusText = response.statusText;

                    // Trigger readystatechange event
                    this.readyState = 4;
                    this.onreadystatechange && this.onreadystatechange();
                    this.onload && this.onload();
                } else {
                    // Fallback to original XHR send if no valid response received
                    originalXHRSend.apply(this, arguments);
                }
            }
        );
    } else {
        // No interception, proceed with original send
        originalXHRSend.apply(this, arguments);
    }
};

排查步骤

  • 验证content脚本注入状态

    1. 确认扩展已获得当前测试站点的权限:进入扩展管理页面(chrome://extensions/),找到该扩展,点击"详细信息",查看"网站权限"是否包含目标站点,特殊站点如chrome://、chrome-extensions://默认不允许content脚本注入
    2. 临时修改run_at为document_idle,避免document_start时机过早导致脚本注入失败,测试后再改回原设置
  • 添加消息发送错误捕获
    content脚本中调用chrome.runtime.sendMessage时未处理错误,需添加捕获逻辑定位问题:
    修改fetch拦截部分的消息发送代码:

    const response = await new Promise((resolve, reject) => {
        chrome.runtime.sendMessage(message, (response) => {
            if (chrome.runtime.lastError) {
                reject(chrome.runtime.lastError);
                console.error("Fetch消息发送失败:", chrome.runtime.lastError);
            } else {
                resolve(response);
            }
        });
    }).catch(err => {
        console.error("Fetch拦截失败:", err);
        return null;
    });
    

    修改XHR拦截部分的消息发送代码:

    chrome.runtime.sendMessage(
        { type: 'intercept-xmlhttp', url, method, body },
        (response) => {
            if (chrome.runtime.lastError) {
                console.error("XHR消息发送失败:", chrome.runtime.lastError);
                originalXHRSend.apply(this, arguments);
                return;
            }
            // 原response处理逻辑
        }
    );
    
  • 修复资源加载时机问题
    当前validDomains和interceptRegex是异步加载,若请求在资源加载完成前触发,会导致拦截逻辑失效,需等待资源加载完成后再处理请求:

    let validDomains = [];
    let interceptRegex = null;
    let isResourcesLoaded = false;
    
    // 使用Promise.all等待所有资源加载完成
    Promise.all([
        fetch(chrome.runtime.getURL('valid-domains.txt'))
            .then(res => res.text())
            .then(text => {
                validDomains = text.split('\n').map(d => d.trim()).filter(Boolean);
                console.log('Valid domains loaded:', validDomains);
            }),
        fetch(chrome.runtime.getURL('intercept-file.txt'))
            .then(res => res.text())
            .then(regexText => {
                interceptRegex = new RegExp(regexText.trim());
                console.log('Intercept regex loaded:', interceptRegex);
            })
    ]).then(() => {
        isResourcesLoaded = true;
        console.log("所有资源加载完成,开始处理请求");
    }).catch(err => {
        console.error("资源加载失败:", err);
    });
    
    // 修改消息监听逻辑
    const messageListener = (message, sender, sendResponse) => {
        if (!isResourcesLoaded) {
            console.log("资源未加载完成,跳过当前请求");
            sendResponse(null);
            return true;
        }
        // 原请求处理逻辑
    };
    
  • 确认控制台查看位置
    content脚本的console.log输出会出现在当前页面的开发者工具控制台(按F12打开,切换到"Console"标签),而非扩展的服务工作者控制台,需确认查看的是正确的控制台。

内容的提问来源于stack exchange,提问作者markt1964

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:09:51