0

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.

Makyen
  • 31,849
  • 12
  • 86
  • 121
Priyanka
  • 806
  • 1
  • 9
  • 21
  • You appear to be confusing the various different Mozilla add-on types (specifically the four different types of extensions. I would suggest you read [Docs:Introduction to Firefox add-ons:Introduction](http://stackoverflow.com/documentation/firefox-addon/3235/introduction-to-firefox-add-ons/13574/introduction) and [Add-ons on MDN](https://developer.mozilla.org/en-US/Add-ons). Hopefully those will help clear up some confusion. – Makyen Aug 28 '16 at 16:22
  • You will need to make a clear choice as to if you are going to write an extension that is based on [WebExtensions](https://developer.mozilla.org/en-US/Add-ons/WebExtensions), or the [Add-on SDK](https://developer.mozilla.org/en-US/Add-ons/SDK). Work through the examples/tour/tutorials for the one you pick. For neither type do you need to do anything special to have your background script run immediately upon installation. If you did, how would the code you are using to add the listener to install events run to set up the listener? That code is already running when it is adding the listener. – Makyen Aug 28 '16 at 16:26
  • Thanks @Makyen for your response and I am developing Add-on SDK and I need to make my add-on applicable from the moment I install it, but it is not happing yet, It works only after page reload. – Priyanka Aug 28 '16 at 16:30
  • Please [edit] the question to add your [*package.json*](https://developer.mozilla.org/en-US/Add-ons/SDK/Tools/package_json) file. What ever background script file is specified in the `main` key/property will automatically execute when the add-on is installed. – Makyen Aug 28 '16 at 16:39
  • I added it, and in my package.json, main includes 'main.js', but still its not executing, as when I reload the page after installation only then my add-on works. am I going wrong somewhere? – Priyanka Aug 28 '16 at 17:45
  • Are you trying to listen for the installation of *any* add-on? Execute code *only* when your add-on is installed? Execute code when your add-on runs (each time Firefox starts *and* when installed)? – Makyen Aug 28 '16 at 18:00
  • yea I am executing code only when add-on is installed, I just drag drop my xpi file and install it, I click on a button to post a message and in my add-on I have eventListener, but it is not listening till I reload the page.I just need to add something to make it work exactly after installation, but how should I do it? like in chrome we have this in background.js: chrome.runtime.onInstalled.addListener(function(details) {chrome.tabs.executeScript(tab.id, {file: "content_script.js"}); – Priyanka Aug 29 '16 at 05:35
  • I think my main.js is not running right after installation.I cannot get consoles which are written there , before I reload the page , if it will execute just after installation without page reload , I can make content_script run easily.but why is this happening? as MDN docs says it will run just after addon is loaded :( – Priyanka Aug 29 '16 at 06:10
  • 1
    Also `onInstalled` is not yet supported - https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Chrome_incompatibilities#runtime – Noitidart Aug 30 '16 at 02:33
  • 1
    @Noitidart, While, in WebExtensions, [`runtime.onInstalled`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Runtime/onInstalled) is not yet supported, the OP is also attempting to use the `AddonManager` `onInstalled` event. The `AddonManager` `onInstalled` event is supported. Being a bit more specific in the text of your comment which one you are referring to when you say it is not yet supported (rather than just a link at the end) might have been helpful. The, OP is already confused between WebExtensions and the Add-on SDK, not being specific does not help to clear that up. – Makyen Aug 30 '16 at 14:59
  • Ah yes my bad, I did like a drive by comment I shouldn't have. Thanks for calling it out, your comment in reply to my comment is important to help the op understand. @Makyen – Noitidart Aug 30 '16 at 16:51

1 Answers1

2

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 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"
    ]
}
Makyen
  • 31,849
  • 12
  • 86
  • 121
  • Deep deep deep! Thank you brother! – Noitidart Aug 30 '16 at 16:51
  • Hey @Makyen that was really really nice, you explained in such a detail.. a big thanks to you for your effort. It helped me to understand what I have to do. Thanks again – Priyanka Aug 30 '16 at 17:46