Extensions Overview

Extensions are a way for you, as a developer, to add features to Safari.

You write Safari extensions using HTML, CSS, and JavaScript, with support for HTML5 and CSS3. Safari exposes a set of methods and properties to JavaScript for extensions to use, letting your extension do things that scripts normally can’t.

Safari Extension Certificates

To develop extensions for Safari, you first need to sign up for the Safari Developer Program online at http://developer.apple.com. You need to join the program and obtain a certificate before your extension can be installed. There is a link to the Developer Certificate Utility on the main Safari developer webpage. Your extension does not load unless you obtain and install a certificate from Apple.

One certificate supports all your extensions. The certificate functions on multiple computers if both the certificate and private key are imported from the originating computer to each new computer.

When the certificate nears expiration, create and download a new certificate from the developer site. Because the two certificates carry the same developer ID, your existing extensions recognize the new certificate. After implementing the new certificate, revoke old certificates.

What Your Extension Can Do

Safari extensions let you add persistent items to Safari, such as controls, local or web-based content, and scripts that modify web-based content.

To see examples of Safari Extensions, visit the Safari Extensions Gallery (https://extensions.apple.com/).

The Extension Parts List

An extension starts as a folder. Depending on what you want your extension to do, you put some or all of the following items into the folder:

Extension Architecture

You can think of extensions as being divided into two parts: a part that interacts with the Safari app, and a part that interacts with web content.

The part of an extension that interacts with the Safari app resides in any of your extension’s global HTML page, extension bar pages, or popover pages. The part that interacts with web content resides in JavaScript files or CSS style sheets that are injected into content pages.

The division between these parts is strict, but you can send messages between them using proxies. If the global HTML page or an extension bar page needs to act on web content, it sends a message via the webpage proxy, where an injected script can act on it.

Similarly, if an injected script needs to make use of code in the global HTML page or an extension bar, it can send a message via the tab proxy.

The extension architecture is illustrated in Figure 1-1.

Figure 1-1  The extension architecture

An extension does not necessarily need to have both of these parts—an extension can operate only on the Safari app or only on web content. For example, a toolbar button to close a window or insert a tab would interact only with the app, while a style sheet that reformats websites into black text on a white background would operate only on web content.

The Safari Extensions JavaScript API

In addition to the usual JavaScript methods, extensions have access to a special JavaScript API that lets them access the Safari app and web content. The full API is documented in Safari Extensions Reference, but this section covers the main things you need to know.

Classes and Properties

The Safari extensions API includes several classes such as SafariBrowserWindow, SafariBrowserTab, and SafariWebPageProxy, representing, respectively, a window, a tab, and the webpage loaded in a tab. You rarely, if ever, use the actual class names in your code, however. Instead, your extension JavaScript uses the SafariNamespace object, safari, followed by a chain of properties. For example:

  • safari.application.activeBrowserWindow returns the active instance of SafariBrowserWindow.

  • safari.application.activeBrowserWindow.activeTab returns an instance of SafariBrowserTab.

  • safari.application.activeBrowserWindow.activeTab.page returns an instance of SafariWebPageProxy.

As usual in JavaScript, there is more than one way to address a particular object and the chain of properties goes both ways—a browser window has a tabs property representing its tabs, for example, and each tab has a browserWindow property representing its parent window.

The Application and Extension Objects

The SafariApplication object allows you to work with windows and tabs, and to respond to commands from toolbar items and contextual menu items (also known as shortcut menu items). For example, you open a new browser window like this:

safari.application.openBrowserWindow();

The SafariExtension object allows you to add and delete buttons, menu items, scripts, and style sheets from your extension. For example, the following code snippet adds a simple black-and-white style sheet to the injected contents of your extension:

var bw = "body { color:black !important; background:white !important }" ;
safari.extension.addContentStyleSheet(bw);

You can access the SafariApplication and SafariExtension classes from your extension’s global HTML page or from an extension bar or popover. The classes are accessed as safari.application and safari.extension.

Web Content Interaction

Scripts that are injected into web content can access the DOM of webpages they are injected into, allowing them to read and modify the content. Injected scripts use the normal JavaScript API—getElementsByTagName(), innerHTML, and so on—but because they are injected into a webpage, they have the privileges of a script loaded from the same domain the content comes from. In other words, a script injected by an extension can do anything the website author’s own scripts can do.

You can also designate style sheets as injected content. Injected style sheets are treated as user style sheets, as defined by the W3C. This means that they can override styles applied by the webpage’s author if they are declared important. For example, to override the body element’s background color, you could declare:

body { background: #ffffff !important }

The style cascades in the following order:

  1. Your injected style sheet’s normal declarations are applied.

  2. The website author’s style sheet’s normal declarations are applied.

  3. Styles declared as important in the website author’s style sheets are applied.

  4. Styles declared as important in your injected style sheets are applied, overriding any previous definitions (you have the last say).

Events—Commands, Messages, and Proxies

You respond to events by installing an event listener—a function that handles a specified type of event. If you’ve written JavaScript event handlers before, you know that you can install an event listener on the event’s target element or any of its parent elements with the window at the top of the tree. The window receives events for all the elements on the webpage. In Safari extensions, there is a higher level still: the app, which receives events for all the open windows.

JavaScript programmers normally have only one place to put an event handler: the script for the webpage. In a Safari extension, there are several places where you can put an event handler, such as your global HTML page, an extension bar or popover page, or an injected script. Different events can be handled in different places.

There are several types of events in the Safari extensions API, but there are three fundamental event types you should be familiar with right away: "command", "validate", and "message" events.

Command events are generated when the user clicks an extension’s toolbar item or chooses an extension’s menu item (including contextual menu items). To handle commands, install a listener function for "command" events. Inside your listener function, test the command name for commands you are responsible for. Add a listener function by calling addEventListener("command", function, capture).

Here’s an example of adding a "command" event handler to the app:

safari.application.addEventListener("command", myCmdHandler, false);

Validate events are sent for important browser events—such as opening a window or loading a page. Prior to any command events or menu displays, these events ensure the menu items and commands are valid. You can respond to a "validate" event by disabling your toolbar item or menu item, modifying what it does, or by doing nothing if the command should be executed normally.

You can respond to "command" and "validate" events in either your global HTML page (recommended) or in an extension bar or popover.

Message events are your way to pass information between parts of the extension. Messages are sent using dispatchMessage(messageName, data). You listen for messages by installing a listener function for "message" events: addEventListener("message", functionName, false).

The message API is accessible from all parts of an extension—the global HTML page, popovers, extension bars, and injected scripts.

Proxies are used to support message passing across the app/content boundary. A proxy object represents a pair of objects on different sides of the boundary. There is a page proxy object (class SafariContentWebPage / SafariWebPageProxy) for sending messages to injected scripts and a tab proxy object (class SafariBrowserTab / SafariContentBrowserTabProxy) for sending messages to an extension bar or to the global page.

How to Create Extensions

Extensions are created using Extension Builder, which is built into Safari 5.0 and later. Enable the Develop menu in the Advanced pane of Safari preferences. Then choose Show Extension Builder in the Develop menu.

An extension consists of an extension package—a signed, compressed folder with the .safariextz extension, containing all your extension's files and a generated plist file that tells Safari how your extension is organized and what it does.

To create an extension, first make an extension folder by clicking the + button in Extension Builder and choosing New Extension. Then create the HTML, CSS, JavaScript, and media files you need and put them in the folder. The folder has the .safariextension extension when it is created.

Use Extension Builder to specify details about the structure and behavior of your extension and to build an extension package. Clicking Build creates a compressed, installable version of your extension with the .safariextz extension. For details, see “Using Extension Builder.”

Here’s a more detailed description of the things you put into the extension folder:

Global HTML Page

Your extension can have a global HTML page, but it is not mandatory. This page is loaded only when Safari first loads your extension, but it is never displayed. It exists as a container for JavaScript. You can add JavaScript to your global page inline, or include it in a separate file or files in your extension.

If you are adding items to the main Safari toolbar, it’s generally best to write a global HTML page to specify what the toolbar items do. But you can also specify what the items do in a extension bar file. For details, see “Adding Buttons to the Main Safari Toolbar.” Toolbar buttons can also invoke extension menus or popovers. The logic for extension menus generally belongs in the global HTML file as well. The logic for popovers can reside in the global HTML file or the popover file itself. For details, see “Adding Extension Menus” and “Adding Popovers.”

If you are adding items to Safari contextual menus, it’s generally best to write a global HTML page and specify what the menu items are and what they do, but again, you can also specify contextual menu items and actions in an extension bar or popover file. For details, see “Adding Contextual Menu Items.”

Putting the code for toolbar items, pop-up menus, and contextual menu items in your global page is more efficient than putting it in an extension bar file. This is because extension bar files are reloaded every time a window is opened, whereas the global file is loaded only once during the app’s lifetime.

If your injected scripts use a large amount of code or data, it should be moved to the global HTML page, so time isn’t spent reloading large blocks of code or data each time the user opens a webpage. Injected scripts can’t call functions defined in your global page directly, but injected scripts can pass messages to the global page, and the message handler in the global page can call other functions. For details, see “Messages and Proxies.”

Extension Bar Files

Extension bars are toolbar-sized strips added to the Safari frame—below the bookmarks bar and above the tab bar—and dedicated to a particular extension. There can be multiple extensions with bars installed, and multiple bars per extension. If more than one extension bar exists, they are stacked. An example of an extension bar is shown in Figure 1-2.

Each extension bar has a label that is listed in the View menu (the View menu is hidden by default in Windows, but can be accessed through the gear button) and the menu item can be toggled to show or conceal each bar in the stack.

Figure 1-2  Extension bar example
A toolbar extension with Emoji emoticons.

You can use extension bars to add controls to Safari or to display other content, such as a stock ticker, weather forecast, flight information, or headlines. Extension bars are only 30 pixels tall, so content that needs a taller display space should be shown in a popover, in its own tab, or injected into the browser content instead.

Extension bar files can access the Safari app to do things like opening and closing windows and tabs, loading URLs, responding to Safari toolbar items, and responding to menu choices in extension menus or contextual menus. Extension bar files cannot access the content layer to manipulate content loaded in a browser tab directly, however; for that you need to use injected scripts or styles (see “About Safari Extensions”). Your extension bars can send messages to and receive messages from your injected scripts.

You create extension bars using HTML (also CSS, JavaScript, and any media files). You don’t need to do anything special in the HTML to have your content displayed in an extension bar—just tell Extension Builder which HTML files are sources for extension bars.

If your extension bar uses images or other media, they can be included in the extension package or loaded from the web at runtime. It is strongly recommended that you use local media whenever possible.

Extension bar files are loaded each time Safari opens a browser window, creating an instance of the bar in every window, so if your extension bar has code or data that needs to load only once, you should put that material in a global HTML page instead.

If you want to create an extension bar, see “Adding Extension Bars.”

Popover Files

If your extension needs more space to display content than fits comfortably in an extension bar, or if you want the content to appear only when the user summons it, you can create a popover—an HTML file that displays in a pop-up window, then disappears when the user changes focus (by clicking in another window, for example).

Popover files have the same access and permissions as extension bar files, but display as pop-up windows instead of persistent bars. A popover is displayed in response to the user clicking, or pressing and holding, a toolbar item defined by the extension.

An extension can have multiple popovers, but only one displays at a time. Each popover is an element in the safari.extension.popovers array. If popovers are specified in Extension Builder, each popover file loads once, when the extension launches. If a popover is created at runtime, the specified popover file is loaded then. There is only one instance of a popover, no matter how many windows are open.

In Safari 5.1 and later, you can add popovers and associate them with Safari toolbar items. For details, see “Adding Popovers.”

Injected Scripts and Style Sheets

You can have Safari inject scripts or style sheets that you provide into the webpages Safari loads. These injected scripts and styles can read and modify browser content.

Scripts can be specified as End Scripts (interpreted when the page’s onload event occurs), or Start Scripts (interpreted before the page is parsed). Most scripts are End Scripts. Scripts that block unwanted content before it displays are the most common use for Start Scripts. You can have both Start Scripts and End Scripts.

Style sheets are applied as user style sheets, so normal declarations in them precede the webpage author’s declarations in the cascade, but !important declarations are applied after the author’s declarations, allowing user style sheets to override the webpage author’s styles.

You can use URL patterns to decide which webpages your scripts and style sheets are applied to. Use URL patterns when creating a blacklist or a whitelist for your extension. The blacklist contains URL patterns for webpages you don’t want to inject scripts or styles into. The whitelist contains URL patterns for webpages you do want your scripts and styles injected into. For details, see “The Extension Builder Interface.”

If you want to inject scripts into webpages, see “Injecting Scripts.”

If you want to apply user style sheets to webpages, see “Injecting Styles.”

The plist Files

The Info.plist file contains your extension’s metadata. This includes the extension name, author, and version, as well as information about how your extension is organized—whether it has a global HTML page, extension bars, or injected scripts, and which files are used for what. If your extension has settings, they are also defined in a plist file—Settings.plist. Settings.plist is optional, but Info.plist is required. When someone talks about your extension’s plist file, they generally mean Info.plist.

The plist files are created for you using Extension Builder, so you shouldn’t need to do anything with them yourself. But to really understand how extensions work, you need to know the plist files exist. All the fields you fill out in the Extension Builder interface are stored in a plist file.

Security

Because extensions have privileges that allow them to go beyond what ordinary scripts can do, you need to be security conscious when writing extensions. The first level of security is provided by the certificate. The certificate ensures that an extension comes from a known source (you) and prevents malicious extensions from masquerading as your extension. It is your responsibility to prevent your extension from being taken over by a malicious script, however. Follow the guidelines in the following sections to prevent security breaches.

Don’t Insert Imported Text Using innerHTML or document.write

It can be tempting to display HTML imported from the web using the innerHTML property, but it is dangerous to do so. It is equally dangerous to insert imported text into your extension using the document.write method. A malicious website can include a script that, when loaded into the HTML of your extension, gains the privileges of your extension. Furthermore, if your extension obtains the imported HTML using HTTP (as opposed to HTTPS), a forged DNS server, such as a Wi-Fi hacker, can substitute a malicious script for the requested item.

If you are displaying imported text, insert it into one or more paragraph elements using the innerText property instead of using the innerHTML property or the document.write method.

If you are displaying imported HTML, sanitize it by removing any material enclosed by dangerous tags, such as scripts or HTTP requests, before inserting the HTML into your extension. Use a whitelist of allowed tags and attributes, and remove any HTML that does not match the whitelist. Insert the HTML into your extension using DOM methods such as appendChild, which inserts only safe elements.

Don’t Use eval with Imported Text

Using the eval method to parse imported data allows the data to be executed as code, enabling malicious scripts to execute from within your extension. Use the JSON.parse method instead.

Don’t Use HTTP to Add HTML, CSS, or Scripts

Include any scripts, HTML, and CSS that your extension uses directly within your extension, or obtain them from a trusted source using HTTPS. If you import items using HTTP, a person with network access, such as a Wi-Fi hacker, can insert malicious scripts in place of the requested items.

Private Browsing

Private Browsing mode prevents Safari from storing cookies, browsing history, search history, caches, and AutoFill information. Your extension should not store any information about the user’s actions when in Private Browsing mode.

In Safari 6.0 and later, you can check if Safari is in Private Browsing mode by querying safari.privateBrowsing.enabled, or by listening for activate and deactivate events that have the SafariPrivateBrowsing object as their targets. Private Browsing will not be enabled until after the activate event is completed, allowing extensions to do any necessary cleanup. See SafariApplication Class Reference for more information.