87

I'm developing an extension in Chrome, and there's a problem. In my inject.js, I make a request like:

chrome.extension.sendRequest({command:'skip'},callback)

and in my `background.js I simply add a request Listener like:

chrome.extension.onrequest.addListener(function(req,sender,res){console.log("procession"})

But there's an error:

Port error: Could not establish connection. Receiving end does not exist

It seems a bug in chrome? PS:
part of my manifest.json

"background": {
    "scripts": ["background.js"]
},
"content_scripts": [
  {
    "matches": ["< all_urls >"], 
    "js": ["inject.js"]
  }
],

I'm in Chromium 17, and I tried reloading the extension, reopening the browser... nothing happened
some one get some ideas?

yunzen
  • 32,854
  • 11
  • 73
  • 106
Lanston
  • 11,354
  • 8
  • 32
  • 37
  • 2
    I had a similar issue. Although this may not help, make sure that the 'background.js' is actually loading. My issue was that I used 'background.js' instead of 'scripts/background.js' – Matthew May 11 '17 at 01:19
  • For sendMessage case, it happens because you might have not setup ( or mistakenly removed ) the corresponding onMessage listener in the background script. https://stackoverflow.com/a/54945389/1469614. – bhavya_w Mar 01 '19 at 13:12
  • Question is quite outdated now, though I think it's worth pointing out that in author's script, there is "onrequest" instead of "onRequest" and therefore there is no receiving end recognized. – Emil Kucharczyk Sep 07 '22 at 20:41

22 Answers22

35

Some of the other answers here have good debugging advice or small pieces of the puzzle, however if you want to inject into (3rd party) web pages like me, then no answers here listed the correct components.

There are 4 steps:

  • an external background listener,
  • the foreground message sender,
  • injecting extension id for the message sender
  • and a rule in the manifest to link everything together.

The example below should be everything you need to allow you to inject a js that sends messages from any page on google.com to your own extension

First in the manifest.json you need to add the messaging rules which are described here:

"externally_connectable": {
    "matches": ["*://*.google.com/*"]
}

Then in your background script or page, you need an external listener (Not the regular chrome.runtime.onMessage.addListener which has been mentioned in msot answers), this is described here:

chrome.runtime.onMessageExternal.addListener( (request, sender, sendResponse) => {
    console.log("Received message from " + sender + ": ", request);
    sendResponse({ received: true }); //respond however you like
});

Once you have these parts, you can use a message sender as you usually do, but with the extension id as first parameter:

chrome.runtime.sendMessage(myExtId, { /* whatever you want to send goes here */ },
    response => {
         /* handle the response from background here */
    }
);

If you don't know how to get the external id I used as first param, you can inject your extension id like below. This is required because chrome.runtime.id and @@extension_id both fail to work in injected scripts:

//create a script tag to inject, then set a variable with the id in that script
let idScript = document.createElement("script");
idScript.setAttribute("type", "application/javascript");
idScript.textContent = 'var myExtId = "' + chrome.runtime.id +'";';
let parent = ( document.head || document.documentElement );
parent.insertBefore( idScript, parent.firstChild );

//inject and run your other script here

idScript.remove(); //then cleanup 

Because we set it as a var, the other script can directly access the value now

Nick Cardoso
  • 20,807
  • 14
  • 73
  • 124
  • 2
    Thanks for this. This should probably be the accepted answer. – Joao Ventura Jun 06 '20 at 10:16
  • Does this mean you have to "whitelist" all websites for which you want to allow injection<>bg-script communication? Or is there something I'm missing? – ko0stik Sep 04 '20 at 12:55
  • You seems to have confused the terminology - You're talking about something a bit different. For this you need to add each 3rd party domain that you want to be able to send messages directly to your extension – Nick Cardoso Sep 04 '20 at 13:59
  • 2
    Thanks man. Was trying to figure this out for the last few hours and externally_connectable & onMessageExternal did the trick! – 010011100101 May 25 '21 at 10:06
  • 2
    Lifesaver — onMessageExternal was exactly the thing I was looking for. Thank you! – Jon Jan 10 '23 at 03:22
  • onMessageExternal is designed for [messages sent from another extension/app](https://developer.chrome.com/docs/extensions/reference/runtime/#event-onMessageExternal:~:text=Fired%20when%20a%20message%20is%20sent%20from%20another%20extension/app%20(by%20runtime.sendMessage).%20Cannot%20be%20used%20in%20a%20content%20script.), but the issue in question is caused in the same extension. Why/how does onMessageExternal tackle the problem? – Lerner Zhang Feb 17 '23 at 12:09
  • The extension won't be open for external messages coming from other extensions this way ? – dsenese May 08 '23 at 02:31
27

This isn't always the cause, but if you have an error in your background.js, you should check this link:

Inspect views: _generated_background_page.html

in the Extensions page, which will show you any JavaScript errors.

This was preventing my connections from getting established.

Neil
  • 24,551
  • 15
  • 60
  • 81
  • 3
    Looks like when the there is an error in the background.js the call will fail. – Ibu Jan 16 '13 at 18:56
15

The problem could be that sendRequest() and onRequest have been deprecated and replaced with sendMessage() and onMessage. Since a recent Chrome 20 update they seem to be gone completely.

The official documentation on Message Passing doesn't even mention sendRequest() anymore.

Here is a link which documents the change a little bit: http://codereview.chromium.org/9965005/

António Almeida
  • 9,620
  • 8
  • 59
  • 66
Jannes Meyer
  • 1,228
  • 1
  • 9
  • 18
13

I found myself having the same issue as you describe here. The solution I found that works for me is to use a backgroundpage instead of a background script, like so:

"background_page": "src/background.html",
  // maybe this seems to work instead of background { scripts [] }

  /* this gives a Port error: Could not ...
  "background": {
  "scripts": ["src/background.js"]
  },
  */

I hope this works for you too.

Nakilon
  • 34,866
  • 14
  • 107
  • 142
Jasper
  • 226
  • 2
  • 2
  • I had the same issue and once adding a background_page it worked. The page is empty but it looks like the API requires one to use the "port". – John Feb 13 '12 at 18:05
  • 1
    I'm trying to upgrade to Manifest v2, and unfortunately the "background_page" setting throws a warning when loading an unpacked extension and isn't listed on the Manfest page (http://code.google.com/chrome/extensions/manifest.html). Still trying to track a working fix in the new format. – IBBoard Jul 04 '12 at 19:23
  • 3
    @IBBoard, please see my answer. I think the problem is that sendRequest() has been deprecated in favor of sendMessage() which should work in Manifest v2 extensions. – Jannes Meyer Jul 20 '12 at 21:21
  • 61
    You can't use a `background_page` any more with Manifest >= version 2.0, so this answer no longer works. – Neil Jul 27 '12 at 05:19
  • 11
    @Neil the feature isn't gone, it's just defined differently `"background" : { "page" : "html/background.html" }` as per http://developer.chrome.com/extensions/background_pages.html – Gus Jan 11 '14 at 01:12
  • 1
    The "`background.scripts`" key cannot be used with manifest version 3. Use the "`background.service_worker`" key instead. `"background":{ "service_worker": background.js"}` – Kannan Suresh Aug 22 '22 at 08:30
6

An HTML background page didn't work for me in Chrome 20.0.1132.57 m on Windows 7 with the same error:

Port error: Could not establish connection. Receiving end does not exist. miscellaneous_bindings:232

I tried using a background.js script with the following content:

chrome.extension.onConnect.addListener(function(port) {
    port.onMessage.addListener(function(msg) {
        // May be empty.
    });
});

That solved the immediate onDisconnect in my content script:

port = chrome.extension.connect();
port.onDisconnect.addListener(function (event) {
    // Happened immediately before adding the proper backend setup.
    // With proper backend setup, allows to determine the extension being disabled or unloaded.
});

The correct code came from Chromium Extensions messaging example: http://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/extensions/docs/examples/api/messaging/timer/page.js?view=markup

The example also contains the second part that serves as a backend for sendRequest, but I haven't tested it myself:

chrome.extension.onRequest.addListener(
    function(request, sender, sendResponse) {
        // Don't know if it may be empty or a sendResponse call is required.
    });
sompylasar
  • 924
  • 11
  • 15
  • 2
    Can you elaborate on which file each block of code goes in, in which order? I'm still getting the same error... – Neil Jul 27 '12 at 05:20
5

Confronting with the same issue now.

//Here is my former background.js:

chrome.runtime.onInstalled.addListener(function () {
  //some other code here
  chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
    if (message.url) {
        chrome.downloads.download({
            url: message.url,
            conflictAction: 'uniquify',
            saveAs: false
        });
    }
});});//(Sorry, I tried but failed to put the last bracket on next line)

