What's New in Safari Extensions

Session 720 WWDC 2019

Safari Extensions surface your app's unique capabilities within Safari. Discover how the latest features such as content blocking notifications and user interface management and control innovations for pages, tabs, and popovers make your Safari App Extensions and Content Blockers even more powerful. Learn about the latest APIs and best practices for communicating between your extension and your app.

[ Music ]

Hi. I'm Brian Weinstein, an Engineer on the Safari Team.

And today, I'm going to talk about what's new in Safari Extensions.

Safari Extensions let you extend Safari's behavior.

You can enhance a user's browsing experience by adding scripts or style sheets to web pages, blocking content, and adding to Safari's UI.

We are going to cover how Safari Extensions are built and distributed, how your Safari App Extension can be notified about the activity of your Content Blocker, better support for window, tab, and page management in your extension, improvements made to Safari App Extension popovers, and some of the best practices for communicating between your Safari Extension and its containing app.

There are two types of Safari Extensions that I'm going to focus on today, Content Blockers and Safari App Extensions.

Both of these extensions are bundled with Mac apps which are built with Xcode.

When you install an app with a Safari App Extension from the Mac App Store, the extension shows up immediately in Safari's preferences, ready to turn on.

However, you can also distribute your app directly from your website after running it through the notarization service.

Notarized apps need to be launched at least once for their extensions to show up in Safari.

We love the Safari App Extensions and Content Blockers you've created.

We also really appreciate those of you who suggested improvements, filed bug reports, and commented on the Safari developer forums.

One of the features we've heard the most requests for is a way for extensions to know when their Content Blocker performs an action.

Content Blockers provide a declarative list of rules defining the content to block or hide while using Safari.

Safari requests the list of rules from your Content Blocker when it's turned on.

The user does some browsing, and when your Content Blocker flags a resource as something that should be blocked, now in Safari 13, Safari tells your Safari App Extension associated with your Content Blocker about it.

Your users will be able to turn on the associated Safari App Extension if they want to see statistics from you or they can turn on only the Content Blocker to keep the most private experience.

Let's take a look at how easy this is to do.

The first step is associating your Content Blocker with your Safari App Extension in your extensions info.plist file.

To do this, add a new entry to the NSExtension section of your Safari App Extension's info.plist.

The key is SFSafariAssociatedContentBlockers.

And the value is an array of Content Blocker bundle identifiers that the Safari App Extension wants to be notified about.

One Safari App Extension can be notified about multiple Content Blockers.

One thing to keep in mind is that the Content Blockers and the Safari App Extension must be in the same containing app.

Then, once you've set up your info.plist, you will need to implement a delegate method on your extension's principal object.

That method is content blocker with identifier, blocked resources with URLs on page.

These notifications are batched and you will only be notified about URLs that your Safari App Extension has permission to access in the website access section of its info.plist.

That's all it takes.

Now, let's move on to the improvements made to window, tab, and page management.

The first enhancement is another frequent request, an API to tell you when a page is about to perform a navigation.

You can pair this with the Content Blocker notifications you just heard about to know any future notifications will be for a new page load.

The first enhancement is another frequent request, an API to tell you when a page is about to perform a navigation.

You can pair this with the Content Blocker notifications you just heard about to know any future notifications will be for a new page load.

Or you can use this to follow the redirect chain across the loading of a specific page in order to redirect to a specific version of a website or to look at the URL parameters to determine if your extension has already shown its UI to the user.

This method will be called on your extension's principal object even if your extension doesn't have access to the target URL.

In that case, the URL will be nil.

The URL will also be nil if the user opened their favorites or their history.

Let's adopt these new APIs in a sample extension.

At a previous WWDC, when Safari App Extensions were introduced, we created an extension that replaces the word "bear" with the bear emoji.

Today, I'd like to expand this extension to make life easier for bears on the internet.

So far, I've made a Content Blocker that blocks all images with honey in the URL to remove the temptation for sweets.

Let's associate that Content Blocker with our existing Safari App Extension.

I'll start by opening the Safari App Extension's info.plist.

To associate a Content Blocker, I'll add an SF Safari associated Content Blockers entry to the NSExtension section.

I'm going to open the XML viewer to paste the source code in.

And then go back to the plist viewer.

It's an array with one entry, the bundle identifier of my new Content Blocker.

The next step is to listen for the content blocking notifications.

What we'll do is build up a map between SF Safari pages and the list of resources that have been blocked on that page.

When we get a notification that the Content Blocker blocked resources, we find the list of blocked resources for that page and add the resources we were just told about.

When a page navigates, we want to clear that list.

To do this, we will override page will navigate to URL and use that to clear the blocked resources for the page.

The last thing we want to do is set the badge of our toolbar item to be the number of blocked resources on that page.

When we're asked to validate a toolbar item for a window, we find the active tab in that window, find its active page, and get the number of blocked resources on that page from our map.

Let's see it in action.

We'll build and run Animalify so the system can discover our extensions.

Once we've run the app, you can see the splash screen with a button to take your users to Safari's extensions preferences.

You can get the splash screen for free in your app if you create a Safari Extension App from Xcode.

Let's launch Safari, which takes me to my homepage, bearseating.com, so I can have some food inspiration for the day.

Since I'm just experimenting with writing Safari Extensions, I don't have an Apple developer certificate yet.

That's OK because I can temporarily tell Safari to run these extensions by first opening Safari's preferences, going to advanced, and turning on the develop menu.

Once I've done that, I can open the develop menu and check allow unsigned extensions.

Let's go in to Safari's extensions preferences, turn on our Content Blocker and our Safari App Extension, and reload bearseating.com.

