Thursday, July 8, 2010

View Tab : The Implementation - Part 3

So far, I've discussed how the page to be displayed in the preview thumbnail (PT) is scaled down, snipped and drawn on a canvas, which, indeed, was the trickiest part of the implementation. Well, it seems straightforward once you know what is to be done, but less than six weeks ago, we hardly knew the A,B,C of Javascript, and this seemed quite an impossible task. We learnt about the canvas element almost by chance. I'd noticed, during my initial, failed attempts, that attempting to drag a tab in Firefox creates a tiny thumbnail showing the contents of that tab (you can try that right now, if you've never noticed it). So, I decided to look into the browser's main javascript file : chrome://browser/content/browser.js, which is where I came across the canvas element for the first time. It was only after that, that we could actually start working on View Tab.

As I said at the end of the last post, we are done with creating a preview canvas, and all that's left to do is make sure it shows up at the right place. For this, first we create a popup element.

//Create a Tooltip to show the canvas
    prevTooltip = document.createElement("popup");
    prevTooltip.id="viewTabTooltip";
    prevTooltip.position="after_pointer";
    prevTooltip.appendChild(prevCanvas);
    prevTooltip.setAttribute("style","margin:21px 0px;
        -moz-box-shadow:10px 10px 2px black;
        text-rendering:geometricPrecision !important;");

The popup element is, as the name suggests, a popup, which is not shown in the document, and has to be attached to another element in one of three ways, to cause it to show on clicking, right-clicking and hovering the mouse over the element. More on popups here.

The popup, called prevTooltip, is given the id "viewTabTooltip" and its position is set to "after_pointer", which ensures it shows up below the mouse pointer, when it is invoked. Then, the canvas, containing the preview is appended to prevTooltip, and the popup is styled.

Finally, the popup is appended to the browser's mainPopupSet, and attached to aTab using it's tooltip attribute.

//Attach the tooltip to aTab
    document.getElementById("mainPopupSet")
            .appendChild(prevTooltip);
    aTab.setAttribute("tooltip","viewTabTooltip");
    }
}

With this the function viewTab.createPreview() is complete, and will do the job of showing the PT if called when the mouse pointer is moved over a tab.

Next, the function to remove the popup is defined.

/* Function to End Preview */
viewTab.endPreview = function(aTab,firsttab){
    if(firsttab) aTab=gBrowser.tabContainer.
       getElementsByClassName("tabbrowser-tab")[0];

The first line of code in the function, again, as I mentioned in the first post, is included to avoid a specific error.

if(document.getElementById("viewTabTooltip")){
   document.getElementById("mainPopupSet").
     removeChild(document.getElementById
                          ("viewTabTooltip"));
   aTab.removeAttribute("tooltip");
}}

If the document contains an element with the id "viewTabTooltip", which is the popup that contains the preview canvas, then it is removed, and the tooltip attribute of aTab is also removed. This completes viewTab.endPreview().

Next, a function to set the onmouseover and onmouseout attributes of tabs, so that the functions defined earlier are invoked when they are needed, is defined.

