How can I make a userscript run inside an iframe?
-
4This is a known bug: [Greasemonkey issue 2574](https://github.com/greasemonkey/greasemonkey/issues/2574) states "Greasemonkey 4 as of today only detects navigation events at the top level, so it effectively applies [@noframes](https://wiki.greasespot.net/Metadata_Block#.40noframes) to every script." – Adam Katz Nov 28 '18 at 23:42
6 Answers
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.

- 1
- 1

- 90,639
- 22
- 233
- 295
-
3Note that this is [broken](https://github.com/greasemonkey/greasemonkey/issues/2574) in GreaseMonkey 4 – Mathnerd314 Dec 29 '18 at 23:20
-
1Thanks, @Mathnerd314. Updated the answer. Note that there is a separate tag now for GM4, to distinguish it from GM versions that worked well. – Brock Adams Dec 29 '18 at 23:33
-
3Greasemonkey doesn't seem to recommend that you don't use 4 or later. They are merely informing it breaks backward compatibility. – szpanczyk Oct 18 '20 at 11:23
-
It should probably be mentioned at the top of this answer that it does not work with Greasemonkey currently, only with Tampermonkey and Violentmonkey. So people directly know for which addon the solution applies, I almost missed it. – baptx Nov 19 '22 at 15:19
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.

- 14,455
- 5
- 68
- 83
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.

- 63
- 2
- 6
-
1I can confirm this works with Tampermonkey for Goggle Chrome too and looks much more elegant comparing to the original answer. A big thanks, this was **really** helpful! – Ivan Shatsky Jan 17 '21 at 09:08
-
Nice find! It's too bad I found this after independently coming to pretty much the same conclusion. I've updated [my answer](https://stackoverflow.com/a/55837286/519360) with new code that uses the `load` listener in place of the `waitForKeyElements` listener because it's cleaner and doesn't need to mess with the `wfke_found` tracker. WFKE merely applies the event listener and is then done. – Adam Katz Aug 06 '22 at 20:45
-
@AdamKatz I had to use this code instead of the one from your answer which was not executing in the iframe. I added an answer with script examples (you can see that it will not work if we replace it with your code): https://stackoverflow.com/a/75268492/1176454. – baptx Jan 28 '23 at 15:05
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

- 3,681
- 3
- 28
- 52
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);

- 2,425
- 1
- 24
- 15
Here is how I was able to execute JavaScript in an iframe with Greasemonkey 4. I had a hard time to do it even after reading other answers so maybe it will help others.
If you want to do it in an iframe that has a location, for example on a CodePen like https://codepen.io/abbassac/pen/Oazgjx where the iframe is https://cdpn.io/abbassac/fullpage/Oazgjx?anon=true&view=, you just need to run the script on the iframe URL.
The Greasemonkey script will look like this:
// ==UserScript==
// @name JavaScript in iframe with location
// @version 1
// @grant none
// @include https://cdpn.io/abbassac/fullpage/Oazgjx?anon=true&view=
// ==/UserScript==
// works randomly when loading the top page https://codepen.io/abbassac/pen/Oazgjx but works every time when reloading the iframe (the reload option is displayed when doing a right mouse click in the iframe)
document.getElementById("button").value = "changed"; // value is set but quickly overwritten
console.log(document.location);
Note: You can see that the button value is changed but often quickly overwritten even if we use // @run-at document-idle
, I don't know if there is a way to keep the new value (maybe it is overwritten due to how CodePen works). I also don't know why we often have to reload the frame to execute the Greasemonkey script (maybe it is a bug from Greasemonkey).
If you want to do it in an iframe that has no location, for example on https://www.w3schools.com/js/tryit.asp?filename=tryjs_addeventlistener_displaydate, you can do it like this:
// ==UserScript==
// @name JavaScript in iframe without location
// @version 1
// @grant none
// @include https://www.w3schools.com/js/tryit.asp?filename=tryjs_addeventlistener_displaydate
// @require https://git.io/waitForKeyElements.js
// ==/UserScript==
// works when iframe has no location (a different location will display an error in the console: waitForKeyElements: actionFunction error SecurityError: Permission denied to access property "eval" on cross-origin object)
function main(where) {
where.eval(`
document.getElementById("myBtn").firstChild.nodeValue = "changed";
`);
}
// based on https://stackoverflow.com/questions/37616818/apply-a-greasemonkey-tampermonkey-userscript-to-an-iframe/65249968#65249968
waitForKeyElements("iframe, frame", function(elem) {
elem.addEventListener("load", function () {
elem.removeAttribute("wfke_found");
});
main(elem.contentWindow);
});
Update: You may not need the waitForKeyElements function and can directly execute JavaScript code in an iframe that is on the same origin like this:
window[0].document.getElementById("myBtn").firstChild.nodeValue = "changed";

- 3,428
- 6
- 33
- 42