167

I am trying to pass messages between content script and the extension

Here is what I have in content-script

chrome.runtime.sendMessage({type: "getUrls"}, function(response) {
  console.log(response)
});

And in the background script I have

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if (request.type == "getUrls"){
      getUrls(request, sender, sendResponse)
    }
});

function getUrls(request, sender, sendResponse){
  var resp = sendResponse;
  $.ajax({
    url: "http://localhost:3000/urls",
    method: 'GET',
    success: function(d){
      resp({urls: d})
    }
  });

}

Now if I send the response before the ajax call in the getUrls function, the response is sent successfully, but in the success method of the ajax call when I send the response it doesn't send it, when I go into debugging I can see that the port is null inside the code for sendResponse function.

Xan
  • 74,770
  • 16
  • 179
  • 206
Abid
  • 7,149
  • 9
  • 44
  • 51
  • Storing a reference to the sendResponse parameter is critical. Without it, the response object goes out of scope and cannot be called. Thanks for the code which hinted me towards fixing my problem! – TrickiDicki Jan 30 '18 at 12:01
  • maybe another solution is to wrap everything inside an async function with Promise and call await for the async methods? – Enrique Jan 01 '19 at 13:08

3 Answers3

380

From the documentation for chrome.runtime.onMessage.addListener:

If you want to asynchronously use sendResponse(), add return true; to the onMessage event handler.

So you just need to add return true; after the call to getUrls to indicate that you'll call the response function asynchronously.

