如何从Safari工具栏项扩展获取内容拦截器扩展的启用状态?
首先,直接在Safari工具栏项扩展中调用SFContentBlockerManager.getStateOfContentBlocker获取内容拦截器状态总是返回false,核心原因是Safari的扩展沙箱隔离机制:工具栏项扩展和内容拦截器扩展是两个独立的沙箱进程,默认没有权限跨扩展访问其他扩展的状态信息,所以这个调用无法拿到真实状态。
支持跨扩展直接获取状态吗?
目前Safari扩展框架不支持直接跨扩展获取其他扩展的状态,沙箱安全限制阻断了这种直接访问。不过我们可以通过主应用(Containing App)作为中间层来实现状态共享,这是官方推荐的合规方案。
具体实现方案
方案1:通过App Group共享状态(最简单高效)
利用App Group让主应用、工具栏项扩展、内容拦截器扩展共享一份存储,主应用负责获取并同步内容拦截器的状态,工具栏扩展从共享存储读取状态。
步骤如下:
配置App Group:
在Xcode中,给主应用和两个扩展都开启App Groups能力,添加同一个Group ID(比如group.com.xyz.macapp),确保三者都加入同一个App Group。主应用同步状态:
主应用负责获取内容拦截器的真实状态,并写入共享的UserDefaults,同时监听状态变化实时更新:// 主应用中的代码 func setupContentBlockerStateSync() { // 初始同步状态 updateContentBlockerState() // 监听状态变化通知,实时更新 NotificationCenter.default.addObserver( self, selector: #selector(updateContentBlockerState), name: SFContentBlockerManager.stateDidChangeNotification, object: nil ) } @objc private func updateContentBlockerState() { SFContentBlockerManager.getStateOfContentBlocker(withIdentifier: "com.xyz.macapp.contentblocker") { state, error in guard let state = state else { // 处理错误 return } let sharedDefaults = UserDefaults(suiteName: "group.com.xyz.macapp") sharedDefaults?.set(state.isEnabled, forKey: "ContentBlockerEnabled") sharedDefaults?.synchronize() } }记得在主应用启动时调用
setupContentBlockerStateSync()。工具栏扩展读取状态:
修改你的onPopoverVisible方法,从共享UserDefaults读取状态:func onPopoverVisible() { let sharedDefaults = UserDefaults(suiteName: "group.com.xyz.macapp") // 默认值设为false,避免读取失败时出错 let isContentBlockerEnabled = sharedDefaults?.bool(forKey: "ContentBlockerEnabled") ?? false if isContentBlockerEnabled { // 显示工具栏项内容,隐藏警告 } else { // 显示警告提示,隐藏工具栏功能内容 } }
方案2:通过XPC通信(更灵活,适合复杂交互)
如果你的应用需要更复杂的跨进程通信(比如除了状态还要传递其他数据),可以用XPC服务:
- 主应用作为XPC服务的提供者,暴露一个获取内容拦截器状态的接口。
- 工具栏项扩展作为XPC客户端,向主应用发送请求,主应用处理后返回真实状态。
这种方式比App Group更安全,适合敏感数据,但实现起来稍复杂一些。
替代方案:内容拦截器主动同步状态
当内容拦截器被启用时,它可以主动将自身状态写入共享的App Group存储,但要注意:内容拦截器只有在启用状态下才会运行,如果用户禁用了它,它无法主动更新状态,所以这个方案必须和主应用的状态同步结合使用,作为补充。
比如在内容拦截器的SFSafariExtensionHandler的init方法中写入状态:
class ContentBlockerExtensionHandler: SFSafariExtensionHandler { override init() { super.init() let sharedDefaults = UserDefaults(suiteName: "group.com.xyz.macapp") sharedDefaults?.set(true, forKey: "ContentBlockerEnabled") sharedDefaults?.synchronize() } }
内容的提问来源于stack exchange,提问作者Kevin Adesara

