Changes

Jump to: navigation, search

Dive into Mozilla Modifying Firefox using an Extension Lab

9,052 bytes added, 11:21, 13 March 2008
m
addtabbeside/
[[Dive into Mozilla]] > [[Dive into Mozilla Day 5]] > Modifying Firefox using an Extension Lab
 
'''...in progress...'''
 
=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': change the way new tabs get created in Firefoxwrite a tab creation extension=
As was [[Dive into Mozilla Modifying Firefox Lab#The_.27What.27:_change_the_way_new_tabs_get_created_in_Firefox|previously the case]], our goal is to modify Firefox so that new tabs are created (i.e. positioned) beside the current tab instead of being appended to the end of the list.
However, unlike last time where we modified tabbrowser.xml directly, this time we will '''overlay''' our changes onto the browser at runtime, and alleviate the need for any direct changes to the code. This is possible using '''extensions'''.
=The 'Where': figuring out where to put our code=
The first thing we should address is why we're doing this at all: why bother creating an extension in order to do what we've already done in the tree? There are a number of answers to this question.
==Changes in the tree vs. an extension==
The first problem with doing things in the tree is that in order for you to distribute the local changes you've made, people will have to use your custom build of Firefox. While this might seem like a good idea the first time you build the browser, it isn''overlayt sustainable long term, and users will always want to get the browser from Mozilla for security fixes, new features, etc.js'''
var AddTabBeside = { // State info on A logical alternative might be to try and get your change accepted into the last tab to be selectedtree. mPreviousIndex This involves filing a bug on https: 0, onLoad: function() { // Add bugzilla.mozilla.org and then creating and attaching a patch with your changes. The problem here is that even though you might think it is a listener good idea for tabs to be created in the way we've specified, the community may not--people have already put thought into the TabOpen eventway things work now, users are accustomed to it, which gets called as part of addTab in tabbrowseretc.xml var container = gBrowser.tabContainer; container.addEventListener("TabOpen" In this case your bug will likely be marked '''WONTFIX''', thiswhich means that your patch won't make it into the tree.onTabOpen, false);
What does this leave? You could [http:// Also add a listener for TabSelect so we know when focus changes to a new tab containeren.wikipedia.addEventListenerorg/wiki/Fork_("TabSelect"software_development) fork] Firefox, as some people have done, and create your own version of the browser. Obviously thisisn't what we'd like to do.onTabSelect Rather, false);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.
// FinallyExtensions allow third-party developers (and Mozilla, add a listener for shutdown window.addEventListener("unload", this.onUnload, false); }, onUnload: function() { var container = gBrowser.tabContainer; container.removeEventListener("TabOpen", this.onTabOpen, false); container.removeEventListener("TabSelect", this.onTabSelect, false); }, onTabSelect: function (ethat matter) { // when a different tab is selected, remember which one. This is // necessary because when a new tab is created, it will get pushed // to the end of the list, but we need to know where to put it. this.mPreviousIndex = gBrowser.tabContainer.selectedIndex; }, onTabOpen: function (e) { // Get the newly created tab, which will be last in the list var newTab = e.target; // Move this new tab write add-on packages that users can install to extend or modify the right of the previously selected tab, // checking to see how many tabs there are currentlystandard browser. By default // there is 1 tabrewriting our earlier code as an extension, and the first time onTabOpen is calledwe can give our users a small add-on, there which will // be 2 (the default plus have the newly created tab)same effect as our custom build. In this case, don't // move For all but the new tabmost universal of changes, since it is already in extensions are the right spot. In all // other cases, move the tab best way for developers to write code that targets the right of the current tab. if (gBrowser.tabContainer.childNodes.length > 2) { gBrowserbrowser.moveTabTo(newTab, this.mPreviousIndex + 1); } }, }; window.addEventListener("load", function(e) { AddTabBeside.onLoad(e); }, false);
==Planning the extension==
Having decided to rewrite the code as an extension and remove our changes from the tree, a logical first thought would be: "how can I modify my existing code to create an extension." While we learned some useful things modifying the browser directly, our extension will require a completely new approach to solving the same problem.
Instead of searching for existing code to change, we have to take the browser as a given and find ways to work with it. To that end, let's look again at the code for [http://lxr.mozilla.org/seamonkey/source/toolkit/content/widgets/tabbrowser.xml#1122 tabbrowser's addTab] method:
var t = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "tab");
...
this.mTabContainer.appendChild(t);
...
// Dispatch a new tab notification. We do this once we're
// entirely done, so that things are in a consistent state
// even if the event listener opens or closes tabs.
var evt = document.createEvent("Events");
evt.initEvent("TabOpen", true, false);
t.dispatchEvent(evt);
return t;
After the new tab is created and appended to the list, an '''event''' is [http://developer.mozilla.org/en/docs/DOM:element.dispatchEvent dispatched] to the new tab element. The developers of tabbrowser.xml wanted their code to be extensible; that is, they wanted to provide a way for future developers to extend the code by inserting a hook. In this case, after a tab is created an opportunity is provided to do ''something''. Their foresight makes our job much easier.
===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. 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. 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">
<parameter name="aTab"/>
<parameter name="aIndex"/>
...
The '''moveTabTo''' method takes a tab to be moved, and a new position. This is exactly what we need. Now there are just two things left to figure out: 1) how to get the newly inserted tab; and 2) the index of currently selected tab so we can go one to the right.
=The 'Where': finding the right spot ==How to make get the changesnewly created tab using code?===
ItHaving already studied and worked with the code in '''addTab''s one thing to say you'd like , and knowing that new tabs are always appended to change the browser's behaviourend of the list, but quite another to actually we could do it. The change you have in mind might be quite simple, the following in the end (ours is). But you still have to figure out where that simple code needs order to go. That can be difficult. However, difficult isn't get the same as impossible.new tab:
How do you begin? First// In an extension, let's start at gBrowser is a global reference to the top and find some UI notation we can search for in the codetabbrowser element var container = gBrowser.tabContainer; var lastIndex = container.childNodes. length - 1; In our case, we can focus on the various methods for creating a new tab:var newTab = container.childNodes[lastIndex];
* CTRL+T* Right-Click an existing However, looking back at the event dispatching code for '''TabOpen''' we see that the event is dispatched to t, where t is the newly created tab and select New Tab* File > New Tab:
The second and third methods are useful, as they provide us with a unique string we can search for in the code. Before we can change anything, we have to search and read existing code in order to understand where to begin--this is the standard pattern for open source and Mozilla developmentt.dispatchEvent(evt);
==Search 1 - finding a UI string==This means that in the event system, we'll be able to listen for and capture this event, and in so doing get at the tab object via the event's '''target'''. Three lines of code become one:
We're looking for a unique string--"New Tab" var newTab ==, so we'll use [http://lxre.mozilla.org LXR's] '''Text Search''' feature. Here are the results you get when you search for "New Tab":target;
<blockquote>http://lxrWe'll discuss this code in more detail below.mozilla.org/seamonkey/search?string=New+Tab</blockquote>
Lots of results, many of which point ===How to comments in get the code. However, index of the first result looks interesting:currently selected tab?===
<blockquote>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 again, we see [http://lxr.mozilla.org/seamonkey/source/toolkit/locales/en-US/chromecontent/globalwidgets/tabbrowser.dtdxml#2</blockquote>1453 references] to code like this:
Here we see the DTD file describing the key/value pairs for the en-US localized strings. Mozilla uses var currentIndex = this technique to allow localizers to translate strings in an application into many different languages without having to change hard-coded strings in the code (you can read more about localization, DTDs, and Entities [http://developer.mozillamTabContainer.org/en/docs/XUL_Tutorial:Localization here]) selectedIndex;
Looking closely at Obviously we can't use '''this'''outside the context of tabbrowser.dtd, so in an extension we use '''gBrowser''' we see that our English string, "New Tab", has the following entityinstead to get a reference to tabbrowser:
<!ENTITY newTabgBrowser.label "New Tab">tabContainer.selectedIndex
This is good information, because it allows us If we want to repeat our search with an entity instead of a string, which should help us get closer the tab to the code right of the current tab, we're after.simply do this:
var positionPlusOne ==Search 2 - finding an ENTITY==gBrowser.tabContainer.selectedIndex + 1;
Repeating Are we done? Not quite. This code will work, but the problem we now face is that our code will run ''after'' the tab has been placed at the search with end of the list, when '''TabOpen'''newTabis dispatched. As soon as the new tab is created, it becomes the active tab.label That means that '''selectedIndex''' ENTITY value instead will always be the index of the "New Tab" string makes a big difference--we have many fewer hits:last tab, and moving the last tab one to the right is nonsense.
<blockquote>http://lxrWhat we ''really'' need is the position of the tab that we were on when he made the new tab.mozilla We even know how to get this information, using '''tabContainer.org/seamonkey/search?string=newTabselectedIndex'''. What we don't know is how to get this information, since it is lost by the time our code runs.label</blockquote>
Not surprisingly, the first result is the same DTD file (i.e., tabbrowser.dtd) we already found. The second result looks interesting, though:===Saving tab position state===
<blockquote>http://lxrIt's clear that we need data from the past--the position of the tab we were on before this new tab was created.mozilla We can't go back in time, so we'll have to store this data in a variable for inspection later.org/seamonkey/source/toolkit/content/widgets/tabbrowser.xml#80</blockquote>
Here In order to accomplish this, we see need another event that tells us when a tab has become the code to generate active tab so we can store the pop-up context menu position state. A quick search through tabbrowser for a tab other events (ie.eg., what you get when you right-click search on a tab in the browser'''"dispatchEvent"''')shows these possibilities:
<pre>* TabSelect <xul:menuitem label="&newTab.label;" accesskey="&newTab.accesskey;"* TabOpen xbl:inherits="oncommand=onnewtab"/>* TabClose </pre>* NewTab* TabMove
Having found The first event looks interesting. All we need to do now is have a variable that gets updated with the appropriate entity value, we also notice the use of a function name, '''onnewtabtabContainer.selectedIndex'''. This line of code says that the xul:menuitem will inherit the every time '''oncommandTabSelect''' value from its parent (you can read more about XBL attribute inheritance [http://developer.mozilla.org/en/docs/XUL_Tutorial:XBL_Attribute_Inheritance here])occurs. In other wordsThen, when we can use this menu item is clicked, call variable's value to calculate the desired position in '''onnewtabmoveTabTo''' function.
==Search 3 - finding a Function==Finally, we've got all the pieces in place and can write some code.
Armed with this new information, we are even closer to finding =The 'How': the right spot to begin working. Weextension've gone from UI string to XML ENTITY to function. All we have to do now is find that function:s code=
<blockquote>httpNow 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 time. When we modified the code in the tree this wasn't a concern://lxrwe 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.mozilla We do this using '''event listeners'''.org/seamonkey/search?string=onnewtab</blockquote>
This returns many results for things From our previous research, we aren't interested know that the methods intabbrowser 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, including files rooted nothing is done in /suite, /db, etcresponse to events. Since we are interested However, any developer can decide to do something in finding this behaviour response to an event, which is what extension developers must do in Firefox, we need order to focus on get their code into the files rooted in '''/browser'''. One looks particularly interesting:
<blockquote>We need to wire our code to a number of events by [http://lxrdeveloper.mozilla.org/seamonkeyen/sourcedocs/browser/base/content/browserXUL_Event_Propagation#Adding_an_Event_Listener adding event listeners]. Two of the events we know already, '''TabOpen''' and '''TabSelect'''. However, we also need to 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.xul#503</blockquote>
In this case, the tabbrowser widget has the onnewtab property set to another function, '''BrowserOpenTab();''' (i==addtabbeside.e., Firefox seems to handle tab creation in a non-standard way, providing its own method instead of using the default). Since we want to find the definition of this function, we search for '''"function BrowserOpenTab("''', which returns two results:js==
<blockquote>http://lxrTo keep things clean we'll write all this code in a custom object called '''AddTabBeside'''.mozilla Here is '''addtabbeside.org/seamonkey/search?string=function+browseropentab%28</blockquote>js''':
Again var AddTabBeside = { // State info on the last tab to be selected. mPreviousIndex: 0, onLoad: function() { // Add a listener for the TabOpen event, which gets called as // part of addTab in tabbrowser.xml var container = gBrowser.tabContainer; container.addEventListener("TabOpen", this.onTabOpen, false); // Also add a listener for TabSelect so we're interested in Firefox know when focus changes to a new tab container.addEventListener("TabSelect", this.onTabSelect, false); // Finally, add a listener for shutdown window.addEventListener("unload", this.onUnload, false); }, onUnload: function() { // Remove our listeners var container = gBrowser.tabContainer; container.removeEventListener("TabOpen", this.onTabOpen, false); container.removeEventListener(i"TabSelect", this.onTabSelect, false); }, onTabSelect: function (e) { // When a different tab is selected, remember which one. This is // necessary because when a new tab is created, browser) instead it will get pushed // to the end of SeaMonkey the list, but we need to know where to put it. this.mPreviousIndex = gBrowser.tabContainer.selectedIndex; }, onTabOpen: function (ie) { // Get the newly created tab, which will be last in the list var newTab = e.etarget; // Move this new tab to the right of the previously selected tab, // checking to see how many tabs there are currently. By default // there is 1 tab, and the first time onTabOpen is called, suitethere will // be 2 (the default plus the newly created tab). In this case, don't // move the new tab, so we skip since it is already in the right spot. In all // other cases, move the tab to the second result:right of the current tab. if (gBrowser.tabContainer.childNodes.length > 2) { gBrowser.moveTabTo(newTab, this.mPreviousIndex + 1); } }, }; // Insure that our code gets loaded at start-up window.addEventListener("load", function(e) { AddTabBeside.onLoad(e); }, false);
<blockquote>http://lxrThis code will become part of an overlay that will be merged with the browser at runtime.mozilla But where do we put this file? Firefox needs to know where it is in order to load and run it at startup.org/seamonkey/source/browser/base/content/browser We also need to create an installation package for our overlay.js#1802</blockquote>
This shows us that we need to be looking for yet another function, '''loadOneTab()'''. Another search:==Creating the rest of the extension==
<blockquote>Firefox extensions are packaged as compressed zip files with a '''.XPI''' extension. This is what you download when you visit http://lxraddons.mozilla.org and choose to install an extension. 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/seamonkeyfirefox/search?string=loadonetab<16/blockquote>ChatZilla's .xpi] to your computer and decompress it (NOTE: if your built-in unzip program won't do this, try changing the file's extension to .zip).
The first result An extension is not surprisinga 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're back to the tabbrowser widgetwrote above. The Other files contain information ''about'loadOneTab''' method calls another method the extension, metadata telling the browser how to actually create integrate and insert install things, what the new tab:extension is called, its version number, etc. We need to create these other files now.
var tab = this.addTab(aURI, aReferrerURI, aCharset, aPostData, owner, aAllowThirdPartyFixup);==Extension files and directory structure===
Since '''addTab''' is a method of '''this''' we can search within Start by creating the current document (CTRL+F) to find the '''addTab''' method. Finally we've found the right spot!following directory structure:
<blockquote>http: addtabbeside/ chrome/lxr.mozilla.org/seamonkey/source/toolkit/ content/widgets/tabbrowser.xml#1160</blockquote>
Because thisis a first extension, we will skip some [http://developer.mTabContainermozilla.appendChildorg/en/docs/Building_an_Extension#Setting_up_the_Development_Environment other directories and files] that more complete extensions would include (te.g., localization, skins);.
Now all that we have to do is modify it to insert rather than append.====addtabbeside/chrome/content====
=The first thing to do is to copy the '''addtabbeside.js''How': the necessary changes file we wrote earlier to the code='''addtabbdeside/chrome/content/addtabbeside.js'''.
There are different ways you could go about making This code now needs to get merged into the browser so it can access elements within the application. We do this change, and someone with more experience using tabbrowser might recommend by providing a different strategy or outcome[http://developer.mozilla.org/en/docs/XUL_Overlays XUL Overlay] file. I decided to work on something A XUL Overlay is a .xul file that I knew nothing about in order specifies XUL fragments to highlight the process one goes through, or insert at least the process I went through, when working with someone else's codespecific merge points within a "master" document. Since my goal Often this is used to show you how add new UI to do thisan application (e.g., a new menu item), I also discuss my errors and mistakes below--they are an important part of but in our case we'll use it to merge our addtabbeside.js script into the process toobrowser.
==First Attempt==We need to create '''addtabbdeside/chrome/content/overlay.xul'''
The goal is to make as small a change as possible, since the existing code works well<pre><?xml version="1.0" encoding="UTF-8"?> <overlay id="addtabbeside-I just want it to work slightly differentoverlay" xmlns="http://www.mozilla. I'm also not interested in reading all of the code in order to make such a small changeorg/keymaster/gatekeeper/there. I want to leverage as much of what is already there as I can.only.xul"> <script type="application/x-javascript" src="addtabbeside.js"/> </overlay></pre>
I assume that the '''appendChild()''' method is responsible for the behaviour I don't like (i.e., adding new tabs to the end of the list). I'm not sure what to replace it with, so I do another search inside tabbrowser.xml (i.e., using CTRL+F) looking for other methods====addtabbeside/attributes of '''mTabContainer'''. I come-up with some interesting options:====
index = this.mTabContainer.selectedIndex; ... this.mTabContainer.insertBefore(aTabHaving added our script and overlay files, this.mTabContainer.childNodes.item(aIndex)); ... var position = this.mTabContainer.childNodeswe now need to add a couple of metadata files to help Firefox understand and install/load our extension.length-1;
I decide that I can probably accomplish my goal using these alone, and so start working on a solutionThe first is '''addtabbeside/chrome. Here is my first attempt, showing the changes to manifest''', which is a [http://developer.mozilla.org/toolkiten/contentdocs/Chrome_Manifest Chrome Manifest]. This file helps Firefox translate between chrome:/widgets/tabbrowserURIs and actual files on disk.xml''' and It also allows us to specify where our overlay will be merged into the '''addTab''' methodbrowser:
# Chrome package addtabbeside has it's content in ./chrome/ Insert tab after current tab, not at end.content if (this.mTabContainer.childNodes.length == 0) { content addtabbeside chrome/content/ this # Overlay the browser.mTabContainerxul file with overlay.appendChild(t);xul } else { var currentTabIndex = thisoverlay chrome://browser/content/browser.mTabContainerxul chrome://addtabbeside/content/overlay.selectedIndex; this.mTabContainer.insertBefore(t, currentTabIndex + 1); }xul
I then repackage The first line registers the toolkitlocation 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.jar file (change xul ''objdiris'' to your objdir namethe browser (try typing it into the address bar): .
$ cd mozillaThe second metadata file we need to create is '''addtabbeside/install.rdf''objdir''. This is an [http://developer.mozilla.org/en/toolkitdocs/content $ makeInstall_Manifests install manifest] that tells the Firefox add-on manager how to install the extension, with information like who wrote it, the version number, compatibility information, etc.
then run <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>addtabbeside@senecac.on.ca</em:id> <em:name>Add Tab Beside</em:name> <em:version>0.1</em:version> <em:creator>David Humphrey</em:creator> <em:description>New tabs are created beside the current tab instead of at the end of the browser to test (NOTEtab list.</em:description> <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox --> <em:minVersion>2.0</em:minVersion> <!--<em:maxVersion>3.0a9pre</em:maxVersion>--> <!-- trunk build Oct 14, 2007 --> <em:maxVersion>3.0+</em: ''minefield'' is my testing profile)maxVersion> <!-- work for v3.0 and above --> </Description> </em:targetApplication> </Description></RDF></pre>
$ ../../dist/bin/firefox.exe -p minefield --no-remote===Testing the extension===
I try to create Eventually we'll package our extension properly into a new tab using redistributable .xpi. However, while we're testing it''File > New Tab''' and nothing happenss nice to be able to use it in an expanded form so we can make changes.
==Second Attempt==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):
Clearly my code has some problems, since I C:\Documents and Settings\'ve completely broken addTab. I decide to look for clues in the 'Username''\Application Data\Mozilla\Firefox\Profiles\'Error Console'dev-profile'' (Tools > Error Console) and notice the following exception whenever I try to add a new tab:\extensions\addtabbeside@senecac.on.ca
<code>Error: uncaught exception: [Exception... "Could not convert JavaScript argument" nsresult: "0x80570009 (NS_ERROR_XPC_BAD_CONVERT_JS)" location: "JS frame :: chrome://global/content/bindings/tabbrowser.xml :: addTab :: line 1161" data: no]</code>Or in Mac OSX
I make a guess that childNodes.length is not zero, but 1 by default (i.e., there is always at least one tab, even if it isn't visible). A quick modification to the code, and I test again:~/Library/Application Support/Firefox/Profiles/
if (this.mTabContainer.childNodes.length '''== 1''') { ...This file should contain a single line of text--the full path to your extension, for example:
==Third Attempt== C:\temp\addtabbeside
This worksStart your browser and make sure your extension is loaded (check '''Tools > Add-ons''') and working properly by creating some tabs to see where they get positioned. If you're having problems, but only check the first time I create a new tab. Clearly I still have some misconceptions about how '''mTabContainer.selectedIndexError Console''' and ('''mTabContainer.insertBefore()Tools > Error Console''' really work) for hints.
I can't yet see how my code is wrong, but ===Packaging the exception I'm getting clearly indicates that I've got some sort of type conversion problem. I decide to look again at the code examples in tabbrowser.xml that I'm using as a guide, specifically '''insertChild()'''.extension===
After a few seconds the error Once debugging and testing is obviouscomplete, you'll want to [http: I've used //developer.mozilla.org/en/docs/Extension_Packaging create an Integer where installable .xpi file] to give to your users. As was previously mentioned, .xpi files are just .zip files with a Tab was requireddifferent extension. Here is All .xpi files must contain the '''install.rdf''' file in the root (i.e., when you unzip it, install.rdf should be in the corrected code:top directory).
// Insert tab after current tab, not at endFollow these steps to create an installable . if (this.mTabContainer.childNodes.length == 1) { this.mTabContainer.appendChild(t); } else { var currentTabIndex = this.mTabContainer.selectedIndex; this.mTabContainer.insertBefore(t, '''this.mTabContainer.childNodes.item(currentTabIndex + 1)'''); }xpi:
==Success# 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, and some bugs==just it's contents).# Rename the resulting file to '''addtabbeside.xpi'''
After repackaging the toolkit.jar file and running the browser, I'm able to confirm that this last change has been successful. Opening a new tab now works You can try installing your extension in the way I originally described. I make a few more tests to insure browser that I havendoesn't broken anything else, for example, what happens if I am on the last tab and not in the middle. This works, which makes me realize that using have it by simply dragging '''append()addtabbeside.xpi''' is probably not necessary at all, into it. This should trigger the add-on manager and I can safely shorten my code down give you the option to install your extension and restart the following:browser.
// Insert tab after current tab==Success, not at end. var currentTabIndex and some bugs== this.mTabContainer.selectedIndex; this.mTabContainer.insertBefore(t, this.mTabContainer.childNodes.item(currentTabIndex + 1));
This means that six lines of code become two, and with that reduction in number of lines, hopefully The rewrite to an extension has been a reduction in new bugs Isuccess. In both cases we've added (NOTE: within reason, favour fewer rather than more lines of code)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.
Speaking of bugsHowever, as was the case in our previous attempt, our code has a closer read of bug. Moving existing tabs doesn't update our position state, since we only modify ''addTab'mPreviousIndex'' ' when a new tab is selected; moved tabs remain selected, but change their order (see [http://lxri.mozillae.org/seamonkey/source/toolkit/content/widgets/tabbrowser.xml#1219 line 1219]) would indicate that we, TabSelect won've introduced t get dispatched on a few with our new positioning code:move).
// wire up a progress listener for Luckily we've already stumbled upon the solution to this problem--the new browser object'''TabMove''' event. var position = Here is the updated version of '''thisaddtabbeside.mTabContainer.childNodes.length-1;js''' var tabListener = this.mTabProgressListener(t, b, blank); ... this.mTabListeners[position] = tabListener; this.mTabFilters[position] = filter; ... t._tPos = position;with the changes in bold:
Where the assumption before was that var AddTabBeside = { // State info on the newly created last tab was at to be selected. mPreviousIndex: 0, onLoad: function() { // Add a listener for the end TabOpen event, which gets called as // part of the listaddTab in tabbrowser.xml var container = gBrowser.tabContainer; container.addEventListener("TabOpen", the new code breaks thatthis. onTabOpen, false); Therefore, // Also add a listener for TabSelect so we also need know when focus changes to update the value of a new tab container.addEventListener("TabSelect", this.onTabSelect, false); '''position// And a listener for TabMove to fix an edge case''' '''container.addEventListener("TabMove", this.onTabSelect, false);''' // wire up Finally, add a progress listener for shutdown window.addEventListener("unload", this.onUnload, false); }, onUnload: function() { // Remove our listeners var container = gBrowser.tabContainer; container.removeEventListener("TabOpen", this.onTabOpen, false); container.removeEventListener("TabSelect", this.onTabSelect, false); '''container.removeEventListener("TabMove", this.onTabSelect, false);''' }, onTabSelect: function (e) { // When a different tab is selected, remember which one. This is // necessary because when a new tab is created, it will get pushed // to the end of the list, but we need to know where to put it. this.mPreviousIndex = gBrowser.tabContainer.selectedIndex; }, onTabOpen: function (e) { // Get the newly created tab, which will be last in the list var newTab = e.target; // Move this new browser objecttab to the right of the previously selected tab, // checking to see how many tabs there are currently. By default // there is 1 tab, and the first time onTabOpen is called, there will // be 2 (the default plus the newly created tab). var position = currentTabIndex In this case, don't // move the new tab, since it is already in the right spot. In all // other cases, move the tab to the right of the current tab. if (gBrowser.tabContainer.childNodes.length > 2) { gBrowser.moveTabTo(newTab, this.mPreviousIndex + 1); } }, };
No other obvious defects are visible from // Insure that our changescode gets loaded at start-up window.addEventListener("load", function(e) { AddTabBeside.onLoad(e); }, false);
=Reflections=
The change I was making was simple enough that I didnMany of the steps we did in order to create the extension't bother looking at any documentation or using s installation and registration files will be the JavaScript debuggersame each time. I found out afterward that tabbrowser has good Rather than doing it by hand, you can use [http://developerted.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/enSetting_up_extension_development_environment some preferences] to the browser, as well as the [http:/docs/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:tabbrowser documentation on MDC]/JS files without having to restart the browser.
Another trick worth trying when youYou can read complete details on how to set-up an extension developer're making lots of JavaScript changes like this is to add the following line to your s environment [http://kb.mozillazine.org/Setting_up_extension_development_environment here].mozconfig file:
ac_add_options --enable-chrome-format=flatResources=
This will cause the * [http://developer.jar files to be expanded so that you can edit the mozilla.xmlorg/.jsen/docs/.xul files in place and skip the repackaging step above (see Extensions Extensions on MDC]* [http://wwwkb.mozillamozillazine.org/build/jar-packaging.html). If you also use the Extension_development Extension Development on Mozillazine]* [http://teddeveloper.mielczarekmozilla.org/code/mozillaen/extensiondevdocs/index.html Extension Developer's extensionXUL_Event_Propagation XUL Event Propagation] you can reload the chrome without restarting the browser.

Navigation menu