0

I'm currently learning to develop a google chrome extension. I'm trying to get a button to list all of the span elements in the console for now. However, I can't seem to set the function of the button and keep getting a weird Uncaught TypeError: Cannot set property 'onclick' of null error.

index.html:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Simple Greeting Dashboard</title>
    <link rel="stylesheet" href="style.css" />
</head>

<body>
    <button id="main-switch">Update prices:</button>
    <script src="main.js" defer></script>
</body>


</html>

main.js:

window.onload = function() {
    console.log("This is working!");

    chrome.runtime.onInstalled.addListener(function() {
        chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
            chrome.declarativeContent.onPageChanged.addRules([{
                conditions: [new chrome.declarativeContent.PageStateMatcher({
                    pageUrl: { hostEquals: 'steamcommunity.com' },
                })],
                actions: [new chrome.declarativeContent.ShowPageAction()]
            }]);
        });
    });

    function update_prices() {
        var elements = document.getElementsByTagName('span');
        console.log("Elements:", elements);
    }
    document.getElementById('main-switch').onclick = update_prices;
};

manifest.json:

{
    "name": "Steam price helper",
    "version": "1.0",
    "description": "Helps with prices on the Steam community market",
    "incognito": "split",
    "background": {
        "scripts": ["main.js"],
        "persistent": true
    },
    "permissions": [
        "activeTab",
        "storage",
        "declarativeContent"
    ],

    "page_action": {
        "default_popup": "index.html",
        "default_icon": {
            "256": "download.png"
        }
    },

    "manifest_version": 2
}

I've looked at other questions that have the same problem, but none of those solutions seem to work. Thanks in advance!

Dion
  • 222
  • 4
  • 13
  • 1
    You didn't show manifest.json which is one of the most important things in an extension so I can only guess you're loading the same script main.js as a background script which is totally wrong (you need two separate scripts because the background script is already running in its own hidden background page). – wOxxOm Oct 29 '20 at 11:00
  • @wOxxOm updated with manifest – Dion Oct 29 '20 at 11:02
  • So my guess was right. – wOxxOm Oct 29 '20 at 11:08
  • Why are you using `defer` when the script tag is already at the end of the body? –  Oct 29 '20 at 11:26
  • @wOxxOm could you please explain a bit further? don't get it fully from your comment – Dion Oct 29 '20 at 11:57

1 Answers1

1

When you declare background in manifest.json, it creates a background page where the specified scripts run. It's a hidden separate page. You can debug it its own separate devtools.

The browser_action or page_action popup is also a separate page. It's not related to the background script/page. It also has its own separate devtools which you can access by right-clicking inside the popup and choosing "Inspect".

You can use both devtools to debug the problem: set breakpoints, reload the page via Ctrl-R or F5 key, step through the code and actually see what happens so there's no need to guess.

Currently you run the same main.js in two pages. So, one instance of main.js runs in the background script's page where DOM is empty and naturally it doesn't find any elements.

The solution is to split it into two scripts. One script will be the background script that registers API stuff. Another script will run in the popup and deal with UI stuff.

  1. In manifest.json declare "background": {"scripts": ["bg.js", "persistent":false]}

  2. Make bg.js:

    chrome.runtime.onInstalled.addListener(() => {
      chrome.declarativeContent.onPageChanged.removeRules(() => {
        chrome.declarativeContent.onPageChanged.addRules([{
          conditions: [
            new chrome.declarativeContent.PageStateMatcher({
              pageUrl: {hostEquals: 'steamcommunity.com'},
            }),
          ],
          actions: [new chrome.declarativeContent.ShowPageAction()],
        }]);
      });
    });
    
  3. Make popup.js:

    document.getElementById('main-switch').onclick = function () {
      var elements = document.getElementsByTagName('span');
      console.log('Elements:', elements);
    };
    
  4. Load it in your html: <script src=popup.js></script>

    No need for defer or async attributes. No need for window.load either: the script already runs when DOM is ready as it's the last thing in HTML.

Notes:

  • To view the console output, use the correct devtools as explained at the beginning of this answer.
  • To gray out the icon on non-matching sites, see this answer.
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • This helped remove the errors. However, I might have not made this clear in the question, my bad: I want to list all of the elements on the page of where the popup is active, not of the popup itself - so all of the `span`s on the steam page in my example, and currently this looks for the elements on the popup page – Dion Oct 30 '20 at 06:18
  • [How to access the webpage DOM rather than the extension page DOM?](https://stackoverflow.com/a/4532567) – wOxxOm Oct 30 '20 at 06:26