Search code examples
google-chromegoogle-chrome-extension

Chrome Extension: Message Passing from Content to Background Script


I have already searched and tried different acceptable answers from SO to this problem but none of them are working in my case. I'm pretty sure I'm not understanding something. I just started this chrome extension journey 24 hours ago so I admit I have so many things to learn.

Nevertheless, I'm trying to create a downloader for a specific website. I have three javascript files:

  1. "script_injection.js", which is added in the content_scripts., is a pure javascript that calls on "injection_ui.js" As I understand, I cannot use chrome APIS directly on the content_scripts so I created another JS file and just call it.
  2. "injection_ui.js", which is part of the "web_accessible_resources", adds the button to all images and bind a functionality for message passing on click.
  3. background.js, which is in the background, which listens for a message sent by a button.

manifest.json


{
    "name": "Dummy Extension",
    "version": "1.0",
    "manifest_version": 2,

    "description": "This is a dummy extension.",
    "browser_action": {
        "default_title": "Download Images from Anywhere",
        "default_popup": "popup.html"
    },

    "content_scripts" : [
        {  
            "matches": ["https://dummywebsite.com/*"],
            "js": ["script_injection.js"],
            "css": ["style.css"]
        }
    ],

    "permissions": [
        "storage",
        "downloads",
        "activeTab",
        "declarativeContent"
    ],

    "web_accessible_resources": ["injection_ui.js"],

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

script_injection.js


var s = document.createElement('script');
s.src = chrome.runtime.getURL('background.js');
s.onload = function() {
    this.remove();
};
(document.head || document.documentElement).appendChild(s);

injection_ui.js

$(document).ready(function(){
    
    $(".image-thumbnail").each(function(){
        var src = $(this).attr("src");
        $(this).append("<a class='DummyAddedButton' download='"+ src +"'>Download</button>");
    }); 

    $(".DummyAddedButton").off().on("click", function(){
        var source = $(this).attr("src");
        
        chrome.runtime.sendMessage("mlbcmjpahokfgkghabmfjgmnafffphpd", source, function(){
            console.log("sending success: " + source);
        });
    });
});

background.js


chrome.runtime.onMessage.addListener(
    function(message, callback) {
            chrome.downloads.download({url:message, filename:"image.jpg"});
});    

When clicking a button, it displays a message on a console log as dictated by the sendMessage. However, it also displays an error: "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist."

Disclaimer. If I use the same button on a popup.js, it downloads the image. But I want to bind the functionality on a button injected on the website, not via the extension panel.


Solution

  • Currently you're injecting code in page context which is both an unnecessary overcomplication and wrong as you inject the background script which already runs in the hidden separate background page of the extension that's not related to the web page. See how to open background.js devtools and console.

    Remove script_injection.js, injection_ui.js and web_accessible_resources section and unnecessary permissions from manifest.json as well as jquery.js.

    Declare parameters of onMessage listener correctly per the documentation.

    Use "persistent": false, more info.

    {
      "name": "Dummy Extension",
      "version": "1.0",
      "manifest_version": 2,
      "description": "This is a dummy extension.",
      "browser_action": {
        "default_title": "Download Images from Anywhere",
        "default_popup": "popup.html"
      },
      "content_scripts": [{
        "matches": ["https://dummywebsite.com/*"],
        "js": ["content.js"],
        "css": ["style.css"]
      }],
      "permissions": [
        "storage",
        "downloads"
      ],
      "background": {
        "scripts": ["background.js"],
        "persistent": false
      }
    }
    

    Let's use a standard name content.js with standard JS inside, no jquery. If you still want jquery, then declare it in manifest.json as "js": ["jquery.js", "content.js"]

    content.js:

    for (const thumb of document.querySelectorAll('.image-thumbnail')) {
      thumb.appendChild(Object.assign(document.createElement('button'), {
        className: 'DummyAddedButton',
        textContent: 'Download',
      }));
    }
    
    document.addEventListener('click', e => {
      if (e.target.matches('.DummyAddedButton')) {
        chrome.runtime.sendMessage(e.target.closest('.image-thumbnail').src);
      }
    });
    

    background.js:

    chrome.runtime.onMessage.addListener((message, sender, sendMessage) => {
      chrome.downloads.download({url: message, filename: 'image.jpg'});
    });    
    

    Don't forget to reload the extension on chrome://extensions page and the web page tab after you edit manifest.json or the content scripts.