As you can see, the Content Blocker blocked the pictures of the bears eating honey, and our toolbar time is badged with the number 3, because there were 3 resources blocked.

If I reload the page without Content Blockers, the toolbar item's badge clears and you can see the images that were blocked again.

And when you reload one more time, those images are gone.

And that's how easy it is to adopt content blocking and page navigation notifications.

Those are just a couple of the many new APIs available to Safari App Extensions.

Let's look at some others.

We've added the ability to get the visible contents of an SF Safari page.

Your extension will need to have access to the URL of the page in order to get image data from this API.

With the latest version of Safari, it is now much easier to show users content from your Safari Extensions bundle.

You can now get the base URL of your extension from native code.

There's no need to inject script to do this anymore.

You can navigate a tab to a URL directly also without needing an injected script.

And finally, Safari now injects the Safari JavaScript object into any frames loaded with content from your extension, meaning those frames can send messages to the Safari App Extension and receive messages from it.

Safari extensions can now find out about all the open tabs and windows in Safari, not just the active window and tab.

Here, you can see code that gets all the windows in the application, and then for each window gets all of its tabs.

You can also get a reference to a pages containing tab and the tabs containing window.

For example, consider handling a message from your content script that requires you to update the toolbar item in that window.

You can get the pages containing tab and then the tabs containing window.

One thing to keep in mind is that a pinned tab will have a nil containing window because they belong to all windows instead of a single parent window.

Those were the improvements made to window, tab, and page management.

The last set of API improvements are for Safari App Extension popovers, which you can now programmatically show and dismiss.

A popover is shown by calling show popover on an SF Safari toolbar item, which you can get from the window that will present the popover.

The popover is dismissed from your popovers view controller itself.

All you have to do is call self.dismissPopover.

Let's take a look at improving our extension by adding some of these new APIs.

Using some of these new APIs, I'd like to upgrade my Safari Extension.

When I click my toolbar item, I'd like to display a popover with a table view.

There will be one row per tab and two columns.

The left column will be a screenshot of the tab and the right column will have the title of the page and how many resources were blocked from each domain.

The first step in this process is telling the popovers view controller the state of the Safari window before showing the popover.

To do this, I'll override popover will show and have it collect this information.

As you can see, the function iterates over all the tabs in the window and fills up three arrays, one for blocked resources, one for images, and one for titles.

For each tab, we get a screenshot of the page's visible area, the title of each page from the pages' properties, and the list of blocked resources for each page.

We've also started overriding popover view controller to return our view controller.

Let's take a look at our view controller.

Set popover state, set some instance variables in the class, and then reloads the table view.

The last thing we want to do is when the user clicks on one of these rows, we want to activate that tab and then dismiss the popover.

When the table view selection changes, we activate the tab the user clicked on, and then dismiss the popover.

Now let's build and run Animalify again.

And we'll launch Safari again which loads our homepage.

And you can see the extensions aren't turned on anymore.

That's because you must allow unsigned extensions each time you launch Safari.

Let's do that from the develop menu.

And then we'll go to our app and have it take us to our extension.

We'll turn on the extension and the Content Blocker and reload the page.

We'll also open a couple more pages.

You'll notice that when we change tabs away from bearseating.com, the toolbar item's badge is cleared.

Let's open our popover.

And you can see each tab in the window.

In the first row, you can see that our Content Blocker blocked two resources from beardiet.com and one from bearseating.com.

Let's activate that tab by clicking on the first row, and we're taken back to that tab, and the popover is dismissed.

And those are just some of the new APIs that we've talked about today.

The last thing I'd like to cover are some best practices about how to communicate between your Safari Extension and your app.

Safari launches your app extension when necessary.

There is no guarantee that your app is running.

But if you have an app with an XPC service, your extension might want to communicate with that XPC service.

Or you might want to share data between your Safari Extension and your app.

Your extension could have preferences that the user configures in your app.

To do this, make your Safari Extension and XPC service part of the same App Group as your app.

This will allow your Safari Extension to look up named mock services in the same App Group and you can use the user defaults suiteName initializer to share data across the App Group.

However, let's say the user performs an action in your app that is best handled by a Safari App Extension.

For example, you're writing a password manager and the user selects a credential in your app to fill.

There's no guarantee that your extension is running or that Safari is even running.

There's an API you can call from your app to send a message to your Safari App Extension.

Your app calls SFSafariApplication, dispatch message with name to extension with identifier.

This method will only have an effect if your extension is enabled and the extension must be in the same app bundle as the code that calls the API.

Calling this API will send a message to Safari, launching it if necessary.

And Safari will forward this message to your Safari App Extension.

Your Safari App Extension can listen for these messages by implementing message received from containing app with name user info in your extension's principal object.

Let's take a step back and look at all the possible ways your app and your Safari Extensions can communicate and share data.

If your app and Safari Extension are in the same App Group, you can use NSXPCConnection to communicate between them or share data using user defaults.

If you want to send a message from your app to your Safari App Extension, you can use SFSafariApplication dispatch message to send the message, and listen for message received from containing app in your Safari App Extension to handle the message.

And that's how you communicate between your app and your Safari Extension.

That concludes what we're covering for today.

A few of the major things I'd like you to take away from this video are, you can distribute your Safari Extensions through the Mac App Store or as a notarized app through your website.

You can associate your Content Blocker with your Safari App Extension so you can find out when your Content Blocker performs a blocking action.

You can use App Groups to communicate between your Safari Extension and your app.

All of these APIs we talked about today were implemented because of your feedback and requests.

Please continue to keep filing enhancement requests and reaching out on the forums.

Thanks, everyone, for taking the time to listen, and we can't wait to see the extensions you come up with.

Apple, Inc. AAPL
1 Infinite Loop Cupertino CA 95014 US