Rolling Your Own Browser

The browser is dead. It is a two dimensional piece of software that is slowing the evolution of the Web. Are you shocked by these statements, reading this article from the comfort of your browser of choice? Well you should be because no matter what some people lead you to believe, browsers will be around for a while longer. The purpose of this article is to introduce you to the concept of customisating, or creating your own browser, and how you can do it with the wonderful toolkit that is Mozilla.

Why your own browser?

Before proceeding, it must be noted that the rest of this article focuses on the technical issues involved in getting a XUL browser up and running in the application framework. This is distinct from embedding Gecko, which was discussed in a previous article on the DevCenter, 'Let One Hundred Browsers Bloom'.Gecko is the Mozilla layout rendering engine. The merits of having multiple browser projects has been touched on already, so lets look at some of the practical uses that an integrated browser can bring to your application, apart form the traditional usage for web navigation.


Of course, when you have the raison d'etre for your browser, you have to start putting the pieces in place to get it up and running. This will take the form of a full blown Mozilla application, complete with the XPFE toolkit for building your own UI, or perhaps just an add-on to the existing suite that can be distributed in the form of an XPI. The term XPI refers to Cross-Platform Install, the native installer for Mozilla.

The Widgets

To get you started straight away, lets look at two if the most straightforward content widgets available in the Mozilla toolkit, namely the browser and the iframe. These are marked up with tags of the same name, <browser>and <iframe>, and placed within your XUL window or binding. Both are essentially designed for holding web content, which can be anything from HTML and XML to just text and images in formats recognised by Gecko such as jpeg, png, gif and bmp. Here is a look at a browser in its simplest form:

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<window id="browser-window" title="Simple Browser Widget Display"
    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    width="640"
    height="480">
<browser src="http://mozdev.org" flex="1"/>
</window>


The code to look out for here is the line with the browser tag which creates a content area. It is the sole markup in the XUL window. It takes up all the available space because of the flex attribute, and into is loaded the index page of mozdev.org. This is the result, a window with web content.

SimpleBrowser

Simple Browser Window

What is missing in the picture and the example code for it is some sort of context. It is lacking an application space, and has no accompanying widgets to carry out any complimentary functionality for the browser. In traditional browsers, including the Mozilla browser that comes with the application suite, there is a menubar and a toolbar, each containing easy access to functionality such as Back, Forward, Reload, View Source, and so on.

The <iframe> widget is a more lightweight version of the <browser>, yet is just as useful if your needs are just to display some somple we content. It was designed to act much like the HTML iframe widget, to be an inner frame within a document. It too has a src atribute to specify the content. It is useful mainly for dislaying one-off content such as a preview of content, or a results page.

<iframe class="results" id="resultsframe" type="content" src="results.xul" flex="1"/>

Remember that XUL content can be loaded into the content areas, as for example is the case in the Mozilla suite global preferences which has a right frame into which the various preferences panels are loaded into when selected.

The Script

Let's look now at the source code that enables the initialization of the browser. These are the basics to get some browser functionality working. I find the best way to find out how Mozilla works is to look at the code. The perfect way to do this is via LXR, the Mozilla Cross Reference. The particular files you should be looking at are navigator.js and help.js, the main JavaScript files associated with the Browser and Help windows respectively. The following code is a generic way of setting things up but is based loosely around the starting up of these windows and setting up of the browser. This script is best placed in the function associated with the load handler of the XUL window.

The first step is to give the browser widget in the XUL file a couple more attributes added to it.

<browser
id="browser-content" type="content-primary"
        src="about:blank" flex="1"/>


The type attribute is optional but in this instance is important because it affects the way the content is treated in the application. The value of "content-primary" means that this is the main browser content area (e.g.content.focus()), and should gain precedence over other areas. When you use the content keyword in your script, it is the content in this browser that will be accessed. The id attribute gives the browser a unique identifier within the XUL content.This allows you to easily get a handle on the browser widget from your script:
var myBrowser    // declare globally for re-use
myBrowser = document.getElementById("browser-content");
Next is to create the browser instance and set up browser window:

