0

I am really struggling with passing an array from my background.js to my content.js. Before I start, I will list my scripts.

manifest.json

{
"manifest_version": 2,

"name": "Tab Highlighter",
"description": "Highlight already open tabs in a google search query",
"version": "1.0",
"content_scripts":[
{
  "matches": [ "http://www.google.com/*", "https://www.google.com/*", "https://*/*", "http://*/*" ],
  "js": ["content.js"],
  "run_at": "document_end"
}

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

content.js

chrome.runtime.sendMessage({data: "getTabs"}, function(response) {
var tabs = response.data;
    if(!tabs) {
        console.log("No Tabs could be found!!");
    }
    else {
        console.log("TABS FOUND");
        console.log(tabs);
        for(var k = 0; k < tabs.length; k++) {
            console.log(tabs[k]);   
        }
        var links = document.getElementsByTagName("a");
        for(var i = 0; i < links.length; i++) {
            var link = links[i];
            var real_href = link.getAttribute("href");
            if(real_href) {
                //console.log(real_href);

                for(var j = 0; j < tabs.length; j++) {
                    if(i == 0) {
                        console.log("tab open: ", tabs[j]);
                    }
                    if(real_href == tabs[j]) {
                        console.log("duplicate found: ", real_href);
                        link.innerHTML = "Already Open";
                    }
                }
            }
        }
        console.log("End printing tabs");
    }
});

background.js

chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if(request.data === "getTabs"){
var fourmTabs = new Array();
chrome.tabs.query({}, 
        function(t) {
            for (var i = 0; i < t.length; i++) {
                fourmTabs[i] = t[i].url;
            }
            // Moved code inside the callback handler
            for (var i = 0; i < fourmTabs.length; i++) {
                if (fourmTabs[i] != null)
                    console.log(fourmTabs[i]);
                else {
                    console.log("??" + i);
                }
            }
        }
    );

    if(!fourmTabs) {
        console.log("Tabs could not be generated!!");
    }
    else {
        for(i = 0; i < fourmTabs.length; i++) {
            console.log(fourmTabs[i]);
        }
    }
    sendResponse({data: fourmTabs});
    //return true;    used to see if this was asynchronous or not
}
});

UPDATED background.js

chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if(request.data === "getTabs"){
var fourmTabs = new Array();
chrome.tabs.query({}, 
        function(t) {
            for (var i = 0; i < t.length; i++) {
                fourmTabs[i] = t[i].url;
            }
            // Moved code inside the callback handler
            for (var i = 0; i < fourmTabs.length; i++) {
                if (fourmTabs[i] != null)
                    console.log(fourmTabs[i]);
                else {
                    console.log("??" + i);
                }
            }
            if(!fourmTabs) {
                console.log("Tabs could not be generated!!");
            }
            else {
                for(i = 0; i < fourmTabs.length; i++) {
                    console.log("fourmtabs is here: ", fourmTabs[i]);
                }
            }
            sendResponse({data: fourmTabs});
        }
    );
}
});

Currently, What gets printed from content.js is just an empty array, I believe. It shows up as "[ ]" (no quotes). Whenever I change sendReponse in background.js to {data: "Test"} then content.js prints out what I expect, Test, and then each letter. Hopefully, everything is here, I have been trying multiple things to get this to work. I'll be up for another hour, so if I don't respond, I will be back on tomorrow. Thanks for any help!

The way I see it in my head is this. The page loads, and when it is done loading, it runs content.js. content.js sends a message to background.js, and gets its response (the array I am having trouble with). Then, all the links are gotten from the page and it is checked to see if any of the hrefs on the page are equal to any pages open in another tab. I would then like to change the style of this link to be a different color or something to indicate it was already open. I plan on this only working on google searches, which I think I would do by getting rid of the last two elements in the "matches" part of the manifest.json.

Another quick edit: I was attempting to follow this kind of question however, I got stuck on send the array from background.js to content.js

Please let me know if you need any more information.

Devin Wright
  • 191
  • 1
  • 11

2 Answers2

0

sendResponse is being called before fourmTabs gets populated, so it's still a blank array when you send it. All of your code from if(!fourmTabs) through the sendResponse call should be moved inside the callback from tabs.query.

