Transcript
[ 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.