Explore Safari Web Extension improvements
Media & Web 进阶 24m

探索 Safari Web Extension 的改进

Explore Safari Web Extension improvements

2021年6月10日

在 Apple 官方观看视频

一句话判断

Safari Web Extensions 在 iOS 15 上终于拿到了同步能力和更好的调试工具——如果你有现成的 Chrome Extension,迁移到 Safari 的门槛又降低了不少。

这场 Session 讲了什么

Safari Web Extensions 在 macOS 上已经存在一段时间,iOS 15 把它带到了 iPhone 和 iPad 上。Session 重点介绍了 Safari 15 对 Web Extensions API 的几项关键改进。

扩展同步是最大的新特性。在 macOS 上安装的 Safari 扩展会自动同步到同一 iCloud 账号下的 iOS 设备上。这意味着用户只需要安装一次,所有设备都能用。对开发者来说,你的扩展只需要支持一次权限声明,就能覆盖全平台。

调试体验也得到了改善。Safari Web Inspector 现在可以直接连接到 iOS 设备上的扩展,检查 popup 页面、content script 和 background script 的运行状态。这在之前只能通过 Safari 的 Develop 菜单间接完成。

另外还介绍了新的 declarativeNetRequest API 支持,允许扩展在不运行 JavaScript 的情况下修改网络请求。这对广告拦截类扩展是重大利好——纯声明式的规则匹配比 JavaScript 注入高效得多。

值得深挖的点

Chrome Extension 到 Safari 的迁移成本

Apple 提供了一个命令行工具 xcrun safari-web-extension-converter,可以把 Chrome Extension 项目转换为 Xcode 项目。转换后生成的代码包含了 manifest.json 的适配层,大部分 Chrome Extension API 都有对应的 Safari 实现。但不支持的 API(比如 chrome.cookies 的某些高级用法)需要手动处理。

Session 建议:如果你的 Chrome Extension 只使用了 Manifest V3 的标准 API,迁移到 Safari 基本上是零成本。但如果依赖了 Chrome 特有的 API(如 chrome.tabCapture),需要做功能裁剪。

declarativeNetRequest 的性能优势

传统的广告拦截扩展使用 webRequest API 拦截每个请求,这需要在 JavaScript 层面处理所有网络流量,开销很大。declarativeNetRequest 把规则交给浏览器引擎处理,不需要唤醒扩展的 background script。Safari 15 支持最多 150,000 条规则(与 Chrome 的限制一致),这对大多数广告拦截规则库来说足够了。

代码片段

// manifest.json 中声明 declarativeNetRequest 规则
{
    "manifest_version": 3,
    "name": "广告拦截器",
    "version": "1.0",
    "permissions": ["declarativeNetRequest"],
    "declarative_net_request": {
        "rule_resources": [{
            "id": "ad_blocker",
            "enabled": true,
            "path": "rules.json"
        }]
    }
}
// rules.json - 声明式网络请求规则
[
    {
        "id": 1,
        "priority": 1,
        "action": {
            "type": "block"
        },
        "condition": {
            "urlFilter": "||doubleclick.net^",
            "resourceTypes": ["script", "image", "sub_frame"]
        }
    },
    {
        "id": 2,
        "priority": 1,
        "action": {
            "type": "redirect",
            "redirect": {
                "extensionPath": "/styles/clean.css"
            }
        },
        "condition": {
            "urlFilter": "||ads.example.com/*.css",
            "resourceTypes": ["stylesheet"]
        }
    }
]
// Swift 端控制扩展的启用/禁用状态
import SafariServices

class ExtensionSettingsViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // 检查扩展是否已启用
        SFSafariExtensionManager.getStateOfSafariExtension(
           (withIdentifier: "com.example.ad-blocker.extension"
        )) { state, error in
            if let state = state {
                print("扩展启用状态: \(state.isEnabled)")
            }
        }
    }
    
    func openExtensionPreferences() {
        // 直接跳转到 Safari 扩展设置页面
        SFSafariApplication.showPreferencesForExtension(
            withIdentifier: "com.example.ad-blocker.extension"
        )
    }
}

最佳实践

在开发 Safari Web Extension 时,同时测试 macOS 和 iOS 两个平台。iOS 上的扩展 popup 尺寸有限,需要做响应式布局。建议 popup 页面使用 CSS 的 max-width: 300px 来适配 iPhone 屏幕宽度。

规则文件的更新不需要重新发布扩展。你可以把 rules.json 放在一个远程服务端,配合 declarativeNetRequest.updateEnabledRulesets API 动态更新规则库。这样广告拦截规则可以随时更新而不用提交 App Store 审核。

还有什么值得关注

  • Safari 15 支持了 Web Extension 的 sidebery(侧边栏)模式,扩展可以在 Safari 侧边栏中展示 UI。
  • browser.storage.sync 在 Safari 中通过 iCloud 实现,数据上限是 100KB。
  • 如果你的扩展需要访问 nativeMessaging,需要在 Xcode 项目中配置 App Group 来建立扩展和宿主 app 之间的通信通道。
WWDC 2021