17

I'm trying to access some DOM elements from a webpage:

<html>
  <button id="mybutton">click me</button>
</html>

I want to access the innerHTML ("click me") through a chrome extension:

chrome.browserAction.onClicked.addListener(function(tab) {
    var button = document.getElementById("mybutton");
    if(button == null){
        alert("null!");
    }
    else{
        alert("found!");
    }
});

When I click the extension, the popup says: "null". My manifest.json:

{
    "name": "HackExtension",
    "description": "Hack all the things",
    "version": "2.0",
    "permissions": [
    "tabs", "http://*/*"
    ],
    "background": {
    "scripts": ["contentscript.js"],
    "persistent": false
    },
    "browser_action": {
    "scripts": ["contentscript.js"],
    "persistent": false
    },
    "manifest_version": 2
}
TheChosenOne
  • 705
  • 1
  • 6
  • 14
  • 2
    possible duplicate of [Chrome Extension - Get DOM content](http://stackoverflow.com/questions/19758028/chrome-extension-get-dom-content) – Rob W Jan 24 '14 at 20:02

2 Answers2

35

The solution: You need a manifest file, a background script and a content script. This is not really clear in the documentation that you have to use it and also, how to use it. For alerting the full dom, see here. Because I have a hard time finding a complete solution that actually works and not just snippets that are useless for newbies, like me, I included a specific solution:

manifest.json

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

    "background": {
        "persistent": false,
        "scripts": ["background.js"]
    },
    "content_scripts": [{
        "matches": ["file:///*"],
        "js":      ["content.js"]
    }],
    "browser_action": {
        "default_title": "Test Extension"
    },

    "permissions": ["activeTab"]
}

content.js

/* Listen for messages */
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
    /* If the received message has the expected format... */
    if (msg.text && (msg.text == "report_back")) {
        /* Call the specified callback, passing 
           the web-pages DOM content as argument */
    sendResponse(document.getElementById("mybutton").innerHTML);
    }
});

background.js

/* Regex-pattern to check URLs against. 
   It matches URLs like: http[s]://[...]stackoverflow.com[...] */
var urlRegex = /^file:\/\/\/:?/;

/* A function creator for callbacks */
function doStuffWithDOM(element) {
    alert("I received the following DOM content:\n" + element);
}

/* When the browser-action button is clicked... */
chrome.browserAction.onClicked.addListener(function(tab) {
    /*...check the URL of the active tab against our pattern and... */
    if (urlRegex.test(tab.url)) {
        /* ...if it matches, send a message specifying a callback too */
        chrome.tabs.sendMessage(tab.id, { text: "report_back" },
                                doStuffWithDOM);
    }
});

index.html

<html>
  <button id="mybutton">click me</button>
</html>

Just save the index.html somewhere and load in the folder as an extension, containing the three other files. Open the index.html and push the extension button. It should show "click me".

TheChosenOne
  • 705
  • 1
  • 6
  • 14
  • To add to this: The content script is the script that can access the web page's DOM. It can pass elements to another script if you need to. The example above uses a background script, but it could be a popup. – Bees Apr 01 '15 at 15:08
  • do you mind explaining the `"matches": ["file:///*"],` line? Isn't this just for local files? – max pleaner Aug 26 '16 at 17:56
  • 1
    It works, but it stop working as soon as I add a `"default_popup": "popup.html"` in "browser_action" (manifest.json) – Elia Weiss Aug 07 '17 at 08:34
  • Is there a way to listen to DOM elements after the extension has been installed? i mean without the onclick event listener?i tried it inside chrome.runtime.onInstalled.addListener but didn't work or i didn't do it correctly. – mr.339 Aug 29 '19 at 08:15
0

Starting with Manifest V3, your content scripts won't be able to access anything generated by other loaded scripts and using a trick like inlining a your code inside <script> tag won't work due to stricter CSP rules. This caused me a lot of head ache since I couldn't figure out how to access library-generated DOM properties similar to React or Redux DevTools.

Instead, you have to now inject your script inside the service_worker with eg:

chrome.scripting.registerContentScripts([
  {
    id: 'inject',
    matches: ['<all_urls>'],
    js: ['inject.js'],
    runAt: 'document_end',
    world: 'MAIN'
  }
])

Notice the 'MAIN' property, not the default 'ISOLATED'. Then inside my inject.js I do whatever, eg:

window.addEventListener('load', () => {
  findReact()
})

Also you have to add the script to the manifest.json:

  "web_accessible_resources": [
    {
      "resources": ["inject.js"],
      "matches": ["<all_urls>"],
      "extension_ids": []
    }
  ],
  "externally_connectable": {
    "ids": ["*"]
  },

Not sure is "externally_connectable" needed. And you need to add at least "scripting" permissions. I used the React DevTools migration as my source https://github.com/facebook/react/pull/25145

TeemuK
  • 2,095
  • 1
  • 18
  • 17