Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2010-506
$eventId
ID of event: wwdc2010
$eventContentId
ID of session without event part: 506
$eventShortId
Shortened ID of event: wwdc10
$year
Year of session: 2010
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2010] [Session 506] Creating Ex...

WWDC10 • Session 506

Creating Extensions for Safari, Part 2

Internet & Web • OS X • 42:21

Take your Safari extension to the next level by learning how to modify the appearance and behavior of any web page. Find out what methods and properties are exposed to JavaScript specifically for extensions, and see how your Safari extension can execute scripts, apply CSS styles, and display context menus. Learn how you can easily adjust an existing extension for use in Safari, and discover how easy it is to publish updated versions of your extension.

Speakers: Adam Roben, Cabel Sasser

Unlisted on Apple Developer site

Downloads from Apple

HD Video (241.8 MB)

Transcript

This transcript has potential transcription errors. We are working on an improved version.

Good morning. My name is Adam Roben. I'm an engineer on the Safari and WebKit team and I've been working on extensions in Safari for a while and I'm really excited to talk to you about them. So if you were at the Safari Internet and Web State of the Union on Monday you heard about how Safari 5 includes support for building extensions for new developers; and if you were in the last session just before this one you heard a lot about what extensions were and what they can do and how you can get started building them. So let's start with a brief overview of what we saw in the last session and then we'll move into the topics for this one.

So over the last hour you heard about how Safari is made up of multiple windows and each window can have multiple tabs in it and each tab has a web page. And Safari's user interface also has bars and toolbar items like the bookmarks bar and the back and forward buttons. So extensions can add into this experience in Safari. An extension can include in it other toolbar items that can be added to Safari as well as context menu items that Safari will show, and one or more bars that will be included in Safari's user interface.

Also if you want to modify web pages with your extension your extension can include content style sheets and content scripts that will be run in the web pages that the user browses to. And finally your extension can include a global page that's kind of the control unit that you use to tie all these pieces of your extension together. Now in the last session Tim also talked about how extensions and Safari are both split into an application layer and a web content layer; and how there's a separation of communication between these two that can be bridged using message passing.

So Tim gave you an overview of all of these things and he also went into a lot of detail on the application layer. Now we're going to mostly focus on the web content layer and then how to tie the two together. So specifically, we're going to be talking about three things; how to modify web pages behavior and appearance; how to add items to Safari's context menus and how to publish new versions of your extensions so that your users can get the latest versions.

So first we're going to talk about how to modify web pages and maybe the best way to start by talking about it is with a demo to show you what's possible. So here we are at vimeo.com. The folks at Vimeo have made a great HTML5 video player that's all built using standards technology. And if you were at the Delivering Audio and Video Using Web Standards talk yesterday morning you learned a lot about how you can do this on a website as well.

So I've made an extension that modifies this website to add a new feature to it. So I'll install it here; I'll just double-click on the extension and Safari will ask if I want to install it and now if I reload this page you'll see that we're going to add a new button over here on the side. We have a new lights button here. And if I click on it, the rest of the page dims out so that you can really focus on the video itself.

So this is just one simple example of what you can do with an extension. So let's talk about how this is possible. Again, we're going to be focusing on the web content layer. That's the layer that can actually interact with web pages directly and so specifically we're talking about content style sheets and content scripts.

Now as Tim told you in the last session content style sheets are added to the web pages that the user browses to. If you're familiar with Safari's user style sheet preference in the Advanced preference pane you already kind of have an idea of what these can do. A style sheet can be applied to a page to modify the size of the text, the colors on the page or the images or to hide certain elements. Now the style sheets are called user style sheets and that has a very specific meaning in the CSS specification.

But all that you really need to know is if you want to override rules that are already in the page with rules in your style sheet, you just need to mark them with the !important keyword and that will overrule any rules that the page has. So there are two important differences from Safari's user style sheet preference. You can have more than one content style sheet where Safari can only have one user style sheet, and you can apply your style sheets to specific pages while Safari's user style sheet is applied to all pages.

