Tag: open source

  • Writing and Running Automated Firefox Browser Chrome Tests

    As a followup to making my first patch for Firefox, it came time to write some automated tests for it. Turns out Mozilla has an entire suite of automated testing tools for their platforms. All thoroughly documented, they range from the ultra low-level compiled code tests to “record and play” macro testing for human UI interactions.

    Selecting the Test Suite

    The hardest part was figuring out what test suite would best compliment my patch, but given that my patch affected the Browser Chrome I went with that section of the Mochitest suite. Browser Chrome tests are really just javascript files running at elevated chrome-level privileges. This means that in them you get to access the tab browser API, more window objects, and other goodies. They’re really simple to write, just follow one convention: put everything in function test(). This is the main line of your test mini-application. You can add more functions, but they’ll be ignored by the tester.

    At first I’d put my js file in the source tree directory only ($srcRoot/browser/base/content/test) but turns out that builds compiled with testing enabled pull from a different directory ($objDir/_tests/testing/mochitest/browser/browser/base/content/test). Oh yeah that’s right: Firefox has to be built with the –enable-tests option. This pushes the test code from the source into the appropriate sub-directory of your output object directory (notice how both end in “/browser/base/content/test”?). Since I had written my test after compiling, I had to copy it over myself!

    With the right location, it was time to write the test. Simple enough, I just looked at existing tests. The browser_allTabsPanel.js one looked very similar to what I was trying to do, so I used it to get a feel for how to write a test.

    Writing the Test

    When writing a test you must work by assertions, specifying a message to output when the expected value matches the actual. A few assertion functions are:

    ok(val, "val exists!");                        // Tests existence
    is(val1, val2, "val1 equals val2");            // Tests equality
    isnot(val1, val2, "val1 does not equal val2"); // Tests if not equal

    For a successful test run, you want all of the above comparisons (if used) to be true. As for writing the code itself: I wanted to test the addTab function I’d modified in tabbrowser.xml. To do this, I used the gBrowser object, which seems to be the global object holding the current browser (and giving access to functions in tabbrowser.xml). Here was my test function:

    function test() {
     var stubTab = gBrowser.addTab();
    
     var tabs = gBrowser.tabs;
     var owner;
    
     is(tabs.length, 2, "2 tabs are open");
     is(gBrowser.selectedTab._tPos, 0, "First tab is selected");
    
     var newTab = gBrowser.addTab();
    
     is(gBrowser.selectedTab, tabs[0], "First tab is still selected");
     is(gBrowser.selectedTab._tPos+1, newTab._tPos, "Was inserted at #2");
     is(newTab._tPos+1, stubTab._tPos, "Old #2 shifted to #3");
    
     gBrowser.moveTabTo(newTab,2);
     is(newTab._tPos-1,stubTab._tPos, "Successfully moved new tab, older one shifted down");
    
     gBrowser.removeTab(stubTab);
     is(newTab, tabs[1],"Successfully deleted stub tab, new tab moved down");
    
     var newTab2 = gBrowser.addTab();
     is(newTab, tabs[2],"Successfully moved newTab down on newTab2 creation");
     is(newTab2, tabs[1],"Successfully added newTab2 adjacent to selected tab");
    
     gBrowser.selectTabAtIndex(1);
     var newTab3 = gBrowser.addTab();
     is(newTab3, tabs[2],"Successfully added newTab3 in middle of list");
    
     while (tabs.length > 1)
     gBrowser.removeCurrentTab();
    }

    Running the Test

    With the test written, it was time to run it. For that, I had to go back to command line and from the source root run

    make -C $(OBJDIR) mochitest-browser-chrome

    This ran the entire suite! I didn’t want to wait around for my one test to run, so instead I specified the TEST_PATH variable before running it:

    TEST_PATH=browser/base/content/test/browser_tab_addBeside.js make -C $(OBJDIR) mochitest-browser-chrome

    Much better, it only runs my test. And 11/11 passed! Perfect.

  • InnerHTML vs. DOM Manipulation

    In working on pushing out my 0.3 Release for the BBB Player, I’ve had to look a great deal into element generation and the best ways to do it. But how exactly do you measure “best”? Is best the best performance, the least lines of code, or the most compatible? And if we go by all three, what’s the answer then?

    Usage and Support

    Turns out, this has been a long-lasting debate on the interwebs. Internet Explorer’s handy but non-standard innerHTML property lets you do things quick and painlessly by specifying the HTML code as a string right in the Javascript. You can build this dynamically then via simple string concatenation. It’s short and quick, but not guaranteed to be supported everywhere (though it happens to be by just about every browser out there). Seems to be a carry over from IE5.5 when IE had 95% of the market. What were other browser makers to do to have their browser work with a web site but to implement innerHTML? Besides though, watch how friendly it is to use.

    var mySpanId = "spanID";
    var myDiv = document.getElementById("myDivId");
    myDiv.innerHTML = '<span id="'+mySpanId+'">Embedded Content</span>';

    A quirk about it though is that as simple as it is, it will not work with tables. A price to pay for simplicity, but unfortunately quirks happens when the specifications are not standardized. Now lets compare DOM Manipulation:

    var mySpanId = "spanID";
    var myDiv = document.getElementById("myDivId");
    var mySpan = document.createElement('span');
    var spanContent = document.createTextNode('Embedded Content');
    mySpan.id = mySpanId;
    mySpan.appendChild(spanContent);
    myDiv.appendChild(mySpan);

    Longer code, that’s for sure. But this is guaranteed to work on every browser, now and forever (well, almost). Though there are still a few cross-browser quirks. Turns out you can create input elements both ways, but IE does not support the DOM methods outlined above; it balks if you try and set the type of the newly created input element like so:

    var myInput = document.createElement('input');
    myInput.type = 'text';

    And there is no pure DOM way around this. No using myInput[“type”] = ‘text’, no nothing. In IE, you simply can’t change the type of an input element once it’s been created. As John Resig notes, not even jQuery is immune, and so he had to implement a work around in his API. Turns out you must specify the entire element in HTML markup for it to work:

    var myInput = document.createElement('<input type="text" />');

    So there are compatibility concerns on both sides of the fence, but one thing DOM does which innerHTML doesn’t is allow for easy mixing and merging of Document Fragments: collections of nodes outside of the main document structure which can be built, appended and removed as children in any order desired. Contrast this with innerHTML where you need to know what you’re concatenating as strings before doing it, unless you want to worry about complicated substringing later on.

    While in the end it’s personal preference, the standard compliance and dynamic changing nature of Document fragments gives DOM the nod in my book.

    Performance

    So with that being that, how do things compare in performance? Turns out there’s been a lot of benchmarking done on this. John Resig has done some studies comparing the performance of appending Document fragments as opposed to appending many individual nodes. Testing on a wide variety of browsers, he showed that it can vary from two-four times faster using Document Fragments. So lesson learned: Favour Document Fragment usage. They result in only a single redraw operation for the browser, since there is only one modification of the displayed nodes.

    Before going on to other studies though, another interesting difference between document fragments and individual nodes: changes to individual nodes after appending them as children persist to the appended-to node, but modifying a document fragment after appending will not. In fact, appending a document fragment as a child will result in clearing the nodes from it in the process of appending to the parent. Clearly, this is the result of pointers and linked lists being worked with, but it’s still good to know that upon appending a document fragment, the parent element is considered the sole owner of the child nodes.

    On to other studies! Several years ago Peter Paul Koch conducted his own comparing IE5.5, 6 and 7 to FF 2 and 3 beta, Opera 9.5 Safari 3. At that time, innerHTML won. It makes sense in a way, IE was the monster browser of the time, why not optimize a browser to use IE’s innerHTML well? Well, a little bit later, once a full iteration of browser development had occurred (including the emergence of Google Chrome), Andrew Hedges conducted his own studies. Turns out he was able to show DOM pulling much closer, to the point that the difference was negligible. Perhaps this was the document fragment factor or the result of other optimizations not included in PPK’s method?

    In the end, they are now almost interchangeable in speed. So which to use? Andrew Hedges clearly prefers DOM, but as for you and I, pick your pony. I’ll be sticking with DOM, it’s a bit more code but a lot less compatibility to worry about in years to come.

    myDiv
  • Modding Firefox

    As the semester is drawing to a close and we the open source crew are feeling comfortable with the JavaScript language, Dave took us to the next level. From the content scripts we had been working on, we got our first exposure to chrome scripts: JavaScript that doesn’t just run in the browser: it runs the browser. With elevated privileges (like hard drive access) and a larger object model (like controlling the tabs) things were getting interesting. And how else would we work with this than by rewriting a very small part of Firefox to give it some added spark?

    So that’s exactly what we had to do. Something simple, just changing the behaviour of new tabs from by default being appending to the end of the tab collection to appearing just to the right of the currently active tab. It could be done in just a few lines of code, he said. He was right but it took sifting through millions.

    Enter MXR: Mozilla Cross-Reference. It’s a powerful web tool that allows you to search through and jump all over the source code through cross-referenced links. Every function call and definition, regular expression and plain text searching, everything was made easier with this tool. The hard part was finding a good starting ground. Kenneth in class suggested a search for “New Tab”. This didn’t get us exactly what we wanted, but it was the best we could hope for considering our ignorance and we ended up following a quick chain.

    All of this was done by the class, but conveniently we ran out of time before we could get right to the bottom of how to do this mini-assignment. Though ending on the cusp of discovery, some self research into the rest of tabbrowser.xml quickly found me a solution. 2 lines of code later, I had something that seemed to be working. After generating a diff file using Mercurial…

    hg diff -p -U 8 > changes.diff

    I had a portable way to model and ship my changes. I had been working off an older code base (current from when I had last built Firefox) so line numbers are slightly skewed with respect to the current code. It seemed to work on my computer, but I had to test it on other’s. So I got experimenting some more and found out I’d stopped just shy when building Firefox last time: I had never packaged it!

    make -C objdir/browser/installer                // ZIPs/Tarballs to objdir/dist
    make -C objdir/browser/installer installer      // Builds MSI to objdir/dist/install/sea

    After a few quick commands from the source root (and replacing “objdir” with my object dir), I was ready. Gave the ZIP file to a friend on USB and she tried running it. Error. Hmm. Turns out there were a few missing DLLs. My compiler (Visual Studio 10) had a few dependent dlls in C:\Windows\system32 that hadn’t been archived. Remedying that though, and everything was a-ok.

    What did I take away from this? I felt this was a great way to not only learn about chrome scripts, but drilling down from the UI “New Tab” label to the code that made it happen gave me an appreciation not just for the size of the Mozilla code base, but the architecture as well. I learned more about various tools at public disposal to make the 500 foot dive into the Mozilla project seem more like 5. And most of all, I learned the excitement of shaping Firefox.

  • BBB Subtitle Fixes, On/Off and Remoting

    I’ve been continuing to work on the BBB player and from the list and I decided to tackle two of the items from the list that were quick-fix oversights from past releases. I’m talking about the promised features of toggling subtitles on/off and hooking dummy server calls up for the chapter addition/deletion demo. Also included is Bookmark object and API refactoring.

    Hooking up dummy server calls on chapter addition/deletion

    The purpose for this change was to emulate the final deployed environment from within the library.  I had already hooked up a call using XmlHttpRequest to retrieve chapter data, so this one should’ve been fairly simple. Just modify the two library calls to include XHR and it would be done. Not quite. The strategy was to make the remote call, and then to add/delete it locally only if the remote call succeeded. We want to keep everything in sync, don’t we?

    Turns out syncing that up highlighted an existing synchronization-related bug in the library. The printTOC() function, which prints the Table of Contents, had some weird funny business going on which resulted in the outputted rows being deleted and occasionally not repopulated. A quick fix to moving the deletion code inside the check for if the Table of Contents had been modified fixed this. Not sure why it was placed before it, but it’s fixed now.

    For this to work, I had to add an optional second parameter to the addChapter() function named “updateServer”. Give it true to update the server remotely (required for saving persistent data). See the DEMO!

    Toggle Subtitles On/Off

    This one was really fun, what should’ve been a simple fix had me debugging a bug deep within the VideoJS library. VideoJS has recently launched version 2.0.1, but for our development purposes it isn’t stable enough for us to use. The bug may very well’ve been fixed; the engine has really received a large overhaul.

    The bug itself is that when subtitles are playing, scrolling forwards through the video will update the subtitles appropriately. Scrolling backwards however, does not. The subtitles do not update at all, but rather freeze in place. The issue was that in the OnTimeUpdate function, a loop for finding the newly scrolled to position was only checking forwards. A quick change and it was working. From here the changes were simple. To enable subtitle toggling on the version of VideoJS in the BBB project:

    1. Setup a video.js page as before
    2. Make a checkbox, give it an id (lets say you give it the id chkCC)
    3. Add this attribute to the video element: data-subToggle=”chkCC”
    4. VideoJS automatically hooks into the click event for the checkbox and will toggle subtitle display

    I’ve also submitted a patch to the VideoJS team. Watch for it in new releases of VideoJS or help them build a stable 2.0. You can see the fix in action here.

    Bookmark/API changes

    To stick to conventions a bit better, we’ve renamed our global object from “Mgr” to “bbb”. This way it’s indicative of the project and is lowercase so as not to be confused with Constructor conventions.

    The Bookmark object has also been refactored for convention and memory usage reasons. The equals and getJSON functions have been moved to the prototype, and instantiated Bookmark objects now requires using the “new” keyword. Before, it was simply bbb.Bookmark(params). These were completed as part of Kevin’s and my effort at merging our 0.2 releases together. You can download the unified 0.2 here.

    Looking Forward

    The easy stuff is off the list, which now stands at:

    • Hooking up dummy server calls on chapter addition/deletion
    • Toggle subtitles on/off
    • Refactor Bookmark object to optimize for memory consumption
    • Time In/Out Buttons for setting chapters rather than manual typing
    • Popcorn-formatted metadata generation (will be sent to same dummy server)
  • BBB 0.3 Preview Change

    In my last BBB-related blogging, I mentioned some upcoming features I’ll be focusing on implementing for this next release. After meeting and discussion, we’re switching focus a bit to further fleshing out the chaptering functionality introduced in BBB 0.1. We’ll still be keeping with some of the simpler oversight fixes, but the new list of “to-be-implemented” for me now stands at:

    • Hooking up dummy server calls on chapter addition/deletion
    • Toggle subtitles on/off
    • Refactor Bookmark object to optimize for memory consumption
    • Time In/Out Buttons for setting chapters rather than manual typing
    • Popcorn-formatted metadata generation (will be sent to same dummy server)

    Time permitting, I would love to be able to continue working on the old list as well. As before, the release date for these and Kevin’s features is December 7, 2010.

  • BBB Player – 0.2 Release

    Here we are, deliverable day. Kevin and I have been gearing up for today’s release by fleshing out the feature set on our player project and iterating/fine tuning what was already there. If you’ve been following my series of blog posts you may have an idea of my portion, but I’m only half the team! Watch out for the second half of the release notes on Kevin’s blog. We’ve been working on entirely different feature sets, so we decided to split release notes up.

    Kevin’s own work has been on local data storage, statistics and user interactivity. Looking forward to December 7th’s 0.3 Release, I’ll be focusing on inclusive and accessible design, a few bug fixes, and data display. These come in the form of:

    • A multilingual (French and English) interface
    • Keyboard-driven player control
    • Ratings display
    • Captions distinct by movie
    • Captions on/off

    Time permitting, I’ll also try to integrate captioning with the Google Translate API. Stay tuned for further project updates! You can view the current status at version 0.2 here.

  • BBB Remote Bookmark Querying

    In preparing for tomorrow’s 0.2 release of the BBB Player, I’ve taken the first step to moving towards remote data retrieval. While right now we’re still using that terrific stub data, it’s at least now not hard-coded into the JavaScript. Instead, it has been hard-coded into a server script which is queried to return the same stub data in JSON format. Compare this to our original interface which treated the library more like a collection that a catalog. Stub data was populated into the library right in the HTML page something like this:

    Mgr.addBookmark(/* code to create stub bookmark 1 */);
    Mgr.addBookmark(/* code to create stub bookmark 2 */);
    ...
    Mgr.addBookmark(/* code to create stub bookmark 6 */);

    While now it’s a little more like this:

    Mgr.fetchBookmarks();

    There have been a lot of little change like this that have tried to separate the HTML and JS code to minimize the library interface and make it more of a simple “include this library and watch it work”. Many functions now take a single object as an argument rather than a list of parameters, which gives a much simpler and flexible library interface. Another benefit is that less JS code in the HTML page means more bandwidth saved as more JS is cached in the form of an external JS file. And less bandwidth means better! 🙂

    Secondly, this change means that we can separate the code and the data to better test the library. But how does this work when we need to absolutely resolve a URI to XmlHttpRequest for both local development, deployed testing and final deployment? The Same Origin Policy is in place for security reasons, but poses difficulties when moving an application across domains: we can’t always use the same remote location for all environments but we don’t want to have to keep changing the hard-coded URI from localhost to our remote testing environment. We get around this by adopting conventions:

    var baseUri = params.remoteServer || location.href.substring(0, location.href.lastIndexOf("/BBB/")+5);

    The above line resolves where to query for data. If no remote server is specified (default) and assuming that the project is located in directory “BBB” (and no URL rewriting), the above line will just look in the root of the project folder for data. This is exactly how our GitHub project has been structured. Just append the name of the data source script to the end of baseUri and you have a dynamic way to use the above remote querying anywhere.

    We’re also working on other data storage techniques, but I’ll let Kevin divulge those as it is his domain.

  • HTML5 Video Mashup: Green Screen Travel Agency

    Last week, Humph traveled partway across the world to attend the Mozilla Drumbeat Festival in Barcelona, Spain. Turns out, he had a great time. Before going though, he gave us a mini-assignment to be creative and come up with video mash-ups using HTML5 Video, jQuery, and web APIs like Flickr and Google.

    Inspired by world travel, I remembered the internet sensation “Where the Hell is Matt” that swept the world in 2006, and then again in 2008. It’s just a simple video of a guy dancing, but those two combined have been been seen by north of 50 million eyes. In late 2008, he gave a demonstration announcing it as a “hoax”, but then followed that up on MacWorld less than a month later denouncing the hoax. This got me thinking… why not do something similar for this mini assignment?

    So I got to work. MDC has a great article on how to manipulate video using canvas, with their example being chroma-keying (aka green screening) where a colour in the video is replaced by an image. With this skeleton, I went to work. Using the Flickr API Public Feed it is possible to get the urls of a series of images. I did this by a jQuery call to return a JSON formatted string.

    var tempId = document.getElementById('flickrUser').value;
     if (tempId !== currId) {
     $.getJSON('http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?',
     {
     id: currId = tempId,
     format: "json"
     },
     function(data) {
     var items = [];
     var img = document.getElementById('img');
     
     $.each(data.items, function(i,item) {
     items.push(item.media.m);
     });
     
     img.src = items[0];
     processor.doLoad(img, items);
     
     });
     }

    This code was more or less adapted from the jQuery example on ajax calls. This already gave the “slide show” illusion of various locations, but there was no way to know where I was making the Man in the Mozilla Video go. Luckily, Flickr photos contain GeoTags and an entire subsection of their API for Geo information.

    Here’s where I ran into an issue: not realizing two distinct APIs. While I didn’t need an API key for feeds, retrieving the latitude, longitude city and more for an image requires using the private API with a registered API key. After getting my API key, I was able to call the getLocation() method to retrieve location information. Another issue came up of not wanting to hardcode the API key into the JavaScript, but that was worked around by creating a PHP script to serve as a proxy, substituting my API key in for any requests I wished.

    With that done, I had all the information I needed to be rolling. With the video playing, I included two buttons: one to view the original picture source and one to find out “Where the Hell is Mozilla Guy?” The picture source was easy, all I did was another Flickr API call (this time to getInfo) to retrieve the user id and to then formulate a url. Flickr urls for a particular image follow the format:

    http://www.flickr.com/photos/{userID}/{photoID}/

    Displaying the location was a bit trickier but still quite doable. It took a call to the aforementioned Flickr getLocation() function to get the latitude and longitude, as well as information about the neighbourhood, county, city and country of the photo. Turns out, I got a lot of crashes on this part: even if a photo is geotagged, not all of this information is publicly shared.

    Once that was all hooked up, it was time to embellish a bit. Feature-wise, I mean. The final version looks a little rough, but encompasses everything mentioned here. Although it requires HTML 5 to work, those without see the action shot below

    A video superimposed on a night at Nuit Blanche 2010 in Toronto
    Where is He?

    So that’s that. What’s missing from this and how could it be taken further? Well, we’re dealing with public photo streams, some of which have all rights reserved. While the combo box eliminates this issue, the public feed still has potential to bring in photos with All Rights Reserved. Unfortunately, I didn’t have the time to investigate how to check for and eliminate this pictures from the stream. So please, stick to the pre-selected photographers 🙂

    Also, it may’ve been nice to slow down the image rotation rate and super-impose the locations right onto the video like they are in “Where the Hell is Matt” but there were two issues:

    1. I couldn’t find a video long enough that had green screen potential
    2. Geo-tagging has its own set of permissions on Flickr and most of the GeoTagged images that were retrieved had the given information limited to latitude and longitude.

    Be those as they may, this was a great experiment and I had a great time with it.

  • BBB Video Player Test Framework Skeleton

    Early on in the prototyping stage when I was first getting used to HTML5 and JSON, I’d written a very rough and primitive suite of tests to test what I was trying to do. In fact, if you look back to the earliest commits into my github repo, you’ll see it floating around here or there.

    While it may still be a little rough, I’ve spent a bit of time pushing it together into a structure to allow some level of testing automation with the BBB Video Player. It’s pretty much an array of functions, each running a test case and returning true/false but it does the job pretty well. In addition to return value, each test case may also alert from within to display specific messages. UI on the page still allows for manual testing. The test page can be found here.

    Interestingly the process of testing the framework already helped identify a bug in the BBB Player. The proof is in the pudding?

  • TTXT Subtitling for BBB Video Player

    In my last blogging I discussed the research I’d made into forcing XML-formatted plain text to being DOM-parsed. This became an essential task for the BBB Video Player as TTXT subtitle files are just that: XML-structured documents which were coming across as plain text. This could be fixed server-side by associating extension “ttxt” with “text/xml”, but given our work with Video.js I went for the client-side approach to ensure TTXT subtitling would always be possible and extended Video.js to ensure the data will be structured into an XML DOM tree client-side. I’ve posted a demo and my implementation code below:

    function XHRFormatter(request, type) {
      var canForce = request.overrideMimeType;
      var forceToXML = canForce && type === 'ttxt';
      var isXml = forceToXML || type === 'xml';
    
      if (forceToXML) {
        request.overrideMimeType('text/xml');
      }
    
      return {
        // Select elements from aRef element tree based on expression aExpr
        // aExpr is a query string (delimiter to split on for text, XPath for xml)
        select: function(aRef, aExpr) {
          if (isXml) {
            var xpe = aRef.ownerDocument || aRef;
     
            if (xpe.createNSResolver && xpe.evaluate) {
              // Thanks to https://developer.mozilla.org/en/Using_XPath
              var nsResolver = xpe.createNSResolver(xpe.documentElement);
              var result = xpe.evaluate(aExpr, aRef, nsResolver, 0, null);  
              var found = [];  
              var res;  
              while (res = result.iterateNext())  
                found.push(res);  
              return found;  
            } else if (xpe.selectNodes) { // IE
              return xpe.selectNodes(aExpr);
            }
          } else if (isText) {
            return aRef.split(aExpr);
          }
        },
     
        getType: function() { return type; },
     
        format: function(str) {
          if (isXml) { // Return str as xml
            // Mime type has been overridden as XML or is XML by default
            if (canForce || !forceToXML) {
              return request.responseXML;
            // IE, mime type not overridden, must parse into XML
            } else if (typeof ActiveXObject !== 'undefined') { 
              var doc = new ActiveXObject("MSXML.DomDocument");
              doc.loadXML(str);
              return doc;
            } else {
              throw new Error("Can not process as XML!");
            }
          } else if (isText) {
            return request.responseText;
          }
        }
      }
    }
    

    My goal was to decouple the (potentially browser-specific) code that formats and selects data from xml from the code that works with that data. In use, it could work like this:

    var request = new XmlHttpRequest();
    var formatter = XHRFormatter(request, 'ttxt');
    ...
    // Further work here
    ..
    request.send();
    var nodeTree = formatter.format(request.responseText);
    var wantedNodes = formatter.select(nodeTree, "//My/XPath/Query/");
    ...
    

    I’ve recently submitted a patch back to the Video.js team integrating this code into their player. Keep an eye out for their future releases, it may have official support for multiple subtitling formats 😉