You see, the chrome.runtime.onMessage.addListener is fired when the event onInstalled happpens, and when I get it out of this scope, and make my code like this:

chrome.runtime.onInstalled.addListener(function () {
  //some other code here
});  
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
    if (message.url) {
        chrome.downloads.download({
            url: message.url,
            conflictAction: 'uniquify',
            saveAs: false
        });
    }
});

It works well now. Hope to be helpful.

Xiao Teng
  • 59
  • 1
  • 2
  • This should be listed as a gotcha in Google's Chrome extension documentation. I had my onMessage listener inside onInstall like you did, and it worked fine until the background script became inactive. Subsequent messages would activate the background script but not be handled, resulting in the same error as OP. – Chris Parton Jun 17 '19 at 01:02
4

I was seeing this error using manifest_version: 2 and chrome.runtime.sendMessage. I am connecting from a web page to the extension instead of within the extension.

The solution was to make sure I had the correct values in the externally_connectable.matches array in manifest.json. You need to list the URLs that you want to be able to connect to the extension. See https://developer.chrome.com/extensions/manifest/externally_connectable#without-externally-connectable.

salsbury
  • 2,777
  • 1
  • 19
  • 22
  • 1
    Yep that did it, had to add `https://localhost:3000` there for local dev. Avoided `Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.` Also make sure you're requesting the right extension ID... – rogerdpack Dec 31 '19 at 05:40
