'Apply a Greasemonkey/Tampermonkey userscript to an iframe

How can I make a userscript run inside an iframe?



Solution 1:[1]

This is a solution for cases where the iframe has no location to trigger @include or @match.

This works with Greasemonkey 4.

We must wait for each frame to be loaded before we can operate on it. I do this by using waitForKeyElements.js, which waits for elements matching a given CSS selector, just like looping through the matches in document.querySelectorAll("selector"), and then applies a given function to the response:

// ==UserScript==
// @include https://blah.example.com/*
// @require https://git.io/waitForKeyElements.js
// ==/UserScript==

function main(where) {
  // do stuff here with  where  instead of  document
  // e.g. use  where.querySelector()  in place of  document.querySelector()
  // and add stylesheets with  where.head.appendChild(stylesheet)
}

main(document); // run it on the top level document (as normal)

waitForKeyElements("iframe, frame", function(elem) {
  elem.removeAttribute("wfke_found"); // cheat wfke's been_there, use our own
  for (let f=0; f < frames.length; f++) {
    if (!frames[f].document.body.getAttribute("been_there")) {

      main(frames[f].document);

      frames[f].document.body.setAttribute("been_there", 1);
    }
  }
});

Note that the selected element is just a placeholder indicating that an iframe has loaded. We remove the "been there" tracker from waitForKeyElements because the frame may be loaded again later (we can't just use that iframe because its contents are loaded elsewhere).

When we know a frame has loaded, we loop through each frame and look for our marker, an HTML attribute in the frame's body called been_there (like <body been_there="1">). If it is missing, we can run our main() function on the frame's document. When we're done, we add the been_there attribute so we don't get triggered again.

Solution 2:[2]

Note that if you're making a chrome extension for your userscript, you also need to add "all_frames": true to your manifest or your extension won't work on iframes.

Example from manifest file:

"content_scripts": [
    {
      "matches": ["*://*/*"],
      "all_frames": true,
      "js":["dont.js"],
      "run_at":"document_start"
    }
  ]

Example use case: https://github.com/NavinF/dont

Solution 3:[3]

The solution from https://stackoverflow.com/a/55837286/12469007 wasn't working for me so I have changed it a bit. It works with Greasemonkey 4.10.

// ==UserScript==
// @include https://blah.example.com/*
// @require https://git.io/waitForKeyElements.js
// ==/UserScript==

function main(where) {
  // do stuff here with  where  instead of  document
  // e.g. use  where.querySelector()  in place of  document.querySelector()
  // and add stylesheets with  where.head.appendChild(stylesheet)
}

main(document); // run it on the top level document (as normal)

waitForKeyElements("iframe, frame", function(elem) {
  elem.addEventListener("load", function () {
    elem.removeAttribute("wfke_found");
  });
  main(elem.contentDocument);
});

The big change is that it now works even when navigating an iframe.

Solution 4:[4]

iframe.contentWindow.document allows obtaining iframe content in FireMonkey, e.g.:

const iframeDocument = document.getElementById('iframe_id').contentWindow.document;
const iframeAnchors = iframeDocument.evaluate(
  '//a',
  iframeDocument,
  null,
  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  null);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Max Brauer
Solution 4 Sergey Ushakov