Search code examples
javascriptgoogle-chrome-extensionmessagingchrome-native-messaging

Chrome extension message passing not working (background.js to content.js)


I am at my wits end with trying to figure out why I can't get message passing to work in my Chrome extension.

Keep in mind, I am absolutely brand new to javascript within the last month and I've been teaching myself with videos online (my coding background is exclusively java).

All I want is for my background script to be able to notify my content script of something happening and then subsequently have some code execute within the content script. As you can see below, I have set up my code identically to the documentation, and yet it's not working!

Here are the errors that I am getting when I load the extension:

Errors

manifest.json

{
   "content_scripts": [
      {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
      }
   ],

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

   "permissions": [
      "activeTab",
      "tabs",
      "storage"
   ],

   "manifest_version": 2,
   "name": "eSports YT Viewer",
   "version": "1.0.0",
   "description": "An eSports viewer for youtube"
}

background.js

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
   chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
      console.log(response.farewell);
   });
});

content.js

chrome.runtime.onMessage.addListener(
   function(request, sender, sendResponse) {
      console.log(sender.tab ?
      "from a content script:" + sender.tab.url :
      "from the extension");
      if (request.greeting == "hello") {
         sendResponse({farewell: "goodbye"});
      }
});

Any help is massively appreciated! Thanks.


Solution

  • Your current background script is wrong or rather doesn't do anything meaningful/useful. The background script runs in a separate hidden background page (or a service worker in ManifestV3). See also Accessing console and devtools of extension's background.js.

    A background script runs when a) the browser profile starts, b) when the extension is re-enabled/re-loaded, or c) installed/updated. So, your code immediately queries the tabs and sends a message to whatever tab is active, hence there's no guarantee it even contains a web page: there can be nothing (an empty new tab) or it can be an internal chrome:// page for browser settings or the tab may still be pending content script execution (see the note at the end of this answer).

    Note for ManifestV2 extensions: switching the background script to "persistent": false won't help you either. It will only make the script wake up on an API event or when the popup is opened, but you're not registering any API events nor have you declared a popup. You should switch to "persistent": false anyway but for a different reason: to have it running only on demand.

    Solution

    The usual purpose of the background script is to register handlers for chrome API events such as chrome.tabs.onUpdated or chrome.webNavigation.onHistoryStateUpdated and so on, otherwise it's highly probable you don't need the background script at all. In the event handler you can send a message to the tab using chrome.tabs.sendMessage or chrome.tabs.connect with this tab's id. A lot of people mistakenly send it to the active tab but usually it doesn't make sense as the event may have occurred in an inactive/backgrounded tab. Usually you should send it to the tab that generated the event, look for tab or tabId in the parameter passed to the handler, refer to the documentation on those events.

    Also note that the content scripts run after DOMContentLoaded by default so if you send a message while the page is still loading it'll fail. In that case you can either run them at document_start or use a different approach e.g. don't declare a content script but rather inject it programmatically. There's no universal answer, it depends on the use case.

    If you really need to send a message to the content script on extension startup/restart then you'll have to reload all matching tabs first via chrome.tabs.reload (so the content scripts will run automatically) or reinject the scripts explicitly.