6

I'd like to run some javascript in the context of an iframe's window. Right now the only way I can think to do that is to inject a script tag:

myIframe = document.createElement('iframe');
myIframe.setAttribute('name', 'xyz123');
document.body.appendChild(myIframe);

myIframe.contentWindow.document.write(`
    <script>
        console.log('The current window name is:', window.name);
    </script>
`);

Note: this is a same-domain iframe, without a src, so I have full access to the contentWindow.

It's important for my use case that the code runs with the correct globals; window, document etc should all be scoped to the iframe itself.

Is there any other way I can do this? The above works, but the script needs to run on different domains all with different CSP rules, which means adding support for nonces/hashes etc.

Is it possible to do something like:

myIframe.contentWindow.run(function() {
    console.log('The current window name is:' window.name);
});

I've tried myIframe.contentWindow.setTimeout but that still seems to run the code in the context of the parent window.

bluepnume
  • 16,460
  • 8
  • 38
  • 48

4 Answers4

3

You can actually create that run function, and then apply a callback function to this which of course will be the iframe context. Then you can access iframe elements by using this:

myIframe.contentWindow.run = function(fn) {
    fn.apply(this);
};

myIframe.contentWindow.run(function() {
    console.log('(run) The current window name is:', this.window.name);
});

Console output

(run) The current window name is: xyz123

You can check my example here: http://zikro.gr/dbg/html/con-frame/

EDIT

If you want to just use window rather than this.window, then you can create a parameter to the inline function with he name window, and then just pass this.window to that function like this:

myIframe.contentWindow.run = function(fn) {
    fn.call(this, this.window);
};

myIframe.contentWindow.run(function(window) {
    console.log('(run) The current window name is:', window.name);
});

And it still works as expected.

Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • This is a great example -- only problem is, it changes the semantics of any functions that will exist in the iframe to need to reference `this.window` rather than just `window` -- but this is definitely the best answer so far. Thanks! – bluepnume Sep 06 '17 at 02:15
  • @bluepnume you're welcome. I updated my answer to have an inline function that has a parameter with the name `window` that you can use; it eliminates the need of using `this.window`. This will work unless if you need the whole inline function to be explicit applied from a specific source which then won't be possible to have that specific parameter. – Christos Lytras Sep 06 '17 at 07:50
2

Maybe split the javascript to part run from main window (let's call it main.js) and from iframe (let's call it iframe.js). Then in iframe's src place link to iframe.js or iframe.html which loads js file (I'm not sure if you can include javascript straight from src attribute).

If you load js into the iframe, use solution at Calling a function inside an iframe from outside the iframe.

Marcin Szwarc
  • 569
  • 3
  • 13
  • Thanks for the suggestion -- but for my use case, I need to be able to insert arbitrary javascript into the frame, which isn't necessarily hosted anywhere -- hence the script tag. – bluepnume Sep 06 '17 at 02:10
1
window.name='main window'; // just for debugging
var myIframe = document.createElement('iframe'), frameScript = document.createElement('script');
document.body.appendChild(myIframe);
frameScript.textContent = "window.top.name='topWindow';window.name = 'xyz123';function WhereAmI(){return(window.name);} window.parent.postMessage('frame_initialized', '*'); "
myIframe.contentWindow.document.documentElement.appendChild(frameScript);
function OnMessage(event) {
  if(typeof event.data == 'string') switch(event.data) {
  case 'frame_initialized':
    myIframe.contentWindow.document.body.appendChild( document.createTextNode(myIframe.contentWindow.WhereAmI()) );
    console.log('Now running in', myIframe.contentWindow.WhereAmI());
    break;
  }
}
window.addEventListener('message', OnMessage, false);

Tested with Firefox and Chromium.

Instead of .textContent you can apply the .src to frameScript, so the script can load asynchronously. Then you can call postMessage as shown above or call a callback function to notify the parent window.

Note that in your original code window.frameElement.name is initialized. Your script asks then for window.name. FireFox copies the value automatically to the window, causing some confusion.

gnblizz
  • 66
  • 3
  • To improve this answer I need to understand your scenario better. Have you access to the HTML on both domains? Should it run only for you locally or for everybody? Is the use of script manager like GreaseMonkey possible? – gnblizz Sep 06 '17 at 01:43
  • Thanks for the suggestion, but I'm not sure window.postMessage would specifically help me to run arbitrary code in the frame -- I'd need some way to add a message listener in the frame for this to work, which brings me back to the original problem. – bluepnume Sep 06 '17 at 02:12
  • To answer your question, this needs to work for everyone so greasemonkey won't work. There's no second domain -- the iframe is loaded without an `src` – bluepnume Sep 06 '17 at 02:13
1

You need to load the script asynchronously (i.e. $.getScript()) and then invoke it on .contentWindow. I haven't tested but this should work.

(function() {

   var myIframe = document.createElement('iframe#iframe'):

   var jsSnippet;
   var iframeScript = function(url) {
        script = document.createElement('script'),
        scripts = document.getElementsByTagName('script')[0];
        script.src = url;
        return jsSnippet = scripts.parentNode.insertBefore(script, scripts);
    };

    myIframe.setAttribute('name', 'xyz123'):
    myIframe.appendChild(jsSnippet);
    document.body.appendChild(myIframe);
    return document.getElementById('#iframe').contentWindow.iframeScript();
})

These two resources will be helpful if this solution doesn't work: https://plainjs.com/javascript/ajax/load-a-script-file-asynchronously-49/

Invoking JavaScript code in an iframe from the parent page