Miscellaneous

This page contains small, self-explanatory code snippets.

Output

Saving the current web page to a local file

Although the following code does not prompt the user for a filename, you can do so using the file picker component.

var file = Components.classes["@mozilla.org/file/local;1"]
           .createInstance(Components.interfaces.nsILocalFile);
file.initWithPath("C:\\filename.html");
var wbp = Components.classes['@mozilla.org/embedding/browser/nsWebBrowserPersist;1']
          .createInstance(Components.interfaces.nsIWebBrowserPersist);
wbp.saveDocument(content.document, file, null, null, null, null);

See also Downloading Files.

Saving an arbitrary URL to a local file

 var file = Components.classes["@mozilla.org/file/local;1"]
            .createInstance(Components.interfaces.nsILocalFile);
file.initWithPath("C:\\filename.html");
var wbp = Components.classes['@mozilla.org/embedding/browser/nsWebBrowserPersist;1']
          .createInstance(Components.interfaces.nsIWebBrowserPersist);
var ios = Components.classes['@mozilla.org/network/io-service;1']
          .getService(Components.interfaces.nsIIOService);
var uri = ios.newURI("http://www.google.com/", null, null);
wbp.persistFlags &= ~Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_NO_CONVERSION; // don't save gzipped
wbp.saveURI(uri, null, null, null, null, file);

See also Downloading Files.

Printing webpages without user input

Get an nsIPrintSettings instance. Set printSilent to true and then, instead of using window.print(), get nsIWebBrowserPrint and give the nsIPrintSettings instance as an argument to the nsIWebBrowserPrint.print().

System info

Operating system detection

// Returns "WINNT" on Windows Vista, XP, 2000, and NT systems;
// "Linux" on GNU/Linux; and "Darwin" on Mac OS X.
var osString = Components.classes["@mozilla.org/xre/app-info;1"]
               .getService(Components.interfaces.nsIXULRuntime).OS;

In cases when nsIXULRuntime is not available (older SeaMonkey versions), you can use nsIHttpProtocolHandler.oscpu or navigator.oscpu (this will also provide you with the OS version, not just its name):

Components.classes["@mozilla.org/network/protocol;1?name=http"]
          .getService(Components.interfaces.nsIHttpProtocolHandler).oscpu;

Detecting the host application and version

var info = Components.classes["@mozilla.org/xre/app-info;1"]
           .getService(Components.interfaces.nsIXULAppInfo);
// Get the name of the application running us
info.name; // Returns "Firefox" for Firefox
info.version; // Returns "2.0.0.1" for Firefox version 2.0.0.1

Retrieving the version of an extension as specified in the extension's install.rdf

Prior to Firefox 4, you can use the nsIExtensionManager service for this task.

var em = Components.classes["@mozilla.org/extensions/manager;1"]
         .getService(Components.interfaces.nsIExtensionManager);

// Change extension-guid@example.org to the GUID of the extension whose version
// you want to retrieve
var addon = em.getItemForID("extension-guid@example.org");
alert("My extension's version is " + addon.version);

Starting with Mozilla 2/Firefox 4, the old nsIExtensionManager implementation was replaced by the new AddonManager. Hence you must update your extensions.

Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.getAddonByID("extension-guid@example.org", function(addon) {
  // This is an asynchronous callback function that might not be called immediately
  alert("My extension's version is " + addon.version);
});

It should be noted that the new API is now asynchronous.

Bringing both, the old nsIExtensionManager and the new AddonManager, together for backwards-compatibility is pretty straight-forward, too.

try {
  // Firefox 4 and later; Mozilla 2 and later
  Components.utils.import("resource://gre/modules/AddonManager.jsm");
  AddonManager.getAddonByID("extension-guid@example.org", function(addon) {
    alert("My extension's version is " + addon.version);
  });
} catch (ex) {
  // Firefox 3.6 and before; Mozilla 1.9.2 and before
  var em = Components.classes["@mozilla.org/extensions/manager;1"]
           .getService(Components.interfaces.nsIExtensionManager);
  var addon = em.getItemForID("extension-guid@example.org");
  alert("My extension's version is " + addon.version);
}

Receiving notification before an extension is disabled and/or uninstalled

