Creating a simple Safari App Extension in 10 minutes
Safari app extensions can help to extend the web-browsing experience by bringing some of your app functionality to Safari browser
Let’s start by creating a new Xcode project. Open Xcode, click on “Create a new Xcode project” and then select “Safari Extension App” template under “macOS” tab.
In the next screen enter project details i.e. Name, Bundle identifier etc. Make sure Type is set to “Safari App Extension” and Language is set to “Swift”
Click on next to create the project. Don’t worry about all the files and code that Xcode generates. For now build and run the app.
You should see a screen like this
Next open Safari browser and go to preferences (or press Cmd+,) to see preferences pane. You should see a new extension called “MySafariExtension Extension 1.0” added by the app under the “Extensions” tab
Enable the extension by clicking the checkbox next to the extension name. You should see a new icon added to your toolbar as shown below
Congratulations!!! You have created your first Safari extension 🚀🚀
Getting into Action
OK! we have our extension up and running but it doesn’t do much. Clicking the toolbar icon doesn’t do anything. What is happening?
Let’s get to project and see the code that is generated by Xcode. Ignore the folder “MySafariExtension” this folder contains code to create a mac app (which is not the scope of this article). Our focus is only on the second folder called “MySafariExtension Extension” this is where all our extension code lives.
Go ahead and open the folder you should see the following files. We’ll go into detail about what each file does in the next section.
Showing popover from toolbar icon
As we have seen, clicking on the toolbar icon doesn’t do much. Wouldn’t it be nice to show a popover when we click on the icon? Hmm.. yeah may be, but how can we do that?
There are couple of things we need to understand
SafariExtensionViewController
Firstly this is the view controller that is responsible for showing User Interface from the associated “SafariExentionViewController.xib” file. The .xib is used to build the user interface
Go ahead and open the xib file. You’ll see the interface builder with a Custom View and Label.
If you don’t see the document outline click on the icon next to “View as” in the bottom left corner
Go ahead and change the label text. Click on the label and open property inspector and change the title property to whatever text you want (alternatively double click on the label to change the text). Align the label in the centre of the view as shown below
Info.plist
OK, we have our interface ready. We want to display this when user click on the toolbar icon.
Open “Info.plist” file expand NSExtension key you should see a property called Action inside SFSafariToolbarItem property
Change the value of Action property from Command to Popover. This will show a popover when we click on the toolbar icon. After the change your Info.plist should look like this
Now go ahead and run the project. Open safari and click on the toolbar icon. You should the User interface that you built earlier
Awesome job!!! Next we’ll see how to add our own command to the context menu.
Adding items to Context Menu
What if we want to show an action item when the user right click (or control-click) on a webpage. The menu that appear when we right click is called a context menu
Let’s say we want to get the selected text from a webpage so that we can perform some action on the text (like translating or find meaning etc.). To do this we can add a context menu item
Go ahead and open Info.plist file and add the following property inside NSExtension dictionary. SFSafariContextMenu is responsible for adding new items in the context menu
Make sure the type for SFSafariContextMenu is set to Array. Add a new item in the array by clicking the plus icon and make the new item type (in this case Item 0) Dictionary. Then add to keys “Text” and “Command”
Text is the name of the item that is shown in the context menu and command is what gets passed to the handler when the menu item is clicked.
Since SFSafariContextMenu is of type array, you can add multiple items.
Now if you run the project you don’t see the new context menu item. This is because by default our extension doesn’t have access to all webpages. To change this open “SFSafariWebsiteAccess” property within NSExtension and, delete “Allowed Domains” key and change the value for “Level” property from Some to All
Once you have done all the changes, your Info.plist file should look as follows
Now build and run the project. Safari will show the following dialog informing that our extension is requesting additional permissions.
Click on “Open Safari Extensions Preferences” and enable the plugin. At this point Safari asks if the plugin should be turned on, click on “Turn On”
Open any webpage and right click, you should see our new and shiny menu item added to the context menu
So far we haven’t written a single line of code to accomplish all this. Now it’s time to dive into some code to connect the menu item to the extension handler.
Connecting context menu item to code
Safari emit “contextmenu” event when the context menu is shown. So we need to listen to this event. Add the following code in script.js (inside Resources directory)
document.addEventListener(“contextmenu”, handleContextMenu, false);function handleContextMenu(event) { let selection = window.getSelection().toString(); safari.extension.setContextMenuEventUserInfo(event,
{ “selection”: selection });}
Whenever context menu is presented, our “handleContextMenu” function is called. Inside the function we are getting the currently selected text and passing that information into userInfo dictionary.
Now we need to write a function in our swift code to receive the information from the extension when the context menu item is clicked. Thankfully Swift provides a function called contextMenuItemSelected and we can simply override this function in our SafariExtensionHandler class
Open SafariExtensionHandler.swift file and add the following code
override func contextMenuItemSelected(withCommand command: String, in page: SFSafariPage, userInfo: [String : Any]? = nil) { switch command { case “Find”: if let userInfo = userInfo,
let text = userInfo[“selection”] { NSLog(“Received text: \(text)”) } default: break }}
This function is called when our context menu item is clicked. If we have multiple menu items defined in the Info.plist, we can use a switch statement to execute code based on the command received.
Passing data back to the browser
Now that we got data from the browser, we can do whatever we want with that data. But how can we pass data back to the browser
We can use dispatchMessageToScript function to pass data back to the browser
Sending message from handler
Inside our SafariExtensionHandler we need to call dispatchMessageToScript function whenever we want to pass data to browser. Add the following code to SafariExtensionHandler.swift
func sendResponse(to page: SFSafariPage) { page.dispatchMessageToScript(withName: “Find”,
userInfo: [“response”: “I am message from handler”])}
Call the above function from contextMenuItemSelected function passing in the page as parameter. Your final code for contextMenuItemSelected should look like this
override func contextMenuItemSelected(withCommand command: String, in page: SFSafariPage, userInfo: [String : Any]? = nil) { switch command { case “Find”: if let userInfo = userInfo,
let text = userInfo[“selection”] { NSLog(“Received text: \(text)”) sendResponse(to: page) } default: break }}
Configure script to listen to messages
Finally we need to listen to “messages” event in the browser. When we send data to the script, this event is triggered. Add the following code to script.js file
safari.self.addEventListener(“message”, onMessageReceive, false);function onMessageReceive(event) { window.alert(event.message.response);}
Whenever message event is triggered, onMessageReceive function is called. We get the data in the message attribute of the event object. So we can use event.message.response to get the response (response is the key we used in the dispatchMessageToScript function). For the purpose of demo we are showing it as an alert, but ideally you should do whatever action appropriate for your use case
If all goes well (if not please retrace the above steps) 😀 you should see the following dialog in your browser window when you click on Find Meaning menu item
Well done 🙌🏻🚀 you have learned how to create an extension, show UI when toolbar item clicked, add context menu, passing data between script and your handler code.
Use your new found knowledge to explore and create extension for your applications. Happy coding 👨💻👩💻
Get the code for this article https://github.com/yogasaikrishna/MySafariExtension
Thank you
Useful Resources