如何在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 }); }); } } }); });
Popup Script
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; } } });
问题根源
- 程序化标签页:新标签页程序化打开后,焦点切换可能导致
lastFocusedWindow查询不准确,且tabs.onUpdated的事件未正确关联到当前打开扩展的标签页,导致状态更新时序混乱。 - 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
相关产品推荐
相关产品推荐