And we'll be talking more about how to do that in a moment. Now content scripts are very similar to content style sheets. They also run in the page. Content scripts can run at two specific points while the page is loading. We call them "start" and "end". The start time is roughly equivalent to when the DOMContentLoaded event is fired.

This is right after the main document of your page has finished loading and if DOM is available to you. But the sub resources of that page have not finished loading yet so images on the page and sub-frames and scripts may not have finished at that point. Now the end time is roughly equivalent to when the load event fires and that is after all those sub resources have finished.

These content scripts have access to special APIs that are only available to extensions, and the APIs are similar to the ones that are available in the application layer. But the content layer APIs are a little more limited and this is to enhance the security of your extension. These content scripts are interacting directly with the page and we want to provide a minimal interface so that if your extension is compromised the web page won't really be able to cause any harm. Now just as in the application layer all of these APIs are available through the Safari namespace object so it's very easy to find them.

And just as with content style sheets you can have more than one content script and you can apply them to specific pages. So let's talk about how you would modify only specific web pages instead of all web pages. Well they're really two settings that are important here-- one is the website access setting and one is the whitelist and blacklist and you can configure both of these in the Extension Builder.

Now both of these settings are based around URLs so it might be a good idea to just talk about what a URL is as far as extensions are concerned. So here's an example URL; you've all seen them before but as far as extensions care there are really only three important parts to a URL.

First on the left before the first colon is the scheme in this case http; between the double slashes and the first slash after that is the host often also called the domain, so here it's www.apple.com; and then from the first slash after the host to the end of the URL is the path; so here it's /Safari/what-snew.html.

Now you may know that URLs can also have other parts such as user names and passwords and ports and queries and fragments, but as far as we are concerned in the extension for setting what pages your extension is going to modify these three parts are all you need to worry about. So now that we've talked about what a URL is let's talk about these settings. Website access is the top level setting that affects what web pages your extension can run on.

So it controls both where you content style sheets and scripts are injected, and it also controls what websites and servers you can send XMLHttpRequests to. Tim mentioned in the last session how content scripts are running in the security context of the webpage itself and so are subject to all the same restrictions of that page which means that from a content script you can't send an XMLHttpRequest to another server.

But in the application layer we do allow that and this website access setting is how you specify which servers you need to send those requests to. Now the website access setting only cares about the host or the domain part of the URL. And we actually allow you to specify patterns so let's take a look at what some of the potential patterns are that you could list.

So this first one here is just the host or domain that we had in that last URL www.apple.com; and this will match just that single host. Another one is apple.com and that will match just apple.com; it won't match www.apple.com. But if you wanted to have your extension to be able to access both www.apple.com and apple.com and maybe also trailers.apple.com and any other subdomains that exist, you can write *.apple.com and this will match all of those hosts so you don't have to list them separately. Now there's also one more part of the website access setting which is the Include Secure Pages checkbox. If you have that checked, then you could access for example the https version of www.apple.com and the http version.

If you don't have it checked, you won't have access to the https version. So the whitelist and blacklist is the other setting that I mentioned and this controls just where your content scripts and style sheets are injected. And the control is more fine grained than the website access level. You can actually target specific paths on a particular domain instead of just targeting a whole domain. And for that you have to use something that we call URL patterns so let's take a look at some.

So here's a very simple URL pattern-- it's just that same URL that we looked at before, and so this will match that particular page and no other ones. But if you want to start matching more than a single page you can start using stars to act as wild cards.

So in this case we've replaced the "www." with "*." just like we did in the domain pattern and so this will match this scheme and path on any apple.com or apple.com subdomain. So here are some examples-- it will match it on www.apple.com; or on just apple.com or on any other subdomain. It can even be more than a single level of subdomains.

So here's another example where we've put the * in the path. This will match sites that are only on www.apple.com but anything could come before the "what-s new.html" part of the path. So this would match Safari's What's New page or iTunes' What's New page or again you could even have multiple parts of the path in there that the * will stand in for.

