Extension Points

From FirebugWiki

Revision as of 18:59, 14 March 2011 by Honza (Talk | contribs)
Jump to: navigation, search

Firebug provides number of ways how to append new features by developing a new extension. This document is intended to summarize and describe purpose of all these extension points. This document isn’t intended as a tutorial how to write an extension for Firefox.

Contents

Core Extension Points

This section describes ways how to extend core Firebug features that are *not* specific to any existing panel.

TabWatcher

The TabWatcher monitors Firefox tabs/windows. TabWatcher events primarily go to Firebug for redispatch to modules. This object could also be called ContextFactory, it creates instances of tabContext and maintains the mapping from window to contexts.

  • shouldCreateContext(win,uri): return true to force a URI to be debugged (new in 1.4, was acceptContext)
  • shouldNotCreateContext(win, uri): return true to prevent a URI from being debugged (new in 1.4, was declineContext)
  • initContext
  • showContext(browser, context): This is re-dispatched to modules, so TabWatchers get it first.
  • loadContext
  • destroyContext

Firebug Module

ModulesHier.png

One of the first things the developer needs to do when building a new FB extension is creation of a new module. Each Module is represented by an object that’s usually derived from one of the predefined Module object.

Firebug.MyFirstModule = extend(Firebug.Module,
{
    initialize: function()
    {
        Firebug.Module.initialize.apply(this, arguments);

        // TODO: Module initialization
    },

    shutdown: function()
    {
        Firebug.Module.shutdown.apply(this, arguments);

        // TODO: Module cleanup
    }
});

Each module must be registered in Firebug as follows:

Firebug.registerModule(Firebug.MyFirstModule);

Module Events Sent By Firebug

  • initialize // window is opened. Non-UI setup
  • initializeUI: function(detachArgs) UI setup like addEventListener
  • shutdown: function() UI teardown like removeEventListener
  • initContext: function(context, persistedState), Called when a new context is created but before the page is loaded
  • reattachContext: function(browser, context)
  • destroyContext: function(context, persistedState) { },
  • watchWindow: function(context, win) { }, // Called when a FF tab is create or activated (user changes FF tab)
  • unwatchWindow: function(context, win) { }, // Called after context is created or with context == null (to abort?)
  • showContext: function(browser, context) { },
  • loadedContext: function(context) Called after a context's page gets DOMContentLoaded (not 'load')
  • showPanel: function(browser, panel) { },
  • showSidePanel: function(browser, panel) { },
  • updateOption: function(name, value) { },
  • getObjectByURL: function(context, url) { },
  • isReadyElsePreparing: function(context, win) { }, For intermodule dependency

Firebug Panel

PanelsHier.png

Each panel in Firebug (like Console, Script, Net, etc.) is represented by an object that is derived from one of the predefined Panel objects. In order to create a new Panel a new object must be created.

This snippet shows how to derive a new panel from Firebug.Panel.

function MyFirstPanel() {}
MyFirstPanel.prototype = extend(Firebug.Panel,
{
    name: "MyFirstPanelID",
    title: "My First Panel Title"),

    initialize: function(context, doc)
    {
        Firebug.Panel.initialize.apply(this, arguments);

        // TODO: Panel initialization
    },

    destroy: function(state)
    {
        Firebug.Panel.destroy.apply(this, arguments);

        // TODO: Panel cleanup.
    }
});

Each panel must be registered in Firebug as follows:

Firebug.registerPanel(MyFirstPanel);

New Side Panel

TBD

Searchable Panel

TBD

Activable Panel

TBD

Editable Panel

TBD

Toolbar Buttons

Part of Firebug's UI is a toolbar with extensible set of buttons. In order to append a new button we have to define an overlay for Firebug's toolbar. See an example.

<?xml version="1.0"?>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <toolbar id="fbToolbar" align="center">
        <hbox id="fbToolbarInner" insertbefore="fbDetachButton" flex="1" align="center">
            <hbox id="myFirstSetOfButtons" insertafter="fbNetButtons">
                <toolbarseparator/>
                <toolbarbutton label="My Button" class="toolbar-text-button"
                    oncommand="Firebug.MyFirstModule.onMyButton(FirebugContext)"/>
            </hbox>
        </hbox>
    </toolbar>
</overlay>

A few notes:

  • Notice that "My Button" label should be properly localized.
  • The <toolbarbutton> can be associated with a <command> element.
  • New set of buttons should start with <toolbarseparator/> so, it's visually separated from an existing buttons from the left side.
  • ID of our set (myFirstSetOfButtons in this case) should be unique enough so, there are no collisions with other IDs within browser.xul scope.

If you need to display new set of buttons only for a specific panel (which is recommended tactic) use show and hide methods of your panel object.

function MyFirstPanel() {}
MyFirstPanel.prototype = extend(Firebug.Panel,
{
    name: "MyFirstPanelID",
    title: "My First Panel Title"),

    show: function(state)
    {
        this.showToolbarButtons("myFirstSetOfButtons", true);
    },

    hide: function()
    {
        this.showToolbarButtons("myFirstSetOfButtons", false);
    },
});

The showToolbarButtons method updates internally collapsed attribute of the myFirstSetOfButtons element so, it's visible as appropriate.

Panel Options

Every panel in Firebug can utilize an Options menu that is displayed at the most right side of Firebug's tab bar. This menu is intended for panel's options (if any) and it's content is provided by current panel object. In order to populate the menu with some items, the panel object must override a getOptionsMenuItems method.

function MyFirstPanel() {}
MyFirstPanel.prototype = extend(Firebug.Panel,
{
    getOptionsMenuItems: function(context)    {
        return [{
            label: "My First Menu Item",
            nol10n: true,
            type: "checkbox",
            command: function() { alert("Hello from the Options menu!"); }
        }];
    }
});

Every object returned from the method (within an array) represents one menu item in the Options menu. There are following properties you can set on such an object.

  • label {string} The label that will appear on the menu item.
  • nol10n {boolean} Indicates whether the label should be localized.
  • type {string} Specifies type of the menu.
  • checked {boolean} Specifies type of the menu.
  • disabled {boolean} Indicates whether the element is disabled or not.
  • image {URL} The URL of the image to appear on the menu item.
  • command {js-function} java script menu item handler.

Localization

TBD

InfoTip

Infotips are used in Firebug UI for displaying additional information. Infotips are similar to tooltips but providing richer content. The content doesn't have to be only a text but any HTML. The content is usually generated using Domplate.

The info tip is usually displayed by panels. See the following example that shows how to override/implement showInfoTip method within a custom panel.

var MyInfoTip = domplate(Firebug.Rep,
{
    tag:
        DIV("Hello from an info tip!"),
});

function MyFirstPanel() {} 
MyFirstPanel.prototype = extend(Firebug.Panel, 
{ 
    name: "MyFirstPanelID", 
    title: "My First Panel Title", 

    showInfoTip: function(infoTip, target, x, y)
    {
        if (!hasClass(target, "helloWorld"))
            return false;

        MyInfoTip.tag.replace({}, infoTip);
        return true;
    }
}); 

Firebug.showInfoTip method is called automatically by the framework for the currently selected panel. In case we want to handle the infotip by non-panel object we need to register a listener.

var MyInfoTip = domplate(Firebug.Rep,
{
    tag:
        DIV("Hello from an info tip!"),

    // InfoTip listener
    showInfoTip: function(infoTip, target, x, y)
    {
        if (!hasClass(target, "helloWorld"))
            return false;

        FBTrace.sysout("showInfoTip", target);

        this.tag.replace({}, infoTip);
        return true;
    }
});

Firebug.MyModule = extend(Firebug.Module,
{ 
    initialize: function()
    {
        Firebug.NetMonitor.NetInfoBody.addListener(this);
        Firebug.InfoTip.addListener(MyInfoTip);
    },

    shutdown: function()
    {
        Firebug.NetMonitor.NetInfoBody.removeListener(this);
        Firebug.InfoTip.removeListener(MyInfoTip);
    },
}); 

An example extension is available here: http://code.google.com/p/fbug/source/browse/#svn%2Fexamples%2Ffirebug1.7%2Finfotip

See also Extending Firebug tutorial.

Custom CSS

TBD

Panel Context Menu

TBD

FBTrace

TBD

Monitor HTTP Request

In order to monitor network activity, Firebug implements a component that register itself as an observer for HTTP-ON-MODIFY-REQUEST, HTTP-ON-EXAMINE-RESPONSE, HTTP-ON-CACHED-RESPONSE.

If any FB extension needs to handle these events a new observer object (nsIObserver) should be registered within the component as follows.

var httpObserver = Cc["@joehewitt.com/firebug-http-observer;1"]
    .getService(Ci.nsIObserverService);

Firebug.MyFirstModule = extend(Firebug.Module,
{
    initialize: function()
    {
        Firebug.Module.initialize.apply(this, arguments);

        httpObserver.addObserver(this, "firebug-http-event", false);   
    },

    shutdown: function()
    {
        Firebug.Module.shutdown.apply(observer, arguments);

        httpObserver.removeObserver(this, "firebug-http-event");
    },

    /* nsIObserver */
    observe: function(subject, topic, data)
    {
        try
        {
            if (topic == "http-on-modify-request")
                // TODO: onModifyRequest
            else if (topic == "http-on-examine-response")
                // TODO: onExamineResponse
            else if (topic == "http-on-cached-response")
                // TODO: onCachedResponse
        }
        catch (err)
        {
            if (FBTrace.DBG_ERRORS)
                FBTrace.sysout("MyFirstModule.observe EXCEPTION", err);
        }
    }
});

