Search code examples
javascripthtmliframename-collision

Reset/Reload IFrame JavaScript context


I am creating an interactive code editor that consists of a text box and an <iframe> element. Whenever the user types code into the text box, the content of the IFrame is written to and a <script> tag inside the IFrame is populated with the contents of the code text box, causing the code that the user typed to be run inside the IFrame. Every time the user types, the IFrame should be reset/reloaded and the document written to again with the new contents of the text box in the IFrame script tag, essentially auto-running any code they type as they type it.

However, when doing this I noticed that namespace conflicts came about as variables the user would declare in their code would be persisted between IFrame refreshes. I fixed this by wrapping the inserted code into into an IIFE, but then noticed that when the user would run a loop and then reload the IFrame twice, two loops would be running.

Evidently, the JavaScript context of the IFrame is not being reset. I have tried the answers given here including reloading the IFrame using .contentWindow.location.reload() as well as changing the IFrame source with .src = "about:blank" but these just reset the content of the document, not the IFrame JavaScript context which according to these answers are persisted if the IFrame is on the same domain.

Here is the actual code that I am working with (Clone the repository -> Run npm install -> Run npm run dev -> Open examples/Sandbox/index.html).

Here is a minimal reproducible example of the issue (Relies on the existence on an IFrame element with ID frame):

const frame = document.getElementById("frame");
let i = 1;

function updateFrame() {
    frame.contentWindow.location.reload();

    frame.contentDocument.open();

    frame.contentDocument.write(`
        <html>
            <body>
                <script>
                    (function() {
                        let a = 0;

                        function print() {
                            console.log(${i++} + ": " + a++);

                            requestAnimationFrame(print);
                        }

                        print();
                    })();
                </script>
            </body>
        </html>
    `);

    frame.contentDocument.close();
}

updateFrame();
updateFrame();

See on JSFiddle.

Notice that updateFrame() is called twice, which should reload the content of the IFrame leaving only the second loop running, however both print() loops are simultaneously printing to the console, showing that the context is kept the same throughout reloads.

The intended behaviour is that the IFrame content and the JavaScript context should be completely reset between reloads. In the example, only 2: ... should be repeatedly printed to console, instead of both 1: ... and 2: .... How can I refresh an IFrame such that its JavaScript context is not persisted throughout reloads, and essentially acts the same as reloading a page manually?


Solution

  • Once you create an IFrame, you cannot edit its source in order to prevent people using IFrames to inject code into sites for purposes of stealing information etc or creating fake websites that have IFrames with malicious code included to otherwise secure domains.

    You will have to render the updates from the editor via AJAX to a backend such as PHP server or node.js or completely remove the IFrame element and create a brand new one via

    var if = document.createElement("IFRAME");
    if.src = /* your code */
    var container = document.getElementById("container");
    

    Then use a similar system to delete the item each time you want an update.

    container.innerHTML = "";