Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple chrome.runtime.onMessage-Listeners - disconnected port error

Tags:

I´m working on a chrome-extension which uses the indexedDB-Wrapper Dexie, several jQuery-Libraries and syncfusions eGrid to display and modify the database. I know this question was asked several times before, but now I discovered some weird behaviour.

My Setup:

  • background-script
  • content-script (injected into a website)
  • index.html (for displaying the Grid)
  • manifest.json

When I´m reloading my extension everything is fine. I´m injecting a scrollable list of names with my content-script. This also works when I´m reloading the tab. But when I opened the index.html before (popup.html in the official documentation) the sendResponse from background-script stops working after some time. Reloading the tab would not help then. I was searching for hours, found a lot of similiar cases and some entrys on googles bugtracker where they claimed this problem was already fixed. I also know that I have to use return true if I have async funtions like with indexedDB.

So to give you some more information, I will list my shortened scripts.

Content-Script

chrome.runtime.sendMessage({greeting: 'getNames'},function(response){
    $('.names-container').append(response.farewell);
});
chrome.runtime.sendMessage({greeting: 'getData'+name},function(name){
    chrome.runtime.sendMessage({greeting: 'delete'},function(response){
        console.log(response.farewell);
    });
    chrome.runtime.sendMessage({greeting: 'set'}, function(response) {
        console.log(response.farewell);
    });
});

Background-Script

chrome.runtime.onMessage.addListener(function(message,sender,sendResponse){
  if(message.greeting == 'getNames'){
    // Get array of names from idb and send it back
    idb.opendb().then(function(a){
      sendResponse(a);
    });
    return true;
  } else if(message.greeting.indexOf('getData') >= 0){
    // get more information for selected name
    idb.opendb().then(function(a){
      sendResponse(a);
    });
    return true;
  } else if(message.greeting == 'delete'){
    // further processing to delete some stuff
    idb.opendb().then(function(a){
      sendResponse(a);
    });
    return true;
  } else if(message.greeting == 'set'){
    // synchronous function
    // do something ...
    return false;
  } else {
    // Do something
  }
  //return true;
});

funtion indexhtml(){
  chrome.runtime.sendMessage(
    "notifications",
    function (response) {
      console.log(response);
    }
  );
}

Index.html

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
  if(request == 'notifications'){
    notifications();
  }

  sendResponse("Message received from background-page for "+request);
});

Manifest

{
    "manifest_version": 2,
    "name": "MyExtension",
    "description": "Some desc...",
    "version": "0.2",
    "background": {
        "scripts": ["js/jquery-2.1.4.min.js", "js/background.js", "...",
        "persistent": true
    },
    "default_locale": "en",
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "js": ["js/jquery-2.1.4.min.js"]
        },
        {
          "matches": ["https://domain.tld/*"],
          "js": ["js/jquery-2.1.4.min.js", "js/contentscript.js"],
          "css" : ["css/contentscript.css",
          "run_at": "document_start"
        }
    ],

    "icons": {
        "16": "images/icon-16x16.png",
        "48": "images/icon-48x48.png",
        "128": "images/icon-128x128.png"
    },
    "browser_action": {
        "default_icon": "images/icon-128x128.png"
    },
    "permissions": [
        "cookies", "tabs", "alarms", "webRequest", "webRequestBlocking", 
        "contextMenus", "<all_urls>", "storage"
    ],

    "content_security_policy": "script-src 'self' 'unsafe-eval'; 
    object-src 'self'"
}

It is difficult for me to debug this behaviour, since I can´t exactly know when the Error Attempting to use a disconnected port object appears. It looks like the port doesn´t gets closed correctly after the sendResponse is executed and only if the index.html was opened.

Finally the shortened Error-Message:

Error: Attempting to use a disconnected port object
    at Error (native)
    at PortImpl.postMessage (extensions::messaging:65:22)
    at Port.publicClass.(anonymous function) [as postMessage] (extensions::utils:94:26)
    at responseCallback (extensions::messaging:163:16)

The idb-Functions are customized and won´t work like I wrote them here, but I just want to show what I´m doing there. I´m using .then() to sendReponse when the result is available and return true; to wait for the result if async. I also searched every script for onMessage and sendMessage. The listed functions related to onMessage/sendMessage are all that I´m using, they are not used elsewhere. I´m using a timer for indexhtml() to save an array containing the notifications in a specified interval and then executing a function at index.html to reload the notifications.

The index.html Tab gets opened via browserAction:

chrome.browserAction.onClicked.addListener(function(tab){
    chrome.tabs.create({url: chrome.extension.getURL('index.html')});
});
like image 720
Pandora Avatar asked Mar 22 '16 21:03

Pandora


1 Answers

Okay, I found the answer by myself after adding more and more debugging. The problem here is, that I am using multiple(2) onMessage-Listeners. There is no specific listener to only listen for messages send to my content-page (index.html). Everytime I opened the index.html the additional onMessage-Listener would then be called first if sendMessage were used within my content-script. I wasn´t aware that this onMessage-Listener, embedded into index.html, is called everytime too. I then changed the destination for sendMessage in my content-script - with the same result. There is also a note on googles documentation about this topic:

Note: If multiple pages are listening for onMessage events, only the first to call sendResponse() for a particular event will succeed in sending the response. All other responses to that event will be ignored.

Link here

The second problem was, that I used the sendResponse outside of the if clause and this way it got called everytime. So in the end I only had to put the sendResponse inside the if-clause so it only gets called if a specified message from background-page got called via sendMessage.

Background-Script

  • get the tab.id by searching for the chrome-extension://extid/index.html
  • send the message to index.html only
chrome.windows.getAll({populate:true},function(windows){
  windows.forEach(function(window){
    window.tabs.forEach(function(tab){
      if(tab.url.indexOf(chrome.extension.getURL('index.html')) >= 0){
         chrome.tabs.sendMessage(tab.id,{ greeting: "notifications"}, function(response){
           console.log(response);
         });
      }
    });
  });
});

Index.html

chrome.runtime.onMessage.addListener(function(message, sender, sendResponse){
        if(message.greeting == "notifications-trades"){
            Notifications();
            sendResponse("Message received from bg "+message.greeting);
            //if async add return true;
        } else {
            //Do something
        }
});
like image 131
Pandora Avatar answered Oct 12 '22 10:10

Pandora