Highlight
Safari 在 macOS 上支持用 JavaScript、HTML 和 CSS 构建的 Web Extensions,并提供 Xcode 转换工具、按站点授权、Web Inspector 调试和 native messaging,让现有 Chrome、Firefox、Edge 扩展进入 Safari 与 App Store 分发流程。
核心内容
如果你已经有一个浏览器扩展,最麻烦的部分往往是迁移成本。旧的 Safari app extensions 面向熟悉 Swift 或 Objective-C 的 native app 开发者。Web 开发者手里已有 JavaScript、HTML、CSS 扩展时,不想为了进入 Safari 再重写一套 native 实现。
这场 Session 宣布 Safari 在 macOS 上支持 Safari web extensions。它采用其他主流浏览器熟悉的 extension model 和 API,同时保留 Safari 的用户隐私控制。扩展仍然随 native app 一起打包;安装这个 app 后,扩展也会安装到 Safari,分发路径则进入 App Store。
Apple 把迁移入口放进 Xcode 12。已有扩展可以用 Safari web extension converter 生成 Xcode 项目,converter 会读取 manifest,提示 Safari 当前不支持的 key,并把扩展包装进 native app。没有现成扩展时,也可以在 Xcode 里用 Safari extension app template 从头创建。
后半段解决真实上线前的几个问题。权限不再是扩展想读哪个站点就直接读,Safari 会把 activeTab、content scripts、optional permissions 和 all_urls 的选择交给用户确认。调试时,Web Inspector 可以分别检查 popover、background page 和 content scripts。需要和容器 app 交换数据时,扩展通过 native messaging 与 app extension 和 app 通信。
详细内容
用 converter 把现有扩展变成 Xcode 项目
(02:16)Safari web extensions 和其他 Safari extensions 一样,打包在 native app 里。native app 安装后,扩展也会安装到 Safari。这个 app 可通过 App Store 分发;开发和运行需要 Xcode 12 或更高版本。
(02:52)Xcode 附带的命令行 converter 只需要运行一次。它会根据扩展 manifest 创建默认 Xcode 项目,让项目能 build 并运行包含扩展的 native app。manifest 里出现当前 Safari 不支持的 key 时,converter 会提示开发者。
(04:19)演示里的 Sea Creator 扩展被 converter 处理后,工具提示 manifest 信息是否正确,也报告 notifications API 在当前 Safari 版本不支持。继续转换后,Xcode 项目可以运行一个默认 app;这个 app 显示扩展是否开启,并提供打开 Safari Preferences 的按钮。
关键点:
- converter 使用 manifest 信息创建 Xcode 项目,目标是包装现有 Web 扩展,而不是重写扩展逻辑。
- manifest 中最大的 icon 会成为 app icon,Session 建议添加 512 x 512 和 1024 x 1024 图标。
- 如果后续给扩展添加新文件,这些文件还要加入 Xcode project。
- ad-hoc signed app 的扩展默认不会出现在 Safari,测试时要先打开 Develop menu,再允许 unsigned extensions。
从 Xcode 新建 Web Extension 时先看 manifest
(07:21)没有其他浏览器扩展时,可以用 Xcode 的 Safari extension app template 创建。已有 Mac app 时,则可以在 macOS tab 下添加 Safari Extension target,并把 type 选成 web extension。演示里的场景是给一个 recipe app 添加 Safari 扩展,让用户在 Safari 中保存食谱。
(08:44)manifest 定义扩展结构。name key 可以指向可本地化字符串,再由 locales 文件夹里的 messages.json 继续定义。扩展的三个主要部分是 background scripts、content scripts 和 popover。
(09:32)background scripts 没有可见 UI,可放驱动扩展的逻辑。content scripts 会注入网页,并运行在 isolated world,避免和网页 JavaScript 冲突;matches 决定注入到哪些域。popover 由 browser action key 定义,用户点击 toolbar button 时显示。
关键点:
- manifest 是 Safari web extension 的入口文件,先定义名字、结构、脚本和权限。
- background scripts 适合放扩展内部逻辑。
- content scripts 能改网页外观和行为,但运行在隔离环境里。
- permissions key 可放 API name,例如 cookies,也可放 URL match strings。
把权限请求缩到用户真正触发的范围
(11:23)Safari 的权限模型从浏览隐私出发。用户安装扩展后,Safari 会让用户控制哪些网站数据可以被扩展访问。演示先用 activeTab:扩展只有在用户通过 toolbar icon、context menu item 或 keyboard shortcut 明确使用扩展时,才能访问当前 tab 的页面并注入脚本。
(12:52)如果把 content script 写进 manifest,让它自动注入 wikipedia.org,Safari 会在 toolbar button 上显示 warning badge。用户点击后,会看到这个扩展想访问哪个网站,并可允许一天。之后访问其他 Wikipedia 页面时,选择会被记住,content script 会直接注入。
(14:10)optional permissions 用来处理非核心能力。演示把 shiny.com 的 URL match pattern 放进 manifest,然后在用户点击 Share button 时调用 browser.permissions.request 请求访问这个 origin。用户批准后,扩展才继续执行分享相关逻辑。
(16:24)Session 给出的建议很直接:优先使用 activeTab。activeTab 不适合时,请求扩展能工作的最小范围。非核心功能用 optional permissions。只有扩展确实需要访问所有网页时,才考虑 all_urls。
关键点:
- activeTab 把访问网页的权限绑定到用户动作。
- manifest 声明的 content script 仍要经过 Safari 的站点授权体验。
- optional permissions 适合分享、集成第三方站点这类附加功能。
- all_urls 会让 Safari 提醒用户扩展拥有访问所有网站的能力。
用 Web Inspector 分别查 popover、background page 和 content scripts
(18:16)演示里的 popover 图片坏了。右键 popover inspect 后,错误显示扩展资源 URL 写死成 moz-extension scheme。Safari 里不能改成固定的 safari-web-extension scheme,因为扩展 URL 的 host 每次 Safari 启动都会变化,用来防止用户被 fingerprinted。
(18:53)正确做法是使用 browser.runtime.getURL 生成扩展资源 URL。修复后,图片能正常显示。background page 也可以通过 Develop menu 的 Web Extension Background Pages 找到对应扩展并检查。
(19:42)content scripts 的调试入口在页面本身。去到 content script 已注入的页面,右键 inspect 后,Sources tab 会列出注入页面的 extension scripts 和 style sheets。由于 content scripts 运行在 isolated world,Console 默认指向页面 JavaScript world,调试时要切到扩展的 isolated world。
(20:51)转换扩展时还有几类常见 bug。不要依赖 user agent checking,feature detection 更可靠。不要假设扩展资源 URL 的 scheme 或 host 固定。content scripts 也不能假设自己一定在 DOM content loaded 前注入,因为用户可能先打开页面,再授权扩展注入脚本。
关键点:
- popover、background page、content scripts 都有独立的检查入口。
- 扩展资源 URL 要通过
browser.runtime.getURL生成。 - content scripts 的 Console 要切到扩展 isolated world。
- 转换扩展时优先做 feature detection,并检查注入时机假设。
通过 native messaging 连接扩展和容器 app
(22:03)Native messaging 让 app 和 extension 通信。Safari 和其他浏览器不同的一点是,扩展只能和自己的 container app 通信。background page 到 app extension native code 使用其他浏览器也有的 API:browser.runtime.sendNativeMessage 或 browser.runtime.connectNative,并在 manifest 里请求 native messaging permission。
(22:54)反向消息也有对应入口。app extension native code 要发消息到 background page,可使用传入 SafariWebExtensionHandler.beginRequest 的 NSExtensionContext completion handler。app 要发消息到 extension background page,则使用 SFSafariApplication.dispatchMessage。使用前,Session 建议先通过 SFSafariExtensionManager.getStateOfSafariExtension 检查扩展是否开启。
background page -> browser.runtime.sendNativeMessage / browser.runtime.connectNative -> app extension native code
app extension native code -> SafariWebExtensionHandler.beginRequest 的 NSExtensionContext completion handler -> background page
app -> SFSafariApplication.dispatchMessage -> background page
app 与 app extension -> NSUserDefaults 或 XPC connection
(24:01)演示把文字替换脚本运行次数显示到 native app UI。流程是:manifest 增加 native messaging permission,content script 通知 background page,background page 调用 sendNativeMessage,native handler 读取消息并写入 NSUserDefaults,父 app 再读取这个值并刷新 UI。写 defaults 时要指定 suite,bundle identifier 用来把 app 和 app extension 放进同一个 app group。
关键点:
- Safari 只允许扩展和自己的 container app 通信。
- background page 发 native message 前,要在 manifest 请求 native messaging permission。
- app 发消息给 background page 前,应确认扩展已经在 Safari 中开启。
- app 与 app extension 之间共享数据时,可使用
NSUserDefaults或 XPC connection。
核心启发
-
把现有 Chrome 或 Firefox 扩展迁移成 Safari 版本:做什么:拿一个已有扩展跑 Safari web extension converter,生成 Xcode 项目并在 Safari Preferences 中启用。为什么值得做:Session 明确说明 converter 会读取 manifest、提示不支持的 key,并把扩展包装进 native app。怎么开始:先补齐 512 x 512 和 1024 x 1024 图标,再转换项目,最后检查每个 manifest warning。
-
做一个按站点授权的页面工具:做什么:让扩展默认只在用户点击 toolbar button 后访问当前 tab,对特定站点再请求更长期授权。为什么值得做:activeTab、content scripts 和 optional permissions 都由 Session 展示过,能把访问范围压到用户真正同意的页面。怎么开始:先用 activeTab 完成核心动作,再把非核心站点集成放进 optional permissions。
-
做一份 Safari 扩展调试清单:做什么:为团队固定 popover、background page、content scripts 三个检查入口。为什么值得做:Session 的图片 URL bug 说明,跨浏览器扩展常出错在资源 URL、isolated world、注入时机和 user agent 检测。怎么开始:先把资源 URL 改成
browser.runtime.getURL,再用 Web Inspector 切到 content script 的 isolated world 验证状态。 -
让扩展把结果同步到 native app UI:做什么:把扩展运行次数、网页处理结果或用户动作同步回容器 app。为什么值得做:Session 演示了 content script、background page、app extension native code、
NSUserDefaults和父 app 的完整链路。怎么开始:先请求 native messaging permission,再从 background page 发送一条最小消息,native handler 写入 app group suite。 -
为全站注入扩展设计一次权限体检:做什么:审查扩展是否真的需要 all_urls,能否改成更窄的 URL match pattern。为什么值得做:Session 明确提醒,如果扩展不需要访问所有网页,就应该更精确地限定网页访问请求。怎么开始:列出现有 content script 的目标域,优先改成具体 matches,再把额外站点放进 optional permissions。
关联 Session
- What’s new in Web Inspector — 学会用 Web Inspector 检查 popover、background page 和 content scripts 后,再看这场能系统补齐 Safari 调试工具。
- What’s new for web developers — Safari Web Extensions 运行在 Safari 和 WebKit 之上,这场提供同年 Web 平台更新背景。
- Discover WKWebView enhancements — Safari Web Extensions 依赖 native app 容器,WKWebView 这场帮助理解 App 与 Web 内容的交界。
- Meet Face ID and Touch ID for the web — 同属 Safari Web 平台能力,展示 Web Authentication API 如何把设备认证带到网站登录。
- One-tap account security upgrades — 账户认证修改扩展和 Safari 扩展一样把 Web、系统服务与用户授权体验连接起来。
评论
GitHub Issues · utterances