var appCore = null;    // declare globally for re-use
appCore = Components.classes["@mozilla.org/appshell/component/browser/instance;1"]
.createInstance(Components.interfaces.nsIBrowserInstance);
appCore.setWebShellWindow(window);

The browser is initially blank (the use of  "about:blank" as the src attribute), so you have to load some content into the browser. Here a http URL is used:
const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
myBrowser.webNavigation.loadURI("http://www.mozdev.org", nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null);
This creates an instance of the nsIWebNavigation XPCOM interface, which exposes a number if handy utilities for changing and navigating through the content displayed in the browser. In this instance, to load a page, theloadURI method is being used. Other methods of this interface includegoBack(), goForward(), and reload(). nsIWebNaviagtion is just one of the interfaces available when you use the browser widget. Other include:
A full list can be found in the bindings file for the widget. These few steps provided in this section will send you well on your way to getting a rich content browser set up in your Mozilla application and act as a foundation for adding some of the other bells and whistles associated with a browser.

The Art of Navigation

What follows is a brief description of adding Back and Forward widgets and functionality to your window. This is after all the basis of any reasonable browser, but is used here to illustrate the process for hooking up functionality to your browser. The XUL file is the place to define your UI buttons. If you are proficient at XUL and CSS, you can make them into the style and appearance that you wish, but in this example the current navigator styles (using the Modern theme) are used.

Back and Forward Buttons
Back and Forward Buttons

These are simple toolbar buttons. The back and forward buttons used in the Mozilla browser have an integrated menu that creates a list of pages that are available for each one. Appearance is achieved by including the communicator CSS file and using a class specific to buttons (toolbarbutton-1). Here is the XUL code for the buttons.

...
<?xml-stylesheet href="chrome://navigator/skin" type="text/css"?>
...
<toolbarbutton id="back-button" class="toolbarbutton-1"
                tooltiptext="Go Back"
                oncommand="goBack();"
                observes="canGoBack">
</toolbarbutton>
            
<toolbarbutton id="forward-button" class="toolbarbutton-1"
                
tooltiptext="Go Forward"
                oncommand="goForward();"
                observes="canGoForward">
</toolbarbutton>


Apart from the class attribute on the buttons, the other main ones to watch for are oncommand, which bridges the functionality, and observes. The observes atribute determines if the button is active. There is a little bit more trickery involved to get the buttons to move to and from a disabled state depending on whether navigation can occur either forward and back. We will not go into that here, but the full source code for the XUL file and the JavaScript fileused in this article are available for reference. Lets look at the code for the goBack() and goForward() functions.

function goBack()
{
    var webNavigation = myBrowser.webNavigation;
    if (webNavigation.canGoBack)
        webNavigation.goBack();
}

function goForward()
{
    var webNavigation = myBrowser.webNavigation;
    if (webNavigation.canGoForward)
        webNavigation.goForward();
}


Once again the webNavigation interface is accessed through the browser widget. After an efficiency check on the observer to see if the it is possible, the goBack() or goForward() routine from the interface is called. And that is all there is to it -- 3 lines of code. The foundations were laid in the initialization code, and now the benefits are reaped.

Up to You

The rest, as is said, is up to you. You can choose to make your browser as simple or as complex as possible. Mozilla provides the widgets to hold your content, and as was illustated in this article, it also gives you the source code to add rich functionality. The surface was only touched here. Other features you can hook in are context menus, events such as drag&drop and onclick on the content area, and the viewing of page source. If you are truly ambitious you can even go one step further and use the tabbed browser. Integrated into the Mozilla browser and turned on by default, this has been a popular addition to the browsing experience, and has won many new users. Looking at the <tabbrowser> widget is more complex that the basic browser, but could bring more rewards. And don't forget, if it doesn't exactly meet your needs, you can change the source code!