/* Function to set the onmouseover property of the tabs*/
viewTab.init = function (){
  tabs = gBrowser.tabContainer.
getElementsByClassName("tabbrowser-tab");
for(var i=0;i
    if (i!=0){
    tabs[i].setAttribute("onmouseover",
        "viewTab.createPreview(tabs["+i+"]);");
    tabs[i].setAttribute("onmouseout",
        "viewTab.endPreview(tabs["+i+"]);");
    }
    else
    {
        tabs[0].setAttribute("onmouseover",
        "if(tabs[0] !=gBrowser.selectedTab) 
         viewTab.createPreview(tabs[0],true);");
    tabs[0].setAttribute("onmouseout",
        "viewTab.endPreview(tabs[0],true);");
    }
}
}
The function is pretty straightforward, an array of tabs is created and their respective onmouseover and onmouseout attributes are set to invoke viewTab.createPreview() and viewTab.endPreview(). The portion under else is required because, as mentioned earlier, the first tab causes some trouble, and needs to be dealt with separately.

Finally, event handlers are added to the gBrowser, the primary tabbrowser element, to invoke viewTab.init() whenever any change is made to the tabs.

//Add the event handlers to listen for new tabs
gBrowser.addEventListener
         ("load",viewTab.init,false);
gBrowser.addEventListener
         ("TabSelect",viewTab.init,false);
gBrowser.addEventListener
         ("TabClose",viewTab.init,false);
gBrowser.addEventListener
         ("TabMove",viewTab.init,false);

I'd initially assumed it would be enough to listen just for the "TabOpen" event (which I ultimately removed!), which is supposedly fired whenever a new tab is opened. But apparently it wasn't enough, so I tried listening for just the "load" event, (which is fired in more places than "TabOpen"), but it wasn't enough either,and so, after a experimenting for a while with possible ways of manipulating tabs, I was finally able to come up with this almost exhaustive list of events to listen for, so that the PT's always show up. Including "TabSelect" has an added advantage: even if something's gone wrong and PT's aren't showing up, selecting a different tab invokes viewTab.init and that puts things back in place.

With that, the implementation of View Tab is complete! Hope it was useful.

Monday, July 5, 2010

View Tab : The Implementation - Part 2

This post is the continuation of View Tab : The Implementation - Part 1

The next line of code in viewTab.js ensures that the preview thumbnail (PT) is created only if aTab is not the active tab (only if VIEWTAB_NOPREV_SELECTED is set to true).
if(aTab!=gBrowser.selectedTab && VIEWTAB_NOPREV_SELECTED){

Next, we get the extract the browser window for which the PT is to be created.

//Get the browser's content window
var prevWin=aTab.linkedBrowser.contentWindow;
var prevWinWidth=prevWin.innerWidth;
var prevWinHeight=prevWin.innerHeight;

The linkedBrowser property of a tab, aTab in this case, returns the browser associated with the tab, and the contentWindow property of a browser returns the window element in the browser. Thus, prevWin is now set to the window for which the PT is to be created, and prevWinWidth and prevWinHeight to its width and height respectively.

The PT, however does not show the entire window, instead it only shows a portion of the window, whose dimensions are determined as follows,

//Define the region to snip
var snipHeight=Math.round
(prevWinHeight*VIEWTAB_SNIP_RATIO);
var snipWidth=Math.round
(snipHeight*VIEWTAB_ASPECT_RATIO);

The height of the portion to be shown, the snip, is VIEWTAB_SNIP_RATIO times the actual height of the window, and the width is calculated so as to maintain the aspect ratio. I chose to set the height first and not width because many widths go well with the same height (especially considering the fact that may different screen ratios: 4:3, 5:4, 16:9 etc. are currently in use, and that sometimes users choose to split their screens width-wise to display two windows side by side while maintaining their original heights). All in all, experimenting with this revealed that setting the height first leads to better results, and so it's been done this way.

The next block of code is where the actual business begins, in setting up an HTML canvas to display prevWindow (a portion of it, actually) in the PT.

//Define and prepare the canvas for Drawing
var prevCanvas = document.createElementNS
("http://www.w3.org/1999/xhtml","canvas");
var canHeight = Math.ceil
(screen.availHeight*VIEWTAB_SIZE_RATIO);
var canWidth = Math.ceil
(canHeight*VIEWTAB_ASPECT_RATIO);
prevCanvas.id="viewTabCanvas";
prevCanvas.width=canWidth;
prevCanvas.height=canHeight;

prevCanvas is set to a new empty HTML canvas. A canvas can be used to draw and animate objects, including windows, which is what is being done here. A tutorial on the usage of the canvas element can be found here. I found some code that was useful for View Tab here. This is really the best thing about Mozilla, everything is so well documented. Its not just the reference, there are so many tutorials, examples and code snippets, that make it really easy to follow along and get a hang of things.

Moving on, prevCanvas is given the id "viewTabCanvas", its height is set to VIEWTAB_SIZE_RATIO times the screen height, and width, of course, is set to VIEWTAB_ASPECT_RATIO times the height.

Next, the drawing context of prevCanvas is extracted, and the canvas is cleared for drawing.

//Get the drawing context
var ctx=prevCanvas.getContext("2d");
ctx.clearRect(0,0,canWidth,canHeight);
ctx.save();

The drawing context is used to actually draw on the canvas. More on the drawing context here. The clearRect method clears the canvas, and the save method saves the current state of ctx.

//Decide whether preview window should
//scroll to the current position or not
var initX=0;
var initY=0;

if(VIEWTAB_SCROLL_PREV){
initX=prevWin.scrollX;
initY=prevWin.scrollY;
}

Then, depending on the value of VIEWTAB_SCROLL_PREV, the coordinates on the window from where we are to begin drawing, are decided. If VIEWTAB_SCROLL_PREV is set to true, then the preview in the thumbnail is scrolled by the same amount to which the actual window is scrolled, otherwise, the top-left portion of the page is drawn, even if it is scrolled to a different position.

The next block of code actually draws the window.
//Draw The window in the canvas
ctx.scale(canWidth/snipWidth,canHeight/snipHeight);
ctx.drawWindow(prevWin,initX,initY,
snipWidth,snipHeight,"rgb(255,255,255)");
ctx.restore();
ctx.scale scales whatever is drawn next by the given ratios along the x and y directions. This line of code is responsible for shrinking the preview so it fits in the PT. Next, ctx.drawWindow draws a portion of prevWindow, whose starting coordinates, followed by its width and height, are passed as parameters to the function. The last parameter "rgb(255,255,255)", which corresponds to the color white, sets the background. ctx.restore() restores the previously saved state of ctx.

With this, we are done with creating a preview canvas, and all that's left to do is make sure it shows up at the right place. More in the next post.

Sunday, July 4, 2010

View Tab : The Implementation - Part 1

This post discusses the implementation of the View Tab 1.0. The description and the download link for the extension can be found here. The file structure of the extension is not discussed here, as it is not very different from the simple Hello World extension described in the MDN XUL School Tutorial, except that I haven't used a jar archive to store the chrome elements, and since there are no files under skin and locale, I've omitted these directories and included the content files directly in the chrome directory. The directory tree for the extension is shown below:

ViewTab.xpi :-
  • chrome.manifest
  • install.rdf
  • chrome :-
    • viewTab.xul
    • viewTab.js
The file viewTab.xul is the overlay for the main browser window. It does not introduce any new elements into the window, the only purpose served by it is to include the script from the file viewTab.js.

The script under viewTab.js is responsible for creating and showing the preview thumbnails. To view the source code for this file, if you have the add-on installed, type chrome://viewtab/content/viewTab.js in the URL bar and hit Enter. The code is discussed below.

The first block of code declares some global parameters.
//Some Parameters
var VIEWTAB_ASPECT_RATIO = 16/9;
var VIEWTAB_SIZE_RATIO = .3;
var VIEWTAB_SNIP_RATIO = .55;
var VIEWTAB_SCROLL_PREV=true;
var VIEWTAB_NOPREV_SELECTED=true;
VIEWTAB_ASPECT_RATIO is the aspect ratio i.e. width to height ratio of the preview thumbnail(PT). It is set to 16:9.

VIEWTAB_SIZE_RATIO
is the ratio of the height of the PT to the screen height. It is set to 0.3, i.e. the height of the PT is 30% of the screen height.

VIEWTAB_SNIP_RATIO
is ratio of the height of the portion of the tab which is actually shown in PT to the actual height of the tab. It is set to 0.55, which means only 55% of the tab is actually shown in the PT.

VIEWTAB_SCROLL_PREV
decides whether the preview in the PT is scrolled to the current position of the page in a tab. If it is set to false, then the PT will always show the top of the page in the tab, even if it is scrolled to a different position. It is set to true by default.

VIEWTAB_NOPREV_SELECTED
, when set to true, ensures that a PT is not shown for the currently selected tab.

These parameters should probably have been provided as editable preferences, to allow the user to customize the add-on according to their needs, but weren't, as I wasn't familiar with handling preferences when I created the first version of the add-on. I plan to incorporate this, along with a few other things in a later version of the add-on.

The next block of code declares a new namespace to avoid naming conflicts with already existing functions and objects.

/* Creating the viewTab namespace */
if ("undefined" == typeof(viewTab)){
   var viewTab={
   }
}

Next, the function that creates a preview thumbnail for a given tab, is defined.
/* Function to create Thumbnail Preview */
viewTab.createPreview = function (aTab,firsttab){

The function takes two arguments, the first being the XUL tab object for which the PT is to be created, and the second a boolean firsttab, which indicates whether the tab provided is the first among all tabs. The next line uses firsttab, as follows,

if(firsttab) aTab = gBrowser.tabContainer.
getElementsByClassName("tabbrowser-tab")[0];

gBrowser is the main tabbrowser element in the browser window, and contains all the tabs, and their corresponding browsers. The tabContainer elements is, as the name suggests, the container for the tabs, whose class attribute is set to "tabbrowser-tab" , which allows them to be selected into an array using the getElementsByClassName() method of the tabContainer element. The first tab in the list is obviously stored under the index 0, and so is retrieved by attaching [0] at the end of the array. Thus, if firsttab is true, aTab is set to be first tab in the browser.

This line of code, however, as most people will realize, is not required in the first place, as aTab, passed as an argument to the function, should already be set to the tab for which the PT is to be created, irrespective of whether it is the first tab or not. But, Firefox, for some reason throws an error "aTab is not defined" when the first tab in the browser is passed to the function. This happens only with the first tab, the function works fine for all other tabs. Hence, this corrective measure makes sure we do not receive such an error.


More in the next post.

Friday, July 2, 2010

All About about:config

Most people already know about about:config but I thought I'd mention it anyway, just as a token of appreciation to the the fact that Firefox lets customize it to do things exactly the way you want them to be done, which makes it all the more a wonderful application to use.
All you have to do is type in the words about:config into the URL bar and hit Enter. You might receive a warning that looks something like:


That warning isn't there for no reason, and most people will advise you not to mess with things that don't make obvious sense, so better be careful. However, once you do click on the button, you will see a screen that looks like:


This page is basically a list of preferences (a huge list at that, but, it's not complete, there are lots of other preferences not shown in this list). There's a column for the preference name, which apart from uniquely identifying the preference, also gives you an idea about what it's for (in most cases). If you paln on designing an add-on which depends on one of these preferences, the preference name is what you should be using. More on handling preferences here.

Next, there's column showing the status, whether a preference currently bears it default value, or one set by the user. Preferences set by the user are shown in bold. The term user set doesn't really mean the preference has been set by the user herself, it could have been done by an extension, or a theme, or an external application. So you might want to think twice before you decide to restore defaults.

Then there's a column showing the preference type:string, integer or boolean. The next column shows the value currently held by the preference. You can change the value of a preference by double-clicking on it. If the preference is a boolean, it is toggled automatically, otherwise you are prompted to enter a new value (in case of integers and strings). You can also add new preferences by right-clicking, and selecting New.

There's also a search box(at the top of the page, in case you didn't notice) that allows you to filter preferences that contain the string you enter in the box. This, plus the fact the preferences are extremely well-named, make this page extremely easy to use. For example, you can simply type in tab to see and modify almost all the preferences related to tabs.

If there's something you feel can be done differently, without changing the structure of firefox itself, then you will, in most cases, find a preference that lets you do that. I, for example don't like the downloads window popping up when I start a new download. If you type in download in the filter box, you will find a series of preferences beginning with browser.download.manager that let you control certain properties of downloads window. In my case, all I had to do was double-click on the preference browser.download.manager.showWhenStarting which is a boolean set to true by default, and is responsible for opening the downloads window when a new download starts. You might find some interesting preferences under the keywords: mouse, history, bookmark, extension, url, cache etc.

One must, of course, be extremely careful while changing preferences, for, as the warning indicates, changing them can be harmful to the stability, security, and performance of Firefox. Do so, only if you are sure of what you are doing. It is a good idea to verify what you plan to do, before actually doing it.

Tuesday, June 22, 2010

And Now.......Browser Turns Editor

An add-on a day, that's how it's been going for the last three days. Right now, I'll just post description of the add-on on the Mozilla Add-ons site. It goes like this:

Did you know that you can actually edit pages within Firefox! You can edit the text, apply simple formatting changes and re-size and move images, tables and other objects. All you need to do is turn on the 'Design Mode' of the document in the browser. Unfortunately, there is no way of doing that directly in Firefox. That's where this add-on comes in.

Select Tools->Toggle Design Mode, or just click on the pencil icon on the main toolbar to turn on the Design Mode of the document in the currently selected tab. That's it. Now you can edit the text on the page, and re-size, move, or remove images, tables and other elements on absolutely any web page. Well maybe that isn't too much, without advanced formatting options, but nevertheless it adds functionality to your browser. If you're not familiar with HTML and are looking to make a quick edit to a web page, or to a template you've downloaded, you can now do that without having to take the pain of going through the source or opening the file in another editor (if you have one).

Once you are done editing the the page, click on the menu item, or the toolbar button again, and the Design Mode will be turned off. Then you can save the page as usual by selecting File->Save As. At any moment, if you reload the page, the changes you have made will vanish (though they will still be present in the saved file, if you have saved the page). You can edit many tabs at the same time, and independently i.e. the Design Mode can be toggled on/off independently for each one of them.

I wish to develop this add-on further, like introduce a toolbar, so that it becomes possible to modify every element in page, without having to see the source. Till then, Happy Editing!

Developer Comments

This add-on is nothing more than a simple little tweak, and to many might seem like a silly joke. Well, my idea is to develop it further, and introduce a toolbar for better formatting options. That way people won't have to use other software to modify, or redesign web pages. Do get in touch with me if you are interested in developing this add-on.


Again, I promise I'll post the documentation later. By the way, I discovered a few bugs in View Tab, so I'll have to start fixing them now.

Monday, June 21, 2010

Presenting.........Close Multiple Tabs!

Here goes another..... after, well, a fair bit of struggle around the tricky parts. This add-on was mostly easy, after understanding how dialogs work, but I struggled for some time trying to update the dialog as changes were made in the browser window while the dialog was still open. I had considered restructuring the entire thing, but I realized soon enough that it wasn't such a great idea. Then it happened, almost by mistake. I tried to open the dialog again while it was still open, and magically, the contents of the dialog got updated! I then realized that the trick was to reload the dialog whenever a change was made with the tabs in the main window, and with that I was able to finish the add-on and submit it. I'll discuss the implementation in a later post.

Here's the description I've given on the Mozilla Add-Ons Site:

I'm pretty frequently faced with a situation where I have 20 or more tabs open, and I wish to close all but 3 or 4 of them.It's a pain closing them all one by one, while being careful about not closing the ones I need. So, I built an Add-on to solve this problem.

This add-on allows you to close many tabs with a single click. Select File->Close Multiple Tabs, or just press Ctrl+Q to open a dialog showing the list of tabs that are currently open. You can select the tabs you wish to close from the list, and click OK (or just hit Enter!) to close them. If all the tabs are selected, then you have the option of choosing between a blank page and the browser's homepage.This way you can get rid of all the tabs and start a new session, without having to, perhaps, restart the browser.

Tip: Use Select All and deselect the tabs you need when you wish to get rid of all but a few tabs (like 3 or 4 out of 20), and use Select None and select the tabs you wish to close if they are few in number. This way, you'll find this add-on really handy.

You don't have to worry about cluttering of tabs anymore, because the solution is just one click away.
Happy Surfing!

Developer Comments

I realize that Ctrl+Q is perhaps, too important a shortcut to assign to this add-on, but this has been done only to make the add-on easily accessible, because those who actually find this add-on useful will find themselves using it quite often (at least that's the case with me), and fumbling repeatedly with a Ctrl+Shift+Something combination can be quite annoying.

Sunday, June 20, 2010

Presenting.........ViewTab!

Here it is.... finally. Go ahead,click on the link,and download and install the add-on (oh, and don't forget to give it 5 stars! ).After weeks of research (Googling, actually), a modest one and half day of coding, and another half-a-day of debugging, I was finally satisfied with it, so much that I decided to go ahead and submit it on the Mozilla Add-Ons website. I must say they have tough testing standards, so one can't do funny things and get away.
  • I had to upload it 5 times, as it threw trivial errors the first four times during the verification process.
  • I had to decrease the highest supported Firefox version on the add-on(I'd given 4.* :P), as it wasn't matching an already existing one.Essentially, it means you cannot design add-ons for upcoming versions of Firefox, you have to update your add-on after the new version is released.
  • The Add-On passed all tests, with a warning that said it didn't appear to be localized. Well, there's absolutely no text involved anywhere, so language isn't something I should worry about.
  • I had to select a category for it, before it could be transferred to the Sandbox (whatever that is!).
  • I even had to upload a preview screenshot, before the add-on could be made public.
  • And right now, its sitting there waiting to be reviewed, behind 41 other add-ons, as the page said when I last checked.
I'm too tired to write about the implementation right now, I'll definitely do that soon. Well,right now, it's party time!