16

I am working to learn how to build a Google Chrome Extension. I have a contact form on a webpage that I'm using for testing. I'm trying to create an extension that will read the input field values from that form. At this time, I have:

manifest.json

{
    "manifest_version": 2,

    "name": "Contact Form Friend",
    "description": "This extension gets contact form info.",
    "version": "1.0",

    "browser_action": {
      "default_icon": "icon.png",
      "default_popup": "popup.html"
    },

    "permissions": [
      "activeTab",
      "<all_urls>"
    ]
  }

popup.html

<!doctype html>
<html>
  <head>
    <title>Getting Started Extension's Popup</title>
    <style type="text/css">
      body {
        margin: 10px;
        white-space: nowrap;
      }

      h1 {
        font-size: 15px;
      }

      #container {
        align-items: center;
        display: flex;
        justify-content: space-between;
      }
    </style>

    <script src="popup.js"></script>
  </head>

    <body>
        <h1>Info</h1>
        <div id="info">

        </div>
    </body>
</html>

popup.js

function getHost() {
    return document.documentElement;
}

document.addEventListener('DOMContentLoaded', () => {
    const infoDisplay = document.getElementById('info');    
    const scriptToRun = `(${getHost})()`;

    // Run the script in the context of the tab
    chrome.tabs.executeScript({
      code: scriptToRun 
    }, (result) => {            
      var values = [];

      var inputFields = result.getElementsByTagName('input');
      infoDisplay.innerHTML = 'inputs: ' + inputFields.length;
      for (var i = 0; i < inputFields.length; i++) {
        values.push(inputFields[i].value);
      }

      infoDisplay.innerHTML += '<br />fields: ' + values.length;
    });
});

When I run this, it acts like it can't access the input fields from the web page the user is on (not the extension's web page). What am I doing wrong? Am I missing a permission? I don't believe so. However, it's not working and I'm not sure why.

Thank you for your help.

Some User
  • 5,257
  • 13
  • 51
  • 93
  • Does the code in your extension even get executed? Or is just that the DOM is not the one you were expecting? – marcofo88 Feb 08 '18 at 08:54
  • @Zach Templeton, here answers to your question: https://stackoverflow.com/questions/19758028/chrome-extension-get-dom-content – beaver Feb 09 '18 at 07:23

4 Answers4

11

As reported by Gabriel Bleu you need a content script to interact with web pages inside Chrome tabs.

Here below is an example of Chrome Extension where a content script exposes two "commands" to popup.js.

See code comments for explanation.

manifest.json (similar to yours)

{
  "name": "My ext",
  "version": "0.1",
  "description": "my desc",
  "permissions": [
    "activeTab",
    "<all_urls>"
  ],
  "icons": {
    "128": "icon-128.png"
  },
  "browser_action": {
      "default_title": "My ext",
      "default_icon": "icon.png",
      "default_popup": "popup.html"
  },
  "manifest_version": 2,
  "content_security_policy": "script-src 'self' https://ajax.googleapis.com object-src 'self'"
}

popup.html (similar to yours except for jQuery usage for easy DOM manipulation)

<html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script type="application/javascript" charset="utf-8" src="popup.js"></script>
</head>
<body class="body-popup">
    <button id="btn_check" class="btn btn-warning">check current tab</button>
    <hr>
    <div id="pg_url"></div>
    <hr>
    <div id="log"></div>
</body>
</html>

popup.js

$(function() {
    $('#btn_check').click(function() { checkCurrentTab(); });
});

function checkCurrentTab() {
    chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
        var url = tabs[0].url;
        console.log("checkCurrentTab: "+url);
        $(".pg_url").text(url);

        // request content_script to retrieve title element innerHTML from current tab
        chrome.tabs.sendMessage(tabs[0].id, "getHeadTitle", null, function(obj) {
            console.log("getHeadTitle.from content_script:", obj);
            log("from content_script:"+obj);
        });

    });
}

document.addEventListener('DOMContentLoaded', function () {
    chrome.windows.getCurrent(function (currentWindow) {
        chrome.tabs.query({active: true, windowId: currentWindow.id}, function(activeTabs) {
            // inject content_script to current tab
            chrome.tabs.executeScript(activeTabs[0].id, {file: 'content_script.js', allFrames: false});
        });
    });
});

function log(txt) {
    var h = $("#log").html();
    $("#log").html(h+"<br>"+txt);
}

content_script.js

// you will see this log in console log of current tab in Chrome when the script is injected
console.log("content_script.js");