xulplanet entry on global notifications

  • notification: "em-action-requested"
  • aData: "item-disabled"
  • aSubject is an nsIUpdateItem instance.

This notification is broadcast when the user disables the extension, but before it's actually disabled. The user can cancel his action before the restart, in which case the extension won't be disabled. Some people suggested setting a flag when receiving this notification (and unsetting it if the user canceled the action) and only doing the clean up at the shutdown. The downside is that your code won't run if Firefox shuts down improperly.

You should filter on the item ID to only handle the actions on your extension.

Copying from an input stream to an output stream

There are two ways to do it. First way:

Code for Firefox 3.5 / Gecko 1.9.1 and earlier

// istream is a nsIInputStream and ostream is a nsIOutputStream
// One of the two streams needs to be buffered to work.  If we happen to have two
// unbuffered streams, let's just buffer up the output stream.
var bostream = Components.classes["@mozilla.org/network/buffered-output-stream;1"]
                         .createInstance(Components.interfaces.nsIBufferedOutputStream);
bostream.init(ostream, 0x8000);

// make a stream copier
var copier = Components.classes["@mozilla.org/network/async-stream-copier;1"]
                       .createInstance(Components.interfaces.nsIAsyncStreamCopier);
// Initialize it.  The last three arguments depend on the exact buffering state
// of our streams.  The 0x8000 should match the size of the buffer our buffered
// stream is using, for best performance.

copier.init(istream, bostream, null, false, true, 0x8000);

/* We need our own observer to know when to close the output stream */
var observer = {
  onStartRequest: function(aRequest, aContext) {},
  onStopRequest: function(aRequest, aContext, aStatusCode) {
    bostream.close();
  }
};

// start the copying
copier.asyncCopy(observer, null);

Code for Gecko 1.9.2

// istream is a nsIInputStream and ostream is a nsIOutputStream
// One of the two streams needs to be buffered to work.  If we happen to have two
// unbuffered streams, let's just buffer up the output stream.
var bostream = Components.classes["@mozilla.org/network/buffered-output-stream;1"]
                         .createInstance(Components.interfaces.nsIBufferedOutputStream);
bostream.init(ostream, 0x8000);

// make a stream copier
var copier = Components.classes["@mozilla.org/network/async-stream-copier;1"]
                       .createInstance(Components.interfaces.nsIAsyncStreamCopier);
// Initialize it.  Arguments four through 6 depend on the exact buffering state
// of our streams.  The 0x8000 should match the size of the buffer our buffered
// stream is using, for best performance.
copier.init(istream, bostream, null, false, true, 0x8000, true, true);

/* We need our own observer to know when to close the output stream */
var observer = {
  onStartRequest: function(aRequest, aContext) {},
  onStopRequest: function(aRequest, aContext, aStatusCode) {
    bostream.close();
  }
};

// start the copying
copier.asyncCopy(observer, null);

Second way:

// istream is a nsIInputStream and ostream is a nsIOutputStream

// the output stream needs to be buffered to work.
var bostream = Components.classes["@mozilla.org/network/buffered-output-stream;1"]
               .createInstance(Components.interfaces.nsIBufferedOutputStream);
bostream.init(ostream, 0x8000);

// make a stream pump and a stream listener to read from the input stream for us
var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"]
           .createInstance(Components.interfaces.nsIInputStreamPump);
pump.init(istream, -1, -1, 0, 0, true);

/* we need our own observer to know when to close the file */
var observer = {
  onStartRequest: function(aRequest, aContext) {},
  onStopRequest: function(aRequest, aContext, aStatusCode) {
    bostream.close();
  }
};

// make a simple stream listener to do the writing to output stream for us
var listener = Components.classes["@mozilla.org/network/simple-stream-listener;1"]
               .createInstance(Components.interfaces.nsISimpleStreamListener);
listener.init(bostream, observer);

// start the copying
pump.asyncRead(listener, null);

Restarting Firefox/Thunderbird/SeaMonkey_2.0

bug 338039 tracks improving this situation by providing a simple method to restart the application.

Example for Firefox:

var boot = Components.classes["@mozilla.org/toolkit/app-startup;1"].getService(Components.interfaces.nsIAppStartup);
boot.quit(Components.interfaces.nsIAppStartup.eForceQuit|Components.interfaces.nsIAppStartup.eRestart);

