Search code examples
google-chrome-extensionbackgroundsendmessagecontent-script

Chrome extension: How to send message from background to content script of new page


Sorry for my english, i will do my best.

I need to create an extension where the popup contains a field and a button.

The goal is that once the button is clicked, a new window opens (another website) and you have to send the popin field information in this new window.

There is my manifest.json

{
  "name": "Webrelief extension",
  "version": "1",
  "browser_action":
  {
    "default_icon": "webrelief.ico",
    "default_popup": "popup.html",
    "default_title": "Webrelief extension - Iello"
  },
  "background":
  {
    "scripts": ["background.js"],
    "persistent": true
  },
  "content_scripts": [
    {
      "run_at": "document_end",
      "matches": [
        "https://www.iello.pro/iello-c102x3259540",
        "https://www.google.com/"
      ],
      "js": ["jquery.min.js","content.js"]
    }
  ],
  "permissions": ["tabs","activeTab","<all_urls>"],
  "manifest_version": 2,
  "content_security_policy": "script-src 'self' https://www.iello.pro/iello-c102x3259540; object-src 'self'"
}

My background.js

$('#validate').on('click',function(activeTab){
    var newURL = "https://www.iello.pro/iello-c102x3259540";
    chrome.tabs.create(
        {
            url: newURL
        }, function() {
            chrome.tabs.query({active: false, currentWindow: true}, function(tabs) {
                chrome.tabs.sendMessage(tabs[tabs.length - 1].id, {greeting: "hello"}, function(response) { // tabs[tabs.length - 1] to get the new windows ? I'm not sure
                    console.log(response);
                });
            });
        }
    );
});

My 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"});
    });

And my popup.html

<!doctype html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Webrelief Extension</title>
    <link href="css/main.css" rel="stylesheet">
</head>
<body>
<img width="400px" src="webrelief_logo.svg" alt="Bannière webrelief">
<hr>
<div>
    <textarea placeholder="Veuillez saisir le json ici ..." name="json" id="json_iello" cols="30" rows="10"></textarea>
</div>
<button id="validate">Go !</button>
<script src="background.js"></script>
</body>
</html>

enter image description here

Currently if I press the button, my new window opens, but I do not receive the message on the new window.

Can you tell me my mistakes

Thanks !


Solution

  • There are three principal issues here.

    Timing of sending a message to a tab

    There isn't really a good duplicate of this question (this is the closest candidate), but the main issue you're having is timing.

    After opening a tab, the callback of create is called before the page is sufficiently loaded. Your content script may simply not be there yet to listen to the message. With document_end timing, it's almost certainly not the case.

    The only safe way to do it is to reverse the communication: let the content script request the data, so that way you know it's ready.

    This leads to the next issue..

    Popup closing upon message opening

    When you create a tab that is focused, this causes the popup to lose focus and close.

    When that happens, the script that was running there is terminated - as if you closed a tab with the page.

    So the rest of your code isn't even run (regardless of the previous issue). You have two options here:

    1. You could create a tab with active: false set. This doesn't lose focus, so the popup stays active, but is less convenient for the user, though you can still make it active after that.

      As an unrelated note, you shouldn't query when functions like tabs.create explicitly return a tab object in their callback.

    2. Better option: you should delegate the opening-then-communication to your background page. It is immune to the effect of popup closure and is generally better suited as a "communication router".

    Which brings us to the last problem..

    Misunderstanding of background page architecture

    You declared a background.js script in the manifest as the background script. What does that mean?

    It means that a separate, invisible, empty page is created and loaded with that script running.

    If you include a background.js script in other places in the extension, it won't be "that" instance of the script. It will be an independent copy running and subject to all limitations of its new context.

    Very strong advice: do not attempt to reuse whole scripts in your code. It's fine to have some shared code (say, utils.js holding some common functions, and included before respective scripts), but take care to call your scripts descriptively and separate code.

    To interact with the "real" background page, you'll need to send messages (with chrome.runtime.sendMessage).


    Putting it all together, your logic should be:

    1. In the popup (make that a separate popup.js script), listen for your UI event.
    2. When that happens, either send to background (via messaging) or store (in chrome.storage) the user data.
    3. Then, via messaging, ask your background page to perform the operation. Prepare the popup for closing at this point.
    4. Your background can open the page requested at this point. It will kill the popup, but you don't care.
    5. Your content script, once it initializes, can either directly get the user data from chrome.storage, or request it (via runtime.sendMessage) from the background page.

    There's a concern that your content script is launched every time the target page opens, even when you don't want to use the user data. You could either implement some mechanism to only use the data once (e.g. clear / flag it after use) or only inject the content script programmatically (though you again will have timing issues to deal with after tabs.create)