Safari Web Extensions 新变化
What's new in Safari Web Extensions
2022年6月6日
一句话判断
Safari 16 全面拥抱 Manifest V3——background pages 变成 service workers,tabs.executeScript 被 scripting API 替代,declarativeNetRequest 规则上限提升到 50 个规则集,迁移工作量不小但方向正确。
这场 Session 讲了什么
这场 Session 聚焦 Safari 16 对 Web Extensions 的改进,最大主题是 Manifest V3 的迁移支持。如果你有 Chrome/Firefox 扩展并且已经在做 MV3 迁移,Safari 的适配工作会轻松很多。
Manifest V3 迁移。 Safari 16 正式支持 Manifest V3 扩展。最显著的变化是 background pages 被 service workers 替代。之前你的扩展可以在后台持久运行一个 HTML 页面来维护状态,现在需要改成事件驱动的 service worker 模式。这意味着你不能再用全局变量在事件之间共享状态了——需要用 chrome.storage 或者 IndexedDB。
Scripting API。 chrome.tabs.executeScript() 被 chrome.scripting.executeScript() 替代。新 API 更灵活,支持指定 func(函数引用)或 files(脚本文件),还可以用 args 传参。旧 API 的回调和结果返回方式也被重新设计。
web_accessible_resources 改变。 MV3 对 web_accessible_resources 做了粒度更细的控制。你不再只是声明一个文件列表,而是需要为不同的 URL pattern 指定哪些资源可以被访问。这是一个安全改进——防止你的扩展资源被任意网站引用。
Declarative Net Request 更新。 Safari 16 支持最多 50 个静态规则集(之前限制更多),每个规则集最多 5 万条规则。动态规则的上限是 5000 条。对于广告拦截器这种依赖大量过滤规则的扩展来说,这个提升非常关键。Session 里还特别提到了 getMatchedRules API,可以用来调试规则是否正确匹配。
externally_connectable。 这是一个新支持的 API,允许你的扩展和特定网站进行消息通信。之前这种跨上下文通信只能通过 window.postMessage 这种不太优雅的方式实现。现在你可以在 manifest 里声明允许哪些域名和你通信。
unlimitedStorage。 chrome.storage.unlimited 在 Safari 16 上也可以用了。对于需要存储大量数据的扩展(比如密码管理器),不再受 5MB 的 chrome.storage.local 限制。
跨设备同步。 Safari 16 的扩展现在可以通过 iCloud 在用户的 iPhone、iPad 和 Mac 之间同步。用户在 Mac 上安装的扩展会自动出现在 iOS 的 Safari 里(前提是扩展适配了 iOS)。
值得深挖的点
Service Worker 的生命周期陷阱。 扩展的 service worker 和网站的 service worker 有一个重要区别:扩展的 service worker 在没有事件时会休眠,但收到事件时会自动唤醒。你不能依赖 oninstall 时的状态——因为两次事件之间 service worker 可能已经被完全回收了。所有持久化数据必须存到 chrome.storage,全局变量是不可靠的。
MV2 到 MV3 迁移的真实工作量。 根据已有扩展的迁移经验,background page -> service worker 的改动是最大的工作量。如果你的 background page 里有定时器(setInterval)、WebSocket 连接或长期运行的 Promise,这些都需要重新设计。MV3 的 service worker 模式下,你只能用 chrome.alarms API 来实现定时任务,而且最小间隔是 1 分钟。
代码片段
使用新的 scripting API:
// MV2 旧写法
chrome.tabs.executeScript(tabId, { file: 'content.js' }, (results) => {
console.log(results[0]);
});
// MV3 新写法
const results = await chrome.scripting.executeScript({
target: { tabId: tabId },
files: ['content.js']
});
console.log(results[0].result);
// 直接传函数
const results2 = await chrome.scripting.executeScript({
target: { tabId: tabId },
func: (title) => {
document.title = title;
return document.title;
},
args: ['New Title']
});
Service Worker 中的事件处理:
// background.js (service worker)
// 不要使用全局变量!
// let counter = 0; // 这在 service worker 休眠后会被重置
// 使用 chrome.storage 持久化状态
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'increment') {
chrome.storage.local.get(['counter'], (result) => {
const counter = (result.counter || 0) + 1;
chrome.storage.local.set({ counter });
sendResponse({ counter });
});
return true; // 保持消息通道打开,等待异步 sendResponse
}
});
// 定时任务用 alarms
chrome.alarms.create('cleanup', { periodInMinutes: 30 });
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'cleanup') {
// 清理过期数据
}
});
最佳实践
迁移扩展到 MV3 时,先在 Chrome 上完成迁移和测试,再用 Xcode 的 Safari Extension Builder 转换到 Safari。Safari 的 MV3 实现和 Chrome 高度一致,大部分代码可以直接复用。
service worker 里避免使用 DOM API。document、window、XMLHttpRequest 在 service worker 里都不可用。用 fetch 替代 XMLHttpRequest,用 chrome.offscreen API(Chrome)或 Web Accessible Resources(Safari)来处理需要 DOM 的操作。
declarativeNetRequest 的规则要用 chrome.declarativeNetRequest.getMatchedRules() 来调试。开发阶段开启 rule_resources 里的所有规则集,上线后根据使用频率排序——用户最常触发的规则应该放在前面的规则集里,这样匹配效率更高。
还有什么值得关注
web_accessible_resources的新格式要求你为每个资源指定extension_ids和matches,比 MV2 的简单路径列表复杂,但安全性更好。- Safari 的扩展在 iOS 上的行为有一些限制:popup 的尺寸受屏幕限制,侧边栏在 iPhone 上不可用。
chrome.action.openPopup()可以用代码主动打开 popup,这在 MV2 里做不到。- 跨设备同步功能需要用户在设置里开启 iCloud 钥匙串和 Safari 扩展同步。