Mouse and keyboard

Detecting mouse wheel events

When scrolling the mouse wheel on an element, the DOMMouseScroll event fires. event.detail contains the number of lines to scroll. This event is Mozilla-only; other browsers may support window.onmousewheel.

<div id="scrollArea" style="overflow: scroll; height: 6em; width: 10em;">
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
  This is the scrolling area.
</div>

<script type="text/javascript">
  var elm = document.getElementById("scrollArea");
  elm.addEventListener("DOMMouseScroll", function scroll(event){
    //event.detail is positive for a downward scroll, negative for an upward scroll
    alert("scrolling " + event.detail + " lines");
  }, false);
</script>

If you do not receive a DOMMouseScroll event while holding any of the modifier keys (Ctrl,Shift,Alt,Meta) you should check the mousewheel.withcontrolkey.action and related preferences. The meaning of the action preference is shown in the following table

mousewheel.withXXXkey.action Result
0 Scroll by lines. If set, you will receive DOMMouseScroll events.
1 Scroll by page.
2 Move around in the history. If set, you will not receive DOMMouseScroll events.
3 Change text size. If set, you will not receive DOMMouseScroll events.

You can listen to mouse wheel click events just like normal click events (via mousedown and mouseup events). When the mouse wheel is clicked, event.button will be equal to 1.

Simulating mouse and key events

https://developer.mozilla.org/samples/domref/dispatchEvent.html

Also, new in Firefox 3 / Gecko 1.9:

var req = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
var utils = req.getInterface(Components.interfaces.nsIDOMWindowUtils);
utils.sendMouseEvent("mousedown", 10, 10, 0, 1, 0);
utils.sendMouseEvent("mouseup", 10, 10, 0, 1, 0);

Getting the currently selected text

From browser.xul overlay context:

var selectedText = document.commandDispatcher.focusedWindow.getSelection().toString();

or:

content.getSelection(); // |window| object is implied; i.e., window.content.getSelection()

or:

getBrowserSelection(); // |window| object is implied; i.e., window.getBrowserSelection()

This final option massages the selection to remove leading and trailing whitespace. It also collapses consecutive whitespaces into a single whitespace, and returns at most 150 characters, making it ideal to display text in context menus. It is defined at http://mxr.mozilla.org/mozilla-central/source/browser/base/content/browser.js#4620. Note that it returns the empty string (""), not false, when nothing is selected. getBrowserSelection() is not implemented on some platforms, such as Thunderbird and Songbird.

See also http://mxr.mozilla.org/seamonkey/source/browser/base/content/browser.js#4674

Discovering which element in the loaded document has focus

// focusedControl stores the focused field, or null if there is none.
// This excludes textareas for simplicity, but onPageLoad() can easily be
// modified to cycle through textareas as well. Further enhancements would include
// handling for fields dynamically added to the page (e.g., by page javascript).

var focusedControl;

window.addEventListener("load", function(e) { onWindowLoad(e); }, false);

function onWindowLoad() {
  gBrowser.addEventListener("load", onPageLoad, true);
}

function onPageLoad() {
  pageDoc = document.commandDispatcher.focusedWindow.document;
  var inputList = pageDoc.getElementsByTagName('input');
  for (var i=1; i<inputList.length; i++) {
    inputList.item(i).
    addEventListener("focus", function(e) {onFocusInput(e);}, false);
    inputList.item(i).
    addEventListener("blur", function(e) {onBlurInput(e);}, false);
  }
}

function onFocusInput(focusEvent) {
  focusedControl = focusEvent.originalTarget;
}

function onBlurInput(blurEvent) {
  focusedControl = null;
}

or

var element = document.commandDispatcher.focusedElement;

Inserting text at the cursor

function insertText(element, snippet)
{
  var selectionEnd = element.selectionStart + snippet.length;
  var currentValue = element.value;

  var beforeText = currentValue.substring(0, element.selectionStart);
  var afterText = currentValue.substring(element.selectionEnd, currentValue.length);

  element.value = beforeText + snippet + afterText;
  element.focus();

  //put the cursor after the inserted text
  element.setSelectionRange(selectionEnd, selectionEnd);
}

