I am very new to Mozilla extension development, even I just came to know about extension and add-on development is different and I am quite stuck with what I see at MDN (Mozilla
It appears you are having a few issues:
AddonManager
is primarily intended for your add-on to be able to monitor activity involving other add-ons. Its usefulness in monitoring the activity of the add-on doing the monitoring is limited.AddonManager
onDisabled event if the user disables the add-on prior to removing it. If the user directly removes it, you just get an onUninstalling event.console.log()
from a content script does not show up in the Browser Console, when the content script was attached by the add-on that was loaded as the primary add-on being tested with jpm run. The output is available in the console window which executed the jpm run
. While it is by design that the output goes to the console window, I feel it is either a bug or misfeature that the output does not go to the Browser Console (in addition to the console window).about:*
page results in some behavior different than loading it into a normal webpage. This behavior can include making console.log() output only show up as if it was output using dump() (i.e. it does not show in the Browser Console, but does show in the console which executed Firefox/jpm run
). If you are attempting to do so, you will need to experiment with what you are able to do.console.log()
output, the Browser Console. As such, I have the add-ons below automatically open the Browser Console.
"main"
JavaScript file is evaluated and executed. This function can be used to find the reason why your add-on is being executed. Possible reasons are install
, enable
, startup
, upgrade
, and downgrade
. The add-ons below demonstrate when this code is executed and the reason
passed to it.In order to demonstrate a variety of things, I wrote a Firefox Add-on SDK extension which both loads a couple of content scripts and listens to AddonManager
events. I made two versions of this add-on which are different only in the name and ID that are assigned to each in their respective package.json files. The two add-ons are installinfo@ex1
and installinfo@ex2
.
The first add-on, installinfo@ex1
, is loaded by running jpm run
in its directory. The second add-on, installinfo@ex2
, is installed by dragging and dropping the .xpi created for it by jpm xpi
. Using the Firefox UI, I immediately navigate to about:addons
(Ctrl-Shift-A, Cmd-Shift-A on OSX) and proceed to first disable installinfo@ex2
; then "remove" installinfo@ex2
; then refresh the about:addons
page to make it not possible to "undo" the removal. I then exit the Firefox main browser window.
The add-ons have a significant amount of output to the console so you can take a look at the order in which things happen and which add-on is able to receive which AddonManager
events. The console output is [noted in brackets are what I was doing with the user interface, and some comments]:
[User action: Start Firefox in installinfo@ex1 directory]
installinfo@ex1: In index.js
installinfo@ex1: In installAddonListener: Adding add-on listener
installinfo@ex1: Attaching content script A
installinfo@ex1: In exports.main: This add-on is being loaded for reason= install Object { loadReason: "install", staticArgs: undefined }
installinfo@ex1: Attaching content script B
installinfo@ex1: In exports.main: was passed callbacks= Object { print: print(), quit: function () }
[Note: no console.log output from within conentScriptA loading]
installinfo@ex1: received message from contentScriptA: Is Loaded
[Note: no console.log output from within conentScriptB loading]
installinfo@ex1: received message from contentScriptB: Is Loaded
[User action: Drag and drop .xpi for installinfo@ex2 onto Firefox]
installinfo@ex1: AddonManager Event: Installing addon ID: installinfo@ex2 ::needsRestart= false ::addon object: Object { }
installinfo@ex1: AddonManager Event: Installed addon ID: installinfo@ex2 ::addon object: Object { }
installinfo@ex2: In index.js
installinfo@ex2: In installAddonListener: Adding add-on listener
installinfo@ex2: Attaching content script A
installinfo@ex2: In exports.main: This add-on is being loaded for reason= install Object { loadReason: "install", staticArgs: undefined }
installinfo@ex2: Attaching content script B
installinfo@ex2: In exports.main: was passed callbacks= Object { print: print(_), quit: function () }
installinfo@ex2: In contentScriptA: Loaded
installinfo@ex2: received message from contentScriptA: Is Loaded
installinfo@ex2: In contentScriptB: Loaded
installinfo@ex2: received message from contentScriptB: Is Loaded
[User action: Navigate to about:addons]
[User action: Disable installinfo@ex2]
installinfo@ex1: AddonManager Event: Disabling addon ID: installinfo@ex2 ::needsRestart= false ::addon object: Object { }
installinfo@ex2: AddonManager Event: Disabling addon ID: installinfo@ex2 ::needsRestart= false ::addon object: Object { }
installinfo@ex2: In exports.onUnload: This add-on is being unloaded for reason= disable
installinfo@ex2: In removeAddonListener: Removing add-on listener
installinfo@ex1: AddonManager Event: Disabled addon ID: installinfo@ex2 ::addon object: Object { }
installinfo@ex2: AddonManager Event: Disabled addon ID: installinfo@ex2 ::addon object: Object { }
installinfo@ex1: AddonManager Event: Uninstalling addon ID: installinfo@ex2 ::needsRestart= true ::addon object: Object { }
[Get a warning in Browser Console because installinfo@ex2 did not remove its AddonManager listeners, and AddonManager is still trying to call them.]
1472563865661 addons.manager WARN AddonListener threw exception when calling onUninstalling: TypeError: can't access dead object (resource://gre/modules/AddonManager.jsm:1756:1) JS Stack trace: AddonManagerInternal.callAddonListeners@AddonManager.jsm:1756:1 < this.AddonManagerPrivate.callAddonListeners@AddonManager.jsm:3075:5 < this.XPIProvider.uninstallAddon@XPIProvider.jsm:5041:7 < AddonWrapper.prototype.uninstall@XPIProvider.jsm:7484:5 < uninstall@extensions.xml:1548:13 < oncommand@about:addons:1:1
[User action: Refresh about:addons page to remove "undo" posibility for installinfo@ex2]
installinfo@ex1: Uninstalled addon ID: installinfo@ex2 ::addon object: Object { }
[Get a warning in Browser Console because installinfo@ex2 did not remove its AddonManager listeners, and AddonManager is still trying to call them.]
1472563873408 addons.manager WARN AddonListener threw exception when calling onUninstalled: TypeError: can't access dead object (resource://gre/modules/AddonManager.jsm:1756:1) JS Stack trace: AddonManagerInternal.callAddonListeners@AddonManager.jsm:1756:1 < this.AddonManagerPrivate.callAddonListeners@AddonManager.jsm:3075:5 < this.XPIProvider.uninstallAddon@XPIProvider.jsm:5096:7 < AddonWrapper.prototype.uninstall@XPIProvider.jsm:7484:5 < doPendingUninstalls@extensions.js:1740:5 < gListView.hide@extensions.js:2733:5 < gViewController.shutdown@extensions.js:651:7 < shutdown@extensions.js:184:3 < EventListener.handleEvent*@extensions.js:84:1
[User action: Close main Firefox browser window]
[User action: Close Firefox Browser Console window]
(via dump):installinfo@ex1: In exports.onUnload: This add-on is being unloaded for reason= shutdown
I suggest you experiment the add-on below to get an idea about what is possible with AddonManager events and when code executes within your add-on.
The add-on files:
index.js:
/* Firefox Add-on SDK test when code is executed upon install*/
//For testing:
var doNotRemoveAddonManagerListenersUponUninstall = true
var self = require("sdk/self");
var tabs = require("sdk/tabs");
var myId = self.id;
let myIdText = myId;
if(myId.indexOf('2') > -1){
//Indent console logs for version 2
myIdText = '\t\t' + myIdText ;
}
var utils = require('sdk/window/utils');
activeWin = utils.getMostRecentBrowserWindow();
//This will execute every time the add-on is loaded (e.g. install, startup, enable, etc).
myLog('In index.js');
//Open the Browser Console
activeWin.document.getElementById('menu_browserConsole').doCommand();
function myLog(){
// Using activeWin.console.log() is needed to have output to the
// Browser Console when installed as an .xpi file. In that case,
// console is mapped to dump(), which does not output to the console.
// This is done, I assume, to not polute the console from SDK add-ons
// that are logging information when they should not. Using jpm run,
// console.log outputs to the Browser Console.
let activeWin = require('sdk/window/utils').getMostRecentBrowserWindow();
// If Firefox is exiting (and some other conditions), there is
// no active window. At such times, we must use the version
// of console.log() aliases to dump().
let useConsole = console;
if(activeWin ){
//useConsole = activeWin.console;
}
useConsole.log(myIdText +':',...arguments);
}
function attachScript(script){
let tabWorker = tabs.activeTab.attach({
contentScriptFile: script,
contentScriptOptions: {
//extra \t because content script console.log (from .xpi) doesn't prefix with
// add-on name.
idText: '\t\t\t' + myIdText
}
});
tabWorker.port.on('consoleLog',logMessage);
return tabWorker;
}
function logMessage(message){
myLog(message);
}
exports.main = function (options,callbacks) {
myLog('In exports.main: This add-on is being loaded for reason=', options.loadReason
, options);
myLog('Attaching content script B');
attachScript('./contentScriptB.js');
if(typeof callbacks !== 'undefined'){
myLog('In exports.main: was passed callbacks=', callbacks);
}
switch (options.loadReason) {
case 'install':
//Do actions upon install
break;
case 'enable':
//Do actions upon enable
break;
case 'startup':
//Do actions upon startup
break;
case 'upgrade':
//Do actions upon upgrade
break;
case 'downgrade':
//Do actions upon downgrade
break;
default:
throw 'Unknown load reason:' + options.loadReason;
break; //Not needed, throw will exit
}
};
exports.onUnload = function (reason) {
myLog('In exports.onUnload: This add-on is being unloaded for reason=',reason);
//Your add-on listeners are NOT automatically removed when
// your add-on is disabled/uninstalled.
//You MUST remove them in exports.onUnload if the reason is
// not 'shutdown'. If you don't, errors will be shown in the
// console for all events for which you registered a listener.
if(reason !== 'shutdown') {
uninstallAddonListener();
}
};
const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
let addonListener = {
onEnabling: function(addon, needsRestart){
myLog('AddonManager Event: Enabliling addon ID: ' + addon.id
+ ' ::needsRestart= ' + needsRestart + ' ::addon object:', addon);
},
onEnabled: function(addon){
myLog('AddonManager Event: Enabled addon ID: ' + addon.id
+ ' ::addon object:', addon);
},
onDisabling: function(addon, needsRestart){
myLog('AddonManager Event: Disabling addon ID: ' + addon.id
+ ' ::needsRestart= ' + needsRestart + ' ::addon object:', addon);
},
onDisabled: function(addon){
myLog('AddonManager Event: Disabled addon ID: ' + addon.id
+ ' ::addon object:', addon);
},
onInstalling: function(addon, needsRestart){
myLog('AddonManager Event: Installing addon ID: ' + addon.id
+ ' ::needsRestart= ' + needsRestart + ' ::addon object:', addon);
},
onInstalled: function(addon){
myLog('AddonManager Event: Installed addon ID: ' + addon.id
+ ' ::addon object:', addon);
},
onUninstalling: function(addon, needsRestart){
myLog('AddonManager Event: Uninstalling addon ID: ' + addon.id
+ ' ::needsRestart= ' + needsRestart + ' ::addon object:', addon);
},
onUninstalled: function(addon){
myLog('AddonManager Event: Uninstalled addon ID: ' + addon.id
+ ' ::addon object:', addon);
},
onOperationCancelled: function(addon){
myLog('AddonManager Event: Add-on Operation Canceled addon ID: '
+ addon.id + ' ::addon object:', addon);
},
onPropertyChanged: function(addon, properties){
myLog('AddonManager Event: Add-on Property Changed addon ID: ' + addon.id
+ ' ::properties= ', properties, ' ::addon object:', addon);
}
}
function installAddonListener(){
//Using an AddonManager listener is not effective to listen for your own add-on's
// install event. The event happens prior to you adding the listener.
myLog('In installAddonListener: Adding add-on listener');
AddonManager.addAddonListener(addonListener);
}
function uninstallAddonListener(){
if(doNotRemoveAddonManagerListenersUponUninstall === true){
//Do the WRONG thing to demonstrate what happens when you don't
// remove AddonManager listeners upon your add-on being disabled.
return;
}
//Using an AddonManager listener is not effective to listen for your own add-on's
// install event. The event happens prior to you adding the listener.
myLog('In removeAddonListener: Removing add-on listener');
AddonManager.removeAddonListener(addonListener);
}
installAddonListener();
myLog('Attaching content script A');
attachScript('./contentScriptA.js');
data/contentScriptA:
//console.log is ailiased to dump() when running under jpm run. Thus,
// you will not see the output from this line in the Browser Console
// when run under jpm run. It will appear in the console window from
// which you executed 'jpm run'
// From an .xpi it outputs to the Browser Console, as expected.
console.log(self.options.idText + ': In contentScriptA: Loaded');
//Send a message to the background script that this content script is loaded.
self.port.emit('consoleLog', 'received message from contentScriptA: Is Loaded');
data/contentScriptB:
//console.log is ailiased to dump() when running under jpm run. Thus,
// you will not see the output from this line in the Browser Console
// when run under jpm run. It will appear in the console window from
// which you executed 'jpm run'
// From an .xpi it outputs to the Browser Console, as expected.
console.log(self.options.idText + ': In contentScriptB: Loaded');
//Send a message to the background script that this content script is loaded.
self.port.emit('consoleLog', 'received message from contentScriptB: Is Loaded');
package.json (for installinfo@ex1):
{
"title": "Demo when code executes re install 1",
"name": "installinfo1",
"id": "installinfo@ex1",
"version": "0.0.1",
"description": "Demo when execute various code with respect to install",
"main": "index.js",
"author": "Makyen",
"engines": {
"firefox": ">=38.0a1",
"fennec": ">=38.0a1"
},
"license": "MIT",
"keywords": [
"jetpack"
]
}
package.json (for installinfo@ex2):
{
"title": "Demo when code executes re install 2",
"name": "installinfo2",
"id": "installinfo@ex2",
"version": "0.0.1",
"description": "Demo when execute various code with respect to install",
"main": "index.js",
"author": "Makyen",
"engines": {
"firefox": ">=38.0a1",
"fennec": ">=38.0a1"
},
"license": "MIT",
"keywords": [
"jetpack"
]
}