372 Part VI — Creating Extensions and Themes This event handler (myNewWebPageLoaded) will be called each time a new page is loaded inside the browser. 3. The myNewWebPageLoaded function can examine and modify the loaded page using the DOM interfaces. For example, we can create a function that will add a tooltip with the link target URL to every link found on the web page: function myNewWebPageLoaded(event) { var webPage = event.originalTarget; var allLinks = webPage.getElementsByTagName(“A”); for(var i=0; i < allLinks.length; i++) { var link = allLinks[i]; link.setAttribute(“title”, link.href); } } As you can see, we obtain the root element of the loaded web page from the originalTarget property of the event parameter. Then we get a list of all the links using the getElementsByTagName DOM method. Finally, we go over all the link (anchor) elements and set their title attribute to the value of their target URL. Now, when you hover over a link on the web page, you will see a nice tooltip displaying the link target URL (see Figure 17-28). F IGURE 17-28: A link with the newly added tooltip 25_596500 ch17.qxd 6/30/05 3:14 PM Page 372 373 Chapter 17 — Creating Extensions Something similar can often be achieved by creating a bookmarklet, a small JavaScript snippet that is saved as a bookmark and that when clicked can examine and modify the current page. The mechanism we introduced in this section is much more powerful and flexible. Advanced Packaging This section explores additional packaging-related options and techniques. Optional Install Manifest Items You should already know how to create the install manifest file (install.rdf). Several optional items can be added to this file. Let’s extend the install.rdf file of our SiteLeds extension: <?xml version=”1.0”?> <RDF xmlns=”http://www.w3.org/1999/02/22-rdf-syntax-ns#” xmlns:em=”http://www.mozilla.org/2004/em-rdf#”> <Description about=”urn:mozilla:install-manifest”> <em:id>{E1B2492D-E6AC-4221-A433-C143E3A1C71E}</em:id> <em:version>0.1</em:version> <em:name>SiteLeds</em:name> <em:description>Site Status Monitor</em:description> <em:creator>Alex Sirota</em:creator> <em:homepageURL>http://www.iosart.com/firefox/siteleds</em:homepageURL> <em:contributor>First Contributor</em:contributor> <em:contributor>Another Contributor</em:contributor> <em:optionsURL>chrome://siteleds/content/settings.xul</em:optionsURL> <em:aboutURL>chrome://siteleds/content/about.xul</em:aboutURL> <em:iconURL>chrome://siteleds/skin/logo.png</em:iconURL> <em:updateURL>http://iosart.com/firefox/siteledsUpdate.rdf</em:updateURL> <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:minVersion>0.9</em:minVersion> <em:maxVersion>1.1</em:maxVersion> </Description> </em:targetApplication> <em:file> <Description about=”urn:mozilla:extension:file:siteleds.jar”> <em:package>content/</em:package> <em:skin>skin/classic/</em:skin> <em:locale>locale/en-US/</em:locale> </Description> </em:file> </Description> </RDF> 25_596500 ch17.qxd 6/30/05 3:14 PM Page 373 374 Part VI — Creating Extensions and Themes Let’s examine the items we have added: Ⅲ You can specify one or more em:contributor properties for every person that con- tributed to the extension. Ⅲ The em:optionsURL property specifies the chrome URL of the extension Options dialog (sometimes called Settings). The document found at this URL is a XUL dialog that typically lets the user change the various extension settings and saves these settings as user preferences. If this property is specified, the Options dialog can be opened from the Extensions Manager window. Ⅲ The em:aboutURL property allows you to create a custom About dialog for your exten- sion. By default, this dialog is created automatically and displays the extension name, version, author, home page, and so on. You can create a custom About dialog and specify its chrome URL in the install manifest. Ⅲ You can create a custom icon for your extension and specify its chrome URL using the em:iconURL property. This icon should be 32 × 32 pixels, and when defined, it is dis- played in the Extension Manager window instead of the default icon. Ⅲ Firefox can automatically check if a new version of your extension is available and, if found, show a notification message and let the user update the extension. By default, Firefox contacts the Mozilla site ( addons.mozilla.org) to see if an update is avail- able. By using the em:updateURL property, you can specify a custom URL that will be queried instead. More information on this file appears in the following section. Custom Update File As previously mentioned, by specifying the em:updateURL property in your install manifest file you can have Firefox query a custom URL to see whether updates are available for your extension. At this URL, the browser expects to find an RDF file that specifies the available extension versions. The server must send the RDF file as text/rdf for the update mechanism to work. With the Apache web server, this can be achieved by creating an .htaccess file and adding the following line to it: AddType text/xml rdf. Take a look at a sample update.rdf file: <?xml version=”1.0”?> <RDF:RDF xmlns:RDF=”http://www.w3.org/1999/02/22-rdf-syntax-ns#” xmlns:em=”http://www.mozilla.org/2004/em-rdf#”> <RDF:Description Æ about=”urn:mozilla:extension:{E1B2492D-E6AC-4221-A433-C143E3A1C71E}”> <em:updates> <RDF:Seq> 25_596500 ch17.qxd 6/30/05 3:14 PM Page 374 375 Chapter 17 — Creating Extensions <RDF:li resource= Æ “urn:mozilla:extension:{E1B2492D-E6AC-4221-A433-C143E3A1C71E}:0.2”/> </RDF:Seq> </em:updates> <em:version>0.2</em:version> <em:updateLink>http://www.iosart.com/firefox/siteleds/SiteLeds_0.2.xpiÆ </em:updateLink> </RDF:Description> <RDF:Description Æ about=”urn:mozilla:extension:{E1B2492D-E6AC-4221-A433-C143E3A1C71E}:0.2”> <em:version>0.2</em:version> <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:minVersion>0.9</em:minVersion> <em:maxVersion>1.1</em:maxVersion> <em:updateLink>http://www.iosart.com/firefox/siteleds/SiteLeds_0.2.xpi @ta </em:updateLink> </Description> </em:targetApplication> </RDF:Description> </RDF:RDF> As you can see, the update RDF file specifies that version 0.2 of the extension with GUID E1B2492D-E6AC-4221-A433-C143E3A1C71E is available for download from http:// www.iosart.com/firefox/siteleds/SiteLeds_0.2.xpi . It also specifies that ver- sion 0.2 of the extension is compatible with Firefox (by specifying its GUID) versions 0.1 to 1.1. If, for example, a user has version 0.1 of the SiteLeds extension installed in Firefox 1.0, the update procedure will detect that there is an available update (version 0.2 of the extension) and display the appropriate notification. Automating the Packaging Process As noted in the previous sections, you must perform several operations each time you want to create an XPI package for your extension. You need to package the contents of the chrome directory into a JAR file and then package it along with the install.rdf file into an XPI archive. You can create a simple script that will automate this process for you. All you need is a ZIP utility with a command-line interface and a scripting language. This can be done as follows, using the 7-Zip utility and a simple Windows batch script: cd chrome del siteleds.jar 7z a -tzip -mx=0 -r siteleds.jar * cd del SiteLeds_0.1.xpi 7z a -tzip -mx=9 SiteLeds_0.1.xpi install.rdf chrome\siteleds.jar 25_596500 ch17.qxd 6/30/05 3:14 PM Page 375 376 Part VI — Creating Extensions and Themes We can put the preceding script in a file named package.bat at the top-level directory of our extension (in the same directory as the install.rdf file). Clicking on this file creates a fresh SiteLeds_0.1xpi package in the current directory. As you can see, the script performs a couple of very simple tasks: It first compresses the contents of the chrome directory and then creates the final XPI package by compressing the chrome JAR file along with the install.rdf install manifest. You can create more elaborate packaging scripts that will automatically update the version number in all the needed files, upload the extension package to your web server, and perform additional tasks you are routinely doing when packaging and releasing your extensions. The point is that even a very simple script can be used to make the process much more efficient. Developing an Extension without Repackaging We have seen that you need to install your extension into the browser before you can test it. This means that you have to constantly repackage and reinstall your extension while developing it. This process is highly inefficient and frustrating. There is a simple way you can avoid having to repackage your extension each time you make a change. The idea is that during develop- ment, you change your manifest files to use plain directories instead of the JAR file and then install this modified extension into the browser. Now you can work directly on the installed files without having to repackage anything. If you want your extension to work with plain directories, you need to change a few things: 1. Instead of packaging the content, skin, and locale directories into the siteleds.jar file, cre- ate a siteleds subdirectory inside the chrome directory and copy the content, skin, and locale folders to this new directory. Basically, you are creating a directory at the same place your JAR file would be and with exactly the same contents. 2. Change the following line in the install.rdf file <Description about=”urn:mozilla:extension:file:siteleds.jar”> to <Description about=”urn:mozilla:extension:file:siteleds”> If you are using the new-style chrome registration manifest, you should apply similar changes to your chrome.manifest file instead. 3. Create your XPI file as usual, by compressing the install.rdf file and the chrome directory (which now contains the siteleds subdirectory and not the siteleds.jar file), and install it into the browser. Figure 17-29 shows the contents of the modified XPI package. Now you can locate your extension installation directory (typically in your profile folder) and work directly on the installed files. You don’t have to continually repackage your extension to see the changes. You might have to restart the browser, though, to see the changes made to the overlay elements. 25_596500 ch17.qxd 6/30/05 3:14 PM Page 376 377 Chapter 17 — Creating Extensions F IGURE 17-29: The contents of the XPI package that uses plain directories There is one additional thing you can do to avoid restarting the browser when modifying nonoverlay XUL documents (such as windows and dialogs). By default, Mozilla caches all XUL documents, meaning that once it loads a XUL file, it won’t be read again from the disk until you restart the browser. To disable this XUL caching mechanism, set the nglayout .debug.disable_xul_cache preference to true. Tips and Tricks This section provides various techniques that can prove useful in the extension development process. Avoiding JavaScript Name Collisions As mentioned earlier, the JavaScript identifiers we define in overlays are evaluated in the global scope, meaning that if we are not careful, we can give our function or variable a name that is already taken by another component. This situation causes a name collision and should be avoided. To solve the problem, we used unique prefixes for all our global identifiers. This solu- tion works, but you need to constantly be aware of the problem and remember to add the prefix to each and every identifier. This also makes the code less readable. skin classic contents.rdf siteledsOverlay.css state-error.png state-modified.png state-ok.png state-unknown.png locale en-US contents.rdf siteledsOverlay.dtd SiteLeds_0.1xpi install.rdf chrome siteleds contents contents.rdf siteledsOverlay.js siteledsOverlay.xul 25_596500 ch17.qxd 6/30/05 3:14 PM Page 377 378 Part VI — Creating Extensions and Themes There is a better way of handling this situation. You can put all your JavaScript code inside an object. All the global identifiers, both functions and variables, become members of this object. Now, as long as the name of this one object is unique, you don’t have to worry about name collisions. Let’s rewrite our SiteLeds JavaScript code to use this technique: var SiteLeds = { _siteLedsLastRequest: null, _siteLedsLastContent: null, pageLoaded: function () { }, pageError: function() { }, checkPage: function() { } } window.addEventListener(“load”, SiteLeds.checkPage, false); Testing Your Code without Creating an Extension JavaScript code that uses XPCOM components and other browser APIs needs to have certain privileges to be able to run in the browser. When such code runs from a chrome URL, meaning that it is a part of the browser or an extension, it has such privileges. Does this mean that you have to create a chrome package and register it every time you want to experiment with JavaScript? There is a way to run a privileged JavaScript code from a simple HTML file, as long as it is located on the local file system. The trick is to enable the necessary privilege before attempting to use XPCOM and other restricted interfaces. To enable access to XPCOM from a script, call the netscape .security.PrivilegeManager.enablePrivilege function with the UniversalXPConnect parameter. Now you can create a simple HTML file that contains your JavaScript code and test it without needing to create a chrome package. An example for working with the preferences XPCOM objects using this technique follows: <html> <head> <script> function getHome() { netscape.security.PrivilegeManager. enablePrivilege(“UniversalXPConnect”); var prefs = Components.classes[“@mozilla.org/preferences-service;1”]. getService(Components.interfaces.nsIPrefBranch); var homePage = prefs.getCharPref(“browser.startup.homepage”); alert(homePage); } 25_596500 ch17.qxd 6/30/05 3:14 PM Page 378 379 Chapter 17 — Creating Extensions </script> </head> <body> <button onclick=”getHome();”>Get Home</button> </body> </html> When you click on the Get Home button, the script retrieves the user Home Page preference using the Preferences Service XPCOM object. When a local file requests a privilege using the preceding mechanism, a security dialog is dis- played, and you must explicitly grant this privilege before the script execution can be resumed. You should be very careful when granting such privileges and make sure that you know exactly which file is requesting them. As you saw earlier, you can also experiment with your XUL files without installing them into your browser first. Create a XUL file and open it with your browser just as you would open a local HTML document. Your XUL interface will appear in the browser content area. If the UI looks stretched, add align=”start” to the root element of your XUL document (window, overlay, and so on). Hacking Existing Extensions There is no better way to understand how some component works than to look at its source code. Extensions are no different. Because they are developed using mainly text-based tech- nologies such as XML and JavaScript, they are open source by their nature. You can learn a lot by looking at the code of existing extensions and playing around with them. Suppose that you have a great idea for an extension, but even after you read through all the available documentation you still can’t figure out how to get started. For example, let’s assume that you want your extension to add a context menu item that sends the selected text to some website, and you are not sure how you can modify the context menu, get the selected text, or open a connection to a remote site. There are already dozens of extensions that perform very similar operations, and looking at their code will get you on the right track. Some of the things you can do to start hacking existing extensions are as follows: Ⅲ Find an extension that does something similar to what you are trying to accomplish. Ⅲ Install the extension and play around with it for a while. See how it works and whether it indeed has the needed functionality. Ⅲ Extract the extension using your favorite ZIP utility and start looking at its code. Examine the structure of the XUL files and overlays, see how different elements are styled using CSS, and learn how the extension does things by looking at its JavaScript code. 25_596500 ch17.qxd 6/30/05 3:14 PM Page 379 380 Part VI — Creating Extensions and Themes Ⅲ If there are still things you don’t completely understand, you can do some real hacking — you can modify the extension, comment things out, and add your own pieces of code. You can add dump() and alert() calls to examine the value of JavaScript variables, see how different functions are invoked, and so on. Also, you can use the technique that allows you to modify an installed extension (described earlier in this chapter) to make the hacking process more efficient. One of the greatest benefits of knowing how extensions work is being able to tweak existing extensions to better suit your needs. You can modify the extension UI to make it prettier or to change some of its JavaScript code to slightly adjust its behavior. If you believe that your changes might be useful to anyone besides yourself, you can contact the extension author and suggest these improvements or even offer your help with the project. If you want to use in your own work the code you found in another extension or to modify it in any way, first make sure that the extension license allows it. Many extensions have licenses that allow you to use their code in your extension under different conditions. Always credit the origi- nal authors for their work. Many licenses specifically require that, but this is really a matter of courtesy. Also, if you think that an existing extension is missing some pieces of functionality, con- sider contacting the author and suggesting a contribution to the existing extension before creat- ing your own derivative product. Summary This chapter explained everything you need to know to start writing your own extensions. You should now know how extensions are created, packaged, and distributed; how different parts of Firefox can be customized using extensions; and where to get further documentation and help. Once you see how simple the process of creating Firefox extensions is, you might be tempted to start writing your own extension right away. Don’t resist this temptation! Extensions are among the things that make Firefox the best browser out there, and I’m sure that your new extension can make it even better. 25_596500 ch17.qxd 6/30/05 3:14 PM Page 380 Creating Themes I started creating themes for Mozilla around August 2002 but didn’t release my first theme for Firefox (known then as Firebird 0.7) until November 2003. As with extensions, the Firefox theme process is a work in progress. Those that started creating themes for Firefox when it was still in beta stages know this all too well. Now that Firefox 1.0 has been released, changes are coming more slowly, but they’re still coming. The following section details the theme creation process from start to fin- ish, from defining the files to publishing your theme. Some of the concepts are the same as those used in creating and modifying extensions. Tools for Creating Themes You probably have most if not all of the software required to create a Firefox theme already installed on your computer. There are no theme-specific tools required for theme creation. Themes consist of the following file types: Ⅲ CSS Ⅲ Images Ⅲ RDF Ⅲ XML All these files are packaged into the final product, known as a compressed archive. You will need the following tools to create a theme for Firefox: Ⅲ A text editor Ⅲ An image/graphics editor Ⅲ A compression tool ˛ Tools for creating themes ˛ Building your first theme ˛ Making your theme public ˛ Supporting different operating-system platforms ˛ Hacking existing themes chapter in this chapter by Aaron Spuler 26_596500 ch18.qxd 6/30/05 3:15 PM Page 381 . xmlns=”http://www.w3.org/1999/02/22-rdf-syntax-ns#” xmlns:em=”http://www.mozilla.org/2004/em-rdf#”> <Description about=”urn:mozilla:install-manifest”> <em:id>{E1B2492D-E6AC-4221-A433-C143E3A1C71E}</em:id> <em:version>0.1</em:version> <em:name>SiteLeds</em:name> <em:description>Site. Æ about=”urn:mozilla:extension:{E1B2492D-E6AC-4221-A433-C143E3A1C71E}:0.2”> <em:version>0.2</em:version> <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e9 7384 }</em:id> <em:minVersion>0.9</em:minVersion> <em:maxVersion>1.1</em:maxVersion> <em:updateLink>http://www.iosart.com /firefox/ siteleds/SiteLeds_0.2.xpi @ta. xmlns:RDF=”http://www.w3.org/1999/02/22-rdf-syntax-ns#” xmlns:em=”http://www.mozilla.org/2004/em-rdf#”> <RDF:Description Æ about=”urn:mozilla:extension:{E1B2492D-E6AC-4221-A433-C143E3A1C71E}”> <em:updates> <RDF:Seq> 25_596500