3

I'm using sendMessage and onMessage for communication too, and in my workflow I first send a message from injected.js to my background.js and I also got this error:

Port error: Could not establish connection. Receiving end does not exist.

So I decided to deal with using the responses functionalities ( like ACK ), and if background doesn't respond I keep trying with a setTimeout like so.

background.js

...
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
...
//some code

sendResponse({status: 'everything ok'})
return true;    
});

injected.js

var myInjectedFunctionality = function() {

    chrome.extension.sendMessage({method: "Steroids:loadScripts"}, function(res) {
        if(res && res.status) {
            } else {
                setTimeout(myInjectedFunctionality, 3000);
            }
    });
};
myInjectedFunctionality();

My code is now running properly so I think that the explanation is easy to view. Chrome don't prepare Background.js and connection stuffs when it inject your code in the pages where you want to, and so makes nobody listen for your sent message, so if no one is listening, just keep trying like I do.

Dhia Djobbi
  • 1,176
  • 2
  • 15
  • 35
robertovg
  • 1,018
  • 11
  • 16
2

try to inject some contect scripts into a tab which url is chrome://* or https://chrome.google.com/* , when connect these tabs in backgroundpage ,

for example

chrome.tabs.connect(tab.id).postMessage(msg)

then will throw

Port error: Could not establish connection. Receiving end does not exist.

these page not allow inject content scripts, and the connection will disconnect immediately .

so, check the url and not connect these tabs ,then the exception is not thrown

Nelson
  • 49,283
  • 8
  • 68
  • 81
2

After some time spent on investigation I found the problem in my case.

I'm also getting:

Port error: Could not establish connection. Receiving end does not exist. 

Before explaining, I want to say that I'm using sendMessage and onMessage for communication.

For me this error appear when I'm sending a message from the background page to one tab where my content script is running.

My extension runs only on tabs where youtube is open. So when I'm changing some preferences I'm looking to see what tabs I have open and send a message to update updated preference.

In a normal case it works fine, but if I have n (n >= 1) tabs with youtube open. I click in "Reload" button for extension, I change something ... youtube tabs are not refreshed and they lost message listener so I'm getting this error.

It seems that is pretty normal.

If I refresh youtube tabs after extension reload I don't get this error.

I found one solution, it may not apply for all cases:

When I had this issue my code was:

chrome.tabs.sendMessage(tabId, {proprName: proprName, newValue: newValue}, function(response) {});

Now my code is:

chrome.tabs.sendMessage(tabId, {proprName: proprName, newValue: newValue});

