Using Javascript ( *.jsm ) modules in the SDK

7

The ability of developers using the SDK to get chrome authority and access xpcom services is a key feature. The SDK was designed from the start with the assumption that some developers will always need access to the underlying Mozilla platform. While xpcom is the main way to access low level apis, a lot of functionality in Firefox is instead implemented as JavaScript Modules, and can be used in your code much more naturally than xpcom services because they are just JavaScript, and from a design perspective quite similar to CommonJS modules.

Here’s a code snippet that you can use to import a jsm into your code:

const { Cu } = require("chrome");
 
let AddonManager = Cu.import("resource://gre/modules/AddonManager.jsm").AddonManager;
 
AddonManager.getAddonsByTypes(["extension"], function(addons) {
    var addonData = [];
 
    for (let i in addons) {
		let cur = addons[i];
		addonData.push({
			id: cur.id.toString(),
			name: cur.name,
		});
	};
	console.log(JSON.stringify(addonData, null, '   '));
});


The code requires chrome in order to get the Cu object ( Components.Utils ) and then imports the AddonManager module. Nice and easy! The only real problem with adding code like this to your add-on is that getting chrome authority to access Cu has review implications on AMO, and we in the Jetpack team want to do everything we can to get your add-ons reviewed as quickly as possible.

With that in mind, we have been considering including a utility function in the SDK that allows developers to import jsm modules without having to get chrome authority. Ben Buksch contributed this code snippet in a bug that neatly abstracts the small amount of chrome-level code into a single function:

const { Cu } = require("chrome");
/**
 * Imports a JS Module using Components.utils.import()
 * and returns the scope, similar to Jetpack require().
 *
 * @param targetScope {Object} (optional)
 *     If null, the scope will just be returned
 *     and *not* added to the global scope.
 *     If given, all functions/objects from the JSM will be
 *     imported directly in |targetScope|, so that you
 *     can do e.g.
 *       requireJSM("url", this)
 *       someFuncFromJSM();
 *     which will have the same effect as
 *       Components.utils.import("url");
 *       someFuncFromJSM();
 *     in normal Mozilla code, but the latter won't work in Jetpack code.
 */
function requireJSM(url, targetScope)
{
  var scope = targetScope ? targetScope : {};
  return Cu.import(url, scope);
  return scope;
}

I’ve created a Jetpack module on Builder that you can use to load Javascript modules using Ben’s code:

https://builder.addons.mozilla.org/library/1040049/latest

I’ve also created this example addon that uses my module:

https://builder.addons.mozilla.org/addon/1040048/latest/

Here is the above code, re-factored to use my module:

 
let requireJSM = require("jsmutils/jsmutils").requireJSM;
let AddonManager = requireJSM("resource://gre/modules/AddonManager.jsm").AddonManager;
 
AddonManager.getAddonsByTypes(["extension"], function(addons) {
	var addonData = [];
 
	for (let i in addons) {
		let cur = addons[i];
		addonData.push({
			id: cur.id.toString(),
			name: cur.name,
		});
	};
	console.log(JSON.stringify(addonData, null, '   '));
});

I know what you’re thinking – there isn’t really any less code involved using the abstracted module. I would argue that the advantage is instead that I have isolated the part of my code that needs chrome authority to a single, small file that is very easy to understand. This is better separation and greatly aids the ability of our add-on reviewers to quickly understand your code and any security implications it might have.

Categories: developers, jetpack, sdk

7 responses

  1. jonathan

    Awesome! I can already see how very useful this is going to be for anyone who wants to leverage the many modules that exist already in the Gecko platform and in the specific applications.

    One question though: why do you write

    let AddonManager = Cu.import(“resource://gre/modules/AddonManager.jsm”).AddonManager;

    I thought that the way to use this was:

    Cu.import(“resource://gre/modules/AddonManager.jsm”);

    and then it would import the AddonManager object into the current scope.

    1. Jeff Griffiths Author

      That’s just a personal preference that I have for only importing what I need into the current scope. A JSM could export any number of things, similar to `from foo import *` in Python.

  2. Ben Bucksch

    Sorry, the 2 returns were of course nonsense. Correct is:

    function requireJSM(url, targetScope)
    {
    var scope = targetScope ? targetScope : {};
    Cu.import(url, scope);
    return scope;
    }

    Sorry, guys!

    1. Plz update code & link

      “Sorry, the 2 returns were of course nonsense.”…I was wondering about that, I thought that wouldn’t work.

      The above link is to…

      https://builder.addons.mozilla.org/library/1040049/revision/7

      …so I thought you hadn’t updated the library, however, you have.

      You should change the link above to…

      https://builder.addons.mozilla.org/library/1040049/latest

      …& also update the code example in this post.

      1. Jeff Griffiths Author

        Thanks for pointing that out – I’ve updated the links.

  3. Ben Bucksch

    “The only real problem with adding code like this to your add-on is that getting chrome authority to access Cu has review implications on AMO”

    As I we had discussed on the bug, the requireJSM() doesn’t really change anything about the review requirements. As your example shows, this very example can load arbitrary third-party code from the net using the Addon API (to my knowledge without user confirmation), which will then be executed as privileged extension, so effectively requireJSM() is the same as require(“chrome”), and it should be treated the same in reviews.

    1. Jeff Griffiths Author

      I agree, having this in your own code doesn’t change review policy. What it does change is how reviewers work in practical terms. As I said before, “I have isolated the part of my code that needs chrome authority to a single, small file that is very easy to understand”. Reviewers really appreciate that vs. seeing require(“chrome”) at the top of a 3000 line file with any number of possible uses of Cc and Cu buried in the code.

      I also think we should consider adding this to the SDK itself in some way, so add-on developers don’t have to be concerned with chrome privileges just to access these modules.