0

I'm trying to get a request from my database in my popup.js which responds with an array of objects. I created a for loop so that it iterates through it all, but on each iteration I would like to spawn a new tab. I have no problem spawning the tabs. My problem is that I can't seem to pass any data to them. I've been looking all over SO and cant find how to do this??

popup.js:

var button = document.getElementById("button");


button.addEventListener("click", function() {

chrome.runtime.sendMessage({opened: true}, function(response) {
console.log(response.example);
  });
});

background.js:

    // receives message from popup script
  chrome.runtime.onMessage.addListener( function(request, sender,     sendResponse) {
if (request) {
  // sends response back to popup script
  sendResponse({example: "sent from background.js"});

  // sends response to content script
  var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
        var data = JSON.parse(xhr.responseText);

        for (i = 0; i < data.length; i++) {
            console.log(data)

            chrome.tabs.create({url: "https://www.google.com/", active: false}, function(tab) {
                var tab = tabs[0];
                setTimeout(function(){
                    chrome.tabs.sendMessage(tab.id, data[i], function(response) {
                        console.log(response);
                    });
                }, 6000);
            });

        }

    }
}
xhr.open('GET', 'https://www.somedata.com', true);
xhr.send(null);

    }
  });

contentscript.js:

chrome.runtime.onMessage.addListener(function(request,sender,response) {

console.log(request)
console.log(sender)
console.log(response)
});

manifest.json:

{
"manifest_version": 2,
"name": "demo",
"short_name": "tabs",
"description": "Multiple tabs",
"version": "0.0.1",
"browser_action": {
    "default_icon": "img/logo.png",
    "128": "128.png",
    "default_popup": "popup.html",
    "default_title": "Sample"
},
"content_scripts": [{
    "matches": ["https://www.google.com/*"],
    "js": ["js/contentscript.js"]

}],
"permissions": [
    "storage",
    "tabs",
    "http://*/*",
    "https://*/*"
]

}

Seeker
  • 31
  • 5
  • The first argument to `chrome.tabs.sendMessage` is the tab id. How does `https:somedata.com` know the tab ids? – Teepeemm Jul 18 '15 at 18:33
  • I tried this before: var tab = tabs[0]; // do not forget to declare "tab" variable chrome.tabs.sendMessage(tab.id, { data[i] }, function(response){}); // but nothing ever gets passed to the new tab – Seeker Jul 18 '15 at 18:37
  • The callback for chrome.tabs.create is passed a single tab, not an array of tabs. Try chrome.tabs.sendMessage(tab.id, data[i], function(response){}); –  Jul 18 '15 at 20:28
  • Sorry that was a typo, I tried that didnt work. I think I need to implement a background file. I'm seeing that extensions are in some sort of MVC pattern – Seeker Jul 18 '15 at 23:26
  • Yep I have no idea what to do x_x – Seeker Jul 19 '15 at 02:13
  • Is your `XMLHttpRequest` integral to the problem? Why can't you just make `data` some array to test things out? – Teepeemm Jul 20 '15 at 00:16
  • Yeah the XMLHttpRequest returns an array of objects, I'm able to create as many new tabs as, as many object I have in my array, but I'm not able to pass the data of those objects to any of them. – Seeker Jul 20 '15 at 03:12
  • You're opening a tab at http but only injecting the content script at https –  Jul 20 '15 at 04:04

1 Answers1

0

The problem is that when the new tab is created, the popup dies before the callback can be executed. The popup closes automatically when another tab becomes active, and when it closes its execution stops. Simplest way to solve this is by not making the new tab active:

chrome.tabs.create({url: "http://www.google.com/", active: false}, function(tab){
    chrome.tabs.sendMessage(tab.id, message, function(response) {});
});

Although you could also move this functionality to the background page, if a non-active tab would be weird for the user. Instead of creating the tab in popup.js, message the background page with chrome.runtime.sendMessage (passing any relevant data) and put a listener in the background page that does the tab creation. (Mostly the same code you already have, just moved around.)

Edit: Okay, I figured out the other problem. The popup is sending the message before the message listener in the content script is attached, so it doesn't receive it. Simplest way to fix that is by adding a timer to the message-sending:

setTimeout(function(){
    chrome.tabs.sendMessage(tab.id, message, function(response) {});
} , 600);  //600 works on my computer

This isn't a good solution; just a demonstration that the issue is time. To fix this more reliably, I think you'd have to have a more complicated thing where contentscript.js messages popup.js when it's done loading, and popup.js uses tab.ids or something to associate loaded pages with elements in the data array, and sends the appropriate data as a response to contentscript.js

Second edit: The other other problem is with the for loop. Because it's asynchronous, by the time the callback for chrome.tabs.create is called, the loop ended a long time ago, and i=data.length (as it does when the loop ends). So you can't pass data[i] as a message because data[data.length] is undefined. You can replace it with a recursive call like this:

background.js:

var data = ["x", "y"]; //just test data. make sure "data" has global scope

chrome.runtime.onMessage.addListener( 
    function(message, sender, sendResponse) { //from popup.js
        createTab(0);
    }
);

function createTab(i){
    if (i == data.length){return;}  //this ends the loop
     chrome.tabs.create({url: "https://www.google.com/", 
                            active: false}, function(tab) {
        setTimeout(function(){
            chrome.tabs.sendMessage(tab.id,data[i], function(response) {}); //to script
            createTab(i+1); //this calls the next iteration of the loop
        }, 1000);
    });
}
  • I tried what you said and it creates the tabs but when I do console.log no message is received. I'm doing this for myself I'm basically trying to simulate different users according to their tab. So each iteration of that object will be a new user and a new context, all executed at the same time. – Seeker Jul 20 '15 at 13:24
  • I'm still not getting it, I edited my question to show how I did it. I tried with the timeout of 600 but didnt see it so I tried a timeout of 6000 (6 seconds) and still cant see it. – Seeker Jul 20 '15 at 15:48
  • alright, figured out that problem too. Edit coming momentarily –  Jul 20 '15 at 17:46
  • 2
    An alternative to having the content script message that it's ready would be to have the background script inject the content script and then pass the appropriate data. An alternative to the recursive call would be some javascript closure magic such as [here](http://stackoverflow.com/q/750486/2336725). – Teepeemm Jul 20 '15 at 19:09
  • I dont know but its not even connecting to it at all. This has happened before, I'll get the message passing from my background.js to the content script ONCE and then after that it'll never get any message passed again. It makes no sense. (I'm using the latest version of Chrome) – Seeker Jul 21 '15 at 05:24
  • I got it to work! Thank you @KammyLiu! I''ll try your way as well @Teepeemm – Seeker Jul 21 '15 at 12:40