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

如何在Safari扩展中正确追踪含iframe及程序化标签页的页面加载状态

Safari扩展页面加载状态追踪问题修复

问题场景

  • 程序化打开的标签页:网站通过代码打开新标签页后,在新标签页加载过程中打开扩展,即使页面已完成加载,扩展仍错误显示页面处于加载状态。
  • 含Iframe的页面:页面主框架加载完成但iframe仍在加载时,后台脚本就向弹窗发送加载完成状态,导致扩展提前更新状态。

现有代码

Background Script

页面加载过程中的状态监听

browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
  if (changeInfo.status === "complete") {
    browser.runtime.sendMessage({ type: "PageLoadedEvent", loadingCompleted: true });
  }
});

弹窗打开时的状态查询

const queryOptions = { active: true, lastFocusedWindow: true };

browser.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  let reqData = JSON.parse(request);
  let reqType = reqData?.requestType;

  browser.tabs.query(queryOptions, function (tab) {
    if (reqType === "getCurrentTabStatus") {
      let tabData = tab[0];
      let tabId = tabData.id;
      if (tabData.status === "complete") {
        browser.tabs.query(queryOptions, function (tabs) {
          browser.tabs.sendMessage(tabs[0].id, {
            type: "LoadCompleted",
            loadingCompleted: true
          });
        });
      }
    }
  });
});
let loadingCompleted = false;

function getCurrentTabStatus() {
  chrome.runtime.sendMessage(
    JSON.stringify({
      requestType: "getCurrentTabStatus",
    }),
    function (response) {}
  );
}
getCurrentTabStatus();

browser.runtime.onMessage.addListener((response) => {
  if (response.type === "LoadCompleted" && response.loadingCompleted) {
    loadingCompleted = true;
  } else if (response.type === "PageLoadedEvent" && response.loadingCompleted) {
    if (!loadingCompleted) {
      loadingCompleted = true;
    }
  }
});

问题根源

  1. 程序化标签页:新标签页程序化打开后,焦点切换可能导致lastFocusedWindow查询不准确,且tabs.onUpdated的事件未正确关联到当前打开扩展的标签页,导致状态更新时序混乱。
  2. Iframe场景:tabs.onUpdated的complete状态仅代表主文档加载完成,不包含iframe的加载状态,因此会提前触发加载完成的通知。

解决方案

1. 修复程序化标签页的状态追踪

优化Background Script逻辑

移除lastFocusedWindow,直接通过sender.tab获取弹窗所属的标签页ID,避免焦点切换导致的查询错误,同时直接向弹窗返回状态,无需经过content script中转:

browser.runtime.onMessage.addListener(function (request, sender, sendResponse) {
  let reqData = JSON.parse(request);
  let reqType = reqData?.requestType;

  if (reqType === "getCurrentTabStatus" && sender.tab) {
    const tabId = sender.tab.id;
    browser.tabs.get(tabId, (tab) => {
      // 直接向弹窗返回当前标签的加载状态
      browser.runtime.sendMessage({
        type: "LoadCompleted",
        loadingCompleted: tab.status === "complete"
      });
    });
  }
});

优化Popup Script状态初始化

初始化时直接接收Background返回的状态,同时添加UI更新逻辑,确保状态实时同步:

let loadingCompleted = false;

function getCurrentTabStatus() {
  chrome.runtime.sendMessage(
    JSON.stringify({ requestType: "getCurrentTabStatus" }),
    function (response) {
      if (response?.type === "LoadCompleted") {
        loadingCompleted = response.loadingCompleted;
        updateLoadingUI(loadingCompleted);
      }
    }
  );
}

browser.runtime.onMessage.addListener((response) => {
  if (response.type === "PageLoadedEvent" && response.loadingCompleted) {
    loadingCompleted = true;
    updateLoadingUI(loadingCompleted);
  }
});

// 初始化查询
getCurrentTabStatus();

// 弹窗UI更新函数,根据状态修改显示内容
function updateLoadingUI(isCompleted) {
  const statusElement = document.getElementById('loading-status');
  if (statusElement) {
    statusElement.textContent = isCompleted ? '页面已加载完成' : '页面加载中';
  }
}

2. 处理Iframe加载状态

通过Content Script监听主文档和所有iframe的加载状态,确保整个页面加载完成后再发送状态:

步骤1:配置Content Script注入

在manifest.json中设置注入所有框架:

"content_scripts": [
  {
    "matches": ["<all_urls>"],
    "js": ["content.js"],
    "all_frames": true,
    "run_at": "document_start"
  }
]

步骤2:编写Content Script(content.js)

let pendingIframes = new Set();

// 监听主文档加载完成
window.addEventListener('load', () => {
  checkAllLoaded();
});

// 监听页面中新增的iframe
document.addEventListener('DOMContentLoaded', () => {
  const observer = new MutationObserver((mutations) => {
    mutations.forEach(mutation => {
      mutation.addedNodes.forEach(node => {
        if (node.tagName === 'IFRAME') {
          pendingIframes.add(node);
          // 监听iframe加载完成/失败事件
          node.addEventListener('load', () => {
            pendingIframes.delete(node);
            checkAllLoaded();
          });
          node.addEventListener('error', () => {
            pendingIframes.delete(node);
            checkAllLoaded();
          });
        }
      });
    });
  });
  observer.observe(document.body, { childList: true, subtree: true });
});

// 检查所有资源是否加载完成
function checkAllLoaded() {
  if (window.top === window.self && document.readyState === 'complete' && pendingIframes.size === 0) {
    browser.runtime.sendMessage({
      type: "FullPageLoaded",
      loadingCompleted: true
    });
  }
}

步骤3:修改Background Script监听事件

替换原有的tabs.onUpdated监听,改为接收Content Script发送的全页面加载完成事件:

browser.runtime.onMessage.addListener((message) => {
  if (message.type === "FullPageLoaded") {
    browser.runtime.sendMessage({
      type: "PageLoadedEvent",
      loadingCompleted: message.loadingCompleted
    });
  }
});

总结

  • 程序化标签页:通过sender.tab锁定弹窗所属标签页,直接查询并返回状态,避免焦点切换导致的查询错误。
  • Iframe场景:通过Content Script监听主文档和所有iframe的加载状态,确保整个页面资源加载完成后再触发状态更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:53:16