Undefined response from content script in chrome extension

user2929018 picture user2929018 · Oct 28, 2013 · Viewed 8.1k times · Source

I can't get a response from my content script to show up in my popup.html. When this code runs and the find button is clicked, "Hello from response!" prints, but the variable response is printed as undefined. The ultimate goal is to get the current tab's DOM into my script file so that I can parse through it. I'm using a single time message to a content script to get the DOM, but it's not being returned and is showing up as undefined. I'm looking for any help possible. Thanks.

popup.html:

<!DOCTYPE html>
<html>
    <body>
        <head>
        <script src="script.js"></script>
        </head>

        <form >
            Find: <input id="find" type="text"> </input>
        </form>
        <button id="find_button"> Find </button>
    </body>
</html>

manifest.json:

{
    "name": "Enhanced Find",
    "version": "1.0",
    "manifest_version": 2,
    "description": "Ctrl+F, but better",
    "browser_action": {
        "default_icon": "icon.png", 
        "default_popup": "popup.html"
    },
    "permissions": [
        "tabs",
        "*://*/*"
    ],

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

    "content_scripts":[
        {
            "matches": ["http://*/*", "https://*/*"],
            "js": ["content_script.js"],
            "run_at": "document_end"
        }
   ]
}

script.js:

var bkg = chrome.extension.getBackgroundPage();


function eventHandler(){
    var input = document.getElementById("find");
    var text = input.value;
    chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
        var tab = tabs[0];
        var url = tab.url;
        chrome.tabs.sendMessage(tab.id, {method: "getDocuments"}, function(response){
            bkg.console.log("Hello from response!");
            bkg.console.log(response);
        });

    });
}

content_script.js:

var bkg = chrome.extension.getBackgroundPage();

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
    if(request.method == "getDOM"){
        sendResponse({data : bkg.document});
    }else{
        sendResponse({});
    }
});

Answer

gkalpak picture gkalpak · Oct 28, 2013

There are quite a few issues with your code (see my comment above).


Some suggestions/considerations first:

  • Do not inject your content script into all webpages. Inject programmatically and only when the user wants to search.

  • It might be a better idea to do the "searching" right in the content script, where you have direct access to the DOM and can manipulate it (e.g. highlight search results etc). You might need to adjust your permissions if you go for this approach, but always try to keep them to a minimum (e.g. don't use tabs where activeTab would suffice, etc).

  • Keep in mind that, once the popup is closed/hidden (e.g. a tab receives focus), all JS executing in the context of the popup is aborted.

  • If you want some sort of persistence (even temporary), e.g. remembering the recent results or last search term, you can use something like chrome.storage or localStorage.


Finally, sample code from my demo version of your extension:

Extension files organization:

          extension-root-directory/
           |
           |_____fg/
           |      |_____content.js
           |
           |_____popup/
           |      |_____popup.html
           |      |_____popup.js
           |
           |_____manifest.json

manifest.json:

{
    "manifest_version": 2,
    "name":    "Test Extension",
    "version": "0.0",
    "offline_enabled": true,

    "content_scripts": [
        {
            "matches": [
                "http://*/*",
                "https://*/*"
            ],
            "js":     ["fg/content.js"],
            "run_at": "document_end",
        }
    ],

    "browser_action": {
        "default_title": "Test Extension",
        "default_popup": "popup/popup.html"
    }
}

content.js:

// Listen for message...
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    // If the request asks for the DOM content...
    if (request.method && (request.method === "getDOM")) {
        // ...send back the content of the <html> element
        // (Note: You can't send back the current '#document',
        //  because it is recognised as a circular object and 
        //  cannot be converted to a JSON string.)
        var html = document.all[0];
        sendResponse({ "htmlContent": html.innerHTML });
    }
});

popup.html:

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="popup.js"></script>
    </head>
    <body>
        Search:
        <input type="text" id="search" />
        <input type="button" id="searchBtn" value=" Find "
               style="width:100%;" />
    </body>
</html>

popup.js:

window.addEventListener("DOMContentLoaded", function() {
    var inp = document.getElementById("search");
    var btn = document.getElementById("searchBtn");

    btn.addEventListener("click", function() {
        var searchTerm = inp.value;
        if (!inp.value) {
            alert("Please, enter a term to search for !");
        } else {
            // Get the active tab
            chrome.tabs.query({
                active: true,
                currentWindow: true
            }, function(tabs) {
                // If there is an active tab...
                if (tabs.length > 0) {
                    // ...send a message requesting the DOM...
                    chrome.tabs.sendMessage(tabs[0].id, {
                        method: "getDOM"
                    }, function(response) {
                        if (chrome.runtime.lastError) {
                            // An error occurred :(
                            console.log("ERROR: ", chrome.runtime.lastError);
                        } else {
                            // Do something useful with the HTML content
                            console.log([
                                "<html>", 
                                response.htmlContent, 
                                "</html>"
                            ].join("\n"));
                        }
                    });
                }
            });
        }
    });
});