chrome.runtime.onMessage.addListener(function(cmd, sender, sendResponse) {
    console.log("chrome.runtime.onMessage: "+cmd);
    switch(cmd) {
    case "getHtml":
        // retrieve document HTML and send to popup.js
        sendResponse({title: document.title, url: window.location.href, html: document.documentElement.innerHTML});
        break;
    case "getHeadTitle":
        // retrieve title HTML and send to popup.js
        sendResponse(document.getElementsByTagName("title")[0].innerHTML);
        break;      
    default:
        sendResponse(null);
    }
});

P.S.: obviously jQuery is not mandatory

beaver
  • 17,333
  • 2
  • 40
  • 66
  • 3
    This answer doesn't help with what the OP wants to accomplish: reading some `input` values from a webpage. There is no need to overcomplicate things. – Iván Nokonoko Feb 07 '18 at 19:49
  • @beaver can you please look at my question. https://stackoverflow.com/questions/63285684/issue-in-website-login-using-chrome-extension – suryavansh Aug 07 '20 at 18:08
  • can anyone please suggest me the same? I want to identify password input is exist on any webpage. – Rups Sep 13 '21 at 12:21
6

Here is the error you receive:

Error in response to tabs.executeScript: TypeError: result.getElementsByTagName is not a function
at Object.chrome.tabs.executeScript [as callback] (chrome-extension://lmaefdnejmkjjmgalgfofdbobhapfmoh/popup.js:15:32)
at HTMLDocument.document.addEventListener (chrome-extension://lmaefdnejmkjjmgalgfofdbobhapfmoh/popup.js:10:17)

Instead of trying to get the DOM of the current tab in popup.js, I would suggest to use a content script to do the task and send the result as a message to the popup.

manifest.json

{
    "manifest_version": 2,
    "name": "Contact Form Friend",
    "description": "This extension gets contact form info.",
    "version": "1.0",

    "browser_action": {
        "default_icon": "icon.png",
        "default_popup": "popup.html"
    },

    "permissions": [
        "activeTab",
        "<all_urls>"
    ],

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

In the manifest.json file you must add the content script and set the URLs of the sites in which you want to inject the script.

popup.js

document.addEventListener('DOMContentLoaded', () => {
    const infoDisplay = document.getElementById('info');    

    window.addEventListener('DOMContentLoaded', function () {
        chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
            chrome.tabs.sendMessage(tabs[0].id, {}, function (result) {
                infoDisplay.innerHTML = result
            });
        });
    });

});

In popup.js send a request to the content script, to get the number of input fields and insert the response into the div.

content.js

chrome.runtime.onMessage.addListener(function (msg, sender, response) {
    var values = [];
    var inputFields = document.getElementsByTagName('input');
    var result = 'inputs: ' + inputFields.length;

    for (var i = 0; i < inputFields.length; i++) {
        values.push(inputFields[i].value);
    }

    result += '<br />fields: ' + values.length;

    response(result)
});

Create a new file, named content.js. This file will be injected into the webpage and it listens to messages from the popup.js. When a message arrives it computes the response and sends it back to popup.js.

To learn more about content scripts, check out the documentation.

sc3w
  • 1,154
  • 9
  • 21
2

If your extension needs to interact with web pages, then it needs a content script. A content script is some JavaScript that executes in the context of a page that's been loaded into the browser. Think of a content script as part of that loaded page, not as part of the extension it was packaged with (its parent extension).

https://developer.chrome.com/docs/extensions/mv3/content_scripts/

Gabriel Bleu
  • 9,703
  • 2
  • 30
  • 43
-1

The main problem of your approach is that you try to send a DOM tree via message/sendResponse from content script to popup/background in order to process it there. You cannot send DOM trees via message/sendResponse.

A better approach would be to handle the DOM in the content script and send back the desired information (in your case, the input values) to the popup/background page.

One possible way to do it would be:

popup.js

document.addEventListener('DOMContentLoaded', () => {
    const infoDisplay = document.getElementById('info');    
    const scriptToRun = `
        var values = [];
        var inputFields = document.getElementsByTagName('input');
        for (var i = 0; i < inputFields.length; i++) {
            values.push(inputFields[i].value);
        }
        values;`;  //all this code will be run on the tab page
                   //and the array "values" will be returned.

    chrome.tabs.executeScript({
      code: scriptToRun 
    }, (result) => {

        infoDisplay.innerHTML = `There are: ${result[0].length} inputs, with these values: <ol><li>${result[0].join("<li>")}`;  
    });
});
Iván Nokonoko
  • 4,888
  • 2
  • 19
  • 27