Kuba Suder's blog on Mac & iOS development


App ClipsAppKitExtensionsFoundationLocationLoggingMacMapsPerformancePhotosPrivacySafariSwiftSwiftUIUIKitWatchKitWWDC 14WWDC 16WWDC 18WWDC 19WWDC 20

View as index

Meet Safari Web Extensions

Categories: Mac, Safari, WWDC 20 0 comments Watch the video

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

Improved user privacy controls

Can be sold through the App Store

Some 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 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” 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


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:



Background page  ⭢  extension:


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

What's New in Safari Extensions

Categories: Extensions, Safari, WWDC 19 0 comments Watch the video

Safari app extension can now be notified when something is blocked by the content blocker

  • SFSafariAssociatedContentBlockers list in Info.plist
  • contentBlocker(withIdentifier: blockedResourcesWithURLs: on page:)
  • notifications are batched, and you will only be notified about URLs listed in the access section in the plist

You can also get notified when the user navigates to a different page:

page(_: willNavigateTo url:)

Capturing a screenshot of the visible contents of a page:

page.getScreenshotOfVisibleArea { image in … }

Resources stored in the app extension bundle can be accessed by the browser and JS:

SFSafariExtension.getBaseURI { baseURI in
    tab.navigate(to: baseURI.appendingPathComponent(“index.html”))

Listing all browser windows and tabs:

SFSafariApplication.getAllWindows { window in
    window.getAllTabs { … }



Programatically showing popovers:

window.getToolbarItem { item in item?.showPopover() }

Dismissing popover: SFSafariExtensionViewController.dismissPopover