Adding Buttons to the Main Safari Toolbar

Safari has a user-customizable toolbar that can contain a selection of buttons, such as a Home button, Zoom button, and New Tab button. Your extension can define new toolbar items that can be installed in the toolbar. These items also appear in the Customize Toolbar panel.

You control the actions of a toolbar item from either the global HTML page or from an extension bar by installing a listener function for the "command" event.

Your extension can add more than one button to the toolbar, but if you are adding more than a few, you should not have them installed by default; you might also consider creating an extension bar for them instead.

Adding a button requires three steps: creating an image, filling out the appropriate fields in Extension Builder, and adding logic to make the button do something.

In Safari 5.1 and later, you can add either a pop-up menu or a popover window to a toolbar item. See “Adding Extension Menus” and “Adding Popovers” for details.

Creating an Image

Buttons on the Safari toolbar are largely transparent, allowing them to be filled with the appropriate gradient for the current OS X or Windows user interface. You do not need to draw the button itself, only the opaque part of its contents.

If you are used to working with alpha channels, create a 16x16 image consisting solely of an 8-bit alpha channel, with alpha set to 255 for the transparent part of the button (including the outline); alpha set to 0 for the opaque parts of the button, which will appear in black; and intermediate alpha values for anti-aliasing.

If you are not used to working with alpha channels, create a 16x16 pixel image with a transparent background. Fill in the button contents in black, or simply set the opacity for those pixels to 100%. If you draw or put text on the button in black, notice that some pixels are gray. This is anti-aliasing. These pixels are not completely black, so they are shaded. This is illustrated in Figure 6-1.

Figure 6-1  Anti-aliased text in black on transparent background

If you save your image as-is, these shaded pixels appear as black because they are completely opaque. To preserve the anti-aliasing, make these gray pixels partly transparent. Depending on your image editor, this may be accomplished by partly erasing the pixels or by setting the pixel opacity directly.

Save your image as a .png file.

Setting Up Extension Builder

If you have not already done so, click the + button in Extension Builder, choose New Extension, and give your extension a name. Create an image for each button you are adding and drag it into your extension folder.

Click New Toolbar Item in Extension Builder. This expands the Toolbar Items pane, as shown in Figure 6-2.

Figure 6-2  Adding toolbar items

If you select the Include By Default checkbox, the item is installed in the toolbar when the extension is installed. Otherwise, the user must choose to add the item in the Customize Toolbar window.

Responding to Commands

When the user clicks the button, Safari emits a "command" event, if one is specified. The command property of the event is the string you entered in the Command field in Extension Builder. If you left the Command field blank, the identifier is used instead.

You can respond to the "command" event by installing a listener function in your global HTML page or an extension bar or popover.

You can’t receive the "command" event in an injected script. If you need the command to initiate an action in an injected script, respond to the command in the global HTML page or an extension bar and send a message to the script. For details, see “Messages and Proxies.”

At various times, such as when a tab is added, Safari will ask you to validate the command. If there is any possibility that the command could be invalid, you should add a listener function for the "validate" event.

Your validate function should verify that the command is ready and should be enabled. For example, if your command reloads the active tab, you should verify that the active tab has a URL to reload. If the tab is empty, your validate function should disable the button. You can also modify the item’s image to reflect a modified behavior.

The "validate" event is fired when an item is added to the toolbar, so this is also a good moment to update badges.

You can have multiple UI items that issue the same command, such as a toolbar item and a contextual menu item. You can use the same event handlers, regardless of the source.

If your functions are part of the global HTML page, you should register your listener functions with the app:

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

safari.application.addEventListener("validate", myValidateHandler, false);

If your functions are part of an extension bar, you should register your listener functions with the extension bar’s parent window:

safari.self.browserWindow.addEventListener("command", myCommandHandler, false);

safari.self.browserWindow.addEventListener("validate", myValidateHandler, false);

While you can implement the event handlers in either a global HTML page or in an extension bar, it is more efficient to use a global HTML page, because the code is loaded only once, when Safari loads the extension, instead of once per window.

Deciding Where to Respond

When your button is clicked, a "command" event is generated. You can listen for the event in either a global HTML page, a popover, or in an extension bar. If you put the event handler in the global file or in a popover, you should register for the event at the app level. If your event handler is in an extension bar, you should register with the extension bar’s parent window.

The difference is that there is only one instance of the popover or global HTML page’s functions, but there is an instance of an extension bar in every open window.

If every instance of the extension bar registers for events at the app level, every instance responds to the command. If each instance registers with its parent window, only the instance in the window where the button is clicked responds to the command.

Using the global page is more efficient, as it loads only once. Furthermore, you shouldn’t create an empty extension bar just to hold an event handler. If your extension has a bar, however, it might make sense to put the event handler there, particularly if the action it takes is window-specific, like rearranging the tabs.

The only time you might want to put an event handler in the extension bar and register it with the app is if your command acts on all open windows. Then your choice would be to iterate through the windows in a global function or have a function local to each window that acts independently.

If You Respond from a Global HTML Page

  • Register with the app: safari.application.addEventListener().

  • The window the event comes from is: event.target.browserWindow.

If You Respond from an Extension Bar

  • Register with the parent page: safari.self.browserWindow.addEventListener().

  • The window the event comes from is: self.browserWindow or event.target.browserWindow. The two are equivalent.

Example: Implementing a Reload Button

Listing 6-1 responds to the "reload-page" command event and the "reload-page" validate event. The validate handler disables the control if there’s nothing for it to do, but the command handler checks anyway, in case things have changed since validation.

Listing 6-1  Reload command and validate handlers

function performCommand(event)
{
    if (event.command === "reload-page") {
        var currentURL = event.target.browserWindow.activeTab.url;
        if (currentURL)
            event.target.browserWindow.activeTab.url = currentURL;
    }
}
 
function validateCommand(event)
{
    if (event.command === "reload-page") {
        // Disable the button if there is no URL loaded in the tab.
        event.target.disabled = !event.target.browserWindow.activeTab.url;
    }
}
 
// if event handlers are in the global HTML page,
// register with application:
safari.application.addEventListener("command", performCommand, false);
safari.application.addEventListener("validate", validateCommand, false);
// if event handlers are in an extension bar,
// register with parent window:
// safari.self.browserWindow.addEventListener("command", performCommand, false);
// safari.self.browserWindow.addEventListener("validate", validateCommand, false);