Note this isn't mentioned on other documentation (for example the onMessage documentation) so it's possible developers miss this.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
rsanchez
  • 14,467
  • 1
  • 35
  • 46
  • this is correct, I added a way to automate this in my answer – Zig Mandel May 02 '14 at 17:58
  • 66
    +1 for this. It has saved me after wasting 2 days trying to debug this issue. I can't believe that this is not mentioned at all in the message passing guide at: https://developer.chrome.com/extensions/messaging – funforums Nov 13 '14 at 12:18
  • 8
    I've apparently had this issue before; came back to realize I had already upvoted this. This needs to be in bold in big `` and `` tags somewhere on the page. – Qix - MONICA WAS MISTREATED Aug 14 '15 at 16:54
  • 2
    @funforums FYI, this behavior is now documented in the [messaging](https://developer.chrome.com/extensions/messaging) documentation (the difference is here: https://codereview.chromium.org/1874133002/patch/80001/90002). – Rob W Jun 16 '16 at 22:56
  • 19
    I swear this is the most unintuitive API I've ever used. – michaelsnowden Jul 17 '16 at 08:06
  • I was breaking my head for almost 4 hours i.e. was able to return directly from Background to Popup BUT NOT from Content to Background to Popup. Tried all possible way, passing this, promise etc, but nothing worked out. Seriously , "I swear this is the most unintuitive API I've ever used." – Ravi Roshan Apr 02 '17 at 12:20
  • Adding my query so Google will index this because I know I'll need it again: "chrome extension send async response from background to contentscript" – ow3n Nov 22 '17 at 18:53
  • For the next person to hit such messaging problems, take a look at [@wranggle/rpc](https://github.com/wranggle/rpc). (Author here.) It helps different windows interact. – ferbs Mar 09 '19 at 00:40
  • 2
    Unfortunately this doesn't seem to work with async/await... – Diego Fortes Feb 07 '22 at 07:57
9

The accepted answer is correct, I just wanted to add sample code that simplifies this. The problem is that the API (in my view) is not well designed because it forces us developers to know if a particular message will be handled async or not. If you handle many different messages this becomes an impossible task because you never know if deep down some function a passed-in sendResponse will be called async or not. Consider this:

chrome.extension.onMessage.addListener(function (request, sender, sendResponseParam) {
if (request.method == "method1") {
    handleMethod1(sendResponse);
}

How can I know if deep down handleMethod1 the call will be async or not? How can someone that modifies handleMethod1 knows that it will break a caller by introducing something async?

My solution is this:

chrome.extension.onMessage.addListener(function (request, sender, sendResponseParam) {

    var responseStatus = { bCalled: false };

    function sendResponse(obj) {  //dummy wrapper to deal with exceptions and detect async
        try {
            sendResponseParam(obj);
        } catch (e) {
            //error handling
        }
        responseStatus.bCalled= true;
    }

    if (request.method == "method1") {
        handleMethod1(sendResponse);
    }
    else if (request.method == "method2") {
        handleMethod2(sendResponse);
    }
    ...

    if (!responseStatus.bCalled) { //if its set, the call wasn't async, else it is.
        return true;
    }

});

This automatically handles the return value, regardless of how you choose to handle the message. Note that this assumes that you never forget to call the response function. Also note that chromium could have automated this for us, I don't see why they didn't.

patridge
  • 26,385
  • 18
  • 89
  • 135
Zig Mandel
  • 19,571
  • 5
  • 26
  • 36
  • One issue is that sometimes you will not want to call the response function, and in those cases you should return *false*. If you don't, you are preventing Chrome from freeing up resources associated to the message. – rsanchez May 02 '14 at 18:24
  • yes, thats why I said to not forget calling the callback. That special case you menion can be handled by having a convention that the handler (handleMethod1 etc) return false to indicate the "no response" case (though Id rather just always make a response, even an empty one). This way the maintainability problem is only localized to those special "no return" cases. – Zig Mandel May 02 '14 at 20:01
  • 8
    Don't re-invent the wheel. The deprecated `chrome.extension.onRequest` / `chrome.exension.sendRequest` methods behaves exactly as you describe. These methods are deprecated because it turns out that many extension developers did NOT close the message port. The current API (requiring `return true`) is a better design, because failing hard is better than leaking silently. – Rob W Oct 31 '14 at 17:41
  • @RobW but whats the issue then? my answer prevents the dev from forgetting to return true. – Zig Mandel Aug 13 '15 at 14:42
  • @ZigMandel If you want to send a response, just use `return true;`. It does not prevent the port from being cleaned-up if the call is sync, while async calls are still being processed correctly. The code in this answer introduces unnecessary complexity for no apparent benefit. – Rob W Aug 13 '15 at 14:53
  • @RobW if it returns true always, seems to me it would keep the port open if the response was already sent (not async, from one of the handlers). why my code does is keep track if a handler already did a (non async) call thus it wont return true in that case. the nice part is i dont have to know which handler is async or not. – Zig Mandel Aug 13 '15 at 15:04
  • @ZigMandel My point is, you can safely use `return true` even if the call is synchronous, because after invoking the response callback, the port is already cleaned up. – Rob W Aug 13 '15 at 15:11
  • @RobW hmm o could swear I added this codex because my extension was leaking ports. ill re evaluate. – Zig Mandel Aug 13 '15 at 15:12
  • What does 'bCalled' mean? – mikemaccana Mar 24 '23 at 19:16
  • @mikemaccana its a way to detect if the callback was asynchronous, as that is important when returning from the function. – Zig Mandel Mar 25 '23 at 20:12
  • @ZigMandel what does the word ‘b’ mean? – mikemaccana Mar 26 '23 at 04:20
  • if you mean the "b" in bCalled, its just a naming convention for "boolean" – Zig Mandel Mar 27 '23 at 12:19
  • Ah it's Hungarian notation - got it, though that's not common to see in JS. The code might be clearer with a name like `isCalled` or `hasBeenCalled` etc. Just a suggestion. – mikemaccana Mar 27 '23 at 15:25
  • 1
    true. Im just too accostumed to hungarian, I actually have it as "fCalled" in my code but made it b for this post :) – Zig Mandel Mar 28 '23 at 16:05
2

You can use my library https://github.com/lawlietmester/webextension to make this work in both Chrome and FF with Firefox way without callbacks.

Your code will look like:

Browser.runtime.onMessage.addListener( request => new Promise( resolve => {
    if( !request || typeof request !== 'object' || request.type !== "getUrls" ) return;

    $.ajax({
        'url': "http://localhost:3000/urls",
        'method': 'GET'
    }).then( urls => { resolve({ urls }); });
}) );
Rustam
  • 1,875
  • 2
  • 16
  • 33