0

I have created a Chrome extension that queries for elements on some webpages that feature dynamic content loading via AJAX; -- when on some of these webpages, my content.js script triggers too early (prior to the loading of the elements I need).

As a result, I set up a listener in my background script to listen for when the page has loaded enough of the elements and then it re-injects the content.js script.

background.js:

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

            chrome.windows.get(sender.tab.windowId, function () {
                /*get user's size based on product type (ie. hat)*/
                var res = findSize();
                if (res == -1) {
                    /*do nothing since no size exists for them*/
                }
                else {
                    /*send size back to content script via messaging*/ 
                    chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
                        chrome.tabs.sendMessage(tabs[0].id, { type: "res_size", size: res });
                    });
                }

            });
            
           
        }
    });

var oldTab;
chrome.tabs.onUpdated.addListener(
    function (tabId, changeInfo, tab) {
        var temp = tab.url;
        /*IF: on a product page, execute script.*/
        if (temp && (temp.indexOf("shop.com/shop/") > -1)) && (changeInfo.url === undefined) && (temp != oldTab)) {
            chrome.tabs.executeScript(tabId, { file: "content.js" });
            oldTab = tab.url;
        }
    });

Unfortunately, I am unable to call any functions from content.js that exist in my other js files. Is there a way to invoke them? For context, I've included the js files in question into "web_accessible_resources" and "content_scripts" in my manifest.json.

external.js:

    function monk(x) {
       if (x != 0) {
           console.log(x); 
       }
    }  

content.js:

        /*get current url*/ 
        var cur_url = window.location.href; 
        
        /*check if user is on supported site*/ 
        if (cur_url.indexOf("hello.com/shop/") > -1 && cur_url.indexOf("quantity=1") > -1) { 
 
            /*get relevant user data*/ 
            chrome.storage.sync.get({ data: [] }, async function (user) {

                 /*call prod_res() to get item details from dynamically loaded product page*/ 
                 const res = await prod_res();

                /*if the product is for men*/
                if (res[1] == "man") {

                    /*if the product is a hat*/
                    if (res[0] == "hat") {

                        /*send message to background script that this is a mens' hat*/ 
                        chrome.runtime.sendMessage({ type: "hats_men" },
                            function (response) {

                                /*listen for user's hat size in the request/message from background script*/ 
                                chrome.runtime.onMessage.addListener(
                                    function (request, sender, sendResponse) {

                                /*log user's hat size to console*/
                                if(request.type == "res_size"){
                                        monk(request.size][1]);
                                }
                                        }
                                    });
                            }
                        );
                    }
                }
            });
        }

/nothing logs to console/

manifest.json:

         {
      "manifest_version": 2,
      "name": "E-Co",
      "version": "0.2",
      "background": {
        "scripts": [ "jquery-3.5.1.min.js", "reduce.js", "background.js" ],
        "persistent": false
      },
      "content_scripts": [
        {
          "matches": [
            "<all_urls>"
          ],
          "js": [
            "jquery-3.5.1.min.js",
            "content.js",
            "external.js" 
          ]
        }
      ],
      "web_accessible_resources": [
        "external.js"
      ],
      "browser_action": {
        "default_icon": "logo_active.png",
        "default_popup": "popup.html",  
        "default_title": "E-co Pop-Up"
      },
      "icons": {
        "16": "icon_16.png",
        "48": "icon_48.png",
        "128": "icon_128.png"
      },
      "permissions": [
        "storage",
        "notifications",
        "tabs",
        "*://*/*"
      ],
      "content_security_policy": "script-src 'self' https://ajax.aspnetcdn.com; object-src 'self'"
    }
TmT
  • 3
  • 2
  • `web_accessible_resources` doesn't run anything by itself so you probably don't need it at all. Currently the question doesn't show a complete picture of what you're doing. Add manifest.json and show how external.js is executed. – wOxxOm Jul 16 '20 at 05:50
  • Gotcha, thanks @wOxxOm -- I've updated with my manfest.json and as for external.js, this is just for the purpose of explaining the problem. Invoking the function monk(), as defined in external.js, should log 4 to console, once content.js is executed. Do let me know if you want more detail here. – TmT Jul 16 '20 at 21:06
  • Just updated with more detail so hopefully it's more clear – TmT Jul 16 '20 at 21:43

1 Answers1

0

Your content.js sends a message and uses a callback to wait for a response, which must be sent back by using sendResponse. Your background script doesn't send a response, it only sends a new message, so the first callback never runs.

Use the simple messaging example from the documentation:

content.js

chrome.runtime.sendMessage({type: 'hats_men'}, monk);

background.js:

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.type == 'hats_men') {
    sendResponse(findSize());
  }
});

Note that sendResponse must be called for each message that was sent with a callback like we did above, otherwise you'll see errors in the console (unless you suppress them).

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • thanks for your reply -- this does indeed help to address one of the problems I was having, but I am still unable to call a function (monk() in this example) that exists in a separate script (recall that I've had to inject my content.js due to the dynamically loaded page). I think it may be related to this question https://stackoverflow.com/questions/36154037/how-to-call-a-function-from-injected-script ? – TmT Jul 18 '20 at 20:39
  • I don't see how you execute the script dynamically in your question so I can't help. Normally there's no need for anything special. Definitely no DOM script elements. – wOxxOm Jul 19 '20 at 03:42
  • no worries if not -- I've included the code in background.js where I inject the script. The site that my extension interacts with loads dynamically, so the content script is executed prior to certain elements loading on a given product page. I've a conditional statement that listens for a tab update (specifically URL changes), so that when a product page has loaded completely (checks for all applicable url params), I call content.js again, this time via executeScript. If I don't do this, content.js won't execute again (because it already did, just earlier). – TmT Jul 19 '20 at 06:46
  • This is important because content.js needs access to certain DOM elements that have yet to load. – TmT Jul 19 '20 at 06:51
  • executeScript runs the script at DOMContentLoaded, just like your declared scripts in manifest.json, but there's no guarantee that executeScript's one will run afterwards, so I guess sometimes it runs before external.js. You can switch to messaging instead of re-execution or you can specify "run_at":"document_start" in manifest.json declaration of the content script. – wOxxOm Jul 19 '20 at 07:21
  • I decided to implement messaging as follows: content.js <> background.js <> external.js -- this works well, thank you. i have realized that content.js still needs to be injected programmatically, so i implemented some switches to removeListeners when needed, to eliminate excess messaging. – TmT Jul 19 '20 at 20:47