For my I didn't needed response callback and because that was not responding I had this error.

pauldcomanici
  • 597
  • 5
  • 15
2

Check the latest manuals: http://developer.chrome.com/extensions/messaging.html

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.

Close your tabs, leave only one page, and check. In my case this was the issue.

Andrey Uglev
  • 117
  • 7
2

I met up with this problem because I added a listener in background.js, and when invoked it would send a message to the popup page for view state update. While sometimes the listener is invoked, the popup page does not indeed exist. I check to make sure the popup page exists before sending a message and avoid this problem.

W.Leto
  • 1,013
  • 11
  • 13
2

Following up on Nick Cardosos's response, with the advent of module scripts, v3 manifest, etc...

Enable the background to be modular (manifest.json):

  "background": {
    "service_worker": "background/index.mjs",
    "type": "module"
  },

Enable access to content modules (manifest.json):

  "web_accessible_resources": [
    {
      "resources": [
        "manifest.json",
        "src/index.mjs",
      ],
      ...
    }
  ],

Define the root content module:

  "content_scripts": [
    {
      "js": [ "extension.js" ],
      ...
    }
  ],

Inject a module into the client (from extension.js):

document.querySelector('head').appendChild(
  Object.assign(document.createElement('script'), {
    type: 'module',
    src: chrome.runtime.getURL('./src/index.mjs'),
  }),
);

From the module, get the extension ID (src/index.mjs):

const extensionId = new URL(import.meta.url).hostname

Send a message (./src/index.mjs or any other module):

export const sendMessage = (message) => new Promise((resolve, reject) => {
  try {
    chrome.runtime.sendMessage(
      extensionId,
      message,
      (response) => {
        if (response?.isError === true) {
          reject(Object.assign(
            new Error(response.message),
            response,
          ));
        } else {
          resolve(response);
        }
      },
    );
  } catch (e) {
    reject(e);
  }
});

/* ... */

const responsePayload = await sendMessage(messagePayload);

Receive a message and send a response (./background/index.mjs):

export const onMessageExternal = (listener) => {
  chrome.runtime.onMessageExternal.addListener(
    async (messagePayload, sender, sendResponse) => {
      try {
        const response = await listener(messagePayload, sender);
        sendResponse(response);
      } catch (e) {
        const { message, stack, name } = e;
        // How you wrap errors is up to you, but the client
        // and server must agree on the contract.
        sendResponse({
          isError: true,
          message,
          stack,
          name,
        });
      }
    },
  );
};

/* ... */

onMessageExternal(async (messagePayload, sender) => {
  console.log(messagePayload, sender);
  return handleMessage(messagePayload);
});

It's important to note that if you do not call sendResponse, the client's promise will never resolve. That's why I wrapped sendMessage and onMessageExternal so that they'd deal appropriately.

Fordi
  • 2,798
  • 25
  • 20
2

Although this question is old, the same error message is thrown in manifest V3 when trying to send messages to an injected script without the proper url permissions.

I fixed this by adding host_permissions to manifest.json

Example:

"host_permissions": ["*://*/*"],
kano
  • 5,626
  • 3
  • 33
  • 48
Atav32
  • 1,788
  • 3
  • 23
  • 33
1

I place chrome.runtime.sendMessage and chrome.runtime.onMessage.addEventListener in content script intowindow.onload or self-execution function and it's work. And in manifest I use

"background": { "scripts": ["background.js"], "persistent": false },

manifest_version: 2. Hope it helps.

Dmytro
  • 21
  • 1
1

If the message handling is in a pop-up window or html page that is not always visible, before I send any message, I check if the pop-up is visible, like so:

function isPopupVisible() { //required for firefox before sending any message ore we get the stupid message 'receiveing end does not exist'
    var views = chrome.extension.getViews({ type: "popup" }); //https://stackoverflow.com/questions/8920953/how-determine-if-the-popup-page-is-open-or-not
    if (views.length > 0) {
        console.log("Popup is visible");
        return true;
    }
    return false;
}
john k
  • 6,268
  • 4
  • 55
  • 59
1

I encounter the similar problem, my solution is:

My problem is:

