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>