Edit: You need to add return true; just before your final lines in the addListener callback-- your final three lines then should look like:

return true;
}
});

This is not particularly well documented, and I'm not sure I'm explaining it well, but because tabs.query is asynchronous and you need to run it before you can get your response, the port used for messaging is being closed before tabs.query finishes, and therefore before sendResponse is actually being called, so the content script thinks it's getting a non-response.

Sourced largely from this chromium-extensions thread with a similar but different scenario (waiting on an AJAX call to complete before calling sendResponse)

Sean Adams
  • 453
  • 3
  • 7
  • I moved the code from `if(!fourmTabs) {` to the else's `}` up to be in the `chrome.tabs.query` function callback (below the last for loop). The console does print out the content of fourmTabs correctly (in background.js), however, the sendResponse is still sending a blank array (in content.js). Not entirely sure what I am doing wrong. Does the `sendResponse` need to be in the tabs callback? if so, something more is probably needed, as I tried that in a way. – Devin Wright Jul 08 '14 at 02:56
  • Yes, the `sendResponse` needs to be in the callback. – Sean Adams Jul 08 '14 at 02:59
  • I went and added it also. There aren't any errors being thrown, however, nothing is happening in the content.js. No console logs are being written. I can show you an update of what is currently in my background.js if I need to. – Devin Wright Jul 08 '14 at 03:04
  • updated code has been added via edit under main post Edit: It's like there is no response. If I add just a blank response `sendResponse({});` where the original sendResponse was, content.js logs that nothing was passed via no tabs could be found log. – Devin Wright Jul 08 '14 at 03:09
  • You need to add `return true;` just before your final lines in the `addListener` callback-- your final three lines then should look like: `return true; } });` This is not particularly well documented, and I'm not sure I'm explaining it well, but because `tabs.query` is asynchronous and you need to run it before you can get your response, the port used for messaging is being closed before `tabs.query` finishes, and therefore before `sendResponse` is actually being called, so the content script thinks it's getting a non-response. – Sean Adams Jul 08 '14 at 03:35
  • you are a god! I am so glad you understood what I was saying. Thanks for being so quick. The tabs now show up in the content.js log. I think I can handle it from here. If not, you might be hearing from me haha. Thanks again! – Devin Wright Jul 08 '14 at 03:39
  • Sourced from this chromium-extensions thread: https://groups.google.com/a/chromium.org/forum/#!topic/chromium-extensions/C9RQuRWddj4 with a similar but different idea (doing an async AJAX request). Glad to help! – Sean Adams Jul 08 '14 at 03:39
0

Have you tried storing the value and calling it using chromes storage methods? I'm not sure if this is the best method, but I'm sure this will open up cross communication. Here's an example:

var setArray = [1, 2, 3, 5, [1, 2, 3]];
chrome.storage.local.set({'storageObjectName': setArray}, function () {
    /* optional callback */
});

And from your other script

chrome.storage.local.get('storageObjectName', function (data) {
    console.log(data);
});

Example of combining the two:

var setArray = ['my new data']; /* setting array in script 1 */
chrome.storage.local.set({'storageObjectName': setArray}, function (){
    chrome.storage.local.get('storageObjectName', function (data) {
        otherScriptFunction(data); /* call your other function from script 2 */
    });
});
lindsay
  • 972
  • 2
  • 11
  • 21
  • I have not actually tried this way, though I have seen some local storage examples kind of like this. I was hoping to keep things simple with the messages between content.js and background.js. I feel like I am close, and it is just something dumb I am doing wrong. Thank you for the suggestion, I will try it if nothing else comes about. – Devin Wright Jul 08 '14 at 03:17
  • Glad I could offer some food for thought. I'm not sure on the best practices for sharing variables between javascript files (I believe it relies on have access to global functions). One problem with my method I might point out is that because the get method is asynchronous, you might have to access your other javascript function to process the array from within the set methods callback. I can give an example in my answer if you need further info! :) – lindsay Jul 08 '14 at 03:20
  • Well, I think that using chrome extension api shouldn't make it this difficult, like I said I think I am just missing one detail that is tripping me up. I would be happy to see what you mean with an example however, because that was a little confusing. I am no javascript guru, this is really the first time delving into it all. – Devin Wright Jul 08 '14 at 03:26