Firefox for Android: Docs for Building Add-on

I have talked about how add-ons interact with the new Firefox on Android, but we didn’t have any easily accessible documentation. Now we have some guides and code snippets:

Feel free to help make the MDN documents better!

Remember, the basics of building an add-on for Firefox on Android is no different than building an add-on for Firefox on Desktop.

I also added a basic restartless add-on skeleton for Firefox on Android on github.

Firefox Android: Add-ons in a Native World

One of the first things I yelled about when we were debating switching to a native Android UI for Firefox was add-on support. Using a XUL-based UI meant add-ons were free. The Mozilla platform has support for add-ons baked right in. Moving to a native UI would surely kill our ability to support add-ons, right? Wrong!

Add-ons are an important part of the Firefox story. Native UI builds of Firefox support add-ons. There are some things an add-on developer needs to be aware:

  • The add-ons system is the same one used in other Mozilla applications. We did not invent a new add-on system.
  • Native UI builds are considered a new application and are not add-on compatible with the XUL versions. The application ID for native UI builds is: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
  • There is no visible XUL in the UI, so using overlays to try to add or change UI is useless.
  • There is a simple NativeWindow object that allows you to manipulate parts of the native Android UI.
  • Services like nsIPromptService and nsIAlertsService are implemented to use native Android UI.
  • Since overlays are useless for UI and JavaScript APIs are available for native UI, you should seriously consider just making a restartless add-on.

NativeWindow

We wanted to give add-on developers some APIs to manipulate the native Android UI, so we create a helper object called NativeWindow. The API is still young, but it gives you access to: Android Menu, Doorhanger Notifications, Context Menus (in web content) and Android popup toast alerts. The object is currently part of the main browser window, but we are considering moving it to a JS module. The basic API is here:


/*
 label: menu label
 icon: file:// or data: URI for an icon
 callback: JS function called when menu is tapped
 returns a menu ID that can be used to remove the menu
*/
menuID = NativeWindow.menu.add(label, icon, callback);
NativeWindow.menu.remove(menuID);

/*
 message: displayed text
 value: string based tag
 buttons: array of JS objects used to create buttons in the notification
 tabID: tab associated with this notification
 options: JS object that has 'persistence' and 'timeout' options
*/
NativeWindow.doorhanger.show(message, value, buttons, tabID, options);
NativeWindow.doorhanger.hide(value, tabID);

/*
 label: menu label
 selector: JS object that has a 'matches(element)' function. Used to show the menu.
 callback: JS function called when menu is tapped
 returns a menu ID that can be used to remove the menu
*/
menuID = NativeWindow.contextmenu.add(label, selector, callback);
NativeWindow.contextmenu.add(menuID);

/*
 message: displayed text
 duration: "short" or "long"; Used for alert timeout
*/
NativeWindow.toast.show(message, duration);

Some examples of what the API can do:

Doorhanger Notification


Menu Item


Context Menu Item


Toast Popup Alert


The NativeWindow API will continue to grow and mature, but I think even now it shows that add-ons can have first-class interactions with the native UI of Firefox. I am looking forward to developers trying it out and helping us push the API forward.

Add-ons: Binary Components and js-ctypes

Lots of discussion going on recently about the affect of 6 week development cycles on binary XPCOM components in add-ons. I don’t want to re-hash those discussions, but Daniel Glazman brought up an interesting point in a comment on Wladimir’s post. Wladimir was suggesting binary component developers start moving to js-ctypes. Daniel pointed out that there are two classes of binary XPCOM components:

  • XPCOM wrappers around 3rd-party binary libraries: We use this model for exposing external binary functionality into JavaScript so add-ons and applications can access the libraries. Using js-ctypes should provide a simple, non-breaking way to expose the libraries. You create a simple JavaScript wrapper in a JavaScript XPCOM component. We need more examples of using js-ctypes to do this, but it works.
  • Pure binary XPCOM components built only using the Mozilla platform: Sometimes the functionality you want to expose is actually locked away in the Mozilla platform itself. Maybe there is no public nsIXxx interface or the existing interface has a [noscript] attribute on a property of method. This model shouldn’t be required anymore, in my opinion. Mozilla is pushing JavaScript based components and we should be exposing as much as possible to chrome JavaScript. I would encourage add-on developers to file bugs and lobby to expose binary-only parts of the Mozilla platform to chrome JavaScript.

JavaScript ctypes has come a long, long way since it was started back in 2007. Let’s start leveraging it more. The ctypes model has been used quite effectively in other languages.

Firefly – Remote JS Shell for Firefox Mobile

