// HTML5 text/html .innerHTML getter function (with some optimizations)
(function() {

window.addEventListener("load", function() {
    var start = new Date().getTime();
    var markup = getInnerHTML(document);
    var end = new Date().getTime();
    alert(end - start + "ms");
    alert(markup);
}, false);

var textReserved = /(<)|(>)|(&)|(\u00A0)/g;
function escapeTextNodeValue(data) {
    return data.replace(textReserved, function(m, a, b, c) {
        if (a) return "&lt;";
        if (b) return "&gt;"
        if (c) return "&amp;";
        return "&nbsp;"
    });
}

var attrReserved = /(&)|(")|(\u00A0)/g;
function escapeAttrNodeValue(value) {
    return value.replace(attrReserved, function(m, a, b) {
        if (a) return "&amp;";
        if (b) return "&quot;";
        return "&nbsp;";
    });
}

function isVoidElement(tag) {
     return tag === "area" || tag === "base" || tag === "basefont" || tag === "bgsound" || tag === "br" || tag === "col" || tag === "command" || tag === "embed" || tag === "event-source" || tag === "frame" || tag === "hr" || tag === "img" || tag === "input" || tag === "link" || tag === "meta" || tag === "param" || tag === "spacer" || tag === "source" || tag === "wbr";     
}

function needsNewlinePrefix(tag) {
    return tag === "listing" || tag === "pre" || tag === "textarea";
}

function isCDATA(tag) {
    return tag === "iframe" || tag === "noembed" || tag === "noframes" || tag === "noscript" || tag === "plaintext" || tag === "script" || tag === "style" || tag === "xmp";
}

function getAttrHTML(attr) {
    var markup = attr.name.toLowerCase();
    markup += '="';
    markup += escapeAttrNodeValue(attr.value);
    markup += '"';
    return markup;
}

function getInnerHTML(node) {
    var markup = "";
    if (node.hasChildNodes()) {
        var current = node.firstChild;
        var cdata = null;
        for ( ; ; ) {
            switch (current.nodeType) {
                case 1:
                    var tag = current.tagName.toLowerCase();
                    if (cdata == null && isCDATA(tag)) {
                        cdata = current;
                    }
                    markup += "<";
                    markup += tag;
                    var attrs = current.attributes;
                    for (var i = 0; i < attrs.length; ++i) {
                        markup += " ";
                        markup += getAttrHTML(attrs[i]);
                    }
                    markup += ">";
                    if (needsNewlinePrefix(tag)) {
                       markup += "\n";
                    }
                    break;
                case 3: case 4:
                    markup += cdata != null ? current.data : escapeTextNodeValue(current.data);
                    break;
                case 7:
                    markup += "<?";
                    markup += current.target;
                    markup += current.data;
                    markup += "?>";
                    break;
                case 8:
                    markup += "<!--";
                    markup += current.data;
                    markup += "-->";
                    break;
                case 10:
                    markup += "<!DOCTYPE ";
                    markup += current.name.toLowerCase()
                    markup += ">";
                    break;
                default:
                    throw "INVALID_STATE_ERR";
            }
            if (current.firstChild) {
                current = current.firstChild;
            } else {
                for ( ; ; ) {
                    if (current.nodeType === 1) {
                        var tag = current.tagName.toLowerCase();
                        if (!isVoidElement(tag)) {
                            markup += "</";
                            markup += tag;
                            markup += ">";
                        }
                        if (current == cdata) {
                            cdata = null;
                        }
                    }
                    if (current == node) {
                        return markup;
                    } else if (current.nextSibling) {
                        current = current.nextSibling;
                        break;
                    } else {
                        current = current.parentNode;
                    }
                }
            }
        }
    }
    return markup;
}
})();