When you create a Safari Web Extension, you can help people get common online tasks done more quickly and efficiently. We'll show you how to build a new Safari Web Extension and host it on the App Store, as well as how to use the safari-web-extension-converter tool to migrate existing extensions from other web browsers like Chrome, Firefox, or Edge with very little effort.
I declared that I wanted to use the notifications API in my manifest, but that isn't supported in this version of Safari. Since notifications aren't a critical part of this extension's functionality, I can continue converting. Now let's run the app and see the extension appear in Safari. Click the run button to build and run. Here's the app that was created by the converter tool. It has the extensions icon, some text indicating if the extension is turned on or off, and a button to open up Safari's Preferences pane. It's up to you if you want to customize this app further. OK, let's open up Safari's Preferences. Note that our extension isn't here. Because I am just trying this tool out, I don't have a developer certificate yet, so the parent app is ad-hoc signed. By default, Safari won't show extensions from ad-hoc signed apps. To show these extensions, we need to first turn on the Develop menu. Click in Advanced, and then Show Develop menu in menu bar. Then in the Develop menu, Allow Unsigned Extensions and authenticate. Coming back to the Extension pane, there's our extension installed in Safari. Below the extension description is an explanation of the privileges this extension has. This extension's permissions mean it can only access the current tab's web page after the user interacts with the toolbar icon, a context menu item, or a keyboard shortcut. All right, let's turn this extension on and see it working.
The extension has a toolbar button now but its icon is greyed out. This indicates that the extension is not active on this current page. Now let's find an article about fish. When I click on the toolbar button to use the popover, the extension icon lights up. The first thing you might notice here is this broken image. We will come back and fix that during the debugging portion of this session. Now let's replace all the fish words on this page with a cool emoji. Great. We converted an existing extension and used it in Safari. For more information on this converter tool, consult the documentation linked in the Resources section. Now what if you don't have an extension made for another browser. You can create one from scratch using the Safari extension app template in Xcode with Safari web extension as the type. But it's possible that you have a Mac app that you'd like to add a Safari web extension to. Let's cover how to do that now. Here's my existing Mac app. It's an app that lets me browse and save recipes. Maybe we want to create an extension that lets me save recipes right in Safari. Let's explore how to do that. We're going to add a new target. Select File > New and Target. Make sure you have the macOS tab selected and filter by Safari. Select the Safari Extension option. You'd use the Safari Extension App option if you wanted to create both the extension and the containing app completely from scratch. Let's give this extension a name and then make sure you have web extension as the selected type. So the new target was created, which we can see here. And there's a new folder here in the sidebar with some default extension files. For those who are unfamiliar with this type of development, let's go over what's here. This information I'm about to cover is applicable to all browsers that support web extensions. First let's look at the manifest file. The manifest file defines the overall structure of your extension. For example, you name your extension by giving a value for the name key. Here the value of the name key is a special string that allows the extension's name to be localized. It's further defined in the "locales" folder in the messages.json file.
There are three main parts of an extension. The background scripts, content scripts, and a popover. There's a special API that lets an extension communicate between these three parts as well as create keyboard shortcuts, access cookies, and more.
The background key defines which scripts make up the background page. These scripts have no visible UI and can contain the logic that drives your extension. Let's take a look at the background script. Here we have a snippet of code that receives a message from another part of the extension and sends out a response. Next up, content scripts.
Let's build again.
Now if I come back into the Preferences pane I can see that my previous choices were remembered. If I go to another web page about fish, this time there's no warning badge. The warning badge won't appear every time a user visits a new site. We put the warning badge there the first time your extension wants to inject into a web page as a way to educate users about activating an extension from the toolbar item. When we click the popover, we get a similar dialogue about giving access to a web site. This time I'll allow on all web sites, but this prompt appears to make sure that the user is completely aware of the access they are giving. Back in the Preferences pane, the list of websites where your extension has access has disappeared and has been replaced by this message letting the user know the capability that this extension has. And that's how extension permissions work in Safari. Now let's talk about some best practices when it comes to these permissions. The best way for an extension to respect user intention when it comes to privacy is by using the activeTab permission. With this permission, your extension is only granted access to know things about a tab like its URL, and inject script into the web page when your user expresses intent to use your extension with that tab very clearly by using the toolbar item, keyboard shortcut, or context menu item. Optional permissions are another great way to respect user privacy. These permissions are also declared in the manifest and represent permissions that your extension would like to have but that aren't critical to the core functionality of your extension. For some extensions, you will want to not require a user gesture to take action. For example, you might have an extension that inserts a useful toolbar on certain domains. You might have a manifest that looks something like this where a script is injected onto all domains that match to apple.com. But this doesn't mean that your extension will automatically be given access to inject on those domains. Instead, the user will see your extension's toolbar icon badge the first time they visit a web site that matches what's declared in the manifest. Some extensions ask for access to everything by using the all_urls permissions key in the manifest. If your extension doesn't actually need access to all web pages, scope your web page access requests more appropriately. In summary, active tab is a great way to respect user privacy. If active tab isn't compatible with your extension, request the minimum access you need for your extension to work and use optional permissions to request more access for non-critical features of your extension. And that was an overview of privacy and permissions for Safari web extensions. Now let's talk about some tools we added to make debugging your web extensions easier in Safari. I can right-click in the popover to inspect. Well, there's an error. It looks like I've hard coded my extension resource URLs to use the moz extension scheme. That's not going to work in Safari. Let's go back to Xcode and fix this. One approach might be to change the scheme of this URL to be Safari web extension. This isn't going to work though because the host of your extension URL changes across every launch of Safari to prevent users of your extension from being fingerprinted. Instead, we need to use an extension API that forms extension URLs. It's called browser.runtime.getURL. Let's see if that fixes our bug.
Now let's handle that message in Safari web extension handler. The code that's here was provided by Xcode when we converted our extension. Using the NSextension context object, we can grab the contents of that message and then write to NSuser defaults. Make sure you specify a particular suite. The bundle identifier here matches the ID I used to group my app and app extension in the capability section of the targets. I added the app group capability here for each target and gave them the same identifier. Now that we've written to our defaults, we can read that value in the parent app. I've already created a button that will update the text in our app's UI, so we need to read and then set the text. Let's build and see it in action.
I navigate to the page and the script works. I click this button, and great! This update based on information from the extension. And that was an overview of how to communicate between your extension and app. We've gone over a lot today. First we showed how to convert an existing extension for use in Safari or how to create ones from scratch. Then we covered Safari's permission model developed with privacy in mind. We explored tools added in Safari to help you debug your extension and common pitfalls. And finally, we learned how to communicate with your extension's parent app. Now that you've seen Safari web extensions work in practice, try it out yourself. Start by downloading the sample code project that builds and runs the Sea Creator extension that you saw in this session and play around with it. We're very excited to see you bring your web extensions to Safari. So try using our converter tool to bring over an extension that you've made for another browser. We are just getting started with web extensions in Safari, and you might find that some APIs you need are missing. We are depending on your feedback to make web extensions in Safari even better, so tell us which APIs are the most important to help you provide the best experience for your users. Reach out through feedback assistant or on the Safari developer forums to let us know what you think or of any bugs you find.
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.