Learn how to bring your Content Blocker App Extensions to macOS and how to expose your application's abilities through Safari with Safari App Extensions. Safari App Extensions let you take full advantage of web technologies, Cocoa, and other system frameworks that you already use in your app on macOS.
I'm Brian Weinstein.
I'm an engineer on the Safari team.
And today Zach, Damian and I are here to show you how
to extend your Mac app using Safari app extensions.
In macOS Sierra and Safari 10 on El Capitan,
we are introducing a new way of writing, packaging
and distributing Safari extensions.
These extensions leverage both web technologies
and native code written in Swift.
They're bundled with the Mac app and can be distributed
through the App Store.
That new way is Safari app extensions.
Safari app extensions have the power to customize the contents
They have the power to block loading of specific resources
or elements on a page.
They can add toolbar buttons to Safari's UI.
And those toolbar buttons have the power
to display popovers using fully native technologies
to build the views.
And lastly, Safari app extensions can add items
to context menus on webpages.
Safari app extensions are based on app extensions.
Which are bundles of code and resources
that are packaged inside of your app that are meant
to give your users access to your app's functionality
and content throughout macOS.
And so this means that they are developed using Xcode
and other developing tools you might already be familiar with
and it means that Safari app extensions can run native code
with the power of the APIs available to Mac Apps.
Another major benefit about these being based
on app extensions is that they are distributed with your app.
Which means that they can be sold through the Mac App Store.
And it means that your users don't need
to download your extensions separately
after installing your app.
And for those of you who require communication between your app
and your extension, a major benefit
of Safari app extensions is
that your extension is always revlocked with your app
because they're packaged together.
So, your users will always have matching versions
of your app and your extension.
One of the best parts of an extension platform is
that it's capable of building many different types
Today we're going to build three types of extensions.
The first type is a content blocker which blocks,
which can block the loading of specific resources
or hide elements on the page.
The second type will be an extension
that modifies the contents of webpages and communicates
and the extension's native code.
And the third type of extension will show you how
to extend Safari's UI to add the power and functionality
of your app directly to Safari.
So, let's get started with content blockers.
To build a content blocker for us,
I'd like to bring Zach on stage.
Hi. I'm Zach Li and I'm also an engineer on the Safari team.
Last year in Safari we introduced a new Content
Rather than developing extension code on runtime to block loads
or hide elements, we built a model
where you can declare ahead of time what content to block.
And WebKit optimizes such blocking mechanism
to make it fast and memory efficient.
Best of all, this model preserves user privacy
because the host app isn't consulted
for the resources block.
Since then, we've seen so many awesome content blockers you
have written that really enhance the browsing experience.
And users love them.
Today, I'm excited to talk about how to easily bring
out existing iOS content blocker to macOS.
For those of you who haven't written a content blocker
before, that's no problem.
You can check out the WWDC talk last year
Let's take a look at what a content blocker can do.
I really love eating.
I basically eat all the time.
Obviously I have a food blog that's dedicated
to the food I want to try.
But unfortunately, well, actually fortunately,
I will attend my friend's wedding
as his best man next month.
That I need to stay fit.
At least I need to be able to fit into the suit.
So, I created this iOS content blocker
that blocks all the desserts from my website,
that even if I'm starving and I look for something
on my food blog, I wouldn't think of desserts at all.
Let me show you how the dessert blocker works.
So, this is my source code
and my app dessert blocker is already running.
Let's go to Safari.
And the desserts are present.
Let's reload the page with the dessert blocker enabled.
All the desserts are gone.
Although I'll be missing them.
So, I am an iOS developer with this content blocker
and I really want to bring it to the Mac and distribute it
through the App Store.
It's actually quite simple to do
because the content blocker APIs are the same on both platforms.
So, let's first go to Xcode.
Create a new Mac Application target that's called dessert
blocker for Mac.
And let's quickly switch the Mac Applications theme,
because we are now building a Mac Application.
Then, create a new Mac Application extension target.
Use the content blocker extension template.
That's called dessert blocker extension for Mac.
When users get Apps from the App Store, they don't have
to run the application in order for Safari
to find the app extension.
But outside the App Store, Apps have to be run in order
for Safari to see the extension.
So, in this case, I actually want
to continue using the application scheme.
So, I'm just going to click on cancel.
This template will set everything up correctly.
And it comes with a simple content blocking rule.
To make your own content blocker, you just need
to modify this JSON file.
It's really convenient.
But in our case, we can actually share resources with iOS app.
So, we can get rid of this JSON file.
And the Swift file.
And make the ones we already have be available
to the Mac Application, Mac app extension target.
To accomplish that, go to utility sidebar.
And in the target membership section,
check the Mac app extension target
for the resources we want to share.
Cool. We're pretty much good to go.
But before that, I do want
to make my dessert blocker more polished
by displaying a better name and a better description
in Safari extensions preferences.
To accomplish that, let's go to the app extension info list.
Instead of dessert blocker extension for Mac, let's check,
just called it dessert blocker.
And the description I want
to provide is this content blocker blocks dessert pictures
on my food blog.com.
Also, I want to provide prettier and more vivid icons
for my dessert blocker.
Let's remove the default Asset catalog.
And drag the ones
with predesigned ice cream icons to my Xcode project.
Let's view it and run the application for Mac
and the application is run.
So, let's go to Safari.
As you can see, it shows up in extensions preferences.
Like all other Safari extensions,
content blocker is off by default.
Let's enable this dessert blocker.
Reload the page.
Great. All the desserts are gone.
Awesome. Now that my urges
to eat desserts are completely blocked,
I'm so ready to be the best man.
As you saw, without even writing any code we ported an iOS
content blocker to macOS.
On top of that, we've heard feedback from you
that it would be nice to know whether your content blocker
So, we are introducing a new API,
With this API, for example, if you recall my not so great UI
in the app, I can provide a better experience to users
by giving instructions on how to enable my content blocker
if I detect my content blocker is not enabled.
This API along with other APIs that my colleague Brian
and Damian are going to talk about will be available
on Sierra and El Capitan if users have installed Safari 10.
Because El Capitan, the API availability is dependent
on the presence of Safari 10.
We are providing you with a handy Swift API that you can use
to check at runtime whether Safari services APIs whether
Safari services APIs are available.
Let's step through the Swift code API.
If SFSafari service is available returns true,
you can safely use the new API.
If not, fall back to other behaviors.
These are what's new with content blockers.
To talk about other powerful capabilities you can view Safari
app extensions, I would like to hand the stage back to Brian.
So, the next type of extension I would like to show is one
that modifies the contents of a page and also has the ability
to communicate with the native code provided
by your app extension.
So, we're going to build an extension
that replaces instances of one word
with another throughout the web.
It will get the word to replace and what to replace it
with from a app extension's Swift code.
There are 2 ways for Safari app extension
to modify the contents of the webpage.
extensions can inject CSS style sheets
Let's take a look at how to inject a style sheet.
Stylesheets are injected by specifying them
in the app extensions Info.plist in the NSExtension section.
Stylesheets are specified using the SFSafari style sheet section
of the Info.plist file which inspects an array
of dictionaries defining each style sheet.
And each style sheet is defined by a style sheet key-value pair
where the value is the path to the style sheet relative
to the resources directory of the extensions bundle.
And any style sheet you've written in the past
for a Safari extension will just work with Safari app extensions.
Next up is injecting scripts.
Which is almost exactly the same,
just with two different keys.
Scripts are specified
in the SFSafari content script key of the dictionary.
And the key representing the path is script instead
of style sheet.
And all of your extensions scripts are run
in their own execution context
which means there are never there are never naming conflicts
with variables in webpages content scripts.
to your extension's content scripts
that aren't available to all webpages.
For example, your extension might need to communicate
with the native code in your app extension to read preferences
or perform some action that can only be done in native code.
And to facilitate this, we've added a message passing API
and Swift code can communicate with each other.
So, taking a look at this diagram,
you can see that we have Safari and we have your app extension
with a box representing the Swift code running
in your extension's process which is
of course completely sandboxed.
And the extension has injected a script into Apple.com
and that script would like to ask
that app extension what words it should replace.
To do that, it just calls Safari.extension.dispatch
message and passes along a message name.
Now let's see how the Swift code listens for this message
and responds back with the words it should replace.
Each Safari app extension has a principal class
which is the class that Safari calls methods
on when the user interacts with Safari.
When a message is dispatched from a content script
to the app extension, message received with name
from page userInfo is called.
And once that method is called
on our extension's principal object,
the first thing we do is check the message name and act on it.
And to act on it, our extension will be sending back a message
to the SFSafari page representing Apple.com
with the words to replace and what to replace them with.
And we are structuring this at the dictionary
where each key represents the word that we're going to replace
and the value represents what we're going to replace it with.
So, when the app extension calls dispatchMessageTo
Script(withName,userInfo, that message is sent
from the extension process back to the content script
that is injected into Apple.com.
Now let's see how that content script listens
for these messages and acts on them.
To listen for a message inside a content script,
we start by adding an event listener to Safari.self.
So, the content script can listen for message events
from the app extension.
And if you've written a Safari extension before,
this will look very familiar.
The API to receive messages inside
of a content script is almost exactly the same.
And when our event listener fires because we got a message
from our app extension,
the first thing we do once again is check the message name
as a best practice and then we act on it.
To act on this message, we want to iterate over our dictionary
that was specified in the userInfo of the message
from the Swift code and that is exposed
So, we get our dictionary of words and replacements
and we iterate over them and call a function
in our content script that does those replacements.
So, this word replacement extension
that we've been discussing is meant to work on every website.
But some extensions are tailored to only run
on particular websites.
Safari app extensions support a rich system
for specifying what sites they work on and additionally new
in Safari 10, users will be able to see
which sites your Safari extension requires access to.
Like information about content scripts and style sheets,
your extension's website access is described
in the extension's Info.plist
under the SFSafariWebsiteAccess/key
and it's a dictionary with two parts.
The first part is the website access level.
And this extension is specifying an access level of all,
which means it wants access
to every webpage that the user visits.
And as you can see, Safari's extensions preferences warn the
user about this before they're going to turn on the extension.
And in this example,
the extension has an access level of some.
When the access level is some, the extension specifies a list
of domains that it wants access
to in the allowed domain section of the dictionary.
And if an allowed domain starts with an asterisk, that marks it
as a wildcard and it represents access to all subdomains.
So, now that we've discussed how your extension can modify pages
by injecting content scripts and style sheets,
how those content scripts can communicate
with your app extension's Swift code
and how you specify what websites your extension would
like access to.
I'd like to put this all together in a demo and write
that word replacement extension that we've been talking about.
So, one of the interesting things
about Safari extensions is that since the code that interacts
to bring code that you've written for, an extension
for a different browser, and bring it directly into Safari.
So, what I'm going to do is I'm going to take a Chrome extension
that does this word replacement and I'm going
to create a Safari app extension from it.
And I'm going to extend it so that we get the words to replace
and what to replace them with from Swift code.
So, I already have an app built with an icon and I would
like to create a Safari extension.
To do that, I create a new target in Xcode
and select a macOS Application Extension
and select Safari extension.
And we're going to call this Animalify because we're going
to Animalify the web
by replacing animals with their emoji.
And as Zach mentioned before, we want to run the app
so I'm not going to activate the extension scheme right now.
So, we now have our extension and I'm going
to jump into the Info.plist.
And open up that NSextension section.
And as you can see, automatically created for us
from the template, we have a content script, a toolbar item
and the website access that our extension requires.
I'm going to get rid of this toolbar item
because our extension doesn't need it.
And change the access level to all and get rid of the list
of allowed domains because we want this extension
to run on every page.
The next thing I'm going to do is open up our content script
and I'm just going to bring in the content script I've written
for my Chrome extension wholesale.
And as you can see, when the script is injected
into the page, it just calls our replacement function
and replaces bear with the bear emoji.
Now, what I would like to have my app do is I would
like to have it replaced,
I would like to have it show a list of animals
and the users can choose
which animals they would like to replace.
I'm not going to design any of that UI right now
but to support that, we need to get the list of replacements
from the app extension.
So, instead of doing this replacement straightaway,
to the app extension asking it for the words and replacements.
And so one thing that's interesting here is
that Safari's extensions,
Safari app extension content scripts are injected before the
dom has loaded for more flexibility in your extensions.
But since a word replacer doesn't do much before there's a
dom, we want to wait until the dom is loaded before sending
So, now let's go to our principal class
and as you can see, we already have an implementation
for message received with name from page userInfo.
So, we're going to get rid of this and just replace it
with some, with the code that we talked about in the slides.
Which all it does is check the message name
and sends a response back to the content script.
And we're making two replacements here just
because we can.
So, let's go back to the content script and listen
for this message and act on it.
And once again, this is just the code we talked
about a few minutes ago.
We start by adding an event listener
for the message event to Safari.self.
And when that event listener fires, we check the message name
and get our replacements from event.message.
And we act on them by iterating over all of the replacements
and calling that same replace function that was
at the beginning of the script before.
So, now I would like to build and run this app so Safari
to discover this new extension.
And you can just imagine a list of animals here
and I've checked a few.
So, I'm just trying out Safari app extensions
for the first time.
So, I have not signed up for the app, I have not signed
up for the Apple Developer Program
yet which means I don't have a Developer certificate.
By default, Safari will only show,
allow users to enable extensions that have been signed
with the developer certificate.
But for those of you who just want to try this out,
we've added way for you to be able
to test your unsigned extensions.
To do that, I'm going to open up the Advanced Preferences
and show the Develop menu in the menu bar.
And from the Develop menu,
I'm going to select Allow Unsigned extensions.
I'm going to type my password
and now the animal extension shows
up in our list of extensions.
I'm going to turn it on and before everyone got here,
I was researching the diet of a grizzly bear.
So I'm just going to reload this page and as you can see,
bear has now been placed, replaced with the bear emoji
and salmon has been replaced with a delicious sushi emoji.
And this is just going to make the web a lot more fun
for me to browse.
So, that was how a Safari app extension can modify page
content and how your extension specifies what websites it would
like access to.
The last type of extension that we're going to show you how
to make is one that extends Safari's UI to add the power
and functionality of your native app directly into Safari.
And to show us how to do that,
I'd like to invite Damian on stage.
My name is Damian Kaleta and I'm an engineer on the Safari team.
So, Brian already told you about the fundamentals
of Safari App Extensions and now I want to build on top of that.
I want to tell you how you can extend Safari's UI.
So, let's start.
I've written this simple macOS app.
It's a notebook app.
And as you can see by the screenshot,
the icon got some love from the designers.
Unfortunately, the app itself didn't.
But nevertheless, it lets me insert some note,
saves it so I can view it later.
But now I want to be able to grab notes off
of web pages directly and modify this note in Safari.
And with the new Safari app extension model,
I have all the tools I need in order
to build my extension really easily.
I'm going to need two different things.
So, I want to be able to select a text and I want
to be able to save it.
For that, I'll use a context menu item.
Secondly, I want to be able to display my most recent note
and I want to be able to modify it.
For that, I'll use a popover.
The popover shows up when the user clicks the toolbar button.
So, let's talk about that first.
The toolbar button goes by default next
to the Smart Search field.
That way your users have really quick and easy access
to the functionality that your extension provides.
As you would expect, if you're an Events user
of course you can move it around really easily.
So, how did I add my button?
I went to my extension's Info.plist
and I've added SFSafariToolbarItem.
Together with four different key-value pairs.
Identifier, label, image and action.
And as with all toolbar items on the system, please notice
that I'm using a PDF file here.
Okay, so Safari now displays my toolbar button but what happens
to my extension when the user clicks that toolbar button?
Safari sends toolbar item click in window
to your principal object.
And as a reminder, your principal object is the object
that handles all of the communication
between Safari and your extension.
And also if you want to gray out the button depending
on the context, Safari provides you with the validation method.
And you can also set batch text for your button.
That usually represents a numerical value
such as in red count.
So, we've got ourselves a button but now I want to be able
to display a popover when the user clicks the button.
The popover lets me insert any NSview inside of it
which is great because if you have written any macOS Apps
before, you're going to be able to reuse
that code really easily right over here.
So, let me show you how this works.
You have your extension with the principal object
and then you'll want to define popoverViewController method
on your principal object.
Inside that method, you'll want to return
to custom view controller it represents a view that you want
to insert in that popover.
On the other side, there's Safari process
and Safari process is something that we call remote view.
That remote view will simply grab your review
and display its contents in the popover.
And as you would expect, we will also forward you all
of the events such as click events.
And a way to show the popover is to simply specify an action
of popover instead of command on SFSafariToolbarItem.
And that's it.
At this point, Safari knows that you want to display a popover,
so you will look for that custom view controller.
The popover also comes with a handful of useful APIs.
And as you can see here, the first two popoverWillShow
and the popoverWillClose.
It can help you do some setup or cleanup work.
And the third one we've talked about.
It's the one that basically returns your custom
Okay, so we've added the button.
We can display a popover.
Now let's talk about context menu items.
So, you typically want to use context menu items when you want
to act on part of the page.
But in my case, I want to be able
to select the text and then save it.
So, I went again to my extensions Info.plist
and then I've added SFSafariContextMenu.
It's an array of dictionaries
that hold two different key-value pairs.
Text and command.
And then when the user presses
or clicks your context menu item,
Safari will send contextMenuItemSelected
to the principal object.
Please notice that we are also passing along userInfo.
This will simply represent any additional information you might
want to include from your injected script.
Such as in my case, I want to be able to pass along selectedText.
So, inside my injected script I'm adding event listener
for context menu.
And then inside that function I'm calling set context menu
event userInfo on the Safari extension object.
And notice that I'm actually sending along selectedText.
Okay. So now I'm excited to show you my macOS app how I extended
it into Safari.
So, let's do that.
So, before I show you any code,
this is my simple macOS app right over here.
As you can see, I have just two notes.
I can insert my note here, delete last note,
etc. So, let's go to Xcode.
I want to show you three different things.
So, please notice that I'm adding my context menu item
right over here.
Here's my text and my command.
And I'm also adding my toolbar item.
I have my, all of my four different fields right
And as you can see, I'm using a PDF file.
Second thing I want to show you is my principal object.
Let me just make it like that.
And I have overridden two different methods
on my principal object.
The first one is popoverViewController.
This will simply return my view controller
that represents the view that goes to,
that displaced in the popover.
And the second one is contextMenuItem
And as you can see, I'm getting my userInfo right over here
and assigning my note here and here.
And third thing I want to show you is how I can easily reuse
the code and share it between my macOS app and my extension.
Of course normally you wouldn't use just simple files.
You would use a framework but that will give an idea.
So, this is my notes manager.
It will simply read and save to the user defaults.
It has some simple methods such as removeAllNotes,
removeLastNote, etc.And please notice
that my target membership is set for both targets, my notebook,
which is macOS app and my extension.
Okay. So, now let me go to Safari right over here
and as you can see, my extension is right next
to the Smart Search field.
I can click it and this is where my last,
my most recent note will appear.
So, let's say and, let me just bring up my notebook app also.
Side to side.
So, let's say I'm reading some blog posts
and let's pick this one.
And let's say that I want to be able to save some notes.
So, let's say I select this thing.
I command, CTL click it.
And then I say add snippet to notebook.
Like so. As you can see, my note was added right over here also.
I can open my extension and it's right over here.
And now if I want to let's say modify it,
because let's say I don't want to have my last sentence,
I can simply delete that, hit Modify and voila.
It's reflected in my macOS app.
So, it's that simple to create an extension and share the code
between your macOS app and extension.
So, I showed you how to add a button.
I showed you how to display a popover
and add multiple context menu items.
Now I want to hand it back to Brian.
Except for leaving the clicker over here.
So, today we showed you three types of extensions
that are made possible with our new extension model.
We showed how easy it is to bring your content blocker
from iOS to the Mac and introduced new APIs
to get the state of your content blocker
in response to your feedback.
We saw how simple it is to bring a Chrome extension
that modifies webpages to Safari and enhanced it to communicate
with the native Swift code in your app extension.
And lastly, we saw how it had the power and functionality
of your app directly to Safari.
And remember, Safari app extensions are all based
on app extensions.
Which means you have the power of native technologies and APIs
and your CSS used to modify and enhance webpages.
And since Safari app extensions are distributed
with your Mac app, there's an easier installation experience
for your users and you can now sell your extensions
in the Mac App Store.
For more information about the talk and some useful links,
please visit this page.
Safari app extensions are the future and we need your help
to make them the best that they can be.
Please give us some feedback about our APIs
and any bugs you find.
On the More Information page, you will find a link to leave
that feedback and the email address of John Davis,
our Safari and WebKit evangelist.
And for related sessions, I highly recommend checking
out some of the app extension talks from previous years.
Thank you so much.
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.