SOURCE

console 命令行工具 X clear

                    
>
console
<!DOCTYPE html>
<html>
<head>
    <style>
        body {
            color: green;
        }
        div {
            background: white;
        }
    </style>
    <script>
        function listenMutations() {
            var observer = new MutationObserver(function(mutations) {
                showAllMutations(mutations)
                tryInsertBefore(mutations)
                observer.takeRecords()
            })
            observer.observe(document.getElementById('container'), {
                attributes: true,
                characterData: true,
                characterDataOldValue: true,
                childList: true,
                subtree: true
            })
        }
        function tryInsertBefore(mutations) {
            for(var I = 0; I < mutations.length; ++I){
                if(mutations[I].addedNodes && mutations[I].addedNodes.length>0){
                    for (var j = 0; j < mutations[I].addedNodes.length; ++j) {
                        var addedNode = mutations[I].addedNodes[j]
                        showSelection('before insertBefore--->')
                        var rangeNode = getSelectionRangeNode()
                        console.log('addedNode: ' + getNodeInfo(addedNode))
                        document.getElementById('container').insertBefore(addedNode, null)
                        showSelection('<---after insertBefore')
                        if (rangeNode && !checkRangeNodeTheSame(rangeNode.startContainer, rangeNode.endContainer)) {
                            setNativeRange(rangeNode.startContainer, rangeNode.endContainer, rangeNode.startOffset, rangeNode.endOffset)
                        }
                    }
                }
            }
        }
        function showSelection(tag) {
            console.log('show selection: ' + tag)
            var selection = document.getSelection();
            if (selection) {
                var nativeRange = selection.rangeCount > 0 && selection.getRangeAt(0);
                if (nativeRange) {
                    console.log('startContainer: ' + getNodeInfo(nativeRange.startContainer) + ', offset: ' + nativeRange.startOffset)
                    console.log('endContainer: ' + getNodeInfo(nativeRange.endContainer) + ', offset: ' + nativeRange.endOffset)
                }
            }
        }
        function getNodeInfo(node) {
            if (!node) {
                return null
            }
            return ' [' + node.nodeName + '] innertHtml: ' + node.innerHTML + ', data: ' + node.data + ', parent: ' + node.parentNode + ', parent.innerHtml: ' + (node.parentNode && node.parentNode.innerHTML) + ', parent.data: ' + (node.parentNode && node.parentNode.data)
        }
        function showAllMutations(mutations) {
            for (var i = 0; i < mutations.length; ++i) {
                var mutation = mutations[i];
                var info = {}
                info.type = mutation.type
                info.addedNodes = []
                if (mutation.addedNodes) {
                    for (var j = 0; j < mutation.addedNodes.length; ++j) {
                        info.addedNodes.push(getNodeInfo(mutation.addedNodes[j]))
                    }
                }
                info.removedNodes = []
                if (mutation.removedNodes) {
                    for (var j = 0; j < mutation.removedNodes.length; ++j) {
                        info.removedNodes.push(getNodeInfo(mutation.removedNodes[j]))
                    }
                }
                info.oldValue = mutation.oldValue
                info.previousSibing = getNodeInfo(mutation.previousSibling)
                info.nextSibing = getNodeInfo(mutation.nextSibling)
                info.target = getNodeInfo(mutation.target)
                info.attributeName = mutation.attributeName
                info.attributeNamespace = mutation.attributeNamespace
                console.log(info)
            }
        }
        function checkRangeNodeTheSame(startNode, endNode) {
            var selection = document.getSelection();
            if (!selection) return false;
            var native = selection.rangeCount > 0 && selection.getRangeAt(0)
            if (!native) return false
            if (startNode !== native.startContainer) {
                return false
            }
            if (endNode !== native.endContainer) {
                return false
            }
            return true
        }
        function getSelectionRangeNode() {
            var selection = document.getSelection();
            if (selection) {
                var native = selection.rangeCount > 0 && selection.getRangeAt(0);
                if (native) {
                    var range = {
                        'startContainer': native.startContainer,
                        'endContainer': native.endContainer,
                        'startOffset': native.startOffset,
                        'endOffset': native.endOffset
                    };
                    return range;
                }
            }
            return null;
        }
        function setNativeRange(startNode, endNode, startOffset, endOffset) {
            var selection = document.getSelection();
            if (selection) {
                if (startNode && endNode) {
                    var range = document.createRange();
                    console.log('set native range: start:'+startOffset+',end:'+endOffset)
                    range.setStart(startNode, startOffset);
                    range.setEnd(endNode, endOffset);
                    selection.removeAllRanges();
                    selection.addRange(range);
                }
            }
        }
    </script>
</head>
<body onload="listenMutations()">
Insert a character, and see the console logs. On safari(10.1) or chrome(59dev), rangeNode will change after method 'insertBefore' is called
<div id="container" contenteditable="true"><br></div>
</body>
</html>