A long time ago, in May 2008, I posted about a remote JS shell add-on I was building. I had been tinkering with a few existing projects (JSSH, SD Connect and MozRepl) but wanted to build something small, lightweight and mainly focused on helping add-on / XUL developers interact with JS running in a separate application. I tried to get the protocol closer to that used by Opera DragonFly and Crossfire, but I never had the time to get it exactly right. When I started work on Firefox Mobile, I used the add-on to interact with Firefox running on a mobile device from my desktop machine. Unfortunately, I never felt the UI and the code were good enough for a public release.

Recently, I dusted off the code, converted to restartless add-ons and made a very simple in-content UI. I say add-ons, because there are two: a probe, and a shell. You install the probe into Firefox Mobile and install the shell into Firefox Desktop (or Firefox Mobile running on a desktop – it’s up to you).

Shell

The shell is implemented as an in-content about: page [1]. After installing the shell, navigate to “about:firefly” and you’ll see the simple UI. The shell can act as a listening server or you can connect to a relay server. The listening server is simplest, so start with that.. You start the server on a specific TCP port. Once started, the shell waits for a probe to connect.

Probe

The probe is simple. After installing it, you just point it at an instance of the shell by entering the IP address and port. You can connect and disconnect from the “Remote Debugging” preferences. That’s all you need to do with the probe.

Starting a Session

Once the probe has connected to a shell, you can enter JavaScript commands into the simplistic shell UI, the code is sent to the probe and evaluated in a sandbox running in the application. Because it’s a sandbox, the probe injects some helper properties and methods:

  • window – The active chrome window. With this object, you have full access to the window’s JS and DOM.
  • firefly – Injected API with some special utility methods:
    • getWindow(type) – Returns the chrome window of a given type.
    • getWindows() – Returns an array of all open windows.
    • inspectJS(object) – Lists all properties and functions associated with a given JS object.
    • inspectDOM(selector or element) – Dumps the markup for a given DOM element. You can pass a CSS selector string or a real DOM element to the method.

Examples

Since we are talking about Firefox Mobile, you should be familiar with the internal chrome UI code before starting to poke around. The main browser.js has a Browser object that acts like a manager for the open tabs, so let’s play with it:

URL of active tab:
window.Browser.selectedBrowser.currentURI.spec

Add a new tab:
window.Browser.addTab("http://mozilla.org", true)

Inspect the active tab JS object:
firefly.inspectJS(window.Browser.selectedTab)

