Search code examples
javascriptfirefoxfirefox-addonfirefox-addon-sdkmessage-passing

Firefox addon(JPM) - How to pass message from panel's script to content script?


Im trying to send a click event from panels script(popup.js) to content script(content.js) hers the code that i've tried.and the desired output isnt printing on the console.

popup.html

<button id="start-btn" onclick="myFunction()">Clip</button>

popup.js

function myFunction() {
  addon.port.emit('message', 'hello world');
}

content.js

self.port.on("message", function(text) {
  console.log(text);
});

index.js(main)

var panels = require("sdk/panel");
var self = require("sdk/self");
var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");

pageMod.PageMod({
  include: "*",
  contentScriptWhen: 'ready',
  contentScriptFile:
  [data.url("js/jquery-3.1.0.min.js"),
  data.url("js/content.js")]
});

var panel = panels.Panel({
 contentURL: self.data.url("popup.html"),
 onHide: handleHide
});

Solution

  • In order to pass a message from an sdk/panel to a content script, you have to relay it through your background script. So, the communication looks like panel script ⟷ background script ⟷ content script.

    This is complicated by the fact that you, potentially, have many different content scripts. You could even have multiple content scripts per tab. Thus, you have to track your content scripts as they attach and keep them organized, at least by tab. This is so you can end up relaying the message to the correct content script.

    The panel, because it is a user interface, will normally want to send a message to the active tab. On the other hand, it might want to send a message to a specific tab, or a specific script within a specific tab. You will need to determine what level of granularity you need for your add-on.

    The following script will load, via Page-Mod, a content script into every tab (as per the question the included URLs is '*'). Each content script is tracked by tab. No provision is made for having multiple content scripts per tab. Tab events should really be listened to in order to invalidate entries in the list of content scripts. However, that is not done in this example. The panel will be displayed upon clicking an ActionButton. When the button in the panel is clicked a relay message is sent by the panel to the background script which then decodes the relay message and emits it to the appropriate content script.

    I have imposed a format for the relay messages. That format is:

    {
         target: {
            type:targetType, //Currently, the only valid targetType is 'content'.
            id:targetId //A tab ID, or 'activeTab'
        },
        emitType:emitType, //The type of emit message that will be sent.
        data:message //The contents of the relayed message.
    }
    

    The code:

    index.js:

    var panels = require("sdk/panel");
    var self = require("sdk/self");
    var data = require("sdk/self").data;
    var pageMod = require("sdk/page-mod");
    var tabs = require("sdk/tabs");
    
    //Open the Browser Console
    var utils = require('sdk/window/utils');
    activeWin = utils.getMostRecentBrowserWindow();
    activeWin.document.getElementById('menu_browserConsole').doCommand();
    
    var workers={};
    
    //PageMod
    pageMod.PageMod({
        include: "*",
        contentScriptWhen: 'ready',
        contentScriptFile: [
            //data.url("js/jquery-3.1.0.min.js"),
            data.url("js/content.js")
        ],
        onAttach: attachWorker
    });
    
    function attachWorker(worker){
        if(!workers.hasOwnProperty(worker.tab.id)){
            //Have not previously had this tab
            workers[worker.tab.id]={};
        }
        //This does not account for the possibility of having multiple workers
        //  per tab at one time.
        //Remember the worker
        console.log('index.js: Attached worker on tab=',worker.tab.id);
        workers[worker.tab.id].worker = worker;
    }
    
    
    //Panel
    var panel = panels.Panel({
        contentURL: self.data.url("popup.html"),
        onHide: handleHide
    });
    
    panel.port.on('message',receiveMessage);
    panel.port.on('relay',receiveRelay);
    
    function handleHide(){
    }
    
    function receiveMessage(message){
        console.log('index.js: received message:',message);
    }
    
    function receiveRelay(data){
        console.log('index.js: received relay:',data);
        let emitPort;
        let targetId;
        if(typeof data !== 'object'){
            console.log('index.js: received relay: data was not an object');
            return;
        }//else
        if(!data.hasOwnProperty('target')){
            console.log('index.js: received relay: No target specified');
            return;
        }//else
        if(data.target.type === 'content'){
            if(data.target.id && data.target.id === 'activeTab'){
                targetId = tabs.activeTab.id;
            }else{
                targetId = data.target.id;
            }
            console.log('index.js: received relay: target ID: ', targetId);
            if(!workers.hasOwnProperty(targetId) || !workers[targetId].worker){
                console.log('index.js: received relay: No target worker available for ID: '
                            , targetId);
                return;
            }//else
            emitPort = workers[targetId].worker.port;
        }else{
            //None  yet defined
            console.log('index.js: received relay: Target type not understood. Type: '
                        , data.target.type);
            return;
        }
        console.log('index.js: received relay: emitType=', data.emitType, ' ::data='
                    , data.data);
        emitPort.emit(data.emitType,data.data);
    }
    
    
    //Action button
    var ui = require("sdk/ui");
    
    var action_button = ui.ActionButton({
        id: "panel-show-button",
        label: "Show panel",
        icon: "./icon.png",
        onClick: function(state) {
            panel.show();
        }
    });
    

    data/js/content.js:

    console.log('In  content.js');
    self.port.on("message", function(text) {
      console.log('content.js: received message:',text);
    });
    

    data/popup.js:

    function myFunction() {
        console.log('popup.js: Button clicked. Sending relayed message');
        //addon.port.emit('message', 'hello world');
        sendRelay('content','activeTab','message','Button clicked in panel');
    }
    
    function sendRelay(targetType,targetId,emitType,message) {
        addon.port.emit('relay', {
            target: {
                type:targetType, //Currently, the only valid targetType is 'content'.
                id:targetId //A tab ID, or 'activeTab'
            },
            emitType:emitType, //The type of emit message that will be sent.
            data:message //The contents of the relayed message.
        });
    }
    

    data/popup.html:

    <html>
        <head>
            <meta charset='utf-8'>
            <script type="text/javascript" src="popup.js"></script>
        </head>
        <body>
            <button id="start-btn" onclick="myFunction()">Clip</button>
        </body>
    </html>
    

    package.json:

    {
        "title": "Demo passing a message panel-background-content",
        "name": "relaymessge",
        "id": "relaymessage@ex",
        "version": "0.0.1",
        "description": "Demonstrate passing a message from a panel -> background script -> content script.",
        "main": "index.js",
        "author": "Makyen, vivek",
        "engines": {
            "firefox": ">=38.0a1",
            "fennec": ">=38.0a1"
        },
        "keywords": [
            "jetpack"
        ]
    }