Safari Extensibility: Content Blocking and Shared Links
Safari in iOS 9 and OS X El Capitan delivers new ways for your app to extend Safari's behavior. Learn how to create Shared Links and Content Blocking extensions for iOS and OS X, and about changes to the Safari Extension development process.
BRIAN WEINSTEIN: Hi, everyone.
I'm Brian Weinstein.
I'm a Safari and WebKit engineer and here to talk to you guys
about What's New In Safari Extensibility.
Today we are announcing a few new ways you can extend Safari.
You can write content blockers for iOS and OS X.
There are changes to the Safari Extensions Gallery.
And a new type of app extension for you
to put your content directly in Safari's Shared Links.
Let's get started and jump right into talking
about content blockers.
Let's start with the big question here:
What are content blockers?
Content blockers identify subsets of content or resources
on a page to not show or even load.
I'm sure you can all imagine content you might want to block
when browsing the Web.
There are two main ways you can block content using a
You can hide elements on a page or you can block resources
from loading altogether.
You have been able to do this for awhile
with Safari extensions on OS X, but today we are bringing
that capability to iOS.
Apps will tell Safari the content they want to block ahead
of time instead of Safari consulting
with the app during the actual loading process.
This new model is very fast and efficient
because Safari doesn't have to consult
with the app during loading,
and the content blockers are compiled into byte code
that can be evaluated very efficiently.
Additionally this new model is great for your user's privacy
because content blockers have no knowledge of the browsing
that its users are doing.
And to get started creating a content blocker you will create
an app extension on iOS that will return a list,
a JSON string containing the list
of rules describing the content that you want to block.
So there's this website I go to regularly where the author talks
about different golf courses he's played
and posts some beautiful pictures.
However, there are a few things that annoy me about this site.
I would like to write a content blocker
to block the content that bothered me.
The most obvious annoyance is this list
of click bait links on the left.
They even follow me around on the page when I scroll.
So let's write a rule that will hide this element.
So we get started by seeing which element we should hide.
Looking at web inspector we see there's a div with ID leaks.
When that is selected in web inspector,
we actually get an overlay on the device itself showing
that this is the correct element that we want to hide.
So now that we know the element we want to hide,
let's write a rule to do it.
So each rule, each content blocking rule is a JSON object
containing action and trigger dictionaries.
The action tells Safari what to do when the trigger is matched
and the trigger tells Safari
when it should perform the corresponding action.
So let's start by taking a look at our action dictionary
to hide the element, to hide this element.
Our action has the type CSS display none
which means we are applying the display none style
to an element on the page.
When the action type is CSS display none,
a selector is required in the action dictionary.
All selectors that are supported by Safari
and WebKit are supported by content blockers.
So for this example we want
to block the content with the ID Links.
Now we define when we want this action to take place.
The trigger dictionary is used for that.
Since Links is a pretty generic selector and could be used
for real content on other pages, I only want this rule to apply
when the user is on a page on bigbearsgolfblog.com.
If I wanted this rule to apply when the user was
on any site except bigbearsgolfblog.com,
I would just replace the if domain key with unless domain
and keep the value the same.
And I would like this action to apply when loading any resource.
Once again, as long as I'm on bigbearsgolfblog.com,
so I set my URL filter which is a regular expression that is run
against the URL of every resource being loaded
to match all URLs.
And we'll talk a little more
about URL filter in a later example.
So let's see what this rule looks like all together.
We have our action type, CSS display none,
which says that we are hiding an element.
Our selector which describes the element we are hiding.
And then in our trigger,
we specify which domains we want this style to apply to.
And which resources we want to apply it to when loading.
So when the content blocker is applied,
here's what the page looks like.
The list of links is gone and I can just focus
on the content that interests me.
However, when looking at Web inspector,
I found another annoyance on the page.
If you look at the resources on this page,
you can see that there's a script loaded called Tracking
script.JS from example.com and this script actually tracks me
across various pages that I visit and can build up a profile
about me from my browsing history.
When I look in the console it also helpfully warns me
that I'm being tracked.
Thanks, Tracking script.
Let's block this script from loading altogether.
We start by defining the action for this rule.
The action type is block, which means we will block the load
of any resource matching the corresponding trigger.
So let's take a look at that trigger.
The trigger defines when the action should fire.
So in this case we've defined a URL filter,
a resource type, and a load type.
The trigger will file when all three, the URL filter,
the resource type, and the load type all match.
Let's start by talking about the URL filter.
It is a regular expression that is run against each URL
that is being requested.
So in this case it will match any URLs
that contain the string Tracking underscore script.
This will match example.com/Tracking script.js.
The rest of the components
of the trigger increase the specificity of the trigger
so we don't block more content than we want to.
The first way we do this is by specifying a list
of resource types to match against.
In this case we only want to block the loading of scripts.
So we pass script as the one resource type we want to match.
Lastly, we specify a load type of third-party.
There are two possible values for load type.
There's first party and third party.
A load is considered first party when it comes
from the same scheme, domain,
and port as the main resource on the page.
In our case, we only want to block the loading
of tracking script when it is a third-party load.
So this means if the user was on example.com,
this script would still load successfully.
So let's see what the rule looks like all put together.
And when we reload the page with this content blocking rule,
you can see that we are not loading Tracking script.js
at all and there are no warnings in the console
that we're being tracked.
So your content blockers will just be JSON arrays
of these rules.
Now that you've described the content that you want to block,
the next step is to get your content blocker
on an iOS device.
As I mentioned before, content blockers on iOS are loaded
from an app extension.
It is very simple to make your own.
To begin, you just need to add a new target to your project
and select content blocker extension from the list
of iOS application extensions.
This app extension will be instantiated
when your content blocker is enabled in settings.
And like all other app extensions,
it's disabled by default.
Your rules will be compiled into byte code by Safari
so they can can be loaded
and evaluated quickly each time Safari is launched.
Once you've created your app extension,
you'll have an action request handler
that looks something like this.
All this app extension does is return the contents
of blocker list.JSON to Safari
when your app extension is instantiated.
And blocker list.JSON is automatically
in your extensions target, so all you have to do
to write your own content blocker is
to fill in this JSON file.
And content blockers are automatically applied in Safari
or any apps that use Safari View Controller.
If you have an app that displays Web content you should check
out the talk from earlier this week introducing Safari
However, a static block list might not be enough
for some of your apps.
You might want to provide customization
of your content blockers, either by letting your users select
from multiple different block lists
or by letting your users pick certain sites they don't want it
to apply to.
To do this, just like with other app extensions we recommend
that you put your management settings in your app.
When your users perform actions that would change the contents
of your block list, all you need to do is tell Safari
to re-instantiate your app extension,
which will cause your content blocker to be recompiled.
To do this, we've added a new API
in the Safari services framework called SF content blocker
manager reload content blocker with identifier.
All you need to do is pass it the bundle identifier
of your content blocker.
This will cause Safari to rerun your app extension
and update the byte code generated
from parsing your content blocker.
That was just some of the things that content blockers can do
and how easy it is to get started making your own.
I would like to bring up Alex Christensen to show us a demo.
ALEX CHRISTENSEN: Thanks, Brian.
I'm Alex Christensen from the Safari and WebKit team.
I'm going to show you how to make a content blocker in iOS.
We will block content in two ways,
we're going to hide some elements on a Web page
and we're going to block some loads.
Now, I don't know about you, but I surf the Web all the time
and I feel like I can really enhance a user's experience
by making an extension
that removes some content from the page.
Now, let me show you one of my favorite Web pages.
You'll see what I'm talking about.
There we go.
Oh! Now, I just love Web pages like this.
I got to stay up to date with what's going on in the world
of cute puppy dog pictures.
So I check one of these at least every day and I only have
so much valuable screen space.
The comments here are kind of annoying and they are taking
up the valuable screen space that I want to use
for cute puppy dog pictures.
Let's go to the Web inspector
and see what we can do about this problem.
Open up Safari on Mac.
If we go to the develop menu, simulator,
cute puppies and cats.com.
You can see we are going
to inspect this iOS simulator instance.
If we hold our mouse over this we can see
that this is the div that I want to hide.
It has a class comment.
And if we explore the structure of this Web page we can see
that all the other divs that I want
to hide also have the class comment.
It's pretty easy to write a selector div.comments
that finds all of the elements I want to hide
on this page using the CSS display none style.
Let's make a rule that does that in an iOS content blocker.
Open up Xcode, create a new project, iOS,
single view application for now.
My app. Hopefully you have a better name than "my app".
Create it on the desktop for now.
So from here we go to file, new, target, and under iOS,
application extension, we have the content blocker extension
template that Brian just mentioned.
My content blocker.
Let's not activate this scheme because we will want
to run our app in the iOS simulator.
Now, adding that new target made this right here:
Inside of this, it made this blocker list.JSON.
This is where we want to put the rules.
Now, I have a rule that applies the selector div.comments
and applies the style CSS display none to all
of the elements in the page that match that selector.
And let's have this apply everywhere for now.
So we have the dot star regular expression, meaning everywhere.
Let's run this in simulator.
Here is my app.
Okay. Now if we go to settings, Safari, content blockers,
here is my app's content blocker.
Let's turn this on and reload this page and see what it did.
All those comments are gone and I can use the screen space
for the cute puppy dog pictures, why I came to the site.
If you look really carefully, you might notice something else
that is also not a cute puppy dog picture.
Now, I've always been more of a dog person than a cat person
and I don't want to see these cat pictures.
(Laughter) I don't want to wait for the cat pictures to load
and don't want to use my bandwidth
to download cat pictures that I didn't want to see
in the first place, so let's block these loads completely.
Back in the Web inspector and the resources tab,
you can see that the resources on this page have some names
that I can write regular expressions to recognize.
If it contains slash cat, probably a cat picture.
So let's block those loads.
Here we have another rule.
Action type block, and if it matches the regular expression
slash cat, let's block it.
I think this is a little bit too generic.
I think this will probably block things on pages
that I'm not expecting, like if there's slash category
in the URL anywhere on the, this will block it
but that could cause problems.
Let's just have it
on cutepuppiesandcats.com cause I'm pretty sure that anything
on this website that has slash cat
in its URL is probably a cat picture that I want to block.
All right, let's run this and see what happens.
Let's go back to settings and toggle this
to recompile the content blocker.
You can also use the API to do this from within your app.
Go back to Safari, reload the page, there we go.
The cat pictures are gone, but something has gone wrong here.
My dog picture is too big.
I have no background color.
It looks like the CSS is also gone.
(Laughter) That's definitely not what we wanted.
Let's go back to the Web inspector
and see what's going on.
Right here, you can see that my CSS also starts
with /cat in its URL.
It's cats and dogs.css.
We are blocking that unintentionally.
There are a couple ways we could let this through.
We can have the other rule only apply to images or --
well, be creative, but I'm going to make it so that
if it also matches cats and dogs,
we want to ignore this rule that would block it.
There we go.
Has cats and dogs in it, ignore the previous rules.
This will block the cat pictures, which is what I want
to do, but it will let the cats and dogs.css through.
All right, let's rerun this.
Toggle this to recompile the content blocker
and reload the page.
There we go.
This is how I want users of my content blocker
to view web pages like this,
straight to the puppy dog pictures.
so today we blocked some cat pictures, we hid some comments.
Who knows what you might want to block tomorrow?
So give it a shot.
Try making a content blocker on iOS.
Brian, come on back.
BRIAN WEINSTEIN: Thanks, Alex.
So that was an overview of how
to create a content blocker on iOS.
And just some of the things you can do with them.
So since we think that this block list model is the best way
to block content on the Web we've brought the same model
back to the Mac via the traditional Safari
You have been able to block content using Safari extensions
for a few years now, but the current model
of blocking can actually adversely affect performance
of your users' browsing.
With this new block list model, since Safari knows
about the block list before any loading takes place,
it is a lot faster.
Since we've optimized our compiled byte code that each
of these block lists creates,
it's a lot more memory efficient.
So if anyone has written extensions using --
if anyone has written content blocking extensions in the past,
they know about the can load method
and so we are deprecating the can load method
and if your Safari extension specifies a block list,
calls to can load will become no ops in your extension.
To add a content blocker to your Safari extension, all you have
to do is set the content blocker file
in Safari's extension builder.
This can be the exact same JSON file that your iOS app uses.
However, if you would like to provide your users
with customization options for what content to block,
once again letting them choose between different block lists
or making a block list not apply when a user is
to configure your content blocker dynamically.
You can call Safari.extension.set content
blocker from your extensions global page
and pass it your content blocker.
And the content blocker can either be an object
or a JSON string.
This API accepts both.
So that was an overview
of content blockers for iOS and OS X.
And while we are talking
about traditional Safari extensions we've made some
changes to the Safari Extensions Gallery.
And so for anyone who is not familiar
with the Safari Extensions Gallery, it is the best place
for your users to find your Safari extensions.
It is directly accessible from Safari's menu
and from Safari's extensions preferences
and it is the only place
that your users can install your extensions with only one click.
If you're serious about making Safari extensions,
you are already in the gallery.
Like I said before, it is the easiest way for your users
to discover and install your extensions.
To make things even more secure for our users,
coming soon all extensions
in the gallery will be both signed and hosted by Apple.
To take advantage of this, all you have
to do is resubmit your extensions to the gallery.
And another great thing about this is
that once you've submitted a version to the gallery,
if you submit an update your users will get automatic updates
of your extension for free.
You don't have to write an update manifest anymore.
But if you have a version that is not in the gallery,
when you want your users to update to your newest version
in the gallery, you need to add a new flag
to your update manifest saying that we,
Safari should update your extension
to the version in the gallery.
Also coming soon, the automatic updates will only be available
for extensions in the Extensions Gallery.
So those were the changes to the Safari Extensions Gallery.
I would like to move on to the last topic of the session,
Shared Links app extensions.
Shared Links is a Safari feature that shows up next to your list
of bookmarks and reading lists.
It contains a stream of links
from various sources you are interested in.
Links will show up from your Twitter, LinkedIn,
and Weibo accounts automatically.
Additionally, in iOS 8 and Safari for Yosemite,
we added the ability for users to subscribe
to their favorite websites' RSS feeds to get them to show
up directly in Shared Links.
In this screen shot you can see links from my Twitter account
and some websites I subscribe to, interspersed.
In iOS 9 and Safari for OS X El Capitan, we're adding a new way
for you to get your content directly
into Safari's Shared Links,
and they are Shared Links app extensions.
It's a new type of app extension
that has the same API for iOS and OS X.
To get started you create a new app extension just
like we talked about for content blocking.
You create a new target, and select Shared Links extension
on the platform you're interested in.
How an app extension works on a high level is that,
your app extension is called at the right time
and you return a list of NSExtension items.
In the case of a Shared Links app extension,
every NSExtension item turns directly
into a Shared Links item.
We have a blank slate of an NSExtension item
and we have the Shared Links item we are trying to create.
I would like to take you through the code to do this.
We start by defining some user info on the NSExtension item,
and the first thing we define is an identifier
for each NSExtension item.
No two NSExtension items
that your app extension returns can have the same identifier.
Next we define the URL string and this is just what's loaded
when the user selects your Shared Links item in Safari.
Next, we define the published date of the Shared Links item
and Safari uses the date of each item to intersperse items
from various sources inside of your user Shared Links.
Lastly, you can define a display name which shows up at the top
of the Shared Links item.
A couple notes.
Unique identifier must be consistent
across multiple instantiations of your extension.
So if your extension gets called multiple times
and returns the same Shared Links item,
the unique identifier needs to be the same both times
and display name is completely optional.
If it is not set, Safari will just use the display name
of your Shared Links app extension.
Next, we define the title of the Shared Links item,
which is just the attributed the title of the NSExtension item.
Then we define the description text of the Shared Links item,
which is just the attributed content text
of the NSExtension item.
Both of these will be automatically ellipsized
for you if they are too long.
So that was all the text of the Shared Links item.
What about the images that we see?
The icon in the top left corner is the first entry
in the NSExtension items attachments array.
In this example I'm using an image from my extensions bundle
but you could fetch an image from the Web here.
And the icon in the top right corner is just your apps icon.
So if you're returning multiple Shared Links items
and with different display names,
the icon in the top right corner will provide your app's branding
and to let your users know that this content is from your app.
A pro tip, if this is your first time using NSExtension item,
all of the properties on NSExtension item,
the attributed title, the attributed content text
and attachment, must be set after setting the user info.
That's all it takes to get your content inside Shared Links.
I would like to bring Alex back up to give another demo.
ALEX CHRISTENSEN: All right.
I have some content that I want to put in with the Shared Links.
I'm going to make a Shared Links extension.
Let me show you what I'm talking about first.
If we open up Safari, you can see here in the side bar
that I have a bunch of links from my Twitter feed.
This is where I want to put my content for users
of my Shared Links extension.
So let's make a Shared Links extension.
Open up Xcode.
Create a new project.
Let's make it for OS X, but you can do the same thing for iOS.
Put it on the desktop for now.
Okay. Now, from here we go to file, new, target,
and under OS X application extension we have the Shared
Links extension template that Brian just showed you.
Let's go to next.
My Shared Links extension.
That's a good name.
Activate my extension scheme.
Yeah, let's activate it, because we'll want
to run the extension with Safari.
Now, that new target -- whoa --
made this My Shared Links extension
and it put some template Swift right here.
And this Swift is pretty straightforward.
It just creates an NSExtension item, populates its user info,
gives it an attributed title and some context text.
Content text, excuse me.
And it calls complete request returning items with an array
of in this case one item.
Let's see what it does right out of the box.
Let's run it with Safari.
We are debugging this with Xcode.
Instruments wants to know what's going on.
There we go.
Now, right here we have something
from My Shared Links extension and if I click on it,
then it takes me to Apple.com, which is great.
That's what the template said to do.
Now, this is where I want to put my content, though.
So I'm going to modify this Swift a little bit.
And let me go through with you what this does.
It's pretty straightforward,
it just sends an NSURL request to my server.
HTTPS, make sure you use HTTPS,
cutepuppiesandcats.com slash data.JSON.
Let me show you the data that will be received
when this request is sent.
It is just a JSON API with some links and some content
that I might want to put into the Shared Links.
All right, so it will make a request, get this JSON.
It will parse that JSON and for each value
in that parsed JSON array,
it will get some strings out of there.
And it will populate an NSExtension item
that it just made with some things.
I used my URL as my unique identifier because I know
that for my data they are all unique.
If that's not true for your data, then find a way
to get unique values for each of these.
I have an icon that I need to add.
There we go.
There's my puppy icon.
I put it in the wrong place.
Let's put it in this demo.
I need to add it.
Adding puppy icon.
And I need to also add it to this target,
My Shared Links extension,
so My Shared Links extension has access to this icon.
And let's run this.
Let's see what it does.
Let's close Safari first.
There we go.
Let's run this and see what it does.
Instruments still wants to know what's going on.
And my content is not showing up.
Let's see what went wrong.
If you see right here, we have a bunch of errors.
If you open up the console and look
at the sys log console you'll see a bunch
of sandbox exceptions.
And that's because my extension is sandboxed;
to keep users safe,
these extensions only have access to what they need to.
This particular extension right now does not have access
to the network.
So we are trying to make an NSURL request,
we definitely need access to the network.
So if I click on the project here,
and then go to capabilities, right here I need
to check outgoing connections
to let this extension have the ability to be able to send
and initiate outgoing connections
to get this JSON data.
Now, let's run it.
There we go!
Right where we wanted it.
Now we have my content and links to my content
so I can take users to my website
from the Safari Shared Links.
So if you have some content that you want to put
in the Safari Shared Links, try making a Shared Links extension.
All right, back to you, Brian!
BRIAN WEINSTEIN: Thanks, Alex,
for showing us just how easy it is
to help your users discover your content using Shared Links
So what have we talked about today?
We've unleashed the ability for you
to write content blockers for iOS.
If you have a content blocker on the Mac,
you should definitely adopt this new API.
It will give your users a much faster
and more memory efficient browsing experience.
And if you have an extension in the Safari Extensions Gallery,
please submit it again so your new version can be signed
and hosted by Apple.
If you don't have an extension there,
this is a great time to start.
Lastly, if you have a set of links that makes sense
in Shared Links, you now have the power to put them directly
into Safari where your users can discover them
and go back to your website.
If you have any more questions, contact John Davis,
the Web technologies evangelist and coming soon,
we will have a blog post detailing all
of the capabilities of content blocking for Safari
on the WebKit.org blog.
Feel free to contact us on the Apple developer forums.
Related sessions, if you have an app
that displays Web content check
out the talk Introducing Safari View Controller.
As you saw, we used Web inspector
to help create content blockers.
For more information about Web inspector,
check out Using Safari to Deliver
and Debug a Responsive Web Design, and I will be --
Alex and I will be in Media Lab A directly after this.
Come find us if you have any questions.
Enjoy the rest of your Friday.
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.