I currently have a content.js and a background.js.

  1. I added a method to both chrome.tabs.onActivated.addListener and chrome.tabs.onUpdated.addListener of background.js. If they are triggered, I will send an instruction to content.js to let it scroll the page The bar is set to a specific scrollY.
  2. In content.js, I registered chrome.runtime.onMessage.addListener to accept this command.
  3. My page will be set according to the passed scrollY value.

The problem I'm having is that whenever I refresh the user page, background.js throws an Error: Could not establish connection. Receiving end does not exist. error. But this error doesn't always happen, and when it does, the message is delivered correctly.

I found I sendMessage in the Listener chrome.tabs.onUpdated.addListener, It will trigger 3 times, the changeInfo.status has 3 value: loading undefined complete, so my sendMessage be invoke 3 times. I add the code like this:

chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
  // console.log("chrome.tabs.onUpdated.callback = " + tab.url);
  if (changeInfo.status === 'complete') {
    if (easyReadTools.isSupportedScheme(tab.url)) {
      autoRecordCurrentPage(tab);
      setTabScroll(tab);
    }
  }
});

if (changeInfo.status === 'complete') is my key problem.

Eric Kung
  • 43
  • 1
  • 1
  • 10
0

For manifest 2.0 and sendMessage case its fairly straight-forward:

This happens if you try to use sendMessage inside the popup and there's no listener setup on background script end or the listener has somehow been removed.

By listener I mean - chrome.runtime.onMessage.addListener.

bhavya_w
  • 9,186
  • 9
  • 29
  • 38
0

For me, initially I didn't know why after few mins (3-5 mins), it'll throw thousands of errors in my console (of host page) saying the same message (see image below), browser got hanged, it was tough on finding the root cause, it just suddenly happened, even I haven't opened my devtools panel, like just load the host page, and open its console and leave it there for few mins and the error shows.

The issue happened for both Chrome and Edge (haven't tested with Firefox), both development and production build, both local unpacked and the one published on store

Finally I figured out that in my content-script I have a setInterval which will call chrome.runtime.sendMessage to send some message to background script, and somehow the background script is unreachable, I guess after few mins of doing nothing the background script becomes inactive??? not sure :)

Also I'm not sure why it runs thousands of times, all at once, even my setInterval is 5s

What I tried to reproduce: in my background script, I comment chrome.runtime.onMessage.addListener -> re-run and immediately I can reproduce the issue.

Solution: simply wrap chrome.runtime.sendMessage of content-script in a try/catch and the issue is gone

enter image description here

enter image description here

Duc Trung Mai
  • 2,141
  • 1
  • 24
  • 23
0

I encountered this when writing a Manifest v3 extension in Chrome 115. It turns out that calling chrome.runtime.sendMessage only works from a content script or a popup, not from a function running in the service worker. I wanted to share code between a click handler in my popup and a function called from a chrome.commands.onCommand.addListener callback, which runs in the service worker. I ultimately worked around this by adding a parameter inServiceWorker to the function containing the common code, eventHandler - this parameter is set to true when called from the command handler and false when called from the click handler:

async function eventHandler(tab, inServiceWorker)

Then, I wrote an async function messageHandler(message, sender, sendResponse), which I call from the onMessage listener like this:

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  (async () => {
    await messageHandler(message, sender, sendResponse);
  })();
  return true;
});

In eventHandler I write:

let response;
if (inServiceWorker) {
  response = await messageHandler(message);
} else {
  response = await chrome.runtime.sendMessage(message);
}

and in messageHandler, I rely on the fact that sendMessage is undefined when called via await messageHandler(message) in eventHandler but defined when called from the listener:

sendMessage && sendMessage(response);
return response;

We need to return the response so that await messageHandler(message) works.

telotortium
  • 3,383
  • 2
  • 23
  • 25
-1

If you get the problem, mostly because you are referring the outdated document, update it!

Visit: chrome extension: messaging.html

Franky Yang
  • 221
  • 2
  • 6
-1

I was seeing this problem, where my content script wasn't loading, so posting a message was never happening.

The problem turned out to be that I had the background script inspector open and was just pushing Cmd+R (refresh), but there was a bug in my manifest.json. When I actually went to the extensions page and reloaded that page, I got the alert showing the manifest error.

Basically, I saw this because my content scripts never loaded in the first place and I thought I was refreshing my manifest, but I wasn't.

Will
  • 2,604
  • 2
  • 18
  • 14