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 content blocker. 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.
Great! 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 View Controller. 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? 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. All right. 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: mycontentblocker.
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. All right.
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 extensions API. 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.
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? 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 app extensions.
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.
Thank you! 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.