Apply a Greasemonkey/Tampermonkey/userscript to an iframe?

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

This works with Greasemonkey 4, updated to work with 4.11

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(frame) {
  frame.addEventListener('load', function(e) {
    // give main() the `document` from the frame each time it loads
    main(e.event.contentDocument);
  });
});

Note that the selected element is just a placeholder indicating that an iframe has loaded. Firefox stores the DOM of the frame in a separate document that we have to key on explicitly. See also the MDN docs for EventTarget.addEventListener() and HTMLIFrameElement.contentDocument.

An older version of this code reworked the "been there" tracker from waitForKeyElements, but that is no longer necessary since we're adding a listener to the frame which monitors for future content loading and will then run our inner function anew.


In Greasemonkey (And Tampermonkey and most userscript engines) a script will fire on an iframe automatically if it meets the @include, @exclude, and/or @match directives.
And, a popular question is how to stop Greasemonkey from firing on iframes.

So, if your script had a match like:

@match https://fiddle.jshell.net/*

It would fire on jsFiddle "output" pages whether or not they appeared in an iframe.


If you wanted to fire on a JUST iframed content:

Then you would check the window.self property.
For example, suppose you had a target page like:

<body>
    <h1>I'm some webpage, either same-domain or not.</h1>
    <iframe src="//domain_B.com/somePath/somePage.htm">
...

Then you could use a script like:

// ==UserScript==
// @name    _Fires specially on domain_B.com iframes
// @match   *://domain_B.com/somePath/*
// ==/UserScript==

if (window.top === window.self) {
    //--- Script is on domain_B.com when/if it is the MAIN PAGE.
}
else {
    //--- Script is on domain_B.com when/if it is IN AN IFRAME.
    // DO YOUR STUFF HERE.
}

Important:

With the release of Greasemonkey 4, iframes handling is severely crippled (and many other things are broken, besides).
It still works properly with Tampermonkey, Violentmonkey and just about every other userscript engine.
It is strongly recommended (including by Greasemonkey itself) that you do not use Greasemonkey 4 or later.