Changes

Jump to: navigation, search

Dive into Mozilla Modifying Firefox using an Extension Lab

10,924 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': finding 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 right spot 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't sustainable long term, and users will always want to get the browser from Mozilla for security fixes, new features, etc. A logical alternative might be to try and get your change accepted into the tree. This involves filing a bug on https://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 good idea for tabs to be created in the way we've specified, the community may not--people have already put thought into the way things work now, users are accustomed to it, etc. In this case your bug will likely be marked '''WONTFIX''', which means that your patch won't make it into the tree. 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 a 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== 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.
It's one thing ===How to say you'd like to change the browser's behaviour, but quite another to actually do it. The change you have in mind might be quite simple, in the end (ours is). But you still have to figure out where that simple code needs to go. That can be difficult. However, difficult isn't the same as impossible.move a tab?===
How 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 you begin? . First, letPreviously we replaced '''append''' with '''insertBefore'''s start at the top and find some UI notation we can search for in stopped the codeproblem before it happened. In our However, in this caseby the time the '''TabOpen''' event is dispatched, we can focus on the various methods for creating a new tab:will already be created and positioned at the end of the list.
* CTRL+T* Right-Click an existing 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 and select New Tab* File > New Tabafter 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:
The second and third methods are useful, as they provide us with a unique string we can search for in the code <method name="moveTabTo"> <parameter name="aTab"/> <parameter name="aIndex"/> .. 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 development.
==Search 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 - finding a UI string==) how to get the newly inserted tab; and 2) the index of currently selected tab so we can go one to the right.
We're looking for a unique string--"New Tab"==, so we'll use [http://lxr.mozilla.org LXR's] '''Text Search''' feature. Here are =How to get the results you get when you search for "New Tab":newly created tab using code?===
<blockquote>httpHaving 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 the following in order to get the new tab://lxr.mozilla.org/seamonkey/search?string=New+Tab</blockquote>
Lots of results // In an extension, many of which point gBrowser is a global reference to comments in the codetabbrowser element var container = gBrowser.tabContainer; var lastIndex = container.childNodes. length - 1; However, the first result looks interesting:var newTab = container.childNodes[lastIndex];
<blockquote>httpHowever, 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://lxr.mozilla.org/seamonkey/source/toolkit/locales/en-US/chrome/global/tabbrowser.dtd#2</blockquote>
Here we see the DTD file describing the key/value pairs for the en-US localized strings t. Mozilla uses 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 dispatchEvent(you can read more about localization, DTDs, and Entities [http://developer.mozilla.org/en/docs/XUL_Tutorial:Localization here]evt) ;
Looking closely 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 '''tabbrowser.dtdtarget''' we see that our English string, "New Tab", has the following entity. Three lines of code become one:
<!ENTITY var newTab= e.label "New Tab">target;
This is good information, because it allows us to repeat our search with an entity instead of a string, which should help us get closer to the We'll discuss this code we're afterin more detail below.
==Search 2 - finding an ENTITY=How to get the index of the currently selected tab?===
Repeating the search with the We know that in order to move a tab using tabbrowser's '''newTab.labelmoveTabTo''' ENTITY value instead of method we need the "New Tab" string makes a big differencetab object--which we now have many fewer hits--and the index where it should be placed. Looking through the tabbrowser code again, we see [http://lxr.mozilla.org/seamonkey/source/toolkit/content/widgets/tabbrowser.xml#1453 references] to code like this:
<blockquote>http://lxr var currentIndex = this.mozilla.org/seamonkey/search?string=newTabmTabContainer.label</blockquote>selectedIndex;
Not surprisingly, Obviously we can't use '''this''' outside the first result is the same DTD file (i.e.context of tabbrowser, so in an extension we use '''gBrowser''' instead to get a reference to tabbrowser.dtd) we already found. The second result looks interesting, though:
<blockquote>http://lxr gBrowser.mozillatabContainer.org/seamonkey/source/toolkit/content/widgets/tabbrowser.xml#80</blockquote>selectedIndex
Here If we see want to get the code tab to generate the pop-up context menu for a right of the current tab (i.e., what you get when you right-click on a tab in the browser)we simply do this:
<pre> <xul:menuitem labelvar positionPlusOne ="&newTabgBrowser.label;" accesskey="&newTabtabContainer.accesskeyselectedIndex + 1;" xbl:inherits="oncommand=onnewtab"/> </pre>
Having found Are we done? Not quite. This code will work, but the appropriate entity value, problem we also notice the use of a function name, now face is that our code will run ''after'onnewtab'''. This line the tab has been placed at the end of code says that the xul:menuitem will inherit the list, when '''oncommandTabOpen''' value from its parent (you can read more about XBL attribute inheritance [http://developer.mozilla.org/en/docs/XUL_Tutorial:XBL_Attribute_Inheritance here])is dispatched. In other words, when this menu item As soon as the new tab is clickedcreated, call it becomes the active tab. That means that '''onnewtabselectedIndex''' functionwill always be the index of the last tab, and moving the last tab one to the right is nonsense.
==Search 3 - finding a Function==What we ''really'' need is the position of the tab that we were on when he made the new tab. We even know how to get this information, using '''tabContainer.selectedIndex'''. What we don't know is how to get this information, since it is lost by the time our code runs.
Armed with this new information, we are even closer to finding the right spot to begin working. We've gone from UI string to XML ENTITY to function. All we have to do now is find that function:===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/search?string=onnewtab</blockquote>
This returns many results for things In order to accomplish this, we need another event that tells us when a tab has become the active tab so we aren't interested in, including files rooted in /suite, /db, etccan store the position state. Since we are interested in finding this behaviour in FirefoxA quick search through tabbrowser for other events (e.g., we need to focus search on the files rooted in '''/browser"dispatchEvent"'''. One looks particularly interesting) shows these possibilities:
<blockquote>http://lxr.mozilla.org/seamonkey/source/browser/base/content/browser.xul#503</blockquote>* TabSelect* TabOpen* TabClose* NewTab* TabMove
In this case, The first event looks interesting. All we need to do now is have a variable that gets updated with the tabbrowser widget has the onnewtab property set to another function, value of '''tabContainer.selectedIndex''' every time '''BrowserOpenTab();TabSelect''' (i.eoccurs. Then, Firefox seems to handle tab creation in a non-standard way, providing its own method instead of using the default). Since we want can use this variable's value to find calculate the definition of this function, we search for desired position in '''"function BrowserOpenTab("moveTabTo''', which returns two results:.
<blockquote>http://lxrFinally, we've got all the pieces in place and can write some code.mozilla.org/seamonkey/search?string=function+browseropentab%28</blockquote>
Again, we=The 're interested in Firefox (i.e., browser) instead of SeaMonkey (i.e., suite), so we skip to How': the second result:extension'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.mozilla With an extension, we have to explicitly tell the browser how and when to execute our code.org/seamonkey/source/browser/base/content/browser We do this using '''event listeners'''.js#1802</blockquote>
This shows us From our previous research, we know that we need the methods in tabbrowser cause a variety of events to be looking for yet another functionget 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, '''loadOneTab()'''nothing is done in response to events. Another search: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.
<blockquote>We need to wire our code to a number of events by [http://lxrdeveloper.mozilla.org/seamonkeyen/search?string=loadonetab<docs/blockquote>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 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.
The first result is not surprising, and we're back to the tabbrowser widget==addtabbeside. The '''loadOneTab''' method calls another method to actually create and insert the new tab:js==
To keep things clean we'll write all this code in a custom object called '''AddTabBeside'''. var tab = thisHere is '''addtabbeside.addTab(aURI, aReferrerURI, aCharset, aPostData, owner, aAllowThirdPartyFixup);js''':
Since ''' 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 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("TabSelect", this.onTabSelect, false); }, onTabSelect: function (e) { // When a different tab is selected, remember which one. This is // necessary because when a method new tab is created, it will get pushed // to the end of '''the list, but we need to know where to put it. this''' we can search within the current document .mPreviousIndex = gBrowser.tabContainer.selectedIndex; }, onTabOpen: function (CTRL+Fe) { // Get the newly created tab, which will be last in the list var newTab = e.target; // Move this new tab to find the '''addTab''' methodright 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). Finally weIn this case, don've found 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); } }, }; // 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/toolkit/content/widgets/tabbrowser We also need to create an installation package for our overlay.xml#1160</blockquote>
==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. thisBecause extensions are just .zip files, you can unpack any extension and see how it's built. For example, try saving [https://addons.mozilla.mTabContainerorg/firefox/16/ ChatZilla's .appendChildxpi] to your computer and decompress it (NOTE: if your built-in unzip program won'tdo this, try changing 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, metadata telling the browser how to integrate and install things, what the extension is called, its version number, etc. We need to create these other files now. ===Extension files and directory structure=== Start by creating the following directory structure:  addtabbeside/ chrome/ content/
Now all Because this is a first extension, we will skip some [http://developer.mozilla.org/en/docs/Building_an_Extension#Setting_up_the_Development_Environment other directories and files] that we have to do is modify it to insert rather than appendmore complete extensions would include (e.g., localization, skins).
=The 'How': the necessary changes to the code===addtabbeside/chrome/content====
There are different ways you could go about making this change, and someone with more experience using tabbrowser might recommend a different strategy or outcome. I decided The first thing to work on something that I knew nothing about in order do is to highlight copy the process one goes through, or at least the process I went through, when working with someone else's code''addtabbeside. Since my goal is js''' file we wrote earlier to show you how to do this, I also discuss my errors and mistakes below--they are an important part of the process too'''addtabbdeside/chrome/content/addtabbeside.js'''.
==First Attempt==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 addtabbeside.js script into the browser.
The goal is We need to make as small a change as possible, since the existing code works well--I just want it to work slightly differentcreate '''addtabbdeside/chrome/content/overlay. Ixul'''m also not interested in reading all of the code in order to make such a small change. I want to leverage as much of what is already there as I can.
I assume that the '''appendChild()''' method is responsible for the behaviour I don't like (i<pre><?xml version="1.e0" encoding="UTF-8"?> <overlay id="addtabbeside-overlay" xmlns="http://www., adding new tabs to the end of the list)mozilla. I'm not sure what to replace it with, so I do another search inside tabbrowserorg/keymaster/gatekeeper/there.xml (iis.eonly., using CTRL+F) looking for other methodsxul"> <script type="application/attributes of '''mTabContainer'''x-javascript" src="addtabbeside. I come-up with some interesting options:js"/> </overlay></pre>
index = this.mTabContainer.selectedIndex; ... this.mTabContainer.insertBefore(aTab, this.mTabContainer.childNodes.item(aIndex)); ... var position = this.mTabContainer.childNodes.length-1;==addtabbeside/====
I decide that I can probably accomplish my goal using these aloneHaving added our script and overlay files, and so start working on we now need to add a solution. Here is my first attempt, showing the changes couple of metadata files to '''mozillahelp Firefox understand and install/toolkit/content/widgets/tabbrowserload our extension.xml''' and the '''addTab''' method:
The first is '''addtabbeside/chrome.manifest''', which is a [http:// Insert tab after current tab, not at enddeveloper. if (thismozilla.mTabContainer.childNodesorg/en/docs/Chrome_Manifest Chrome Manifest].length == 0) { this.mTabContainer.appendChild(t); } else { var currentTabIndex = thisThis file helps Firefox translate between chrome:// URIs and actual files on disk.mTabContainer.selectedIndex; this.mTabContainer.insertBefore(t, currentTabIndex + 1); }It also allows us to specify where our overlay will be merged into the browser:
I then repackage # Chrome package addtabbeside has it's content in ./chrome/content content addtabbeside chrome/content/ # Overlay the toolkitbrowser.jar xul file (change ''objdir'' to your objdir name)with overlay.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). $ cd mozillaThe 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 ''objdiris''/toolkit/content $ makethe browser (try typing it into the address bar).
then run the browser The second metadata file we need to test (NOTE: create is '''addtabbeside/install.rdf'minefield'' . This is my testing profile)an [http://developer.mozilla.org/en/docs/Install_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.
<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 tab list.</em:description> <em:targetApplication> <Description> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</distem:id> <!-- firefox --> <em:minVersion>2.0</binem:minVersion> <!--<em:maxVersion>3.0a9pre</firefoxem:maxVersion>--> <!-- trunk build Oct 14, 2007 --> <em:maxVersion>3.exe 0+</em:maxVersion> <!-p minefield -work for v3.0 and above -no-remote> </Description> </em:targetApplication> </Description></RDF></pre>
I try to create a new tab using '''File > New Tab''' and nothing happens.===Testing the extension===
==Second Attempt==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 form so we can make changes.
Clearly my code has some problemsTo this end, since Icreate a file named '''ve completely broken addTabaddtabbeside@senecac.on. I decide to look for clues ca''' and put it in the your development profile's extensions directory (NOTE: replace ''Username''with your username and 'Error Console'dev-profile'' (Tools > Error Consolewith your development profile name) and notice the following exception whenever I try to add a new tab:
<code>Error C: uncaught exception: [Exception\Documents and Settings\''Username''\Application Data\Mozilla\Firefox\Profiles\''dev-profile''\extensions\addtabbeside@senecac.on.. "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>ca
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:Or in Mac OSX
if (this.mTabContainer.childNodes.length '''== 1''') { ...~/Library/Application Support/Firefox/Profiles/
==Third Attempt==This file should contain a single line of text--the full path to your extension, for example:
This works, but only the first time I create a new tab. Clearly I still have some misconceptions about how '''mTabContainer.selectedIndex''' and '''mTabContainer.insertBefore()''' really work.C:\temp\addtabbeside
I canStart your browser and make sure your extension is loaded (check '''Tools > Add-ons't yet see how my code is wrong, but the exception I'm getting clearly indicates that I've got ) and working properly by creating some sort of type conversion problemtabs to see where they get positioned. I decide to look again at If you're having problems, check the code examples in tabbrowser.xml that I'm using as a guide, specifically ''Error Console'''insertChild()'''Tools > Error Console''') for hints.
After a few seconds ===Packaging the error is obvious: I've used an Integer where a Tab was required. Here is the corrected code:extension===
Once debugging and testing is complete, you'll want to [http:// Insert tab after current tab, not at enddeveloper. if (thismozilla.mTabContainer.childNodes.length == 1) { thisorg/en/docs/Extension_Packaging create an installable .mTabContainerxpi file] to give to your users.appendChild(t); } else { var currentTabIndex = thisAs was previously mentioned, .mTabContainerxpi files are just .selectedIndex; thiszip files with a different extension.mTabContainer All .insertBefore(t, xpi files must contain the '''this.mTabContainer.childNodesinstall.item(currentTabIndex + 1)rdf'''file in the root (i.e., when you unzip it, install.rdf should be in the top directory); }.
==Success, and some bugs==Follow these steps to create an installable .xpi:
After repackaging the toolkit# Create a new zip file named '''addtabbeside.zip''' (NOTE: you can use .xpi instead of .zip if your application will allow it).jar file # Add your extension files and running the browserdirectories to '''addtabbeside.zip''', Imaking sure that '''m able to confirm that this last change has been successfulinstall. Opening a new tab now works rdf''' is in the way I originally describedroot (i. I make a few more tests to insure that I havene., don't broken anything elsezip the addtabbeside directory itself, for example, what happens if I am on just it's contents).# Rename the last tab and not in the middle. This works, which makes me realize that using resulting file to '''append()addtabbeside.xpi''' is probably not necessary at all, and I can safely shorten my code down to the following:
// Insert tab after current tab, not at endYou can try installing your extension in a browser that doesn't have it by simply dragging '''addtabbeside. var currentTabIndex = thisxpi''' into it.mTabContainer.selectedIndex; this.mTabContainer.insertBefore(t, this.mTabContainer.childNodesThis should trigger the add-on manager and give you the option to install your extension and restart the browser.item(currentTabIndex + 1));
This means that six lines of code become two==Success, and with that reduction in number of lines, hopefully a reduction in new some bugs I've added (NOTE: within reason, favour fewer rather than more lines of code).==
Speaking of bugs, The rewrite to an extension has been a closer read of success. In both cases we'''addTab''' (see [http://lxrve managed to achieve the same goal using almost completely different methods.mozilla.org/seamonkey/source/toolkit/content/widgets/tabbrowser.xml#1219 line 1219]) would indicate that Using an extension we've introduced made it possible to share our changes with any Firefox user worldwide without having to ship a few with our new positioning code:custom build.
// wire up However, as was the case in our previous attempt, our code has a progress listener for the new browser objectbug. var Moving existing tabs doesn't update our position = state, since we only modify '''this.mTabContainer.childNodes.length-1;mPreviousIndex''' var tabListener = this.mTabProgressListenerwhen a new tab is selected; moved tabs remain selected, but change their order (t, b, blank); i.e.. this.mTabListeners[position] = tabListener; this.mTabFilters[position] = filter; ... , TabSelect won'tget dispatched on a move)._tPos = position;
Where Luckily we've already stumbled upon the assumption before was that solution to this problem--the newly created tab was at the end of the list, the new code breaks that'''TabMove''' event. Therefore, we also need to update Here is the value updated version of '''positionaddtabbeside.js''', with the changes in bold:
var AddTabBeside = { // State info on the last tab to be selected. mPreviousIndex: 0, onLoad: function() { // wire up Add a progress listener for the new browser objectTabOpen event, which gets called as // part of addTab in tabbrowser.xml var position container = currentTabIndex + 1gBrowser.tabContainer; container.addEventListener("TabOpen", this.onTabOpen, false);
No // Also add a listener for TabSelect so we know when focus changes to a new tab container.addEventListener("TabSelect", this.onTabSelect, false); '''// And a listener for TabMove to fix an edge case''' '''container.addEventListener("TabMove", 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("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 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, there will // be 2 (the default plus the newly created tab). In this case, don't // move the new tab, since it is already in the right spot. In all // other obvious defects are visible from cases, move the tab to the right of the current tab. if (gBrowser.tabContainer.childNodes.length > 2) { gBrowser.moveTabTo(newTab, this.mPreviousIndex + 1); } }, }; // 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