Execute script on 'onInstalled' in Firefox Add-on SDK extension

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 Developer Network). I want to executeScript 'content_script.js', as soon as my add-on is installed, so that user did not need to restart the browser.

I just drag drop my xpi file and install it, then I click on a button on webpage which sends message to my add-on, but my add-on listens to this message only after I reload the webpage.

//main.js

       var chrome = require("chrome");      
    chrome.runtime.onInstalled.addListener(function(){
             executeScript (contentscript.js) in tabs});

//also I tried
    browser.runtime.onInstalled.addListener

I was trying with this when I came to know that it is for Mozilla extensions not for add-ons, as it was giving me errors browser undefined and chrome.runtime is undefined.

Then, I found onInstalled() inside AddonManager.addAddonListener().

But, I am still confused how should I use this in my add-on.

Whatever way I tried it is giving me errors.

//main.js
    const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
    Cu.import("resource://gre/modules/AddonManager.jsm");


    AddonManager.addAddonListener.onInstalled.addListener(function(){
        console.log('installed');
        tabs.executeScript(tabId, "../data/content_script.js", function(result) { console.log('result ='+result); });           
    });

Below code removed the error but did not work either:

    const { AddonManager } = require("resource://gre/modules/AddonManager.jsm");
    Cu.import("resource://gre/modules/AddonManager.jsm");


    var listener = {
  onInstalled: function(addon) {
      console.log('installed');
      console.log(addon); 
        tabs.executeScript(tabId, "../data/content_script.js", function(result) { console.log('result ='+result); });           
  }
 };

     AddonManager.addAddonListener(listener);

My package.json looks like this

    {
  "title": "test",
  "name": "test",
  "version": "1.0.0",
  "description": "test addon corp",
  "main": "lib/main.js",
  "author": "test",
  "engines": {
    "firefox": ">=38.0a1",
    "fennec": ">=38.0a1"
  },
  "license": "MIT",
  "keywords": [
    "jetpack"
  ]
}

main includes main.js, but still its not executing, as when I reload the page only then my add-on works.

I think I am doing it totally wrong, I did it in Chrome extension and it was easy, but I don't have any idea for Mozilla add-on. I am not even able to reach "installed". Executing content_script is far away.

Answers:

Answer

It appears you are having a few issues:

  • You are still mixing WebExtensions and Add-on SDK
  • The AddonManager onInstalled event for the installation of the add-on currently being installed happens prior to any code of the add-on being run. Thus, you will not receive this event for the add-on that is listening.
    • The 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.
    • You can get your 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.
  • You may be running into the issue that 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).
  • Loading a content script into an 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.
  • You have stated, "I think my main.js is not running right after installation." This belief may be as a result of not looking in the right place for console.log() output, the Browser Console. As such, I have the add-ons below automatically open the Browser Console.
    • The JavaScript file specified in the package.json key "main" runs when the add-on is installed. It is also run upon Firefox startup.
    • You can have an exports.main function which is automatically called after the code in your "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 [email protected] and [email protected].

The first add-on, [email protected], is loaded by running jpm run in its directory. The second add-on, [email protected], 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 [email protected]; then "remove" [email protected]; 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 [email protected] directory]
    [email protected]: In index.js
    [email protected]: In installAddonListener: Adding add-on listener
    [email protected]: Attaching content script A
    [email protected]: In exports.main: This add-on is being loaded for reason= install Object { loadReason: "install", staticArgs: undefined }
    [email protected]: Attaching content script B
    [email protected]: In exports.main: was passed callbacks= Object { print: print(), quit: function () }
[Note: no console.log output from within conentScriptA loading]
    [email protected]: received message from contentScriptA: Is Loaded
[Note: no console.log output from within conentScriptB loading]
    [email protected]: received message from contentScriptB: Is Loaded
[User action: Drag and drop .xpi for [email protected] onto Firefox]
    [email protected]: AddonManager Event: Installing addon ID: [email protected] ::needsRestart= false ::addon object: Object {  }
    [email protected]: AddonManager Event: Installed addon ID: [email protected] ::addon object: Object {  }
            [email protected]: In index.js
            [email protected]: In installAddonListener: Adding add-on listener
            [email protected]: Attaching content script A
            [email protected]: In exports.main: This add-on is being loaded for reason= install Object { loadReason: "install", staticArgs: undefined }
            [email protected]: Attaching content script B
            [email protected]: In exports.main: was passed callbacks= Object { print: print(_), quit: function () }
                [email protected]: In contentScriptA: Loaded
            [email protected]: received message from contentScriptA: Is Loaded
                [email protected]: In contentScriptB: Loaded
            [email protected]: received message from contentScriptB: Is Loaded
[User action: Navigate to about:addons]
[User action: Disable [email protected]]
    [email protected]: AddonManager Event: Disabling addon ID: [email protected] ::needsRestart= false ::addon object: Object {  }
            [email protected]: AddonManager Event: Disabling addon ID: [email protected] ::needsRestart= false ::addon object: Object {  }
            [email protected]: In exports.onUnload: This add-on is being unloaded for reason= disable
            [email protected]: In removeAddonListener: Removing add-on listener
    [email protected]: AddonManager Event: Disabled addon ID: [email protected] ::addon object: Object {  }
            [email protected]: AddonManager Event: Disabled addon ID: [email protected] ::addon object: Object {  }
    [email protected]: AddonManager Event: Uninstalling addon ID: [email protected] ::needsRestart= true ::addon object: Object {  }
[Get a warning in Browser Console because [email protected] 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: [email protected]:1756:1 < [email protected]:3075:5 < [email protected]:5041:7 < [email protected]:7484:5 < [email protected]:1548:13 < [email protected]:addons:1:1
[User action: Refresh about:addons page to remove "undo" posibility for [email protected]]
    [email protected]: Uninstalled addon ID: [email protected] ::addon object: Object {  }
[Get a warning in Browser Console because [email protected] 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: [email protected]:1756:1 < [email protected]:3075:5 < [email protected]:5096:7 < [email protected]:7484:5 < [email protected]:1740:5 < [email protected]:2733:5 < [email protected]:651:7 < [email protected]:184:3 < EventListener.handleEvent*@extensions.js:84:1
[User action: Close main Firefox browser window]
[User action: Close Firefox Browser Console window]
    (via dump):[email protected]: 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 [email protected]):

{
    "title": "Demo when code executes re install 1",
    "name": "installinfo1",
    "id": "[email protected]",
    "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 [email protected]):

{
    "title": "Demo when code executes re install 2",
    "name": "installinfo2",
    "id": "[email protected]",
    "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"
    ]
}

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.