WWDC 20
Meet Safari Web Extensions
Existing extension ecosystem:
- content blockers (iOS & macOS)
- share extensions – can run JS on the currently opened web page and return data to the extension
- Safari app extensions on macOS
If you’re a web developer and don’t want to learn Swift to build an extension, or you have an existing extension for Chrome/Firefox/etc., you can now use the new Safari Web Extensions API
Extensions built primarily using HTML, JS and CSS, like legacy Safari extensions
API compatible with other browsers (the WebExtensions standard)
Improved user privacy controls
Extensions are sold through the App Store
Some WebExtensions APIs are missing, so provide feedback if you want something added
Like other extensions, Web Extensions must be packaged inside a native Mac app
Xcode 12 is required to build and run
A command-line tool is provided which wraps an exising web extension (e.g. for Chrome/Firefox) into a new app:
xcrun safari-web-extension-converter […] /path
- lets you know if any features are not available
- the largest icon in the manifest is used as the app icon (it’s recommended to include 512×512 and 1024×1024 icons)
To create a new extension from scratch, create a “Safari Extension App” project or add a “Safari Extension” target, and choose Type = Safari Web Extension.
Extension privacy
If your extension needs access to specific sites, the user will be asked for permission to run it on that site for one day or always
Optional permissions: you can include the URL pattern under optional_permissions
key and then ask for access using browser.permissions.request(…)
at the moment when you require access
The Safari preferences window page of your extension shows information about what kind of access was granted to the extension
It’s best to use the activeTab
permission, which grants access to the currently open page when the user interacts with your extension in some way
The hostname of the extension changes every time Safari is launched in order to prevent fingerprinting
→ use browser.runtime.getURL(“/path/to/resource”)
to create URLs to assets
Debugging
Access the background page through the Develop menu
Content scripts are visible in the Sources tab for the page
- to run JS in the console in the context of a content script, choose the script from the pulldown menu in the corner
Don’t rely on code being executed when the page loads, since the extension may not have permission to run yet at this point
Communicating between components
Content script ⭢ background page:
browser.runtime.sendMessage()
browser.runtime.onMessage.addListener()
Background page ⭢ extension:
browser.runtime.sendNativeMessage()
Handled by the SafariWebExtensionHandler.beginRequest(with context:)
delegate method
→ requires nativeMessaging
permission
Extension ⭢ background page:
Use completion handler from NSExtensionContext
object in SafariWebExtensionHandler.beginRequest()
to send back a response
App ⭢ background page:
SFSafariApplication.dispatchMessage()
(check that extension is turned on first)
App ⭤ extension:
Shared NSUserDefaults
from an app group, or NSXPCConnection