大多数浏览器和
Developer App 均支持流媒体播放。
-
Safari 网页扩展的新功能
了解如何利用 Safari 网页扩展的最新改进,为浏览网页的用户提供更出色的体验。我们将介绍如何升级至清单版本 3,为网页扩展采用最新的 API,并跨设备对扩展进行同步。
资源
- Developing a Safari Web Extension
- Learn more about bug reporting
- MDN Web Docs - Web Extensions API
- Messaging a Web Extension’s Native App
- Modernizing Safari Web Extensions
- Safari web extensions
相关视频
WWDC23
WWDC22
WWDC21
-
下载
♪ ♪
Kiara Rose: 大家好 我是 Kiara Rose 我是一名 Safari 扩展工程师 我今天非常开心能跟大家聊聊 今年 Safari Web Extensions 的新功能 在我们开始今天的演讲之前 我想花点时间感谢所有向 App Store 提交 iOS iPadOS 和 macOS 扩展的人员 展望未来 我们的目标是 继续实施新功能和 API 以便你们可以为用户提供 更好的使用体验 今天 我将重点介绍我们去年生效的 一些令人兴奋的新功能 例如用于扩展的新 Manifest 版本 更新的 API 以及跨多个设备同步扩展 我们从 Manifest 版本 3 开始讲起 Manifest 版本 3 是 web 扩展平台的 下一个迭代 它进行了性能和安全性提升 并整合了流行的扩展 API 对于那些已经更新你的扩展 到使用版本 3 的用户 则现在可以在 Safari 15.4 及更高版本中运行 对于那些还没有更新的用户 也不必担心 因为我们将继续支持 在 Safari 浏览器中 使用 Manifest 版本 2 的扩展 Manifest 版本 3 的一个关键新功能是 你的扩展可以使用 service worker 而不是后台页面 如果你是 Web 开发人员 那么可能对 service worker 很熟悉 这些是事件驱动的页面 你可以在其中使用 addEventListener API 注册监听 这些页面还与支持 Manifest 版本 3 的 其他浏览器兼容 如果你希望继续使用背景页面 我们非常欢迎你们这样做 但必须是暂时的 版本 3 的另一个改进是 在网页上执行 JavaScript 和样式 API 从标签 API 移到了新的脚本 API 这些方法的大部分功能保持不变 但脚本提供了一些新的附加功能 例如在网页上注入代码的新方法 更多关于代码应该在页面上的 哪些框架中执行的选项 以及决定代码应该 在哪个执行环境中运行的能力 让我们看一下新脚本 API 的代码 与选项卡 API 有何不同 在此代码片段中 我使用 tabs.executeScript API 将网页的背景色改为蓝色 有了这个 API 我只能通过传递“代码”属性 来注入包含在字符串中的代码 但现在 使用新的脚本 API 我可以传递包含此代码的函数对象 和任何其他函数一样 它可以包含可以传入的参数 这是一种更好的执行脚本方式 因为你们不局限于 用字符串编写代码 注意 在编写脚本时 有一个名为目标的新属性 此属性用于指定脚本应该在何处运行 为了执行脚本 必须指定要在其中执行脚本的 选项卡 ID 如果未指定选项卡 ID 此 API 将返回错误 然后 如果你想选择 注入代码的网页框架 就可以指定框架 ID 请注意 使用选项卡 API 你只能指定一个 ID 但是使用脚本 可以指定多个 ID 但假设我有更多的代码 如果我能把它包含在多个文件中 就会看起来更简洁 在 tabs.executeScript API 中 我只能指定一个文件 但在 scripting.executeScript 中 我可以指定多个文件 同样 该方法也可以用于 insertCSS 你可以在网页上注入样式 removeCSS 也适用可以从网页上 移除注入的样式 这些 API 可用于 Manifest 版本 2 和 3 但是 tabs.executeScript API 不适用于版本 3 除了新的脚本 API 对其他 API 也进行了 一些细微的改进 其中一项就是针对 web_accessible_resources
在 Manifest 版本 2 中 如果你想要包含资源 可以通过传递你想要 网页访问的文件数组来实现 但这可能会有问题 因为它会让任何网页 访问你在清单中指定的所有资源
使用版本 3 中的新格式 你可以控制任何 给定站点上的可用资源 我们看一个例子 以前 cookie 和 pie 图像 每个扩展的网站上都可以访问 但是现在 使用版本 3 我可以使 pie image 仅在 apple.com URL 上可用 而 cookie 图像 仅在 webkit.org 页面上可用 现在让我们看看 对 browser_action 和 page_action API 的改进
在 Manifest 版本 2 中 明确地指定这样操作 但由于这些 API 功能相似 因此它们在版本 3 中 被合并为仅使用一个 API 即操作
我们还更新了你为扩展声明 内容安全策略的方式 在版本 2 中 扩展的策略 是使用字符串定义的 但是 在版本 3 中 策略是 使用带有键 extension_pages 的 对象定义的 请务必注意 版本 3 不再支持远程脚本源 最后的 API 更改是已弃用的 browser.extension.getURL API 版本 3 不再支持此 API 相反 请使用 browser.runtime 中的等效 API 所以我已经谈到了 Manifest 版本 3 中推出的新功能 现在让我们逐步完成更新扩展的过程 以方便你们使用这些新功能
我将从去年的演示中 更新 Sea Creator 扩展 以使用 Manifest 版本 3 这个扩展用表情符号替换了 所有出现的单词 fish 我要做的第一件事是将版本号 从 2 更改为 3
尽管我仍然可以在版本 3 中 使用暂时性背景页面 但我将对其进行更新 以使用 service worker 这样我的扩展就可以 与 Chrome 兼容了
最后 我会将 browser_action 更改为 action
而就 Manifest 的结构而言 这些是关键的变化 我需要使这个扩展 与版本 3 中的新规范兼容 所以为了测试这个 我将构建扩展 并在 Safari 浏览器中启用
然后 导航到 webkit.org 博客页面 在该页面中 我将使用此扩展程序 将单词 fish 的每个实例 替换为鱼表情符号
但似乎出了点问题 如你所见 此页面上的所有单词 都没有被表情符号替换 我们检查一下弹出框 看看是否有任何错误消息
在控制台选项卡中 我看到有一条错误消息 指出 browser.tabs.executeScript 未定义 这是因为版本 3 中 不再支持此 API 所以我应该更新扩展 以使用新的脚本 API 在 Xcode 中 我会回到 popup.js 文件 然后将这一行改为使用脚本
我会添加目标属性 用于指定脚本应注入的位置
使用新的脚本 API 我必须指定选项卡的 ID 可以通过使用 tabs.getCurrent API 来获取包含当前选项卡信息的对象 来做到这一点
然后我可以使用该对象 来检索选项卡 ID
接下来 添加包含要运行的脚本的文件
最后 我要做的最后一个更改 是在 Manifest 中 添加脚本权限
我将继续构建扩展 并在 Safari 浏览器中使用这些更改 如你所见 此扩展现在 可以在 Safari 浏览器中使用 使用 Manifest 版本 3 中的新功能 这就是升级你的扩展是多么简单 但是 如果你对这些新变化还不满意 那么许多功能 例如脚本 和 services workers 也可以在版本 2 中使用 现在让我们看看今年更新的一些 API 从 declarative net request 开始 declarative net request 是一种内容阻止 API 它为 web 扩展提供了一种 快速且保护隐私的方式 以使用规则集阻止或修改网络请求 此 API 允许你将拦截 和修改请求的所有工作 委托给 Safari 浏览器 你所要做的就是 指定该应用的内容屏蔽规则 你可以在 Manifest 中指定规则集
我在这里添加了 declarative net request 权限 并使用 declarative_net_request 键 添加了 一个应该应用于所有页面的规则集 以前 我最多只能在 Manifest 中 声明 10 个规则集 但是现在有了对该功能的更新 你可以声明多达 50 个规则集 这意味着你的扩展 可自定义程度更高 但是请记住 一次只能启用 其中的 10 个规则集 关于如何创建规则集的更多信息 请查看去年关于 Safari Web Extensions 的演讲 我们对这个 API 进行了 更深入的介绍 让我们继续讨论 declarative net request 的一些新功能 以前 你只能在 Manifest 中 声明规则集 但现在我们实现了以下两个 API 可让你动态更新规则 第一个 API 是 updateSessionRules 它允许你为扩展添加或删除规则 但是需要注意的是 这些规则不会在 浏览器会话或扩展更新中持续存在 如果你想更新持久化的规则 请使用 updateDynamicRules API 这将允许你在不更新 整个扩展程序的情况下 更新阻止规则 让我们看看如何使用这些 API 之一 来修改我们的规则集 我将使用 sea creator 扩展 来阻止网页上的一些内容 然后 我将使用新的 API 来 解除对选定页面上内容的阻止 在扩展 Manifest 中 我要做的第一件事 是添加 declarative net request 权限
然后 我将使用 declarative net request 键 来添加一个规则集
正在应用的规则位于 rules.json 文件中 在这个文件中 我声明了一个规则 它阻止所有 URL 上的所有图像 我们构建这个扩展 看看如何在 Safari 浏览器中应用此规则
如你所见 此页面上的图像已经消失 这正是我们所期望的 这表明 Safari 浏览器已经成功地 应用了我们的内容屏蔽规则 如果我浏览维基百科关于鱼的页面 我会看到这个网站上的图片 也被阻止了 但是假设我们想要更新我们的规则 来阻止除 webkit.org 博客 页面外的所有页面上的图像 使用 declarative net request 的 更新 API 之一 我们可以做到这一点 让我们回到 Xcode 并进行一些调整 在 popup.js 文件中 我将声明一个函数来更新 我们的内容阻止规则
我将把规则设置为允许在 webkit.org/blog-files 页面上显示图像 然后 我将使用 updateSessionRules API 将此规则添加到我们的规则集 最后 我将构建扩展并在 Safari 浏览器中测试所做的更改
如你所见 此博客文章中的图片已加载 表明我们允许此站点上 显示图片的新规则已生效 如果我去维基百科网站 我们会看到这个页面上的 图像仍然被屏蔽 这表明新规则没有应用到这个页面 以上就是使用新的 eclarative net request API 来更新内容阻止规则的方法
现在 我们看看你的扩展 如何与网页进行通信 如果用户启用了你的扩展程序 这个很棒的功能允许网站 创建自定义行为 该 API 称为外部可连接 要想使用它 你需要在 Manifest 中声明匹配模式 这些匹配模式决定哪些页面 可以与你的扩展通信
需要注意的重要一点是 该功能仅在 使用浏览器名称空间时有效 最后 用户必须授予你对页面的 扩展访问权限 然后才能发送或接收消息 让我们来看看为了使用这个功能 而在 web 页面中添加的代码 首先 你需要获取 extensionID 它是此格式的扩展包标识符 和团队标识符 你可以在 developer.apple.com 上的 帐户设置的 会员选项中找到你的团队标识符 然后 你将使用发送消息 API 将消息发布到扩展程序 你可以通过传递函数来处理 从扩展接收到的响应 现在让我们看一下你的扩展程序 必须接收消息的代码 你的扩展可以通过监听 onMessageExternal 事件 接收来自网页的消息 扩展可以使用传递给 事件监听器的方法 将消息发送回网页 因为不同的浏览器有 不同的扩展 web 存储 扩展可以有许多不同的标识符 因此 你需要确定要使用 正确的 以确保正在 向 Safari 浏览器网络扩展发送消息 而不是 Chrome 或 Edge 扩展 为此 你可以使用 browser.runtime.sendMessage API 并调用 Promise.all 接下来 让我们看一些示例代码 来帮助你执行此操作 在网页上 你可以通过多个扩展名 广播多条消息 会从扩展中得到确切的响应 这会让你知道使用哪个 扩展 ID 来进行进一步的通信 在这里 我有一个名为 determineExtensionID 的函数 此函数向扩展发送消息 通过使用 browser.runtime.sendMessage API 如果你有多个 ID 并且想确定 要使用的正确 ID 则可以使用 Promise.all 进行多次调用 使用确定扩展 ID 函数 Promise.all 接受一个 Promise 数组 然后返回一个 带有所有已解析值数组的 Promise 你可以使用此数组 来查找用户已安装的扩展 在扩展程序的后台页面中 你需要收听来自网页的消息 当你收到消息时 需要发回一条消息 告诉网页你的扩展程序已安装 这就是使用新的 externally_connectable API 来允许你的扩展程序 与网页通信的方式 我们更新的下一个功能 是我个人最喜欢的 无限存储 我很高兴地宣布 unlimitedStorage 实际上是无限的 鉴于你们对这个功能的要求很高 我们很高兴地分享你的扩展程序 将不再有 10 MB 的配额 而是可以随意使用尽可能多的数据 虽然 重要的是要注意 用户可以在任何给定的时间 清除数据正在使用的扩展 因此 请确保只存储严格必要的数据 这样用户就不会倾向于 清除你的数据 只需 storage 和 unlimitedStorage 权限 在 Manifest 中声明就可以了 以上就是我们去年 为 Web 扩展更新的所有 API 最后 让我们谈谈一个新功能 它可以让你的用户 轻松地在他们的所有设备上 获取你的扩展程序 在 Safari 16 中 我们创造了 使用扩展程序更加无缝的体验 如果用户在他们的 一台设备上打开你的扩展程序 它将在他们的所有设备上打开 最重要的是 我们使 下载扩展程序的过程变得更加简单 让我们看看这是如何工作的 假设用户在他们的 Mac 上 启用了你的扩展之一 在他们任何其他设备上的 Extension Setting 中 可以选择下载你的扩展程序 下载后 在它们的设备上 会自动启用 从而改善它们的用户体验 现在 让我们深入了解 如何为 Web 扩展 和内容拦截器进行设置 首先 我们建议你提交到 App Store 时 列出适用于 iOS iPadOS 和 macOS 的扩展程序 这样 你的扩展程序 将可在所有用户的设备上使用 然后 为了让你的扩展程序 在他们的设备上同步 你需要使用以下两种方法之一 最简单和推荐的方法 就是采用通用购买 通用购买允许用户 在所有平台上享受你的扩展 只需购买一次 如果使用此方法 则一切就绪 你的用户将在 下载你的扩展程序一次后 获得我展示的所有功能 要设置通用购买 你需要在扩展程序中 使用单个捆绑标识符 以便它可以关联 App Store Connect 中的 同一个应用记录 有关如何执行此操作的更多信息 查看我们的文档 了解如何为你的扩展 设置通用购买 但如果你选择不设置通用购买 可以手动链接你的 App 为此 你将使用 Xcode 在信息列表中 为你想要同步的 App 和扩展添加捆绑标识符 要将你的 iOS app 和扩展程序与 macOS 同步 你需要使用信息列表中的特定键 你将把这个密钥 放在你的 macOS app 列表中 以及 macOS 扩展列表中的这个密钥 同样 你将遵循相同的过程 来同步 macOS app 通过将此密钥添加到 iOS app 列表 并将此密钥添加到 iOS 扩展列表 让我们看看在 Xcode 中是如何工作的 在 Xcode 中 我们需要做的第一件事 是更新每个目标的设置 以包括我们想要同步的扩展 和 App 的包标识符 我将首先在 iOS app 的信息列表中 添加相应的 macOS app 的包标识符
可以看到 我对 macOS app 做了相同的处理 添加了 iOS app 包标识符 同样 iOS 扩展也可以 添加 macOS 扩展包标识符 最后 为 macOS 扩展 添加 iOS 扩展包标识符 这就是将你的应用 和扩展连接的简单之处 这样你的用户 就可以在任何地方使用它们 总而言之 你可以让用户使用此功能 通过设置通用购买 或者在 Xcode 中为每 个iOS 和 macOS 应用和扩展添加捆绑标识符 今天 我们讨论了 Manifest 版本 3 我们更新的 API 和跨多个设备同步扩展 我希望你和我一样 对所有这些新功能感到兴奋 用于 Safari Web 扩展 敬请下载今天讲座的 示例代码项目 并使用我们提供的这些 API 接下来 我们很想知道你们的想法 在 Safari Developer Forums 上 使用反馈助手提交错误 或与我们聊天 以提供 有关我们如何更好地 为你们开发扩展的反馈 不 真的 我们想知道你们的想法 考虑一下加入 WebExtensions 社区组 来塑造 web 扩展的未来 最后 看看我们在 WWDC 上 关于创建 web 检查器扩展的演示 感谢收看本期讲座 祝你余下的 WWDC 之旅一切顺利
-
-
2:43 - Executing script on webpages
// Manifest version 2 browser.tabs.executeScript(1, { frameId: 1, code: "document.body.style.background = 'blue';" });
-
3:00 - scripting.executeScript API
// Manifest version 3 function changeBackgroundColor(color) { document.body.style.background = color; }; browser.scripting.executeScript({ target: { tabId: 1, frameIds: [ 1 ] }, func: changeBackgroundColor, args: [ "blue" ] });
-
4:02 - tabs.executeScript file
// Manifest version 2 browser.tabs.executeScript({ 1, file: "file.js" });
-
4:09 - scripting.executeScript API files
// Manifest version 3 browser.scripting.executeScript({ target: { tabId: 1 }, files: [ "file.js", "file2.js" ] });
-
4:15 - scripting.insertCSS
// Add styling browser.scripting.insertCSS({ target: { tabId: 1, frameIds: [ 1, 2, 3 ] }, files: [ "file.css", "file2.css" ] });
-
4:21 - scripting.removeCSS
// Remove styling browser.scripting.removeCSS({ target: { tabId: 1, frameIds: [ 1, 2, 3 ] }, files: [ "file.css", "file2.css" ] });
-
5:08 - Manifest version 3 web_accessible_resources
// Manifest version 3 "web_accessible_resources": [ { "resources": [ "pie.png" ], "matches": [ "*://*.apple.com/*" ] }, { "resources": [ "cookie.png" ], "matches": [ "*://*.webkit.org/*" ] } ]
-
5:42 - Manifest version 3 action
// Manifest version 3 "action": { "default_icon": { "16": "Images/icon16.png" }, "default_title": "defaultTitle" }
-
5:57 - Manifest version 2 content_security_policy
// Manifest version 2 "content_security_policy" : "script-src 'unsafe-eval' https://*apple.com 'self'"
-
6:08 - Manifest version 3 content_security_policy
// Manifest version 3 "content_security_policy" : { "extension_pages" : "script-src 'unsafe-eval' 'self'" }
-
10:31 - Specifying a ruleset
// manifest.json "permissions": [ "declarativeNetRequest" ], "declarative_net_request": { "rule_resources": [ { "id": "my_ruleset", "enabled": true, "path": "rules.json" } ] }
-
11:44 - updateSessionRules
// Rules that won't persist browser.declarativeNetRequest.updateSessionRules({ addRules: [ rule ] }); // Rules that will persist browser.declarativeNetRequest.updateDynamicRules({ addRules: [ rule ] });
-
14:33 - externally connectable
// 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); });
-
15:00 - Message from webpage to extension (in the webpage)
// 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); });
-
15:33 - Message from webpage to extension (in the background page)
// 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!" }); });
-
16:17 - Determining the correct identifier
// 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 }); }); } }); };
-
17:09 - background.js
// background.js browser.runtime.onMessageExternal.addListener(function(message, sender, sendResponse) { if (message.action == "determineID") { sendResponse({ "Installed" }); } });
-
18:07 - Unlimited storage
// manifest.json "permissions": [ "storage", "unlimitedStorage" ]
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。