Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Music ]
[ Applause ]
>> Hello, everyone.
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
of webpages using
JavaScript and CSS.
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
of extensions.
Today we're going to build
three types of extensions.
The first type is a content
blocker which blocks,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
between the JavaScript code
provided by your app extension
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.
Zach?
[ Applause ]
>> Thanks, Ryan.
Hi. I'm Zach Li and I'm also
an engineer on the Safari team.
Last year in Safari we
introduced a new Content
Blocking model.
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
on developer.Apple.com.
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
That's great.
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
All right.
Let's view it and run
the application for Mac
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
[ Applause ]
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
is enabled.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, we are introducing
a new API,
getStateOfContent
Blocker(identifier.
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
[ Applause ]
>> Thanks, Zach.
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
and JavaScript content
scripts into pages.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and JavaScript content
scripts into pages.
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
This also allows Safari to
provide special JavaScript APIs
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
so your app extension's
JavaScript code
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
to JavaScript as event.message.
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
with webpages is JavaScript
based, it's very easy
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
we're going to send
a JavaScript message
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
dom, we want to wait until the
dom is loaded before sending
this message.
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
[ Applause ]
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And to show us how to do that,
I'd like to invite
Damian on stage.
Damian?
[ Applause ]
>> Hello, everyone.
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
view controller.
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
First, Info.plist.
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
over here.
And as you can see,
I'm using a PDF file.
Second thing I want to show
you is my principal object.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
Selected(withCommand.
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
[ Applause ]
>> Thanks, Damian.
Except for leaving
the clicker over here.
So, today we showed you
three types of extensions
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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
in your app extension
alongside your JavaScript
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
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
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.
[ Applause ]