Changes

Jump to: navigation, search

Dive into Mozilla Modifying Firefox using an Extension Lab

2,288 bytes added, 10:21, 13 March 2008
m
addtabbeside/
=Introduction=
In the [[Dive into Mozilla Modifying Firefox Lab|previous lab]] we made a small change to the behaviour of Firefox by modifying the browser's source code. In this lab we explore how to achieve the same effect using an '''extension''' rather than modifying the tree. ''(Thanks to [http://www.starkravingfinkle.org/blog/ Mark Finkle] for the idea of redoing this as an extension.)''
The goal of this exercise is to expose you to Firefox extensions and to show you how to modify or extend the browser without changing it's its source code directly. Some thought will also be given to the two methods of doing this (i.e., in the tree vs. as an extension), comparing their advantages and disadvantages.
=The 'What': write a tab creation extension=
What does this leave? You could [http://en.wikipedia.org/wiki/Fork_(software_development) fork] Firefox, as some people have done, and create your own version of the browser. Obviously this isn't what we'd like to do. Rather, what we need is a mechanism to insert a small change into the browser, and do so in such a way that users can choose to install our code or not. Mozilla provides such a mechanism in the form of Extensions.
Extensions allow third-party developers (and Mozilla, for that matter) to write add-on packages that users can install to extend or modify the standard browser. By rewriting our earlier code as an extension, we can give our users an extension to installa small add-on, which will have the same effect as our custom build. For all but the most universal of changes, extensions are the best way for developers to write code that targets the browser.
==Planning the extension==
===How to move a tab?===
Now we know that there is a logical time/place to run our code via the '''TabOpen''' event. The next thing to consider is what our code will do. Previously we replaced '''append''' with '''insertBefore''' and stopped the problem before it happened. In However, in this case, by the time the '''TabOpen''' event is dispatched, the tab will already be created and positioned at the end of the list.
We need another solution, so we need . It's time to go hunting in tabbrowser's [http://developer.mozilla.org/en/docs/XUL:tabbrowser documentation] and [http://lxr.mozilla.org/seamonkey/source/toolkit/content/widgets/tabbrowser.xml code]. What we need is a way to reposition a tab after it has been created. A quick look through the docs for [http://developer.mozilla.org/en/docs/XUL:tabbrowser tabbrowser] reveals nothing. However, the [http://lxr.mozilla.org/seamonkey/source/toolkit/content/widgets/tabbrowser.xml code] is more helpful:
<method name="moveTabTo">
===How to get the newly created tab using code?===
Having already studied and worked with the code in '''addTab''', and knowing that new tabs are always appended to the end of the list, we could do thisthe following in order to get the new tab:
// In an extension, gBrowser is a global reference to the tabbrowser element
t.dispatchEvent(evt);
This means that in the event system, we'll be able to listen for and capture this event, and in so doing get at t (i.e., the tab object) via the event's '''target'''. Three lines of code become one:
var newTab = e.target;
===How to get the index of the currently selected tab?===
We know that in order to move a tab using tabbrowser's '''moveTabTo''' method we need the tab object--which we now have--and the index where it should be placed. Looking through the tabbrowser code for tabbrowser again, we see [http://lxr.mozilla.org/seamonkey/source/toolkit/content/widgets/tabbrowser.xml#1453 references] to code like this:
var currentIndex = this.mTabContainer.selectedIndex;
=The 'How': the extension's code=
Now that we have the logic for our code, all that remains is to write the necessary pieces in order to have it get executed at the right timestime. When we modified the code in the tree this wasn't a concern: we assumed that code we changed in '''addTab''' would get executed whenever '''addTab''' got called. With an extension, we have to explicitly tell the browser how and when to execute our code. We do this using '''event listeners'''.
From our previous research, we know that the methods in tabbrowser cause a variety of events to get dispatched into the event system. These events move through the event system regardless of whether any code notices them--they are passive. In most cases, nothing is done in response to events. However, any developer can decide to do something in response to an event, which is what extension developers must do in order to get their code into the browser.
We need to wire our code to a number of events by [http://developer.mozilla.org/en/docs/XUL_Event_Propagation#Adding_an_Event_Listener adding event listeners]. Two of the events we know already, '''TabOpen''' and '''TabSelect'''. However, we also need to have the code to accomplish this get called in a sort of global event listener, register our listeners at start-up using the '''window's load event'''. We'll add code to remove our event listeners in the '''window's unload event''' too.
==overlayaddtabbeside.js==
To keep things clean we'll write all this code in a custom object called '''AddTabBeside'''. Here is '''AddTabBesideaddtabbeside.js''':
var AddTabBeside = {
window.addEventListener("load", function(e) { AddTabBeside.onLoad(e); }, false);
We'll save this This code in a file named '''overlay.js''' in order to reflect the fact that we're writing will become part of an overlay that will be merged with the browser at runtime. But where do we put this file? Firefox needs to know where it is in order to load and run it at startup. For this to work we have to do some things We also need to create an installable installation package to wrap around for our codeoverlay.
==Creating the rest of the extension==
Firefox extensions are packaged as compressed zip files with a '''.XPI''' extension. This is what you download when you visit http://addons.mozilla.org and choose to install an extension. This means that Because extensions are just .zip files, you can unpack any extension and see how it's built. For example, try saving [https://addons.mozilla.org/firefox/16/ ChatZilla's .xpi] to your computer and decompress it (NOTE: if your built-in unzip program won't do this, try changing the name of the file 's extension to .zip).
An extension is a series of files and directories containing JavaScript, XUL, CSS, XML, RDF, and other custom text files. Some of these files define scripts to be executed, such as those we wrote above. Other files contain information ''about'' the extension, meta data metadata telling the browser how to integrate and install things, what the extension is called, it's its version number, etc. We need to create these other files now.
===Extension files and directory structure===
====addtabbeside/chrome/content====
The first thing to do is to copy the '''overlayaddtabbeside.js''' file we wrote earlier to '''addtabbdeside/chrome/content/overlayaddtabbeside.js'''.
This code now needs to get merged into the browser so it can access elements within the application. We do this by providing a [http://developer.mozilla.org/en/docs/XUL_Overlays XUL Overlay] file. A XUL Overlay is a .xul file that specifies XUL fragments to insert at specific merge points within a "master" document. Often this is used to add new UI to an application (e.g., a new menu item), but in our case we'll use it to merge our overlayaddtabbeside.js script into the browser.
We need to create '''addtabbdeside/chrome/content/overlay.xul''':
<pre>
<?xml version="1.0" encoding="UTF-8"?>
<overlay id="addtabbeside-overlay"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript" src="overlayaddtabbeside.js"/> </overlay>
</pre>
Having added our script and overlay files, we now need to add a couple of metadata files to help Firefox understand and install/load our extension.
The first is '''addtabbeside/contentchrome.manifest''', which is a [http://developer.mozilla.org/en/docs/Chrome_Manifest Chrome Manifest]. This file helps Firefox translate between chrome:// URIs and actual files on disk. It also allows us to specify where our overlay will be merged into the browser:
# Chrome package '''addtabbeside''' has it's '''content''' in ./chrome/content
content addtabbeside chrome/content/
# Overlay the '''overlaybrowser.xul''' file with '''browseroverlay.xul'''
overlay chrome://browser/content/browser.xul chrome://addtabbeside/content/overlay.xul
The first line registers the location for our content (i.e., .xul, .js). The second line registers our overlay, and says that overlay.xul will be merged with browser.xul. Mozilla uses chrome:// URIs to refer to aspects of the interface, and chrome://browser/content/browser.xul ''is'' the browser (try typing it into the address bar).
The second metadata file we need to create is '''addtabbeside/install.rdf'''. This is an [http://developer.mozilla.org/en/docs/Install_Manifests install manifest] that tells the Firefox addon add-on manager how to install the extension, with information like who wrote it, the version number, compatibility information, etc.
<pre>
<?xml version="1.0" encoding="UTF-8"?> <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>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox -->
<em:minVersion>2.0</em:minVersion>
<!--<em:maxVersion>3.0a3pre0a9pre</em:maxVersion>--> <!-- trunk build Feb 27Oct 14, 2007 --> <em:maxVersion>3.0+</em:maxVersion> <!-- work for v3.0 and above -->
</Description>
</em:targetApplication>
</Description>
</RDF>
</pre>
===Testing the extension===
Eventually we'll package our extension properly into a redistributable .xpi. However, while we're testing it's nice to be able to use it in an expanded state form so we can make changes.
To this end, create a file named '''addtabbeside@senecac.on.ca''' and put it in your development profile's extensions directory (NOTE: replace ''Username'' with your username and ''dev-profile'' with your development profile name):
C:\Documents and Settings\''Username''\Application Data\Mozilla\Firefox\Profiles\''dev-profile''\extensions\addtabbeside@senecac.on.ca
Or in Mac OSX  ~/Library/Application Support/Firefox/Profiles/ This file should contain the a single line of text--the full path to your extension, for example:
C:\temp\addtabbeside
===Packaging the extension===
TODOOnce debugging and testing is complete, you'll want to [http://developer.mozilla.org/en/docs/Extension_Packaging create an installable .xpi file] to give to your users. As was previously mentioned, .xpi files are just .zip files with a different extension. All .xpi files must contain the '''install.rdf''' file in the root (i.e., when you unzip it, install.rdf should be in the top directory). Follow these steps to create an installable .xpi: # Create a new zip file named '''addtabbeside.zip''' (NOTE: you can use .xpi instead of .zip if your application will allow it).# Add your extension files and directories to '''addtabbeside.zip''', making sure that '''install.rdf''' is in the root (i.e., don't zip the addtabbeside directory itself, just it's contents).# Rename the resulting file to '''addtabbeside.xpi''' You can try installing your extension in a browser that doesn't have it by simply dragging '''addtabbeside.xpi''' into it. This should trigger the add-on manager and give you the option to install your extension and restart the browser.
==Success, and some bugs==
The rewrite from changing the tree to using an extension has been a success. In both cases we've managed to achieve the same goal using almost completely different methods. Using an extension we've made it possible to share our changes with any Firefox user worldwide without having to ship a custom build.
However, as was the case in our previous attempt, our code has a bug. Moving existing tabs doesn't update our position state, since we only modify '''mPreviousIndex ''' when a new tab is selected; moved tabs remain selected, but change their order(i.e., TabSelect won't get dispatched on a move).
Luckily we've already stumbled upon the solution to this problem--the '''TabMove''' event. Here is the updated version of '''overlayaddtabbeside.js''', with the changes in bold:
var AddTabBeside = {
=Reflections=
TODOMany of the steps we did in order to create the extension's installation and registration files will be the same each time. Rather than doing it by hand, you can use [http://ted.mielczarek.org/code/mozilla/extensionwiz/ an on-line wizard] to build a basic extension. The process of developing extensions is greatly improved with the addition of [http://kb.mozillazine.org/Setting_up_extension_development_environment some preferences] to the browser, as well as the [http://ted.mielczarek.org/code/mozilla/extensiondev/index.html Extension Developer's extension]. This extension will automate many of the steps described here. It will also allow you to reload the browser's chrome at runtime. This is a great way to test changes to your XUL/JS files without having to restart the browser.  You can read complete details on how to set-up an extension developer's environment [http://kb.mozillazine.org/Setting_up_extension_development_environment here].
=Resources=

Navigation menu