So a very common thing you might want to do is to match any page on any subdomain of apple.com or any other server. And so this is the form that that pattern would take. We have *.apple.com as the host which will match apple.com or any subdomain; and just /* as the path which will match any path on any of those hosts.

And so all of the URL's that we've seen so far would match this pattern and many others that I haven't gone over. Now I should also note that in all of these examples I've used http as the scheme but you could also have https to match the secure versions. So let's look at now we could actually put together this Lights Out Demo now that you've kind of learned the basics.

So here we are back on Vimeo-- I'll turn the lights back on and actually I'm going to uninstall the extension so that we can start from scratch. So I'll go to Safari's preferences and uninstall it and now we're back to a clean slate. If we reload Vimeo you'll be able to see that our button is gone because we've uninstalled that extension.

So before we actually build the extension I think it might be good to look at the code so we get an idea of what this extension is actually doing. There are two main parts to it-- there's the button that we add and then the overlay that dims out the page. And so let's take a look at the code for that. First we have a content style sheet and this style is the overlay.

Now we actually don't need to style the button at all because Vimeo has set up their style sheets in a way that we could just take advantage of their built in styles since they built their player using HTML and CSS. But here we have a rule that is for our overlay div.

It's a fixed position div that covers up the whole viewport and it starts out as transparent so that then we could fade it in. It has a black background color and we've set a WebKit transition on it to give that nice fading effect instead of having it just pop in immediately.

Now we also have a z-index property here to put it in front of all the elements on the page. But of course we want the video to be in front of the overlay and so we also have a rule that we apply to the video that increases it's c-index so it isn't in front of the overlay and doesn't get covered up.

So here's the content script that we actually run in the page. You'll notice that at the beginning of the script we start running code immediately. We don't add any event listeners of wait for a load event or anything like that; and that's because as I said you can specify that your content script run at either the start or the end part of loading. And so this is an end script here and so we're running when the load event has fired so we don't need to listen for the event explicitly-- Safari will just run us at the right time.

So as soon as the script is run we call the Add Lights Button function and you can see that function here. It just is using normal down calls to create a button element, to add an image into that element and then to add all of these into this side doc element that's built into Vimeo's HTML5 player. You'll notice here that we're using the Safari.extension.base URI API so that we can reference an image that's actually inside our extension.

So once this is in the page we also set up a click event listener for it to call this toggle lights button whenever the button is clicked. And toggle lights you'll see right down here is very simple. If the lights are on then we turn the lights off; and if they are off then we turn them on. So the way that we turn the lights on and off, here's the turn lights off function and we just create that overlay if we haven't created it before; we set its opacity to be our maximum opacity value so it will fade it from transparent to opaque.

And we also apply a class name to the video that's built into the player so that we can add that c-index property to it to pull it in front of the overlay. You'll notice that the class attribute that I've used here and the ID that we're using for the overlay itself are all prefixed based on my extensions bundle identifier and that's a good way to isolate your rules from those in the page. So finally when we turn the lights off we just update the image that we're using for the light button so I guess those nice rays coming out of the side; and we set our global lights on variable to false.

So turning the lights back on is very similar, we find the overlay, we make it transparent, we switch the button back to the old image, and then we wait for the fading animation to end by using the webkitTransitionEnd event and when that happens we remove the overlay from the page entirely and remove that class name that we added to the video to put the page back in the state that it was before we had done anything. And again just to show you that there isn't any special magic going on here, here is that create overlay if needed function.

It's just using normal DOM calls to create a div and to give it our overlays ID so that it will get the right styles, and adding another click event listener so that the lights will turn on when you click anywhere on it. So now that we've seen these, let's see how to actually put it together into an extension using the Extension Builder. So I'll come up here to the Develop Menu and go to the Extension Builder and I'll click on the + button to make a new extension. And we will call it Lights Out and let's put it on the desktop.

So here it is as Tim showed you the Extension Builder fills in some things for you automatically. Now the first thing we have to do is set that website access level that I mentioned. So it starts out as none meaning that you can't access any web pages at all but we'll change it to some since we want to access Vimeo.

So I'll add a new domain pattern and I'll do *.vimeo.com. So now we need to add some of these files to our extension. So I'll open up the extension folder here that the Builder created for us and I'll find those files that I already prepared and they're right here. We just have the style sheet and the script they already saw and then the two images for the light button.

So let's add those to our extension. And the Builder will automatically detect that these have been added so we just have to come back to the Builder and tell it about our end script which is right here in the popup menu and the style sheet that we added.

Now down below here we can specify whitelist and blacklist. But Safari actually makes this a little bit easier for you. If you want to access every web page on all the sub-domains of vimeo.com, Safari will actually add that whitelist pattern for you-- the one that I showed before that *.apple.com/*. It's done the same thing here for *.vimeo.com based on the website access level. All right so let's install this and see what happens.

So I'll click Install and we'll reload Vimeo and you can see that the lights button has been added and if we click it, [Laughter] I guess it doesn't have as much power as we thought. It only turned off the lights on the page but it does seem to be working so you can see how the Extension Builder makes it very easy to put these pieces together and get your extension working right away. So we've written this extension and it works pretty well and we sent it out to our users and people are loving it.

Well one thing that we've heard from some users is they really don't like that exact shade of gray that we chose for that overlay and they'd really like to customize it. So an obvious way to do this would be to add some settings to our extension. Tim showed how your extension can have settings that are right in Safari's preferences just alongside all the other preferences that Safari contains. So we'd like to do this for our extension.

But as Tim also mentioned in the last sessions settings are something that are up in the application layer and of course our content script is down in the web content layer. And so we're going to need some way to get the information about the settings from the application down into the content script and into the page. So the way to do this is through messages.

Messages are implemented as events in Safari and they're very simple. They really just have two parts; there's a name and there's some optional data that comes along with it. So here is some examples. We could have a message where the name is "get-setting" and the name of the data of that message is "opacity" to say what setting value we want to retrieve; or we could have a message where the name is "show-bar". You know if you only had one bar in your extension you might not need to say anything other than show that one bar that we have and so here we don't have any data.

Here's another message called "send-xhr" and you can see that we're sending an object as the data of the message telling it what server and what method to use for this XMLHttpRequest. And again this is useful if you need to be sending an XMLHtdpRequest to another server. The content script can't do that directly but the application layer can do it for you and so this message will let that happen.

So these messages are sent using two proxy objects. There's one to go in each direction from the web content layer up to the application; and from the application back down. So going from the content back to the application you would do it like this with safari.self.tab.dispatchMessage. Now this tab object while it looks like the tabs that you see up in the application layer, it's actually just a tab proxy and mostly the only thing that it can do is send a message up into the application.

Going in the reverse is very similar; from the application to content you'll find any tab object so here we're getting the active tab in the front most browser window and you use its page property. Now this isn't the actual web page; it's a page proxy and you can use it to send a message using dispatchMessage again down to the real web page.

So an illustration of what we're going to do in our extension to implement these settings, when the page loads we need to find out the current setting value so as soon as the user clicks on the light button the lights will go out to the right level. And so to do that when the content script runs it will send a message up to the application and in this case the global page is going to receive it. Now you could also send a message and have a bar receive it.

So once the global page gets that message it's going to retrieve the opacity value out of settings and make a new message and send it back down to the script where the script will act on it. So let's see how to do that. So the first thing that we need to do is in that content script we need to send that message up as soon as the page loads to find out the preference value.

So let's go back to our content script and if you'll scroll down a little bit farther you'll see that I've already written that code. So as soon as the script runs we'll send a message that we're calling get maximum opacity and we also add an event listener for the message event so we can receive messages back from the application.

When we receive a message event we first check its name, and if that name is the set maximum opacity message that we are expecting back, then we update our maximum opacity global variable. And we also update the overlay in case it's already showing. These messages are sent asynchronously and so it's possible although unlikely that the user could have already interacted with the page.

So an update overlay was needed. Here we just have normal DOM calls to find that overlay element and update its opacity value so it's all very simple. Now I mention that we are going to be using a global page to receive these messages so let's look at that.

This is our global page it's just a normal HTML page and it adds an event listener for the message event to receive that message from the content, and when we get that message it's that get maximum opacity message that we are expecting, then we send a message back down to that content script and tell it the maximum opacity by retrieving out of the settings object. Now you'll notice that we're using event.target.page here to send the message back down into the particular page that sent us the message; event.target is the tab that contains the page that sent the message.

So there's actually one other time that we would like to be able to update the opacity of this overlay and that's if the user changes their preferences while the overlay is already visible. And so there's actually a way to do that. Every time a preference changes we send a change event to the settings object and so the global page listens for that event here.

And when we get that change event the event will have a couple of different properties on it that help us determine what has happened. So the first one is the key property. This tells you which setting actually changed so in this case if it's our maximum opacity setting that we're going to loop over all of the windows that are open and all the tabs in all of those windows and send a message down to each one of them telling them about the new opacity value.

And we'll just use that same set maximum opacity message that we used before. And the way that we get that new value is it's right here on the event-- event.newvalue gives you the new value of the preference. So let's see how to put this together into our extension. We'll come back to the Extension Builder and really the first thing that we need to do is to get this global page into our extension folder so the Extension Builder can find it.

So let's open up our extension and we'll copy that global page over that I've already written. And so now that it's in our folder the Builder will find it and if we open up this menu it says global.html that's the one that we want. And now we also have to add the setting.

If we were to go into Preferences right now, you could see that we don't have any UI here for setting up what the opacity of that overlay is. So I'd like to add a slider so that users can really tweak it to their exact liking. So we'll come down here to the bottom of the Builder and click on the New Setting item button.

And now the first thing we have to choose is the type; you can see there are a lot of different controls here like text fields and checkboxes and popup buttons but we're going to go with the slider. We'll give it a title which is what is shown in the UI to the user so we'll call it Overlay Opacity.

The key is the settings key that we use in the API to retrieve the value so I'll call that Maximum Opacity; the default value we'll set to 0.8 which is the same one that we had in our content script before; the minimum and maximum value, we'll have the slider go from 0 to 1 because those are the values that the CSS opacity property accepts; and then we'll set the set value to .01 so users can really tweak to exactly the way they want.

So now if I click reload and we go back to Safari's preferences and click on our extension you can see that we have a slider already. I didn't have to write any code; the Extension Builder did it all for me. And if I reload Vimeo so that our content script will be updated, if we turn off the lights and then come back to Preferences here we can actually slide that slider around and see the opacity changing live because of that change event.

[ Applause ]

[Adam Roben]

So let's go over what we've learned in this section. We talked about how to use injected content meaning style sheets and scripts to modify web pages; how you can use URL patterns and domain patterns to specify what web pages you modify; and how you can use messages to tie together the web content layer and the application layer of your extension. So next up is adding context menu items and again we're just going to start out with a demo.

So I'm going to uninstall our Lights Out extension here and install that same Twitter extension that Tim was showing you so I'll just double-click on it and choose to install it. So again we have that Twitter bar here that Tim already showed you but I'll hide that for now because we don't actually need it.

So the basic idea of this context menu item is that I am a new Twitter user and I don't have a lot of content on my Twitter page yet and I would like to get some so this extension has made it really easy for me. If I'm browsing around the web and find something interesting that I really think people should know about like WebKit is an open source browser engine; that sounds pretty important so I'll just right click on that text and you can see in the Context Menu we have this new item that's been added by the extension that's called "Tweet This Text". And so if I click on that the extension will send that text off to Twitter using Twitter's API and post it to my page. There it is and we even have a link back to the original page so this makes it really easy to add new tweets.

So let's talk a little bit about how you could do this in your extension. So in order to add context menus in your extension first you need to know how they work in Safari. So the idea is very simple when the user right clicks on a web page, WebKit itself makes a context menu; but before that context menu is shown to the user it's sends the context menu up to Safari and gives Safari a chance to customize it. And Safari in fact does this; it adds Safari-specific context menu items such as open link in new tab.

Now extensions do something very similar; in fact the beginning of the process is identical. The user right clicks and WebKit creates a context menu, it sends it off to Safari and Safari gets to customize it. But then before the context menu is shown to the user, Safari gives the context menu to each extension that is installed and the extensions can add their own items. So the context menu items before we go any further are very simple. They're very similar to the toolbar items that you saw in detail in Tim's talk. They are made up of four parts.

There's an Identifier which is how you refer to the item in your own code; there's a Title which is shown to the user in the context menu; there's a Command string which is just a string that you use internally to represent the action that this context menu item performs; and then they have a Disabled state.

Now we never show disabled items to the user they would just clutter up the menu and so if you have a disabled item it's actually hidden from the menu entirely. So the way that you get to add your items to the context menu before it is shown is by listening for the contextmenu event. This is sent up in the application layer.

Specifically it's sent to the tab and so that's the target of the event and then it bubbles up to the window and then to the application object. On this event is a context menu property and that context menu is how you interact with the context menu that will be shown and it has a few methods that you can use. There's appendContextMenuItem, insertContextMenuItem, and there's a contextMenuItems array that gives you access to the items that you've added so far. Now this array will only include the items that your extension adds.

You won't be able to see WebKits' items or Safari's items or items from other extensions, and this prevents extensions from interfering with each other or with the default behavior of the browser. Now you can also specify these items in the Builder. If you find that you're adding the same item every time it might be easier just to specify it once in the Builder and Safari will add it to the context menu for you without you having to listen to the contextmenu event. Now the other events that come along with context menu items are the "validate:" and "command:" events that you also use for toolbar items.

Now the validate event is sent for each item just before the menu is shown and this is your chance to disable or hide those items. This is particularly useful when you've added those items in the Extension Builder. Safari will put them in the context menu for you automatically but maybe you don't want to show them right now and so the validate event is your chance to hide them before they're shown.

And then the command event is sent when a particular item is clicked and that will tell you which action to perform. So of course with context menu's the real point is that they're contextual. What is in the menu and what happens when you click on it depends on what the user right clicked in the first place.

And so you need to find this out to really provide a good contextual menu. Now of course as I said the contextmenu event and the validate and command events are all sent up in the application. But the user is right clicking down into the right content layer and so you need to find out what was clicked on. Now you could just do this using messages but there's actually an easier way and a better way. The way to do it is in the content meaning a content script, you can listen for the contextmenu event in the web page.

This is something that's part of the DOM, it's been in WebKit for a long time and in the past really the only useful thing you could do in this case was to prevent a context menu from showing at all say if you had some custom menus to show in your page. But with extensions you can do something new. We have an API on that tab proxy object, safari.self.tab.setContextMenuEventUserInfo. And this is your way to associate some bit of information with this particular contextmenu event.

So after you've called this in the content script, then up in the application when you receive that context menu or validate or command event, this UserInfo will be available to you as a property on the event itself; and so you can use that context to determine whether to hide or show a particular item or exactly what action to perform.

So let's look at how we do this or to tweet this text item. So I'm going to go back to the Builder and I'm actually going to add the Twitter extension which we've already written so instead of choosing New Extension I'll choose Add Extension and I'll navigate to where we have it saved.

And so now here it is in the Builder just like you saw it from Tim. Now if we scroll down you'll see that under Context Menu Items we've added an item here. So the name of it is "Tweet This Text" and the identifier and command are also both "Tweet This Text".

So Safari will add this item for us so now let's look at the code that handles the item. First we have a contents script that listens for that contextmenu event in that web page again this is the DOM contextmenu event; not the one that we send up in the application layer.

When we get that event we call that setContextMenuEventUserInfo API we pass the contentmenu event and then we get the selected text on the page. Now if there's no text selected this will just be an empty string and we'll be using that information shortly. So now up in the global page that's where we listen for the validate and command events.

You've already seen some of this code in Tim's demo so here's our command event listener and here's our validate event listener. So first let's look at validation. Here we are in validate command now if the command that we're validating is "Tweet This Text" command meaning that this is for that context menu item, we choose to disable the item in some cases and remember disabling really means that it will be hidden. So the case where we disable it is when the user info which is the selected text in the page is empty. When the length is zero we hide the item because there's nothing to tweet in that case.

Now when we receive a command event that code is right up here in Perform Command. So we have another command that we use for a toolbar item but here is the one for our context menu item-- again it's just "Tweet This Text". So all that we do is take that same user info which is the selected text, we surround it in some curly quotes and then we append the URL of the front most tab which is where this context menu is being shown.

And so that is the Tweet Text and all we do is sent that off to Twitter using their normal API and a cross-domain XMLHttpRequest. And because in the Builder we've specified that we need to be able to access-- well in this case we've actually changed the access level to all pages, so that will let us both send the XMLHttpRequest to Twitter and it will also let us show the Context Menu on any page on any server.

So that's all that it takes to put this together-- it's very little code and very easy. So just a quick recap to add context menus into Safari the best way to do it is to first listen for the contextmenu event in the web page and that's your chance to provide context to your application so that it knows which items to add. Then you can listen for the contextmenu event up in the application layer and that's your chance to add items. Now you can also add the items using the Extension Builder.

Then the validate event is sent before the menu is shown and that's your chance to disable or hide items; and the command event is when you should perform the selected action that's sent when the user clicks on one of your items. So that's everything for context menus. Now let's talk about publishing updates. So say you've written this great extension and a lot of people are using it and they all really like it and now you've made a better version that you want people to install.

So what are your options? Well you could of course post it on your web page and hope that people go to your web page frequently enough and decide to install it; or if you are a really kind of naggy web developer you could make a mailing list and send e-mails to all of your users and hope that they don't filed as SPAM and hope people actually read them and don't hate you forever because of it; or there's actually a better way and that's to have your updates listed right in Safari's preferences. Safari will automatically discover updates for the extensions that are installed and present them to the user in this UI and the user can install them individually or install them all at once or even check a checkbox to install all the updates automatically as Safari discovers them.

So this is an opt-in feature that you have to opt-in to each extension that you write. And the way you do it is very simple it's through a file that we call the update manifest. This is just a simple XML file that you host on your web server and Safari downloads it periodically. And what the file contains is a list of all the extensions that you develop.

Now for each extension you just need to list a little bit of information. First you need two different identifiers-- one of them is the extension's bundle identifier that you enter into the Extension Builder, and the other is the identifier that's associated with your developer certificate. These two identifiers together uniquely identify your extension.

Now next you need two version numbers. The first is the internal version number that Safari uses to decide whether one version is newer than another. And the other is the display version that's actually shown to the user in the UI. And then finally all you need to list is the URL where Safari can download this particular version of your extension.

So if you make this file and put it on your web server Safari will download it periodically, and any versions or extensions that the user has installed that are newer in the update manifest will be presented in Safari's preferences making it very easy for users to discover and install your updates.

So you've seen a lot of demos in the last session and in this one that have shown you little bits of each part of the API, the extensions and how to use them and implement them. But also in the State of the Union on Monday you saw some great demos of some really awesome extensions that kind of pull all these topics together. And you might be wondering how can I write one of those. So to show you that we actually have Cabel Sasser here from Panic to show you how Code of Notes was constructed.

[ Applause ]

[Cabel Sasser]

All right after listening to all these super smart Safari people I think you guys know probably 90% of what you need to do to develop an extension. I'm going to talk a little bit about the remaining 10%, the stuff that we learned through developing the code of notes extensions that we didn't expect, some tips, some tricks, little things like that.

Code of Notes is a website annotation tool which provides a new toolbar in Safari and allows the user to basically scribble directly on a web page. We have a highlighter and we have sticky notes and things like that. So this was kind of an interesting thing to build and there are a lot of pieces going on here.

First of all we have the toolbar item that we install; we also have our custom toolbar which you can see here. And one thing that we did with that toolbar that's kind of cool is that the bulk of that with the exception of the glyphs on the buttons it's all CSS style; so those buttons are all border radius, gradients, so they can scale well and they're super flexible so I would encourage you guys to use CSS styling as much as possible. Let's see if I can show you that actually. Here's what the bar looks like.

If I zoom that up you see that everything sort of scales appropriately; so that allowed us to really quickly develop this toolbar without having to do a lot of graphics work in Photoshop. So we've got the bar; we've got a global HTML page which I know has been already talked about; and then we've got some JavaScript that we're injecting into every page. The global HTML page mostly controls whether or not this bar should be displayed.

One thing that we learned that was a surprise for us of course we're injecting our JavaScript into every page because we never know when a user is going to want to start drawing. So one thing we learned that a lot of pages imbed a lot of iirames in their pages and your injected JavaScript will also be injected into all of those iirames so we were seeing a lot of duplicate events and surprisingly weird stuff happening until we finally realized that oh it's that little ad in the corner.

So beware of that if you're injecting JavaScript that it will go everywhere. And what we did as a suggestion from these guys we're checking if window top equals window just to make sure that we're only injecting into the top most element. So how are we doing this drawing? Basically, when you activate this tool, actually open the bar, there's a giant canvas element transparent on top of the entire page.

And the canvass is-- if you guys have ever done any work with canvas it's basically-- gives you drawing context and we're just drawing, we're capturing the mouse event and we're scribbling on that canvas. Now one thing that's kind of interesting is if we've got a canvass over that entire page, how are we allowing you to do this text tool? The text tool allows you to actually edit the text on the page directly. If the canvas is covering everything you would think that the pointer wouldn't be able to click through it to get those text events and edit the text.

We're actually setting a CSS style which is pointer-events: none. We set that on the canvas which basically means that the pointer events have been passed through. The canvas no longer gets the pointer events so that was a cool solution to a problem that kind of had us scratching our heads for a little bit. Now there's a couple of interesting things-- the sticky notes that we're doing here are actually dragging around using the actual drag and drop events.

We set the body to the drop zone so of course you can drag them around, then things get really hairy with the notes button we've implemented; because what happens here is we have our style of our postcard and we know what we want our text to look like. But the page that we're injecting this into might have styles that override that; they might have body text set to a huge font, or do things that might mess up the appearance of our postcard.

So we have a crazy little trick where we walk through all the style sheets and essentially disable them, but while we're walking through them we're also storing them and making a note of them so that if a user were to hit cancel and wants to see the page as it looked originally, we now reset all those styles back to what they were before.

So an earlier version of this plug-in actually put the postcard in a new tab and that was certainly a lot easier from a developer perspective because we didn't have to do these tricks. But having it be in the window not opening a new tab is much better; but just something to think about that you're in an environment where there are a lot of styles and things to consider that might surprise you. Some people have asked me how we are doing the screenshot thing-- how we actually get an image of the tab and shrink it down.

If you're looking for the documentation and you're searching for screenshot, you probably won't find it. It's actually called visibleContentsAs DataURL and what that does is take the image contents of the tab and basically give you a standard data URL, base64 encoded I believe, of a PNG version of the tab so look for that visibleContentsAsDataURL. And last but not least you've probably seen in the demo that we do a little animation at the end that people seem to love where a stamp comes down here and everything flies off.

I'm sure you've been hearing this a lot this week but it's all CSS animation; it's all a class that we make called Fly Out that sets the WebKit transform and when the user hits the button we just apply that class to that stamp image, it flies down on the page, it's off and it's running.

So I think that covers most of the stuff that kind of surprised us but we did do this in a short period of time and we're super happy with the results and we think that when you guys dig in-- like I said you've got 90% of what you need and the remaining 10% is pretty easy to solve. So I look forward to seeing what you build.

[Adam Roben]

Thanks Cabel that was really great. I should also mention that we're very excited to be hearing from Cabel and the other guys at Panic and from all of you about what things about extensions you find surprising. Extensions are a developer-only feature at the moment and so we're looking for a lot of feedback in ways that we can improve it. So let's go over a quick summary of what you've seen today.

We've talked about how to modify web pages using content style sheet and scripts; we've talked about how to add context menu items in Safari using the Context Menu and validate and command events; and we've talked about how to publish new versions of your extension by opting into Safari's autoupdate discovery mechanism by posting an update manifest on your server and putting that update manifest's URL into the Builder so it will be embedded into your extension.

Now if you want to find out more information about all of this you can contact Vicki Murley, our Safari Technologies Evangelist; you can also go to the Safari Dev Center where you'll find a lot of documentation. That's also where you sign up for the free Safari Developer Program and get your free signing certificate. And you can also use the Apple Development Forums and we have Apple engineers looking through those forums and trying to answer people's questions all the time.