insertText(document.getElementById("example"), "the text to be inserted");

Poor man's obfuscation

This code is meant as a trivial way to protect somewhat sensitive data (ex: an extension's password stored in a preference) from casual discovery. At glance it looks like gibberish, but it's easily breakable. For storing passwords, this document explains use of nsIPasswordManager.

function encrypt(val) {
  num_out = "";
  if (val == ""){
    return "";
  } else {
    str_in = escape(val);
    for (i = 0; i < str_in.length; i++) {
      num_out += str_in.charCodeAt(i) - 23;
    }
    return unescape(num_out);
  }
}

function decrypt(val) {
  str_out = "";
  if (val == ""){
    return "";
  } else {
    num_out = val;
    for (i = 0; i < num_out.length; i += 2) {
      num_in = parseInt(num_out.substr(i,[2])) + 23;
      num_in = unescape('%' + num_in.toString(16));
      str_out += num_in;
    }
    return str_out;
  }
}

Disabling JavaScript programmatically

// Disable JS in the currently active tab from the context of browser.xul
gBrowser.docShell.allowJavascript = false;

If this isn't your browser, you should save the value and restore it when finished. If you wish to block selected scripts based on their URI, implement nsIContentPolicy.

Using string bundles from JavaScript

Assuming the extension has myext.properties with name/value pairs such as:

 invalid.url=The speficied URL, %S, is invalid. That was attempt number %S.

These properties can be accessed from JavaScript in the following manner:

 var common = {

  _bundle: Components.classes["@mozilla.org/intl/stringbundle;1"]
           .getService(Components.interfaces.nsIStringBundleService)
           .createBundle("chrome://myext/locale/myext.properties"),

           getLocalizedMessage: function(msg) {
              return this._bundle.GetStringFromName(msg);
           }
};

alert(common.getLocalizedMessage("invalid.url"))

Another similar alternative (using both GetStringFromName and formatStringFromName), is:

var fcBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
               .getService(Components.interfaces.nsIStringBundleService)
               .createBundle("chrome://myext/locale/myext.properties");

function getStr(msg, args){ //get localised message
  if (args){
    args = Array.prototype.slice.call(arguments, 1);
    return fcBundle.formatStringFromName(msg,args,args.length);
  } else {
    return fcBundle.GetStringFromName(msg);
  }
}

/* Usage */
alert(getStr("invalid.url", "http://bad/url/", "3")); //for message with parameters
alert(getStr("invalid.url")); //for message without parameters

Getting postData of a webpage

First, you need to get the browser you want, and its historySession.

var Ci = Components.interfaces;
var Cc = Components.classes;

//assume you can not get the main window object directly, if you can, just use it
var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
var mainWindow = wm.getMostRecentWindow("navigator:browser");

//get sessionHistory from the current selected tab
var history = mainWindow.gBrowser.selectedBrowser.webNavigation.sessionHistory;

And then get the page you want, and it's postData. This example obtains the post data of the last page.

var postdata = history.getEntryAtIndex(history.index-1,false).QueryInterface(Ci.nsISHEntry).postData;

If you got here all by yourself, your problem must be at reading the postData, because it's a nsIInputStream object, whose available function always returns 0. And if you read it directly, you always got nothing. And here's how it's done

postdata.QueryInterface(Ci.nsISeekableStream).seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
stream.setInputStream(postdata);
var postBytes = stream.readByteArray(stream.available());
var poststr = String.fromCharCode.apply(null, postBytes);

//Do anything to your poststr
alert(poststr);

Getting a string from the input stream is made somewhat simpler in Firefox 4, by the addition of NetUtil.readInputStreamToString()

Getting postData of a request before the request is sent

The above code will get the postdata for a page that has already loaded. To see what the postdata looks like before the request is even sent, use the 'http-on-modify-request' observer topic:

observerService.addObserver(observer, 'http-on-modify-request', false);

where "observer" is an object that has a method "observe":

function observe(subject, topic, data) {
  subject.QueryInterface(Components.interfaces.nsIUploadChannel);
  postData = subject.uploadStream;
}

Here again, postData is not a string, but an nsIInputStream, so you can use the last code snippet of the previous section to get the data as a string. However, if you are not going to cancel the request, you need to "rewind" it by calling:

postdata.QueryInterface(Ci.nsISeekableStream).seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);

Adding custom certificates to a XULRunner application

You need to ship a XULRunner application with your own SSL certificates? For Firefox, you could use the Client Customization Kit (CCK) to create an extension that does exactly that. To get the same feature for your XULRunner application, follow those steps:

First, put the certificates you want to ship into the content/certs folder of your application.

Then create a XPCOM service that adds the certificates inside that directory to the certs database on application startup. Put this code in the components/certsService.js file:

const Cc = Components.classes;
const Ci = Components.interfaces;

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");

const gObserver = Cc['@mozilla.org/observer-service;1'].getService(Ci.nsIObserverService);
const gIOService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);

function CertsService() {}

CertsService.prototype = {
    observe: function(aSubject, aTopic, aData) {
        switch(aTopic) {
            case "app-startup":
                gObserver.addObserver(this,"xpcom-shutdown",false);
                gObserver.addObserver(this,"final-ui-startup",false);
                break;
            case "xpcom-shutdown":
                gObserver.removeObserver(this,"final-ui-startup");
                gObserver.removeObserver(this,"xpcom-shutdown");
                break;
            case "final-ui-startup":
                this.init();
                break;
        }
    },

    init: function() {
      // add all certificates you want to install here (or read this from your prefs.js ...)
      var certificates = "root.crt,user.crt";

      var certs = certificates.split(',');
      for (var i=0; i<certs.length; i++) {
        this.addCertificate(certs[i], 'C,c,c');
      }
    },

    addCertificate: function(CertName, CertTrust) {
      var certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB2);
      var scriptableStream=Cc["@mozilla.org/scriptableinputstream;1"].getService(Ci.nsIScriptableInputStream);
      var channel = gIOService.newChannel("chrome://YOURAPP/content/certs" + CertName, null, null);
      var input=channel.open();
      scriptableStream.init(input);
      var certfile=scriptableStream.read(input.available());
      scriptableStream.close();
      input.close();
		
      var beginCert = "-----BEGIN CERTIFICATE-----";
      var endCert = "-----END CERTIFICATE-----";

      certfile = certfile.replace(/[\r\n]/g, "");
      var begin = certfile.indexOf(beginCert);
      var end = certfile.indexOf(endCert);
      var cert = certfile.substring(begin + beginCert.length, end);
      certDB.addCertFromBase64(cert, CertTrust, "");
    },

    classDescription: "Certificate Service",
    contractID: "@mozilla.org/certs-service;2",
    classID: Components.ID("{e9d2d37c-bf25-4e37-82a1-16b8fa089939}"),
    QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
    _xpcom_categories: [{
        category: "app-startup",
        service: true
    }]
}

function NSGetModule(compMgr, fileSpec) {
    return XPCOMUtils.generateModule([CertsService]);
}

You need to delete your existing profile, otherwise the XPCOM service is not used. See How to Build an XPCOM Component in Javascript for details. Now are your certificates installed on every application startup. Unfortunately that seems to be the only working solution, apart from modifying the Mozilla source code and recompiling.

Generating Random Bytes

Useful snippet for generating random bytes that can, for example be used as a good source for cryptographic entropy.

const NOB = 128; // number of bytes
var buffer = '';
var prng = Components.classes['@mozilla.org/security/random-generator;1'];
var bytebucket =  prng.getService(Components.interfaces.nsIRandomGenerator).generateRandomBytes(NOB, buffer);

Detecting full screen mode on/off

It works for that global 'window' object at least.

window.addEventListener('fullscreen', function(){
  alert('fullscreen mode on or off')
}, false)

Detecting window move

var window_moved = function(e) {
  if(e.attrName == 'screenX' || e.attrName == 'screenY') ...
}
document.getElementById('main-window').addEventListener('DOMAttrModified', window_moved, false)

Getting a webpage's source code

function getSource(filename) {
  var req = new XMLHttpRequest();
  req.open('GET', filename, false); 
  req.send(null);
  if (req.status == 200) {
    return req.responseText;
  }
  return null;
}

If the filename is a local file, rather than a resource fetched via HTTP, you should compare req.status to 0 instead of 200. See Synchronous and Asynchronous Requests#Example: HTTP synchronous request