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

为何Safari扩展中SFSafariPage类型属性会变为nil?

Safari Extension: SFSafariPage Reference Turns Nil After Assignment in messageReceived

Problem Description

I'm running into a confusing issue while developing a Safari extension. Here's my code:

class SafariExtensionHandler: SFSafariExtensionHandler {
    var somePage: SFSafariPage?
    override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
        // Triggered when content script calls safari.extension.dispatchMessage("message")
        if messageName == "script injected successfully" {
            somePage = page
            NSLog(String(describing: somePage)) // This logs a valid SFSafariPage instance
        }
    }
    
    func performPlayerAction(_ action: PlayerAction) {
        NSLog("page %@", String(describing: somePage)) // This logs nil
        somePage?.dispatchMessageToScript(withName: action.rawValue, userInfo: nil)
    }
}

When the messageReceived method gets the "script injected successfully" message from the content script, I assign the incoming page to the somePage property, and the log confirms it's a valid instance. But when I call performPlayerAction later to use somePage, it's suddenly nil. Can anyone explain why this happens?


Answer

Let's break down the root cause and fix this step by step:

Why somePage Turns Nil

This boils down to how Safari manages extension objects and their lifecycle:

  1. Weak Reference Design of SFSafariPage
    Safari uses weak references for SFSafariPage instances. If the corresponding tab/page context gets recycled (like when the page refreshes, the tab is switched away from long enough for memory cleanup, or the page navigates to a new URL), the SFSafariPage proxy object gets destroyed, and your strong reference to it becomes nil.

  2. SFSafariExtensionHandler Instance Volatility
    The extension handler isn't a persistent singleton. Safari may create or destroy handler instances based on system resource constraints (e.g., if the extension's background process gets suspended and restarted). When this happens, any stored properties like somePage get reset.

  3. Cross-Process Communication Limits
    Content scripts and the extension's background run in separate processes. SFSafariPage acts as a cross-process proxy—if the connection between processes drops, this proxy becomes invalid and resolves to nil.

Fixes You Can Implement

Here are a few practical solutions:

  • Avoid Storing SFSafariPage Long-Term
    Instead of holding onto a page reference, fetch the active page when you need to send a message:

    func performPlayerAction(_ action: PlayerAction) {
        SFSafariApplication.getActiveWindow { window in
            window?.getActiveTab { tab in
                tab?.getActivePage { page in
                    page?.dispatchMessageToScript(withName: action.rawValue, userInfo: nil)
                }
            }
        }
    }
    
  • Track Pages Using pageID
    If you need to target a specific page, save its unique pageID instead of the page instance, then look it up when needed:

    var targetPageID: UUID?
    
    override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) {
        if messageName == "script injected successfully" {
            targetPageID = page.pageID
        }
    }
    
    func performPlayerAction(_ action: PlayerAction) {
        guard let targetID = targetPageID else { return }
        SFSafariApplication.getAllWindows { windows in
            for window in windows {
                window.getAllTabs { tabs in
                    for tab in tabs {
                        tab.getActivePage { page in
                            if page?.pageID == targetID {
                                page?.dispatchMessageToScript(withName: action.rawValue, userInfo: nil)
                            }
                        }
                    }
                }
            }
        }
    }
    
  • Let Content Scripts Initiate Communication
    Instead of relying on a stored page reference, have your background script broadcast a request, and let relevant content scripts respond to establish a fresh connection before you send the action command.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 11:52:42