Unloading lazy getters in boostrap addons?

114 views Asked by At

At the top of my bootstrap.js I define a bunch of lazyGetters, instead of a JSM:

const myServices = {};
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
XPCOMUtils.defineLazyGetter(myServices, 'sss', function(){ return Cc['@mozilla.org/content/style-sheet-service;1'].getService(Ci.nsIStyleSheetService) });

I heard that you have to unload imported modules. But what about "modules" that you create for lazyGetters? How would I unload those? Would I do a delete myServices?

If I do a delete on myServices which is a global, does that mean I should delete all my global variables on unload?

I read here: Forgetting to unload JavaScript modules in restartless add-ons

Forgetting to unload JavaScript modules in restartless add-ons

Another common cause of leaks is forgetting to unload JavaScript code modules in bootstrapped add-ons. These leaks cannot be detected by looking at about:compartments or about:memory because such modules live within the main System compartment.

Also, when your add-on gets updated and re-enabled, the previous module version that is still loaded will be used, which might break your add-on entirely.

The following example shows how to unload your modules again (bootstrap.js):

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

function startup(data, reason) {
  // This assumes your add-on did register some chrome
  Components.utils.import("chrome://myaddon/content/mymodule.jsm");
}

function shutdown(data, reason) {
  if (reason != APP_SHUTDOWN) {
    // No need to do regular clean up when the application is closed
    // unless you need to break circular references that might negatively
    // impact the shutdown process.
    return;
  }

  // Your add-on needs to unload all modules it ships and imported!
  Components.utils.unload("chrome://myaddon/content/mymodule.jsm");
}

Note: Modules not belonging to your add-on — such as Services.jsm — should not be unloaded by your add-on, as this might cause errors and/or performance regressions and will actually increase the memoryusage.

1

There are 1 answers

2
nmaier On BEST ANSWER

The code you provided does not import a module, but defines a service getter, so it is fine.

(Aside: there is also a XPCOMUtils.defineLazyServiceGetter helper...)

However if you did something like this:

XPCOMUtils.defineLazyGetter(myServices, 'SomeSymbol', function() {
   return Cu.import("chrome://myaddon/content/mymodule.jsm", {}).SomeSymbol;
});

then of course you'd need to Cu.unload() that module again. This is best done (IMO) by registering the module to unload as soon as it is loaded, so something like:

XPCOMUtils.defineLazyGetter(myServices, 'SomeSymbol', function() {
   let rv = Cu.import("chrome://myaddon/content/mymodule.jsm", {}).SomeSymbol;
   unload(function() {
     Cu.unload("chrome://myaddon/content/mymodule.jsm");
   });
   return rv;
});

Other people just pro-actively unload all modules that could have been potentially imported or not. This is fine to, as Cu.unload()ing a module that wasn't imported just does nothing.

PS: You can still run into trouble when sticking something into another module, like.

XPCOMUtils.defineLazyServiceGetter(Services /* other module */,
                                   'sss',
                                   '@mozilla.org/content/style-sheet-service;1',
                                   'nsIStyleSheetService');

In this example XPCOMUtils.jsm might still reference the strings passed as arguments from your code and hence leak your code. (Also, it is rather rude to stick stuff into modules that aren't your own and may create conflicts with other add-ons doing the same thing.)