Skip to content

[BUG] 长时间的 xhr 请求,特定时间后超时,不会返回响应 #1343

Description

@enncy

问题描述

Service Worker 有默认的空闲超时(约30秒), 当某个请求需要较长时间处理(例如调用AI),浏览器经过大概几十秒后没有收到响应,就算服务器最终返回了响应,浏览器也无法收到。

重现步骤

上面那段 interval 如果删掉的话,下面如果在 180 秒左右返回数据,浏览器都是收不到的。 定时激活好像才能收到响应。

Image

脚本猫版本

v1.3.1

操作系统以及浏览器信息

win11 chrome 146.0.7680.178

补充信息 (选填)

No response

Activity

  1. added theissue type on Apr 9, 2026
  2. cyfung1031 commented on Apr 10, 2026

    @cyfung1031
    Collaborator

    Xhr是很老舊的設計
    你改用fetch吧

    參數加 fetch: true

  3. cyfung1031 commented on Apr 10, 2026

    @cyfung1031
    Collaborator
    Image Image

    @CodFrm 能重现吗?还是他用 1.3.1 所以有问题?

  4. cyfung1031 commented on Apr 10, 2026

    @cyfung1031
    Collaborator

    @enncy 打勾这个试试看?

    Image
  5. CodFrm commented on Apr 10, 2026

    @CodFrm
    Member

    @enncy 打勾这个试试看?

    Image

    和这个没关系;应该是一个超长的xhr请求,sw没有任何活跃,就被浏览器关闭sw了,然后导致了这个问题;可以考虑给这种情况价格ping/pong?让sw活跃一下

  6. cyfung1031 commented on Apr 11, 2026

    @cyfung1031
    Collaborator

    @enncy 打勾这个试试看?
    Image

    和这个没关系;应该是一个超长的xhr请求,sw没有任何活跃,就被浏览器关闭sw了,然后导致了这个问题;可以考虑给这种情况价格ping/pong?让sw活跃一下

  7. linked a pull request that will close this issue实现 WakeUpPing #1347on Apr 11, 2026
  8. CodFrm commented on May 10, 2026

    @CodFrm
    Member

    测试用的服务端接口已添加:GET https://test-case.ggnb.top/delay?seconds=60,会等待指定秒数后返回 JSON 响应,用于模拟长时间请求。

    以下是用于验证该问题的测试脚本,可直接在脚本猫中安装运行:

    // ==UserScript==
    // @name         测试长时间XHR请求 (#1343)
    // @namespace    test-long-xhr-1343
    // @version      1.0.0
    // @description  验证超过 Service Worker 空闲超时(~30s)的长请求是否能正常收到响应
    // @match        *://*/*
    // @grant        GM_xmlhttpRequest
    // @connect      test-case.ggnb.top
    // ==/UserScript==
    
    (function () {
      "use strict";
    
      const DELAY_SECONDS = 60;
      const url = `https://test-case.ggnb.top/delay?seconds=${DELAY_SECONDS}`;
    
      console.log(`[#1343] 开始测试: 请求 ${url}`);
      console.log(`[#1343] 预期等待 ${DELAY_SECONDS} 秒...`);
    
      const t0 = Date.now();
      GM_xmlhttpRequest({
        method: "GET",
        url,
        responseType: "json",
        onload: function (res) {
          const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
          console.log(`[#1343] onload: status=${res.status}, 耗时=${elapsed}s`);
          console.log(`[#1343] 响应体:`, res.response);
    
          if (res.status === 200 && res.response && res.response.delayed === DELAY_SECONDS) {
            console.log(`[#1343] ✅ 测试通过: 长时间请求(${elapsed}s)正常收到响应`);
          } else {
            console.log(`[#1343] ❌ 测试失败: status=${res.status}, response=${JSON.stringify(res.response)}`);
          }
        },
        onerror: function (res) {
          const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
          console.log(`[#1343] ❌ onerror: 耗时=${elapsed}s, status=${res.status}`);
          console.log(`[#1343] 测试失败: 请求发生错误`);
        },
        ontimeout: function (res) {
          const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
          console.log(`[#1343] ❌ ontimeout: 耗时=${elapsed}s`);
          console.log(`[#1343] 测试失败: 请求超时`);
        },
      });
    })();

    使用方法:

    1. 在脚本猫中安装上面的脚本
    2. 打开任意网页,等待约 60 秒
    3. 查看 F12 控制台输出

    预期结果:

    • 如果长时间请求能正常收到响应,控制台会显示 ✅ 测试通过
    • 如果 Service Worker 空闲超时导致请求丢失,控制台不会收到 onload 回调(或者收到 onerror/ontimeout)

    可以通过修改 DELAY_SECONDS 变量来测试不同的延迟时间(如 30、60、90、180 秒)。

  9. cyfung1031 commented on May 10, 2026

    @cyfung1031
    Collaborator

    测试用的服务端接口已添加:GET https://test-case.ggnb.top/delay?seconds=60,会等待指定秒数后返回 JSON 响应,用于模拟长时间请求。

    也可以用 httpbun

    https://httpbun.com/

    Image
  10. cyfung1031 commented on May 30, 2026

    @cyfung1031
    Collaborator

    @CodFrm 你能重视吗?如果不能就关这个 Issue.

    能重視

    能重视的话,应视作为 browser bug,而不是搞 ping.

  11. added
    externalsomething related to browser bug
    and removed on May 30, 2026
  12. cyfung1031 commented on May 30, 2026

    @cyfung1031
    Collaborator

    测试脚本

    https://www.chaoxing.com/

    // ==UserScript==
    // @name         测试长时间XHR请求 (#1343)
    // @namespace    test-long-xhr-1343
    // @version      1.0.0
    // @description  验证超过 Service Worker 空闲超时(~30s)的长请求是否能正常收到响应
    // @match        *://*/*
    // @grant        GM_xmlhttpRequest
    // @connect      test-case.ggnb.top
    // @connect      httpbun.com
    // ==/UserScript==
    
    (function () {
      "use strict";
    
      const DELAY_SECONDS = 35;
      const TIMEOUT_SECONDS = 40;
      const UI_INTERVAL_MS = 250;
      // const url = `https://test-case.ggnb.top/delay?seconds=${DELAY_SECONDS}`;
      const url = `https://httpbun.com/delay/${DELAY_SECONDS}`;
    
      const COLOR_WAITING = "#2563eb";
      const COLOR_DUE = "#f59e0b";
      const COLOR_TIMEOUT = "#dc2626";
    
      // -------- Utility Functions -----------
    
      // Create an element, apply styles (string or array), set text/html, and append to a parent
      function createEl(tag, styles, props = {}, parent = null) {
        const el = document.createElement(tag);
        if (styles) el.style.cssText = Array.isArray(styles) ? styles.join(";") : styles;
        Object.assign(el, props);
        if (parent) parent.append(el);
        return el;
      }
    
      function makeCard(label, value, parent) {
        const card = createEl("div", "background:rgba(0,0,0,0.25);border:1px solid rgba(255,255,255,0.12);border-radius:8px;padding:8px;min-width:0", {}, parent);
        createEl("div", "font-size:11px;color:rgba(255,255,255,0.65);margin-bottom:2px", { textContent: label }, card);
        const valueEl = createEl("div", "font-size:16px;font-weight:800;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap", { textContent: value }, card);
        return { card, valueEl };
      }
    
      // -------- Setup Visual Logger DOM -----------
    
      const box = createEl("div", [
        "position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:2147483647",
        "width:min(760px,calc(100vw - 32px));max-height:78vh;overflow:auto;padding:16px;box-sizing:border-box",
        "background:rgba(0,0,0,0.9);color:#fff;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace",
        "font-size:13px;line-height:1.5;border:1px solid rgba(255,255,255,0.25);border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,0.45);white-space:pre-wrap;word-break:break-word"
      ]);
    
      createEl("div", "font-weight:700;font-size:15px;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.2)", { textContent: "Long XHR Request Test #1343" }, box);
      const ui = createEl("div", "background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.14);border-radius:10px;padding:12px;margin-bottom:12px", {}, box);
      
      const statusRow = createEl("div", "display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap", {}, ui);
      const elapsedBig = createEl("div", "font-size:24px;font-weight:800;color:#fff", { textContent: "0.0s elapsed" }, statusRow);
    
      const progressOuter = createEl("div", "position:relative;height:22px;background:rgba(255,255,255,0.14);border-radius:999px;overflow:hidden;border:1px solid rgba(255,255,255,0.18);margin-bottom:8px", {}, ui);
      const progressInner = createEl("div", `height:100%;width:0%;background:${COLOR_WAITING};transition:width 0.2s linear, background-color 0.2s linear`, {}, progressOuter);
      createEl("div", `position:absolute;top:0;bottom:0;left:${Math.min(100, (DELAY_SECONDS / TIMEOUT_SECONDS) * 100)}%;width:3px;background:#fff;box-shadow:0 0 8px rgba(255,255,255,0.9)`, { title: `Expected response at ${DELAY_SECONDS}s` }, progressOuter);
    
      const scaleRow = createEl("div", "display:flex;justify-content:center;gap:8px;font-size:12px;color:rgba(255,255,255,0.75);margin-bottom:12px", {}, ui);
      [`Start: 0s`, `/`, `Expected: ${DELAY_SECONDS}s`, `/`, `Timeout: ${TIMEOUT_SECONDS}s`].forEach(text => {
        createEl("span", null, { textContent: text }, scaleRow);
      });
    
      const infoGrid = createEl("div", "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-bottom:10px", {}, ui);
      const elapsedCard = makeCard("Elapsed", "0.0s", infoGrid);
      makeCard("Expected", `${DELAY_SECONDS}s`, infoGrid);
      makeCard("Timeout", `${TIMEOUT_SECONDS}s`, infoGrid);
    
      const hint = createEl("div", "font-size:12px;color:rgba(255,255,255,0.78)", { textContent: `Awaiting response (expected ~${DELAY_SECONDS}s). Request times out at ${TIMEOUT_SECONDS}s.` }, ui);
    
      createEl("div", "font-weight:700;font-size:13px;margin:10px 0 6px;color:rgba(255,255,255,0.9)", { textContent: "Logs" }, box);
      const logArea = createEl("div", "max-height:260px;overflow:auto;padding-top:6px;border-top:1px solid rgba(255,255,255,0.12)", {}, box);
    
      const log = (...args) => {
        const content = args.map(arg => {
          if (typeof arg === "string") return arg;
          try { return JSON.stringify(arg, null, 2); } catch (_) { return String(arg); }
        }).join(" ");
        createEl("div", "margin:2px 0;", { textContent: content }, logArea);
        logArea.scrollTop = logArea.scrollHeight;
        box.scrollTop = box.scrollHeight;
      };
      const prefixLog = (msg, ...args) => log(`[#1343] ${msg}`, ...args);
    
      const mount = () => (document.body || document.documentElement).appendChild(box);
      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", mount, { once: true });
      } else {
        mount();
      }
    
      // -------- UI State Helpers -----------
    
      const t0 = Date.now();
      let uiTimer, finished = false;
    
      const getElapsedSeconds = () => (Date.now() - t0) / 1000;
    
      const STATUS_MAP = {
        running: `Awaiting response (expected ~${DELAY_SECONDS}s). Request times out at ${TIMEOUT_SECONDS}s.`,
        success: "Success — response received before the timeout.",
        fail: "Completed, but the response didn't match the expected result.",
        timeout: "Timed out — no valid response within the time limit.",
        error: "Failed — a network or script error occurred."
      };
    
      function setStatus(type) {
        if (STATUS_MAP[type]) hint.textContent = STATUS_MAP[type];
      }
    
      function updateVisualUI() {
        const elapsed = getElapsedSeconds();
        const elapsedFixed = elapsed.toFixed(1);
        const pct = Math.min(100, (elapsed / TIMEOUT_SECONDS) * 100);
    
        elapsedBig.textContent = `${elapsedFixed}s elapsed`;
        elapsedCard.valueEl.textContent = `${elapsedFixed}s`;
        progressInner.style.width = `${pct}%`;
        progressInner.style.background = elapsed >= TIMEOUT_SECONDS ? COLOR_TIMEOUT : elapsed >= DELAY_SECONDS ? COLOR_DUE : COLOR_WAITING;
    
        if (!finished) {
          if (elapsed >= TIMEOUT_SECONDS) {
            setStatus("timeout");
          } else if (elapsed >= DELAY_SECONDS) {
            setStatus("running");
            hint.textContent = `Past the expected response time. Waiting until timeout at ${TIMEOUT_SECONDS}s.`;
          } else {
            hint.textContent = `Response - expected in ~${Math.max(0, DELAY_SECONDS - elapsed).toFixed(1)}s | Timeout in ~${Math.max(0, TIMEOUT_SECONDS - elapsed).toFixed(1)}s`;
          }
        }
      }
    
      uiTimer = setInterval(updateVisualUI, UI_INTERVAL_MS);
      updateVisualUI();
    
      // -------- Execution & Network Request -----------
    
      try {
        prefixLog(`测试开始 - 请求地址: ${url}`);
        prefixLog(`预期响应时间: 约 ${DELAY_SECONDS} 秒`);
        prefixLog(`超时阈值: ${TIMEOUT_SECONDS} 秒`);
    
        const finish = (statusType) => {
          finished = true;
          if (uiTimer) clearInterval(uiTimer);
          updateVisualUI();
          setStatus(statusType);
          progressInner.style.width = `${Math.min(100, (getElapsedSeconds() / TIMEOUT_SECONDS) * 100)}%`;
        };
    
        const handleXhr = (type, res) => {
          try {
            const elapsed = getElapsedSeconds().toFixed(1);
    
            if (type === "load") {
              prefixLog(`收到响应 (onload) - 状态码: ${res.status} / 耗时: ${elapsed}s`);
              const r = JSON.stringify(res.response);
              prefixLog(`响应内容:`, r);
    
              if (res.status === 200) {
                finish("success");
                prefixLog(`✅ 测试通过 - 长请求 (${elapsed}s) 成功返回,未受 Service Worker 空闲超时影响`);
              } else {
                finish("fail");
                prefixLog(`❌ 测试失败 - 状态码: ${res.status} / 响应: ${r}`);
              }
            } else if (type === "error") {
              finish("error");
              prefixLog(`❌ 请求出错 (onerror) - 耗时: ${elapsed}s / 状态码: ${res && res.status}`);
              prefixLog(`测试失败 - 请求过程中发生网络或脚本错误`);
            } else {
              finish("timeout");
              prefixLog(`❌ 请求超时 (ontimeout) - 耗时: ${elapsed}s`);
              prefixLog(`测试失败 - 在 ${TIMEOUT_SECONDS}s 内未收到有效响应`);
            }
          } catch (e) {
            finish("error");
            prefixLog(`❌ 处理响应时发生异常:`, { error: JSON.stringify(e?.message || e || "") });
          }
        };
    
        GM_xmlhttpRequest({
          method: "GET",
          url,
          responseType: "json",
          timeout: TIMEOUT_SECONDS * 1000,
          onload: (res) => handleXhr("load", res),
          onerror: (res) => handleXhr("error", res),
          ontimeout: () => handleXhr("timeout")
        });
      } catch (e) {
        finished = true;
        if (uiTimer) clearInterval(uiTimer);
        setStatus("error");
        prefixLog(`❌ 脚本执行异常:`, { error: JSON.stringify(e?.message || e || "") });
      }
    })();
  13. CodFrm commented on May 30, 2026

    @CodFrm
    Member

    @CodFrm 你能重视吗?如果不能就关这个 Issue.

    能重視

    能重视的话,应视作为 browser bug,而不是搞 ping.

    没理解你意思 这不是 重现了么,ping能解决这个问题么?

  14. cyfung1031 commented on May 30, 2026

    @cyfung1031
    Collaborator

    浏览器问题

    暂时解决方法:使用 GM_wakeup

    1. @grant 加入 GM_setValue
    2. 加入 // @require https://scriptcat.org/lib/6455/1.0.2/GM_wakeup.js?sha384-rllZRmN/BkHxStnHOajbDjYcCDvWMZtr1oUG0LxAJ9CxjzWGRCIK9ZNOXNJ+Fg31
    3. xhr 开始前加入 GM_wakeup.start()
    4. xhr 结束时加入 GM_wakeup.stop()

    测试 & 结果

    • 注: 如果浏览器/网页不在前台,浏览器内部也会节能而导致无回应。
    • https://test-case.ggnb.top/delay?seconds= 设 180s 的话会返回 504 Gateway Timeout
    // ==UserScript==
    // @name         测试长时间XHR请求 (#1343)
    // @namespace    test-long-xhr-1343
    // @version      1.0.0-wakeup
    // @description  验证超过 Service Worker 空闲超时(~30s)的长请求是否能正常收到响应
    // @match        *://*/*
    // @grant        GM_xmlhttpRequest
    // @grant        GM_setValue
    // @connect      test-case.ggnb.top
    // @connect      httpbun.com
    // @require      https://scriptcat.org/lib/6455/1.0.2/GM_wakeup.js?sha384-rllZRmN/BkHxStnHOajbDjYcCDvWMZtr1oUG0LxAJ9CxjzWGRCIK9ZNOXNJ+Fg31
    // ==/UserScript==
    
    (function () {
      "use strict";
    
      const DELAY_SECONDS = 180;
      const TIMEOUT_SECONDS = 200;
      const UI_INTERVAL_MS = 250;
      // const url = `https://test-case.ggnb.top/delay?seconds=${DELAY_SECONDS}`;
      const url = `https://httpbun.com/delay/${DELAY_SECONDS}`;
    
      const COLOR_WAITING = "#2563eb";
      const COLOR_DUE = "#f59e0b";
      const COLOR_TIMEOUT = "#dc2626";
    
      // -------- Utility Functions -----------
    
      // Create an element, apply styles (string or array), set text/html, and append to a parent
      function createEl(tag, styles, props = {}, parent = null) {
        const el = document.createElement(tag);
        if (styles) el.style.cssText = Array.isArray(styles) ? styles.join(";") : styles;
        Object.assign(el, props);
        if (parent) parent.append(el);
        return el;
      }
    
      function makeCard(label, value, parent) {
        const card = createEl("div", "background:rgba(0,0,0,0.25);border:1px solid rgba(255,255,255,0.12);border-radius:8px;padding:8px;min-width:0", {}, parent);
        createEl("div", "font-size:11px;color:rgba(255,255,255,0.65);margin-bottom:2px", { textContent: label }, card);
        const valueEl = createEl("div", "font-size:16px;font-weight:800;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap", { textContent: value }, card);
        return { card, valueEl };
      }
    
      // -------- Setup Visual Logger DOM -----------
    
      const box = createEl("div", [
        "position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:2147483647",
        "width:min(760px,calc(100vw - 32px));max-height:78vh;overflow:auto;padding:16px;box-sizing:border-box",
        "background:rgba(0,0,0,0.9);color:#fff;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace",
        "font-size:13px;line-height:1.5;border:1px solid rgba(255,255,255,0.25);border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,0.45);white-space:pre-wrap;word-break:break-word"
      ]);
    
      createEl("div", "font-weight:700;font-size:15px;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,0.2)", { textContent: "Long XHR Request Test #1343" }, box);
      const ui = createEl("div", "background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.14);border-radius:10px;padding:12px;margin-bottom:12px", {}, box);
      
      const statusRow = createEl("div", "display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap", {}, ui);
      const elapsedBig = createEl("div", "font-size:24px;font-weight:800;color:#fff", { textContent: "0.0s elapsed" }, statusRow);
    
      const progressOuter = createEl("div", "position:relative;height:22px;background:rgba(255,255,255,0.14);border-radius:999px;overflow:hidden;border:1px solid rgba(255,255,255,0.18);margin-bottom:8px", {}, ui);
      const progressInner = createEl("div", `height:100%;width:0%;background:${COLOR_WAITING};transition:width 0.2s linear, background-color 0.2s linear`, {}, progressOuter);
      createEl("div", `position:absolute;top:0;bottom:0;left:${Math.min(100, (DELAY_SECONDS / TIMEOUT_SECONDS) * 100)}%;width:3px;background:#fff;box-shadow:0 0 8px rgba(255,255,255,0.9)`, { title: `Expected response at ${DELAY_SECONDS}s` }, progressOuter);
    
      const scaleRow = createEl("div", "display:flex;justify-content:center;gap:8px;font-size:12px;color:rgba(255,255,255,0.75);margin-bottom:12px", {}, ui);
      [`Start: 0s`, `/`, `Expected: ${DELAY_SECONDS}s`, `/`, `Timeout: ${TIMEOUT_SECONDS}s`].forEach(text => {
        createEl("span", null, { textContent: text }, scaleRow);
      });
    
      const infoGrid = createEl("div", "display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-bottom:10px", {}, ui);
      const elapsedCard = makeCard("Elapsed", "0.0s", infoGrid);
      makeCard("Expected", `${DELAY_SECONDS}s`, infoGrid);
      makeCard("Timeout", `${TIMEOUT_SECONDS}s`, infoGrid);
    
      const hint = createEl("div", "font-size:12px;color:rgba(255,255,255,0.78)", { textContent: `Awaiting response (expected ~${DELAY_SECONDS}s). Request times out at ${TIMEOUT_SECONDS}s.` }, ui);
    
      createEl("div", "font-weight:700;font-size:13px;margin:10px 0 6px;color:rgba(255,255,255,0.9)", { textContent: "Logs" }, box);
      const logArea = createEl("div", "max-height:260px;overflow:auto;padding-top:6px;border-top:1px solid rgba(255,255,255,0.12)", {}, box);
    
      const log = (...args) => {
        const content = args.map(arg => {
          if (typeof arg === "string") return arg;
          try { return JSON.stringify(arg, null, 2); } catch (_) { return String(arg); }
        }).join(" ");
        createEl("div", "margin:2px 0;", { textContent: content }, logArea);
        logArea.scrollTop = logArea.scrollHeight;
        box.scrollTop = box.scrollHeight;
      };
      const prefixLog = (msg, ...args) => log(`[#1343] ${msg}`, ...args);
    
      const mount = () => (document.body || document.documentElement).appendChild(box);
      if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", mount, { once: true });
      } else {
        mount();
      }
    
      // -------- UI State Helpers -----------
    
      const t0 = Date.now();
      let uiTimer, finished = false;
    
      const getElapsedSeconds = () => (Date.now() - t0) / 1000;
    
      const STATUS_MAP = {
        running: `Awaiting response (expected ~${DELAY_SECONDS}s). Request times out at ${TIMEOUT_SECONDS}s.`,
        success: "Success — response received before the timeout.",
        fail: "Completed, but the response didn't match the expected result.",
        timeout: "Timed out — no valid response within the time limit.",
        error: "Failed — a network or script error occurred."
      };
    
      function setStatus(type) {
        if (STATUS_MAP[type]) hint.textContent = STATUS_MAP[type];
      }
    
      function updateVisualUI() {
        const elapsed = getElapsedSeconds();
        const elapsedFixed = elapsed.toFixed(1);
        const pct = Math.min(100, (elapsed / TIMEOUT_SECONDS) * 100);
    
        elapsedBig.textContent = `${elapsedFixed}s elapsed`;
        elapsedCard.valueEl.textContent = `${elapsedFixed}s`;
        progressInner.style.width = `${pct}%`;
        progressInner.style.background = elapsed >= TIMEOUT_SECONDS ? COLOR_TIMEOUT : elapsed >= DELAY_SECONDS ? COLOR_DUE : COLOR_WAITING;
    
        if (!finished) {
          if (elapsed >= TIMEOUT_SECONDS) {
            setStatus("timeout");
          } else if (elapsed >= DELAY_SECONDS) {
            setStatus("running");
            hint.textContent = `Past the expected response time. Waiting until timeout at ${TIMEOUT_SECONDS}s.`;
          } else {
            hint.textContent = `Response - expected in ~${Math.max(0, DELAY_SECONDS - elapsed).toFixed(1)}s | Timeout in ~${Math.max(0, TIMEOUT_SECONDS - elapsed).toFixed(1)}s`;
          }
        }
      }
    
      uiTimer = setInterval(updateVisualUI, UI_INTERVAL_MS);
      updateVisualUI();
    
      // -------- Execution & Network Request -----------
    
      try {
        prefixLog(`测试开始 - 请求地址: ${url}`);
        prefixLog(`预期响应时间: 约 ${DELAY_SECONDS} 秒`);
        prefixLog(`超时阈值: ${TIMEOUT_SECONDS} 秒`);
    
        const finish = (statusType) => {
          finished = true;
          if (uiTimer) clearInterval(uiTimer);
          updateVisualUI();
          setStatus(statusType);
          progressInner.style.width = `${Math.min(100, (getElapsedSeconds() / TIMEOUT_SECONDS) * 100)}%`;
        };
    
        const handleXhr = (type, res) => {
          try {
            const elapsed = getElapsedSeconds().toFixed(1);
            GM_wakeup.stop();
    
            if (type === "load") {
              prefixLog(`收到响应 (onload) - 状态码: ${res.status} / 耗时: ${elapsed}s`);
              const r = JSON.stringify(res.response);
              prefixLog(`响应内容:`, r);
    
              if (res.status === 200) {
                finish("success");
                prefixLog(`✅ 测试通过 - 长请求 (${elapsed}s) 成功返回,未受 Service Worker 空闲超时影响`);
              } else {
                finish("fail");
                prefixLog(`❌ 测试失败 - 状态码: ${res.status} / 响应: ${r}`);
              }
            } else if (type === "error") {
              finish("error");
              prefixLog(`❌ 请求出错 (onerror) - 耗时: ${elapsed}s / 状态码: ${res && res.status}`);
              prefixLog(`测试失败 - 请求过程中发生网络或脚本错误`);
            } else {
              finish("timeout");
              prefixLog(`❌ 请求超时 (ontimeout) - 耗时: ${elapsed}s`);
              prefixLog(`测试失败 - 在 ${TIMEOUT_SECONDS}s 内未收到有效响应`);
            }
          } catch (e) {
            finish("error");
            prefixLog(`❌ 处理响应时发生异常:`, { error: JSON.stringify(e?.message || e || "") });
          }
        };
    
        GM_wakeup.start();
        GM_xmlhttpRequest({
          method: "GET",
          url,
          responseType: "json",
          timeout: TIMEOUT_SECONDS * 1000,
          onload: (res) => handleXhr("load", res),
          onerror: (res) => handleXhr("error", res),
          ontimeout: () => handleXhr("timeout")
        });
      } catch (e) {
        finished = true;
        if (uiTimer) clearInterval(uiTimer);
        setStatus("error");
        prefixLog(`❌ 脚本执行异常:`, { error: JSON.stringify(e?.message || e || "") });
      }
    })();
    Image
  15. cyfung1031 commented on May 30, 2026

    @cyfung1031
    Collaborator

    @CodFrm 你能重视吗?如果不能就关这个 Issue.
    能重視
    能重视的话,应视作为 browser bug,而不是搞 ping.

    没理解你意思 这不是 重现了么,ping能解决这个问题么?

    我还以为你没重视出来。

    ping ( #1347 ) 能解决但不完全解决。浏览器本身有闲置节能设计。这不只是网络请求的问题。这应该无解了。
    跟手动加 GM_wakeup 是一样的

  16. cyfung1031 commented on Jun 9, 2026

    @cyfung1031
    Collaborator

    W3C 例会讨论结果

    Issue 1016:Service Worker / Event Page 在进行中 fetch / XHR 时的生命周期管理

    问题是长时间网路请求可能因 background service worker 被终止而中断。会议提到之前讨论过的 waitUntil 可能可以解决,而且各浏览器过去都表达过支持。开发者也分享实务上会使用 heartbeat / keepalive hack 延长生命周期。Chrome 补充说 30 秒不是硬性上限,而是 idle 判断;另有 5 分钟 unsupervised activity timeout。下一步是有人提出更明确的 waitUntil 语意提案。


    这个Bug之后应该会有所改善。
    根据我的测试,暂时在开发者层面无法解决 (60 秒还可以强制唤醒一下,180秒的话,如果浏览器一直在后台不活跃,基本上就是死定了。这个要先等 waitUntil 做好了才能提出来)

    waitUntil: w3c/webextensions#416

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    externalsomething related to browser bugwontfixThis will not be worked on

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions