JavaScript Binding

•November 5, 2013 • Leave a Comment

JavaScript’s this keyword has always given me a massive headache. Any JavaScript codebase is inevitably riddled with events and callback functions. This is nice for simple asynchronous web scripting, but when you start writing projects with complicated objects that handle events, it gets hairy.

Until today, the best and essentially only solution I had to date relied on jQuery’s event handling functions. This always involved passing an event a data object containing a self property which pointed to the object I actually wanted “this” to refer to. The code would look something like this:

var Foo = function () {
    this.text = "bar";
    this.button = $('#foo-button');
    this.button.on('click', null, {self: this}, function () {
        alert(self.text);
    });
}

But when we converted all of our widgets to use our new widget creation library, jQuery had to be scrapped. We’re writing pure JavaScript, not Addon SDK content scripts. Including jQuery was not feasible. So I scoured the Internet for some more portable solution. And in a startlingly short time, I found a solution so simple that  I felt very, very dumb.

That solution is the bind() function. All JavaScript functions have the bind method on their prototype, so this worked effortlessly in all of my module code. The syntax is pretty simple. For a function myBoundFunction() whose “this” you want to reassign to the object newThis, you’d make a call to myBoundFunction.bind(newThis). Simpler than the jQuery code, and much more portable. It’s everything I ever dreamed of and more!

Now let’s take a look at it working in the actual code base. The following snippets from the Bugzilla widget’s user.js defines some functions on the User object:

exports.User.prototype = {
    /**
     * Sets the document.
     *
     * @param {XULDocument} document The document in which the user's form exists.
     */
    setDocument: function (document) {
        // Set document
        this.document = document; 

        // Elements
        this.nameElement = this.document.getElementById("user-name");

        // Event handlers
        this.nameElement.addEventListener('change', this.onUserNameSubmit.bind(this), false);
    },
[some other code that's  not relevant to this example]
   /**
     * Username submission event handler.
     *
     * Shuts down the form submission and checks whether the given user is valid.
     */
    onUserNameSubmit: function (event) {
        // Get the user name from the form
        var userName = this.getUserName();

        // Is the user on the Bugzilla server?
        var userNameFound = this.queryUserName(userName);
    },

Inside of User.setDocument() we rig up the User’s single event handler. When a username is entered in the nameElement text box, the change event fires and User.onUserNameSubmit() is executed. In User.onUserNameSubmit(), we really want to refer to  functions that the User has. But unless we bind the function, we’ll only get access to the username entry text box element. By passing the event listener this.onUserNameSubmit.bind(this), we are setting all “this” in User.onUserNameSubmit() to the User object and can call other User member functions.

In addition to setting “this”, bind() allows us to pass additional arguments to pass to the target function. This is extremely useful for writing callback functions which a library or other code you can’t control will use. For example, the User object makes use of a bz.js call to get the number of bugs a user has on the Bugzilla server. The function bz.countBugs() takes a query object and a callback as arguments. The callback function is expected to take error and bugs as arguments, and bz.countBugs() will pass the callback an error object and a bug array when it calls the callback function. But if we want the callback to take a userName as well? bind() to the rescue!

    /**
     * Sends a query to the server to see whether the user exists.
     */
    queryUserName: function (userName) {
        // Start a waiting animation
        this.startSpinner();

        // Does the given user have bugs on the server?
        this.bz.countBugs({
            email1: userName,
            email1_assigned_to: 1,
            email1_qa_contact: 1,
            email1_type: "equals"
        },
        this.onUserNameResponse.bind(this, userName));
    },

    /**
     * Starts the spinner animation.
     */
    startSpinner: function () {
        this.nameElement.className = "spinner";
    },

    /**
     * Handles the response to the user name query.
     *
     * @param  {string} userName The username whose validity is being tested.
     * @param  {error} error Any errors with the request.
     * @param  {array} bugs An array of Bugzilla bug objects.
     */
    onUserNameResponse: function (userName, error, bugs) {
        // Stop the waiting animation
        this.stopSpinner();

        // Let the user know if the given userName had any bugs
        if (bugs > 0) {
            this.setUserName(userName);
        } else {
            this.rejectUserName();
        }
    },

Using bind() we set the “this” object and pass it an additional parameter, userName, to shove in front of the arguments passed in. So bz.countBugs() populates User.onUserNameResponse()’s  error and bugs parameters and the bind(this, userName) call injects the given userName as the first argument.

And that’s why bind() is the coolest thing I’ve learned about JavaScript to date.

Writing a Panel to the Browser

•September 24, 2013 • Leave a Comment

During our most recent client meeting we realized that we should not actually be utilizing the add-on SDK’s Panel object to render a panel. Instead, we were informed that we should be writing XUL directly to the main panel in the browser window. This involves two parts: locating the exact parent element to which we are writing and actually writing XUL from a file to our panel element. We had no idea how to do either of these things.

In our group session on Friday we spent a lot of time as a group tackling grabbing the correct parent panel element. Gijs pointed us toward the Window Mediator, which is a Services module. To load this, we run the following snippet:

let {Cc, Ci, Cu, Cr, Cm, components} = require("chrome");
Cu.import("resource://gre/modules/Services.jsm");

Then, we get a list of all of the browser’s “windows” using the window mediator’s getEnumerator() method. It can enumerate all windows of a certain interface if you pass it arguments, but for now we’re getting all of them by passing it null.

var allWindows = Services.wm.getEnumerator(null); // Use the window mediator object to get all windows in the browser

Once we’ve got all of the windows, we need to find the main browser window. To do so, we iterate over all of the windows using the window mediator’s hasMoreElements() and getNext() methods. When we did this the first time, we manually inspected each window’s innerHTML property to see if we recognized anything. Once we found the main window, we figured out that it has a location.href of ‘chrome://browser/content/browser.xul’. Now we have a condition to check for to indicate whether it’s the main window. The whole loop looks like this:

var allWindows = Services.wm.getEnumerator(null); // Use the window mediator object to get all windows in the browser
var browserWindow, // Firefox's top-level "window"
    thisWindow; // Iterated window
while (allWindows.hasMoreElements()) {
    thisWindow = allWindows.getNext();
    if (typeof(thisWindow.location.href) !== 'undefined' && thisWindow.location.href === 'chrome://browser/content/browser.xul') {
        browserWindow = thisWindow;
        break;
    }
}

So we’ve got the main window. Now we need to write our XUL to it. I spent several hours on Saturday trying to figure out how to use Firefox’s File I/O capabilities to do this, but it turned out to be really simple. We can actually do this with the Add-on SDK’s data loading capabilities. We can access the add-on’s data directory by getting its data property.

let data = require("sdk/self").data;

This property has a very nice method, load(), which reads the contents of a file in our data directory, such as test.xul, and returns that data as a string. If we have an HTML element, we can do something like this to write our file’s contents as HTML to the DOM:

someElement.innerHTML = data.load("test.xul");

Awesome! So easy! How did I waste 5 hours before remembering this was a thing?!

So now that we’ve got all of the pieces, we put all of it together to make a panel and write the contents of our test.xul file to be its inner HTML.

/* ********
 * Requires
 * ********/
// Chrome
let {Cc, Ci, Cu, Cr, Cm, components} = require("chrome");
Cu.import("resource://gre/modules/Services.jsm");

// SDK
let data = require("sdk/self").data;

/* ***********
 * Panel Setup
 * ***********/
// Get the window
var allWindows = Services.wm.getEnumerator(null); // Use the window mediator object to get all windows in the browser
var browserWindow, // Firefox's top-level "window"
    thisWindow; // Iterated window
while (allWindows.hasMoreElements()) {
    thisWindow = allWindows.getNext();
    if (typeof(thisWindow.location.href) !== 'undefined' && thisWindow.location.href === 'chrome://browser/content/browser.xul') {
        browserWindow = thisWindow;
        break;
    }
}

// Put our extension's XUL in the main panel
if (typeof(browserWindow) !== 'undefined') {
    // Create a panel view
    let panelview = browserWindow.document.createElement("panelview");
        panelview.id = "testId";
        panelview.className = "testClass";
        panelview.innerHTML = data.load("test.xul");

    // Inject our panel view into the multiView panel
    let multiview = browserWindow.document.getElementById("PanelUI-multiView");
        multiview.appendChild(panelview);
}

Firefox Add-on Debugging

•September 18, 2013 • Leave a Comment

Last night Dan and I popped into #fx-team wondering how we’re supposed to go about debugging our add-ons. There are several articles on the MDN on this topic, but we weren’t having much luck with them. It turns out that’s partially related to a bug in which loading browser chrome and add-on code in the normal debugger doesn’t work. Apparently it worked a couple weeks ago, and the devs expect it to work properly again tomorrow or the day afterward. But what are we supposed to do for the next two days?

Gijs directed us to a number of alternative solutions in addition to the standard method (which he later pointed out is affected by the above bug). To see the output of some quick and dirty console.log() statements in the add-on code, it turns out you can press Ctrl+Shift+J to open up the Browser Console. Note that this is very different from the normal debug console. I had no idea this thing existed. So I popped this open and ran the following code from my main.js file:

var bz_client = require("./bz.js/bz.js").BugzillaClient;console.log(bz_client);

To which the Browser Console responded:

14:25:44.638 undefined main.js:8

Awesome!

In addition, he linked me to the DOM Inspector Add-on. The add-on gives you the ability to inspect every bit of the browser with an interface similar to that of the main devtools Inspector. This includes the browser chrome and our add-ons! Now we can go about breaking things properly.

Bugzilla Main Panel

•September 17, 2013 • Leave a Comment

I walked through the Add-On SDK‘s tutorial to display a popup to create start work on the Bugzilla widget’s main panel. It just listens for a mouse click event on the button to save a user and displays a “Saved” message at the moment. You can view the commit here. I’ll add screenshots when I figure out how to take one without closing the panel.

Firefox Add-On SDK

•September 16, 2013 • Leave a Comment

I installed the Firefox Add-On SDK per its installation document. I wrote a couple bash aliases to start and kill the virtualenv it seems to run in:

alias ffon='pushd ~/src/addon-sdk-1.14 && source bin/activate && popd'
alias ffoff='gnome-terminal --working-directory=`pwd` && exit'

Once that was set up, I spun up the SDK and initialized the Bugzilla widget’s repository.

ffon && cfx init

With the skeleton set up, I toyed around with lib/main.js and tested out my changes with cfx.

cfx run

Now for the fun part!

Project Plan

•September 16, 2013 • Leave a Comment

We’ve spent the past week and a half working on the project plan for the new Australis-style widgets. I’ve spent most of my time on the Technical Specifications and Testing Plan sections.

Installing Bugzilla

•September 6, 2013 • Leave a Comment

I installed Bugzilla on our development server per the instructions on this forum post. The Apache httpd configuration path was at ‘/etc/apache2/httpd.conf’ on our system. When it came down to the Perl module installation step, I installed cpanm from Ubuntu’s repositories and used that tool to grab everything the check-install script recommended. I got bit by this bug in the Apache2::SizeLimit module. It was resolved by applying Frederic Buclin’s patch manually.