Firebug Cache

TBD

Extending Console Panel

Spy

A Spy is intended to display XHR within FB console. _Show XMLHttpRequests_ option must be enabled (checked) within Console panel’s Options menu.

Following snippet shows how to register a listener in order to hook all XHR.

Firebug.SpyMonitorModel = extend(Firebug.Module,
{
    initialize: function(detachArgs)
    {
        Firebug.Spy.addListener(this);
    },

    shutdown: function()
    {
        Firebug.Spy.removeListener(this);
    },

    onStart: function(context, spy)
    {
        // XHR started.
    },

    onLoad: function(context, spy)
    {
        // XHR finished.
    }
});

Console Listener

Firebug also allows to register a listener into the Console panel. The best place for registration of such a listener are (as usual) initialize and shutdown methods of our model object.

Firebug.MyFirstModel = extend(Firebug.Module,
{
    initialize: function()
    {
        Firebug.Module.initialize.apply(this, arguments);
        Firebug.Console.addListener(ConsoleListener);
    },

    shutdown: function()
    {
        Firebug.Module.shutdown.apply(observer, arguments);
        Firebug.Console.addListener(ConsoleListener);
    }
});

ConsoleListener object can be defined as follows:

var ConsoleListener = {
    onConsoleInjected:function(context, win) {},
    log: function(context, object, className, sourceLink) {},
    logFormatted: function(context, objects, className, sourceLink) {}
};
  • onConsoleInjected [context, win] called after the console injection script for context has been evaluated in window.
  • log [context, object, className, sourceLink] called when a simple text is printed into the Console panel
  • logFormatted [context, object, className, sourceLink] called when a domplate object is printed in to the Console panel (can be a custom domplate object).


Custom Log Format

TBD

New Command Line APIs

TBD

Extending Script Panel

Source Link

TBD

Debugger Listener

There is several events fired by Firebug debugger

  • onStop
  • onResume
  • onJSDActivate
  • onJSDDeactivate
  • onThrow
  • onMonitorScript
  • onFunctionCall
  • onError
  • onEvalScriptCreated(context, frame, url)
  • onEventScriptCreated(context, frame, url)
  • onTopLevelScriptCreated(context, frame, url)
  • onFunctionConstructor(context, frame, ctor_script, url) // not called in Firebug <1.4
  • onSourceFileCreated(context, sourceFile) // when the sourceFile is added to the context.

TBD

Extending Net Panel

Net Panel Listener

TBD

Custom info tab for a network request

Each entry within Net panel represents a network request made by a given page. If this entry is expanded, several tabs are displayed to the user (like, Headers, Response, etc.) showing detailed info. Following code shows how a new tab can be created.

Firebug.MyFirstModule = extend(Firebug.Module,
{
    initialize: function()
    {
        Firebug.Module.initialize.apply(this, arguments);

        Firebug.NetMonitor.NetInfoBody.addListener(this);
    },

    shutdown: function()
    {
        Firebug.Module.shutdown.apply(observer, arguments);

        Firebug.NetMonitor.NetInfoBody.removeListener(this);
    },

    // Listener for NetInfoBody.
    initTabBody: function(infoBox, file)
    {
        Firebug.NetMonitor.NetInfoBody.appendTab(infoBox,
            "Test", "Tab Title"));
    },

    destroyTabBody: function(infoBox, file)
    {
        // TODO: clean up code for tab content.
    },

    updateTabBody: function(infoBox, file, context)
    {
        // Generate content only for the first time and only if the
        // new tab has been just activated.
        var tab = infoBox.selectedTab;
        if (tab.dataPresented || !hasClass(tab, "netInfoTestTab"))
            return;

        tab.dataPresented = true;

        // Get body element associated with the tab.
        var tabBody = getElementByClass(infoBox, "netInfoTestText");

        // TODO: initialize tab content.
    }
});

Net File Link

If you need to create a link within Firebug‘s UI that points to a specific network request within Net panel, use NetFileLink object.

// Navigation by URL
context.chrome.select(new FBL.NetFileLink("http://..."));

// Navigation by request object (in case there can be more requests
// with the same URI in the Net panel).
context.chrome.select(new FBL.NetFileLink(null, request));

// Selection by file object (the object that is used to represent
// a request in the Net panel).
context.chrome.select(file.getFileLink());
Personal tools