Highlight
这场 Session 聚焦 Safari 16 对 Web Extensions 的改进,最大主题是 Manifest V3 的迁移支持。如果你有 Chrome/Firefox 扩展并且已经在做 MV3 迁移,Safari 的适配工作会轻松很多。
核心内容
Safari Web Extensions 的难点通常不在第一个版本,而在持续兼容。
一个扩展要同时服务 Mac、iPhone、iPad,还要跟上 Chrome、Firefox、Edge 的扩展平台变化。Manifest V3 改了后台模型,改了脚本注入 API,也收紧了资源暴露和内容安全策略。浏览器之间如果差异太大,维护成本会很快上升。
这场 session 的核心信息是:Safari 15.4 起支持 Manifest V3 扩展,Safari 16 又补齐了一组 Web Extensions API,并提供跨设备同步体验。已有 Manifest V2 扩展仍然受支持;准备迁移的扩展可以逐步采用 service worker、scripting、declarativeNetRequest、externally_connectable 和 unlimitedStorage。
详细内容
Manifest V3:后台页面变成事件驱动
(01:02)Manifest V3 是 Web Extensions 平台的下一版。Safari 15.4 及以后可以运行 Manifest V3 扩展。Apple 同时说明,Safari 会继续支持 Manifest V2 扩展。
Manifest V3 的关键变化之一,是扩展可以用 service worker 替代 background page。service worker 是事件驱动页面,使用 addEventListener 注册监听器。Safari 也允许继续使用 background page,但必须是 non-persistent。
迁移时,最小的第一步是改 manifest 结构。
{
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"action": {
"default_icon": {
"16": "Images/icon16.png"
},
"default_title": "defaultTitle"
}
}
关键点:
manifest_version改成3,扩展进入 Manifest V3 规则。background.service_worker指向后台脚本,让后台逻辑按事件启动。action合并了 Manifest V2 里的browser_action和page_action。default_icon和default_title继续描述工具栏按钮的默认展示。
scripting:脚本注入不再写在字符串里
(02:03)Manifest V3 把执行 JavaScript 和插入样式的能力从 tabs API 移到新的 scripting API。tabs.executeScript 在 Manifest V3 中不可用,scripting 同时可用于 Manifest V2 和 V3。
旧写法只能把代码塞进字符串。
// Manifest version 2
browser.tabs.executeScript(1, {
frameId: 1,
code: "document.body.style.background = 'blue';"
});
关键点:
browser.tabs.executeScript是旧 API。- 第一个参数指定 tab。
frameId只能指定一个 frame。code传入字符串,复杂逻辑很难维护。
新写法可以传函数、参数和目标 frame。
// Manifest version 3
function changeBackgroundColor(color) {
document.body.style.background = color;
};
browser.scripting.executeScript({
target: { tabId: 1, frameIds: [ 1 ] },
func: changeBackgroundColor,
args: [ "blue" ]
});
关键点:
changeBackgroundColor是普通函数,不再写成字符串。target.tabId是必填信息;没有 tab ID 会返回错误。target.frameIds可以指定多个 frame。func指向要注入的函数。args把参数传给注入函数。
脚本文件也可以一次注入多个。
// Manifest version 3
browser.scripting.executeScript({
target: { tabId: 1 },
files: [ "file.js", "file2.js" ]
});
关键点:
target.tabId指定脚本运行的页面。files接受数组,适合把复杂逻辑拆成多个文件。- 这替代了
tabs.executeScript一次只能指定一个文件的旧限制。
样式也走同一组 API。
// Add styling
browser.scripting.insertCSS({
target: { tabId: 1, frameIds: [ 1, 2, 3 ] },
files: [ "file.css", "file2.css" ]
});
// Remove styling
browser.scripting.removeCSS({
target: { tabId: 1, frameIds: [ 1, 2, 3 ] },
files: [ "file.css", "file2.css" ]
});
关键点:
insertCSS注入样式文件。removeCSS移除已注入的样式文件。frameIds允许一次处理多个 frame。files让样式拆分保持清晰。
Session 里的 Sea Creator 示例展示了真实迁移步骤:把 manifest 改到版本 3,把 browser_action 改为 action,再把 browser.tabs.executeScript 改为 browser.scripting.executeScript。第一次运行失败时,控制台报 browser.tabs.executeScript is undefined,原因正是旧 API 在 Manifest V3 中不可用。
web_accessible_resources:资源只开放给指定站点
(04:43)Manifest V2 的 web_accessible_resources 只声明文件数组。问题是,只要扩展有访问权限,网页就可能访问这些资源。
Manifest V3 改成资源和匹配规则绑定。
// Manifest version 3
"web_accessible_resources": [
{
"resources": [ "pie.png" ],
"matches": [ "*://*.apple.com/*" ]
},
{
"resources": [ "cookie.png" ],
"matches": [ "*://*.webkit.org/*" ]
}
]
关键点:
resources写明要开放的扩展资源。matches写明哪些网页 URL 可以访问这些资源。pie.png只开放给apple.comURL。cookie.png只开放给webkit.org页面。
内容安全策略也改成对象形式。
// Manifest version 3
"content_security_policy" : { "extension_pages" : "script-src 'unsafe-eval' 'self'" }
关键点:
- Manifest V3 用对象声明
content_security_policy。 extension_pages是扩展页面的策略键。- 远程脚本源在 Manifest V3 中不再允许。
declarativeNetRequest:把请求修改交给 Safari
(10:03)declarativeNetRequest 是内容拦截 API。扩展只声明规则集,拦截和修改网络请求的工作由 Safari 执行。
规则集可以写在 manifest 里。
// manifest.json
"permissions": [ "declarativeNetRequest" ],
"declarative_net_request": {
"rule_resources": [
{
"id": "my_ruleset",
"enabled": true,
"path": "rules.json"
}
]
}
关键点:
permissions里声明declarativeNetRequest权限。declarative_net_request.rule_resources列出规则集。id是规则集标识。enabled控制规则集是否启用。path指向实际规则文件。
Safari 现在允许 manifest 中声明最多 50 个规则集;同一时间最多启用 10 个。对于有多个过滤模式的扩展,这让规则组织更灵活。
(11:13)今年还新增了两个动态更新 API。
// Rules that won't persist
browser.declarativeNetRequest.updateSessionRules({ addRules: [ rule ] });
// Rules that will persist
browser.declarativeNetRequest.updateDynamicRules({ addRules: [ rule ] });
关键点:
updateSessionRules添加或移除当前会话规则。- session rules 不会跨浏览器会话或扩展更新保留。
updateDynamicRules更新会保留的规则。- dynamic rules 可以更新拦截规则,而不用发布整个扩展更新。
演示场景很具体:Sea Creator 先用规则阻止所有 URL 的图片,再用 updateSessionRules 允许 webkit.org/blog-files 页面加载图片。结果是 WebKit 博客图片恢复显示,Wikipedia 页面图片仍然被拦截。
externally_connectable:网页可以和扩展通信
(14:17)externally_connectable 允许网站在用户启用扩展时创建自定义行为。使用前,需要在 manifest 中声明 match patterns,决定哪些页面可以和扩展通信。
Apple 特别说明了两个条件:这个能力只使用 browser namespace;用户必须先授予扩展访问页面的权限,扩展才能收发消息。
网页侧发送消息。
// In the webpage
let extensionID = "com.apple.Sea-Creator.Extension (GJT7Q2TVD9)";
browser.runtime.sendMessage(extensionID, { greeting: "Hello!" },
function(response) {
console.log("Received response from the background page:");
console.log(response.farewell);
});
关键点:
extensionID使用扩展 bundle identifier 和 team identifier 组合。browser.runtime.sendMessage向扩展发送消息。- 第二个参数是消息体。
- 回调函数处理扩展返回的响应。
扩展后台页面监听外部消息。
// In the background page
browser.runtime.onMessageExternal.addListener(function(message, sender, sendResponse) {
console.log("Received message from the sender:");
console.log(message.greeting);
sendResponse({ farewell: "Goodbye!" });
});
关键点:
onMessageExternal接收网页发来的外部消息。message是网页传入的数据。sender描述消息来源。sendResponse把响应发回网页。
跨浏览器发布时,同一个扩展可能有多个商店标识。网页要先确认用户安装的是 Safari Web Extension。
// Determining the correct identifier
function determineExtensionID(extensionID) {
return new Promise((resolve) => {
try {
browser.runtime.sendMessage(extensionID, { action: 'determineID' }, function(response) {
if (response)
resolve({ extensionID: extensionID, isInstalled: true, response: response });
else
resolve({ extensionID: extensionID, isInstalled: false });
});
}
});
};
关键点:
determineExtensionID对单个候选 ID 发探测消息。browser.runtime.sendMessage发送determineID动作。- 有响应时返回
isInstalled: true。 - 没有响应时返回
isInstalled: false。 - Session 建议有多个 ID 时配合
Promise.all广播探测,再从结果中找出已安装扩展。
扩展侧需要回应探测消息。
// background.js
browser.runtime.onMessageExternal.addListener(function(message, sender, sendResponse) {
if (message.action == "determineID") {
sendResponse({ "Installed" });
}
});
关键点:
- 后台脚本监听外部消息。
- 只处理
action为determineID的消息。 sendResponse告诉网页扩展已安装。
unlimitedStorage:存储配额不再是 10 MB
(17:28)Safari 的 unlimitedStorage 现在是真正的 unlimited。扩展不再受 10 MB quota 限制。
启用方式很短。
// manifest.json
"permissions": [ "storage", "unlimitedStorage" ]
关键点:
storage开启扩展存储 API。unlimitedStorage移除 10 MB 配额限制。- 用户仍然可以随时清除扩展使用的数据。
- Apple 建议只存必要数据,避免用户主动清理。
Safari 16:扩展跨设备同步
(18:19)Safari 16 改进了扩展跨设备体验。用户在一台设备上打开扩展后,其他设备的 Extension Settings 会提示下载;下载后会自动启用。
要让这个体验生效,Apple 建议提交 App Store 时同时列出 iOS、iPadOS 和 macOS。同步有两种设置方式。
第一种是 universal purchase。用户只购买一次,跨平台使用同一扩展。设置时,需要让扩展使用单个 bundle identifier,使它关联到 App Store Connect 中同一个 app record。
第二种是手动链接 app。在 Xcode 的 Info.plist 中,为 iOS app、iOS extension、macOS app、macOS extension 添加彼此对应的 bundle identifier。Session 的演示就是在每个 target 的设置里填入对应平台的 app 和 extension 标识。
核心启发
1. 做一个 Manifest V3 迁移检查器
- 做什么:扫描扩展源码,列出
tabs.executeScript、browser_action、Manifest V2content_security_policy等迁移点。 - 为什么值得做:Session 中 Sea Creator 的失败来自
browser.tabs.executeScript is undefined,这类问题可以在构建前发现。 - 怎么开始:从
manifest.json解析manifest_version、browser_action、page_action、content_security_policy,再扫描脚本里的browser.tabs.executeScript,给出对应的scripting.executeScript修改建议。
2. 给内容拦截扩展增加站点级开关
- 做什么:让用户在 popup 中临时允许某个站点加载图片、脚本或请求。
- 为什么值得做:
updateSessionRules可以添加不持久化规则,适合“本次会话放行这个网站”。 - 怎么开始:在 manifest 中配置
declarativeNetRequest,默认规则放在rules.json,用户点击放行时调用browser.declarativeNetRequest.updateSessionRules({ addRules: [rule] })。
3. 为网站提供“检测扩展是否安装”的入口
- 做什么:网站在用户访问时检测 Safari Web Extension 是否存在,存在时显示扩展专属功能。
- 为什么值得做:
externally_connectable允许网页和扩展通信,适合网站与扩展配合完成登录、保存、标注、内容增强等流程。 - 怎么开始:网页侧用
browser.runtime.sendMessage(extensionID, { action: 'determineID' })探测,扩展后台用browser.runtime.onMessageExternal.addListener回复。
4. 给跨设备扩展做发布前清单
- 做什么:检查 iOS、iPadOS、macOS 的 app 和 extension 是否具备跨设备下载与启用所需配置。
- 为什么值得做:Safari 16 会把用户已启用的扩展带到其他设备,但前提是 App Store 和 bundle identifier 配置正确。
- 怎么开始:优先采用 universal purchase;如果不能采用,就在 Xcode 的 Info.plist 中为 app 和 extension 填入对应平台的 bundle identifiers。
5. 把大数据扩展改成“可清理”的存储模型
- 做什么:为密码管理、网页剪藏、阅读标注等扩展设计存储占用面板和清理按钮。
- 为什么值得做:
unlimitedStorage取消 10 MB quota,但用户仍然可以清除扩展数据。 - 怎么开始:manifest 声明
"permissions": [ "storage", "unlimitedStorage" ],存储界面只保留必要数据,并显示缓存、索引和用户内容的占用。
关联 Session
- Create Safari Web Inspector Extensions — 同样基于 Web Extensions 标准,把能力扩展到 Safari Web Inspector 的自定义工具。
- Meet Web Push for Safari — 讲 Safari 对 Push API、Notifications API 和 service workers 的标准化支持。
- Explore Safari Web Extension improvements — 介绍 Safari 15 的 Web Extensions 改进,是理解本场 Manifest V3 迁移的前置内容。
- Meet Safari Web Extensions — 介绍 Safari Web Extensions 的基础模型和从其他浏览器迁移的起点。
评论
GitHub Issues · utterances