If you’re interested in using Firefly, you can install the add-ons from here:
Shell: firefly-shell
Probe: firefly-probe (mobile shortcut: http://bit.ly/irvpjc)

Source code:
Shell
Probe

Next Steps

  • Add access to the web content running in the child process. Firefox Mobile is multi-process, so you can’t directly access the web content from the main process.
  • Add a pretty output for the inspectXXX helpers. Instead of just dumping the simple text output into the HTML, we could make the output more dynamic – think Firebug panels.
  • Add helpers to do more profiling and data collection. Many times I want to know what is happening on the device. Things like CPU and memory usage or why the profile data is exploding.

Bug reports and feature requests welcome.

[1] Yep, an about: page in a restartless add-on. It wasn’t too hard. I am using a resource: alias for the external CSS file and the favicon. I could have just move the CSS into the XHTML file and used a data: URI for the favicon.

Firefox Mobile – Monitor HTTP Headers

I just added support to Mobile Tools for monitoring HTTP headers in Firefox Mobile. I just wanted a way to look at some HTTP traffic to help debug some mobile websites. Screen space is limited on mobile devices, so we don’t get to have nice floating panels like in desktop Firefox’s web console or Firebug. Instead I opted for a slide out panel:

Tap the little tab and the panel slides out and back. Use the Pause and Clear buttons to manage the HTTP traffic. Tap an item in the list to expand the request, response and cookie details:

You can pan the list in any direction, as needed to see the contents. The panel tab takes up very little space while browsing, but you can also hide it completely using the add-on options:

In case you don’t know, Mobile Tools also adds support for viewing Page Info and Page Source too.

Cloud Printer – Print from Firefox Mobile

Recently, Google released the beginnings of a cloud-based printing system called – Google Cloud Print. The project is still in Beta, but you can install a recent version of Google Chrome on Windows and attach your local printers into the Cloud Print system (more). Google has added support for Cloud Print to few of their mobile web apps, but has not released a client application API yet. However, they did release a simple webapp demo – and where there is a demo, there are people reverse engineering it.

Cloud Printer is a restartless Firefox Mobile add-on that integrates into the Google Cloud Print system. Firefox Mobile already has code to save web pages as PDF. I took that code and send the PDF to the Google Cloud Print system.

Using the demo code and some other examples, it was fairly simple to get this to work. The current API is fairly simple and nice. I expect a few changes when the APIs become official, which should happen soon.

Note: You need to be signed into your Google Account for this to work. If you are not signed in, you’ll see a prompt and we open the Google Account login page for you.

Let me know how this works for you!

Restartless Fun

I like experimenting with restartless add-ons. This time I used a variation of the include technique to assign an image to the toolbar button and get access to a CSS file and IPC frame script. The CSS file is loaded and unloaded using nsIStyleSheetService. The IPC frame script is loaded using the global message manager. The frame script is needed because the PDF file must be generated in the content process, not the main process. You do remember that Firefox Mobile is multi-process, right?

One gotcha. Once loaded, you can’t currently unload a IPC frame script. So if you enabled/disable a restarless add-on that loads a frame script, you’ll end up with multiple instances running. Which means, when you send an IPC message, multiple listeners will respond – this is bad. I hacked around that by sending a “disable” message to my frame script when uninstalling the add-on. The “disable” message just removes the message listeners in the frame script so they won’t respond to any messages in the future. See the source code.

These restartless techniques are documented in various places, but we should start some code snippet docs on MDC too.

Firefox Mobile Add-on – Cloud Viewer

I just put together a simple restartless add-on, Cloud Viewer, that integrates the Google Docs Viewer into Firefox Mobile. I saw a few similar add-ons on AMO for desktop Firefox, but none for Firefox Mobile. Opera and Dolphin also have add-ons integrating support for online document viewers.

The concept is pretty nice, especially for mobile devices: Open online documents in an online viewer. No need to waste time downloading the file and waste space on your device saving the file. Google’s viewer supports Powertpoint (PPT), Word (DOC, DOCX), Excel (XLS, XLSX), PDF and some non-web image formats. The viewer even uses a mobile UI when running on devices.

Restartless Fun – The Details

I wanted the add-on to be restartless because restartless add-ons rock. I also wanted the add-on to work at a deeper level than a context menu. I could have put a menu item on the context menu to open links in Google’s viewer, but that wouldn’t work on links that weren’t directly pointing to the file – think Google search results. The link Google gives you in a search result redirects you to the actual file, so a context menu item wouldn’t be the best solution. The redirection issue occurs more than you think on the web. Instead, I override an XPCOM component the controls how Firefox treats files the browser doesn’t natively handle. Normally, this component (nsIHelperAppLauncherDialog) starts a file download and will open the file using a local application.

Cloud Viewer overrides the component. I check the extension of the direct URI to the file and if supported, I ask the user if we should open the file in an online viewer. Since the add-on is restartless, I can’t use the normal method of registering JS XPCOM components. I have to manually register and unregister the component myself. This worked fine and was pretty easy. See the source code.

I really think we should be writing more restartless add-ons, especially the simple ones. Great UX and a really small download. Only 2.5KB XPI file for Cloud Viewer. The internal support for restartless add-ons will only keep getting better.

Zippity – Using the Crowd to Collect Performance Data

If you’re serious about improving your application’s performance, you need to collect data. At Mozilla, we use Talos. The Mobile team has been using Talos to track performance on Maemo (using N810 and N900) devices for a few years and recently started tracking Android (using Tegras) as well. Here are a few of the metrics we track:

  • Startup (Ts)
  • Pageload (Tp)
  • HTML Rendering (Tdhtml)
  • Sunspider (Tss)

We build the application every time code is checked into the source repository. After each build, we run these performance tests and watch for changes. Regressions happen and need to be fixed as soon as possible.

One limitation of the current Talos system is the hardware variety. For Firefox Mobile, we run tests on two (2) types of hardware: N900 (Maemo) and NVidia Tegra (Android). One problem is that this doesn’t map to the real world very well. Don’t get me wrong – the current system minimizes measurement noise and we can see regressions fairly easily. However, without testing on a variety of devices, we just don’t get a good picture of how Firefox Mobile runs on your device.

Hello Zippity!

One idea that has talked about is to get the community involved in testing performance. If we could leverage many different people, using many different phones, we might get a better picture of how Firefox Mobile performs in the real world.

I created a simple add-on (Test Harness) that can install in any Firefox 4 for Mobile release and can run a series of tests. Then it posts the result to a public data server (Zippity) and anyone can view the results. When a test result is posted to Zippity, we capture a few bits of information:

  • Test Type: Pageload, startup – whatever the test might be tracking.
  • Test Manifest: What was the actual sequence performed? What series of pages did you load? This is important if we want to try to minimize noise in the results. If you want to only compare test runs that used the same input data, it can help reduce noise and make trends easier to see.
  • User Key: In case you want to only see results submitted by a particular user. The user key is completely optional. It can be used to help filter extraneous results.
  • Device Metadata: Information like device type, OS, OS version. Again, helpful for filtering.

I have been running Firefox Mobile nightly builds on several Android devices using a static set of webpages, hosted from a local webserver. Very controlled, to minimize noise and try to see small changes over time. You can see the results here.

Anyone can run the same set of live, real world pages using the add-on. Because of differences in devices, networking latency and changes to the actual website content – the results won’t be the same. That’s OK! The real world results are just as meaningful and provide just as much valuable insight.

Currently, the Test Harness add-on only sends pageload (Tp) data to Zippity, but plans are underway to extend the test types. If you want to play along, just install the Test Harness add-on. It is already configured to run the pre-defined pageset and submit the results to Zippity. Just push the button:

Zippity is still evolving and doesn’t have a lot of features. The source code should be in my Mercurial user repo soon. I’ll post more about Zippity and the Test Harness add-on as things improve.

Restartless Add-ons – Default Preferences

I saw a neat technique in Bug 564675 for creating default preferences in bootstrapped add-ons. Edward Lee posted a link to some code he uses for a bootstrapped add-on. Essentailly, the default preference branch is writable, but not permanent. This is a good thing for bootstrapped add-ons. The add-on should write it’s default preferences every time it loads and the preferences disappear when the session ends. The code looks like:


const PREF_BRANCH = "extensions.myaddon.";
const PREFS = {
  someIntPref: 1,
  someStringPref: "some text value"
};

function setDefaultPrefs() {
  let branch = Services.prefs.getDefaultBranch(PREF_BRANCH);
  for (let [key, val] in Iterator(PREFS)) {
    switch (typeof val) {
      case "boolean":
        branch.setBoolPref(key, val);
        break;
      case "number":
        branch.setIntPref(key, val);
        break;
      case "string":
        branch.setCharPref(key, val);
        break;
    }
  }
}

function startup(aData, aReason) {
  // Always set the default prefs as they disappear on restart
  setDefaultPrefs();
  ...
}

I haven’t tested to see if there is a clean way to remove these preferences when a bootstrapped add-on is disabled or uninstalled. The default preferences will likely still hang around until a shutdown occurs.

We’re starting to amass a sizable collection of code snippets for bootstrapped add-ons. I think a new section in the MDC Code Snippets area might be needed.

Restartless Add-ons – More Resources

After playing around with bootstrap (restartless) add-ons a bit more, I came to the point where I needed some extra resources in the add-on. For images, I could have stuck with the data: URI approach I mentioned previously, but I also wanted to add an options UI for my add-on. Turns out it was pretty simple. Thanks to Vlad for getting the ball rolling.

The trick I am using now is to programmatically add a resource: alias for my add-on. A resource: alias creates a handy way for you to create URIs pointing to resources, like scripts and images, in your add-on bundle. It’s pretty simple to add a resource: URI pointing to the add-on’s root folder or XPI bundle. Here is the code snippet taken from my bootstrap.js file:


function startup(aData, aReason) {
  let resource = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
  let alias = Services.io.newFileURI(aData.installPath);
  if (!aData.installPath.isDirectory())
    alias = Services.io.newURI("jar:" + alias.spec + "!/", null, null);
  resource.setSubstitution("myaddonpackage", alias);
  ...
}

function shutdown(aData, aReason) {
  if (aReason == APP_SHUTDOWN) return;

  let resource = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
  resource.setSubstitution("myaddonpackage", null);
  ...

The isDirectory check is needed because it is possible for add-ons to be installed in folders or XPI bundles, which require the jar: URI syntax. The code adds a resource alias, after which you can use URIs like this:


resource://myaddonpackage/images/someimage.png
resource://myaddonpackage/content/somescript.js

In these examples, images and content are folders inside your add-on bundle. Now you can add URI-based resources to your install.rdf like this:


  ...
  resource://myaddonpackage/content/icon.png
  resource://myaddonpackage/content/options.xul
  ...

Yes, the em:optionsURL works just fine for providing an options UI in your add-on. I hope this tip gives you more help converting your traditional add-on to a restartless add-on.

Updated: I added the removal of the resource: alias on shutdown based on feedback from Nils in comments. Check out his link to some great bootstrap add-on information too. The part about stale caches after an add-on update presents a painful problem. Waldimir has also previously posted about the limitations of bootstrapped add-ons.