2

In IE, we can listen to onreadystatechange event to know when document.write changes iframe's content. But in Chrome, it doesn't work.

<html>
<script>
function loadFrame() {
    var ifr = document.getElementById("iframeResult");
    var ifrw = (ifr.contentWindow) ? ifr.contentWindow : (ifr.contentDocument.document) ? ifr.contentDocument.document : ifr.contentDocument;
    ifrw.document.open();
    ifrw.document.write("<input type='submit' />");
    ifrw.document.close();
}
</script>
<body onload="loadFrame();">
<div><input type="submit" value="Reload Frame" onclick="loadFrame()" /></div>
<div>
    <iframe frameborder="0" id="iframeResult" style="background-color:red;" onreadystatechange="console.log('ready state changed');">
    </iframe>
</div>
</body>
<html>

In above code, when click Reload Frame button on IE, console outputs ready state changed, but in Chrome, it doesn't output anything.

How should we do in Chrome to know when document.write changes iframe's content?

EDIT:

Gideon is right, we can listen to onload event in Chrome. But if I comment document.open and document.close two lines, onload doesn't work any more. Does anybody have a solution to this?

zhm
  • 3,513
  • 3
  • 34
  • 55
  • This may be the best answer: [http://stackoverflow.com/a/14570614/1790154](http://stackoverflow.com/a/14570614/1790154). – zhm May 12 '16 at 13:22

3 Answers3

2

You can add the load event listener, which will be triggered when the iFrame is modified by the page.

document.getElementById("iframeResult").addEventListener("load", function(){
  console.log("iFrame has been loaded.");
});
Gideon Pyzer
  • 22,610
  • 7
  • 62
  • 68
  • 1
    Yes, it does work. But if I comment `document.open` and `document.close` two lines, it doesn't work any more. Do you know how to deal with this? – zhm May 06 '16 at 13:00
  • @MartinZhai Why do you want to comment those lines out? The `onload` event wont fire if there's nothing being loaded in it. – Gideon Pyzer May 06 '16 at 13:23
  • `document.open` clears current DOM, which triggers `onload`. If we don't do `document.open`, `document.write` will append elements to existing DOM, and `onload` won't be triggered. In my case, I need to know when `document.write` changes content of iframe. Either situation need to be considered. – zhm May 07 '16 at 04:13
1

I think I have come up with a solution to your problem. It involves using the MutationObserver API, in order to detect changes to the iFrame's DOM.

MutationObserver provides developers a way to react to changes in a DOM. It is designed as a replacement for Mutation Events defined in the DOM3 Events specification.

I also used the window.postMessage API to notify the parent page when the MutationObserver has detected DOM events, so as to allow the parent to respond.

I have created a simple example below. Please note that I have used * for origin, but it is recommended that you do origin checks for security reasons. Also note that Chrome doesn't allow frames to access other frames in the local file system, but it will work on a web server or you can test locally using FireFox, which doesn't have that restriction.

iframe.html

<head>
    <meta charset="utf-8">
</head>

<body>

    <script>
        var observer = new MutationObserver(function(mutations) {
            mutations.forEach(function(mutation) {
                if (mutation.type == 'childList') {
                    if (mutation.addedNodes.length >= 1) {
                        if (mutation.addedNodes[0].nodeName != '#text') {
                            window.parent.postMessage("DOMChanged", "*");
                        }
                    } else if (mutation.removedNodes.length >= 1) {
                        window.parent.postMessage("DOMChanged", "*");
                    }
                } else if (mutation.type == 'attributes') {
                    window.parent.postMessage("DOMChanged", "*");
                }
            });


        });

        var observerConfig = {
            attributes: true,
            childList: true,
            characterData: true
        };

        // listen to all changes to body and child nodes
        var targetNode = document.body;
        observer.observe(targetNode, observerConfig);
    </script>
</body>

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
</head>

<body>
    <div>
        <input type="submit" value="Update iFrame" onclick="updateiFrameDOM()" />
    </div>
    <iframe src="iframe.html" id="iframeResult"></iframe>

    <script>
        function updateiFrameDOM() {
            var ifr = document.getElementById("iframeResult");
            var ifrw = (ifr.contentWindow) ? ifr.contentWindow : (ifr.contentDocument.document) ? ifr.contentDocument.document : ifr.contentDocument;

            var div = document.createElement("div");
            var text = document.createTextNode("Hello");
            div.appendChild(text);

            var body = ifrw.document.getElementsByTagName("body")[0];
            body.appendChild(div);

        }

        // Create IE + others compatible event handler
        var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
        var eventer = window[eventMethod];
        var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";

        // Listen to message from child window
        eventer(messageEvent, function(e) {
            console.log(e.data);
        }, false);
    </script>

</body>

</html>

Some additional sources I used:

Respond to DOM Changes with Mutation Observers

window.postMessage Tip: Child-To-Parent Communication

I hope this helps you.

Gideon Pyzer
  • 22,610
  • 7
  • 62
  • 68
  • It works on FF. But if I change `body.appendChild` to `document.write`, it doesn't work anymore. Although, you provide a way to look into. I'll do some research on if we can observe DOM changes in Chrome. Thanks. – zhm May 08 '16 at 09:06
  • @MartinZhai It does work in Chrome but must be done on web server instead of local file system. My code snippet listens on `document.body` so probably doesn't capture changes via `document.write`. Have a play around and see what you come up with. Hopefully my solution has been of help to you. I've learnt a few things myself. – Gideon Pyzer May 09 '16 at 07:25
  • Yes, it helps. I found another helpful answer [here](http://stackoverflow.com/a/14570614/1790154). – zhm May 09 '16 at 08:01
0

This code does the trick:

<script>
var observeDOM = (function(){
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver,
        eventListenerSupported = window.addEventListener;

    return function(obj, callback){
        if( MutationObserver ){
            // define a new observer
            var obs = new MutationObserver(function(mutations, observer){
                if( mutations[0].addedNodes.length || mutations[0].removedNodes.length )
                    callback();
            });
            // have the observer observe foo for changes in children
            obs.observe( obj, { childList:true, subtree:true });
        }
        else if( eventListenerSupported ){
            obj.addEventListener('DOMNodeInserted', callback, false);
            obj.addEventListener('DOMNodeRemoved', callback, false);
        }
    }
})();

window.onload = function() {
    // Observe a specific DOM element:
    observeDOM( document.getElementById("iframeResult").contentDocument ,function(){ 
        console.log('dom changed');
    });
}

function reload() {
    document.getElementById("iframeResult").contentDocument.write("<div>abc</div>");
}
</script>
<body>
<input type="submit" onclick="reload();" value="Reload" />
<iframe id="iframeResult"></iframe>
</body>

Be aware of that, observer must be added to document, not document.body. Because first call to document.write() will auto call document.open(), it will cover old document.body with a new one.

zhm
  • 3,513
  • 3
  • 34
  • 55