DOM MutationObserver vs. CSS Animation vs. Mutation Events (v7)

Revision 7 of this benchmark created on


Preparation HTML

<p id="message"></p>
<div id="haystack"></div>

<style type="text/css">
    @-moz-keyframes nodeInserted {
        from {
            clip: rect(1px, auto, auto, auto);
        }
        to {
            clip: rect(0px, auto, auto, auto);
        }
    }
    @-webkit-keyframes nodeInserted {
        from {
            clip: rect(1px, auto, auto, auto);
        }
        to {
            clip: rect(0px, auto, auto, auto);
        }
    }
    @-ms-keyframes nodeInserted {
        from {
            clip: rect(1px, auto, auto, auto);
        }
        to {
            clip: rect(0px, auto, auto, auto);
        }
    }
    @-o-keyframes nodeInserted {
        from {
            clip: rect(1px, auto, auto, auto);
        }
        to {
            clip: rect(0px, auto, auto, auto);
        }
    }
    @keyframes nodeInserted {
        from {
            clip: rect(1px, auto, auto, auto);
        }
        to {
            clip: rect(0px, auto, auto, auto);
        }
    }
    .inserted {
        -o-animation-duration: 0.001s;
        -ms-animation-duration: 0.001s;
        -moz-animation-duration: 0.001s;
        -webkit-animation-duration: 0.001s;
        animation-duration: 0.001s;
        -o-animation-name: nodeInserted;
        -ms-animation-name: nodeInserted;
        -moz-animation-name: nodeInserted;
        -webkit-animation-name: nodeInserted;
        animation-name: nodeInserted;
    }
</style>

<script>
    console.log("preparation");
    var haystack = document.getElementById("haystack");

    var html = '';
    var batch = 100;
    for (var i = 0; i < batch; ++i) {
        html += '<span class="inserted"></span>';
    }

    function setHaystack() {
        haystack.innerHTML = html;
    }

    var isInserted = function(node) {
        return node.className === 'inserted';
    };
    var globalDeferred;
    var resolve = function() {
        if (globalDeferred) {
            var tmpDeferred = globalDeferred;
            globalDeferred = null;
            tmpDeferred.resolve();
        } else {
            throw 'globalDeferred not found';
        }
    };

    var setMessage = function(messageHtml) {
        document.getElementById("message").innerHTML = messageHtml;
    };

    var mutationListener = function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.type === "childList" && mutation.addedNodes && mutation.addedNodes.length === batch) {
                resolve();
            }
        });
    };

    var observer;
    if (window.MutationObserver) {
        setMessage("Your browser supports <code>MutationObserver</code>.");
        observer = new MutationObserver(mutationListener);
    } else if (window.WebKitMutationObserver) {
        setMessage("Your browser supports <code>WebKitMutationObserver</code>.");
        observer = new WebKitMutationObserver(mutationListener);
    } else {
        setMessage("Your browser does not support <code>MutationObserver</code>. You will not be able to run these tests.");
    }

    var animationType;

    var j = 0;

    function foundOne() {
        ++j;
        if (j === batch) {
            resolve();
            j = 0;
        }
    }

    var animationListener = function(event) {
        var type = event.type;
        if (!animationType) {
            animationType = type;
        } else if (animationType !== type) {
            return;
        }
        if (event.animationName === "nodeInserted") {
            foundOne();
        }
    };
var k = 0;
    var nodeInsertedListener = function(event) {
        console.log(isInserted(event.target), ++k);
        if (isInserted(event.target)) {
            foundOne();
        }
    };

    // Note: cannot use global setup when using ui.benchmarks[i].setup
    ui.benchmarks[0].setup = function() {
        console.log("setup[0]");
        observer.observe(haystack, {
            childList: true,
            subtree: true
        });
    };

    ui.benchmarks[1].setup = function() {
        console.log("setup[1]");
        haystack.addEventListener("animationstart", animationListener, false);
        haystack.addEventListener("MSAnimationStart", animationListener, false);
        haystack.addEventListener("webkitAnimationStart", animationListener, false);
        j = 0;
    };

    ui.benchmarks[2].setup = function() {
        console.log("setup[2]");
        haystack.addEventListener("DOMNodeInserted",
            nodeInsertedListener, false);
        j = 0;
    };

    ui.benchmarks[0].teardown = function() {
        console.log("teardown[0]");
        observer.disconnect();
        haystack.innerHTML = "";
    };

    ui.benchmarks[1].teardown = function() {
        console.log("teardown[1]");
        haystack.removeEventListener("animationstart", animationListener, false);
        haystack.removeEventListener("MSAnimationStart", animationListener, false);
        haystack.removeEventListener("webkitAnimationStart",
            animationListener, false);
        haystack.innerHTML = "";
        j = 0;
    };

    ui.benchmarks[2].teardown = function() {
        console.log("teardown[2]");
        haystack.removeEventListener("DOMNodeInserted",
            nodeInsertedListener, false);
        haystack.innerHTML = "";
        j = 0;
    };
</script>

Test runner

Ready to run.

Testing in
TestOps/sec
MutationObserver
// async test
globalDeferred = deferred;
setHaystack();
ready
CSS Animations
// async test
globalDeferred = deferred;
setHaystack();
ready
Mutation Events (DOMNodeInserted)
// async test
globalDeferred = deferred;
setHaystack();
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.