diff --git a/build.json b/build.json
index 9808f3c..f2cd87e 100644
--- a/build.json
+++ b/build.json
@@ -1,4 +1,5 @@
[
+ "../observe-js/src/observe.js",
"src/sidetable.js",
"src/wrappers.js",
"src/wrappers/events.js",
diff --git a/shadowdom.js b/shadowdom.js
index 55691ef..3ceef66 100644
--- a/shadowdom.js
+++ b/shadowdom.js
@@ -13,32 +13,32 @@
base = src.slice(0, -match[0].length);
}
});
- base += 'src/';
[
- 'sidetable.js',
- 'wrappers.js',
- 'wrappers/events.js',
- 'wrappers/NodeList.js',
- 'wrappers/Node.js',
- 'querySelector.js',
- 'wrappers/node-interfaces.js',
- 'wrappers/CharacterData.js',
- 'wrappers/Element.js',
- 'wrappers/HTMLElement.js',
- 'wrappers/HTMLContentElement.js',
- 'wrappers/HTMLShadowElement.js',
- 'wrappers/HTMLTemplateElement.js',
- 'wrappers/HTMLUnknownElement.js',
- 'wrappers/generic.js',
- 'wrappers/ShadowRoot.js',
- 'ShadowRenderer.js',
- 'wrappers/elements-with-form-property.js',
- 'wrappers/Document.js',
- 'wrappers/Window.js',
- 'wrappers/MutationObserver.js',
- 'wrappers/Range.js',
- 'wrappers/override-constructors.js'
+ '../observe-js/src/observe.js',
+ 'src/sidetable.js',
+ 'src/wrappers.js',
+ 'src/wrappers/events.js',
+ 'src/wrappers/NodeList.js',
+ 'src/wrappers/Node.js',
+ 'src/querySelector.js',
+ 'src/wrappers/node-interfaces.js',
+ 'src/wrappers/CharacterData.js',
+ 'src/wrappers/Element.js',
+ 'src/wrappers/HTMLElement.js',
+ 'src/wrappers/HTMLContentElement.js',
+ 'src/wrappers/HTMLShadowElement.js',
+ 'src/wrappers/HTMLTemplateElement.js',
+ 'src/wrappers/HTMLUnknownElement.js',
+ 'src/wrappers/generic.js',
+ 'src/wrappers/ShadowRoot.js',
+ 'src/ShadowRenderer.js',
+ 'src/wrappers/elements-with-form-property.js',
+ 'src/wrappers/Document.js',
+ 'src/wrappers/Window.js',
+ 'src/wrappers/MutationObserver.js',
+ 'src/wrappers/Range.js',
+ 'src/wrappers/override-constructors.js'
].forEach(function(src) {
document.write('');
});
diff --git a/src/ShadowRenderer.js b/src/ShadowRenderer.js
index a533848..fac6c52 100644
--- a/src/ShadowRenderer.js
+++ b/src/ShadowRenderer.js
@@ -48,64 +48,52 @@
updateWrapperDown(parentNodeWrapper);
}
- // This object groups DOM operations. This is supposed to be the DOM as the
- // browser/render tree sees it.
- // When changes are done to the visual DOM the logical DOM needs to be updated
- // to reflect the correct tree.
- function removeAllChildNodes(parentNodeWrapper) {
+ function insertBefore(parentNodeWrapper, newChildWrapper, refChildWrapper) {
var parentNode = unwrap(parentNodeWrapper);
- updateAllChildNodes(parentNodeWrapper);
- if (parentNode.firstChild)
- parentNode.textContent = '';
- }
+ var newChild = unwrap(newChildWrapper);
+ var refChild = refChildWrapper && unwrap(refChildWrapper);
- function appendChild(parentNodeWrapper, childWrapper) {
- var parentNode = unwrap(parentNodeWrapper);
- var child = unwrap(childWrapper);
- if (child.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
- updateAllChildNodes(childWrapper);
+ remove(newChildWrapper);
+ updateWrapperUpAndSideways(newChildWrapper);
- } else {
- remove(childWrapper);
- updateWrapperUpAndSideways(childWrapper);
- }
+ if (!refChildWrapper) {
+ parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild;
+ if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild)
+ parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild;
- parentNodeWrapper.lastChild_ = parentNodeWrapper.lastChild;
- if (parentNodeWrapper.lastChild === parentNodeWrapper.firstChild)
- parentNodeWrapper.firstChild_ = parentNodeWrapper.firstChild;
+ var lastChildWrapper = wrap(parentNode.lastChild);
+ if (lastChildWrapper)
+ lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling;
+ } else {
+ if (parentNodeWrapper.firstChild === refChildWrapper)
+ parentNodeWrapper.firstChild_ = refChildWrapper;
- var lastChildWrapper = wrap(parentNode.lastChild);
- if (lastChildWrapper) {
- lastChildWrapper.nextSibling_ = lastChildWrapper.nextSibling;
+ refChildWrapper.previousSibling_ = refChildWrapper.previousSibling;
}
- parentNode.appendChild(child);
+ parentNode.insertBefore(newChild, refChild);
}
- function removeChild(parentNodeWrapper, childWrapper) {
- var parentNode = unwrap(parentNodeWrapper);
- var child = unwrap(childWrapper);
-
- updateWrapperUpAndSideways(childWrapper);
+ function remove(nodeWrapper) {
+ var node = unwrap(nodeWrapper)
+ var parentNode = node.parentNode;
+ if (!parentNode)
+ return;
- if (childWrapper.previousSibling)
- childWrapper.previousSibling.nextSibling_ = childWrapper;
- if (childWrapper.nextSibling)
- childWrapper.nextSibling.previousSibling_ = childWrapper;
+ var parentNodeWrapper = wrap(parentNode);
+ updateWrapperUpAndSideways(nodeWrapper);
- if (parentNodeWrapper.lastChild === childWrapper)
- parentNodeWrapper.lastChild_ = childWrapper;
- if (parentNodeWrapper.firstChild === childWrapper)
- parentNodeWrapper.firstChild_ = childWrapper;
+ if (nodeWrapper.previousSibling)
+ nodeWrapper.previousSibling.nextSibling_ = nodeWrapper;
+ if (nodeWrapper.nextSibling)
+ nodeWrapper.nextSibling.previousSibling_ = nodeWrapper;
- parentNode.removeChild(child);
- }
+ if (parentNodeWrapper.lastChild === nodeWrapper)
+ parentNodeWrapper.lastChild_ = nodeWrapper;
+ if (parentNodeWrapper.firstChild === nodeWrapper)
+ parentNodeWrapper.firstChild_ = nodeWrapper;
- function remove(nodeWrapper) {
- var node = unwrap(nodeWrapper)
- var parentNode = node.parentNode;
- if (parentNode)
- removeChild(wrap(parentNode), nodeWrapper);
+ parentNode.removeChild(node);
}
var distributedChildNodesTable = new SideTable();
@@ -114,14 +102,6 @@
var rendererForHostTable = new SideTable();
var shadowDOMRendererTable = new SideTable();
- var reprCounter = 0;
-
- function repr(node) {
- if (!node.displayName)
- node.displayName = node.nodeName + '-' + ++reprCounter;
- return node.displayName;
- }
-
function distributeChildToInsertionPoint(child, insertionPoint) {
getDistributedChildNodes(insertionPoint).push(child);
assignToInsertionPoint(child, insertionPoint);
@@ -239,18 +219,15 @@
var renderTimer;
function renderAllPending() {
- renderTimer = null;
pendingDirtyRenderers.forEach(function(owner) {
owner.render();
});
pendingDirtyRenderers = [];
}
- function ShadowRenderer(host) {
- this.host = host;
- this.dirty = false;
- this.invalidateAttributes();
- this.associateNode(host);
+ function handleRequestAnimationFrame() {
+ renderTimer = null;
+ renderAllPending();
}
/**
@@ -279,10 +256,88 @@
return getRendererForHost(getHostForShadowRoot(shadowRoot));
}
+ var spliceDiff = new ArraySplice();
+ spliceDiff.equals = function(renderNode, rawNode) {
+ return unwrap(renderNode.node) === rawNode;
+ };
+
+ /**
+ * RenderNode is used as an in memory "render tree". When we render the
+ * composed tree we create a tree of RenderNodes, then we diff this against
+ * the real DOM tree and make minimal changes as needed.
+ */
+ function RenderNode(node) {
+ this.node = node;
+ this.childNodes = [];
+ }
+
+ RenderNode.prototype = {
+ append: function(node) {
+ var rv = new RenderNode(node);
+ this.childNodes.push(rv);
+ return rv;
+ },
+
+ sync: function(opt_added) {
+ var nodeWrapper = this.node;
+ // plain array of RenderNodes
+ var newChildren = this.childNodes;
+ // plain array of real nodes.
+ var oldChildren = getChildNodesSnapshot(unwrap(nodeWrapper));
+ var added = opt_added || new SideTable();
+
+ var splices = spliceDiff.calculateSplices(newChildren, oldChildren);
+
+ var newIndex = 0, oldIndex = 0;
+ var lastIndex = 0;
+ for (var i = 0; i < splices.length; i++) {
+ var splice = splices[i];
+ for (; lastIndex < splice.index; lastIndex++) {
+ oldIndex++;
+ newChildren[newIndex++].sync(added);
+ }
+
+ var removedCount = splice.removed.length;
+ for (var j = 0; j < removedCount; j++) {
+ var wrapper = wrap(oldChildren[oldIndex++]);
+ if (!added.get(wrapper))
+ remove(wrapper);
+ }
+
+ var addedCount = splice.addedCount;
+ var refNode = oldChildren[oldIndex] && wrap(oldChildren[oldIndex]);
+ for (var j = 0; j < addedCount; j++) {
+ var newChildRenderNode = newChildren[newIndex++];
+ var newChildWrapper = newChildRenderNode.node;
+ insertBefore(nodeWrapper, newChildWrapper, refNode);
+
+ // Keep track of added so that we do not remove the node after it
+ // has been added.
+ added.set(newChildWrapper, true);
+
+ newChildRenderNode.sync(added);
+ }
+
+ lastIndex += addedCount;
+ }
+
+ for (var i = lastIndex; i < newChildren.length; i++) {
+ newChildren[i++].sync(added);
+ }
+ }
+ };
+
+ function ShadowRenderer(host) {
+ this.host = host;
+ this.dirty = false;
+ this.invalidateAttributes();
+ this.associateNode(host);
+ }
+
ShadowRenderer.prototype = {
// http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#rendering-shadow-trees
- render: function() {
+ render: function(opt_renderNode) {
if (!this.dirty)
return;
@@ -290,15 +345,20 @@
this.treeComposition();
var host = this.host;
- var shadowDOM = host.shadowRoot;
+ var shadowRoot = host.shadowRoot;
- this.removeAllChildNodes(this.host);
+ this.associateNode(host);
+ var topMostRenderer = !renderNode;
+ var renderNode = opt_renderNode || new RenderNode(host);
- var shadowDOMChildNodes = getChildNodesSnapshot(shadowDOM);
+ var shadowDOMChildNodes = getChildNodesSnapshot(shadowRoot);
shadowDOMChildNodes.forEach(function(node) {
- this.renderNode(host, shadowDOM, node, false);
+ this.renderNode(shadowRoot, renderNode, node, false);
}, this);
+ if (topMostRenderer)
+ renderNode.sync();
+
this.dirty = false;
},
@@ -308,80 +368,84 @@
pendingDirtyRenderers.push(this);
if (renderTimer)
return;
- renderTimer = window[request](renderAllPending, 0);
+ renderTimer = window[request](handleRequestAnimationFrame, 0);
}
},
- renderNode: function(visualParent, tree, node, isNested) {
+ renderNode: function(shadowRoot, renderNode, node, isNested) {
if (isShadowHost(node)) {
- this.appendChild(visualParent, node);
+ renderNode = renderNode.append(node);
var renderer = getRendererForHost(node);
renderer.dirty = true; // Need to rerender due to reprojection.
- renderer.render();
+ renderer.render(renderNode);
} else if (isInsertionPoint(node)) {
- this.renderInsertionPoint(visualParent, tree, node, isNested);
+ this.renderInsertionPoint(shadowRoot, renderNode, node, isNested);
} else if (isShadowInsertionPoint(node)) {
- this.renderShadowInsertionPoint(visualParent, tree, node);
+ this.renderShadowInsertionPoint(shadowRoot, renderNode, node);
} else {
- this.renderAsAnyDomTree(visualParent, tree, node, isNested);
+ this.renderAsAnyDomTree(shadowRoot, renderNode, node, isNested);
}
},
- renderAsAnyDomTree: function(visualParent, tree, node, isNested) {
- this.appendChild(visualParent, node);
+ renderAsAnyDomTree: function(shadowRoot, renderNode, node, isNested) {
+ renderNode = renderNode.append(node);
if (isShadowHost(node)) {
- render(node);
+ var renderer = getRendererForHost(node);
+ renderer.render(renderNode);
} else {
var parent = node;
var logicalChildNodes = getChildNodesSnapshot(parent);
// We associate the parent of a content/shadow with the renderer
// because we may need to remove stale childNodes.
if (shadowDOMRendererTable.get(parent))
- this.removeAllChildNodes(parent);
+ this.associateNode(parent);
logicalChildNodes.forEach(function(node) {
- this.renderNode(parent, tree, node, isNested);
+ this.renderNode(shadowRoot, renderNode, node, isNested);
}, this);
}
},
- renderInsertionPoint: function(visualParent, tree, insertionPoint, isNested) {
+ renderInsertionPoint: function(shadowRoot, renderNode, insertionPoint,
+ isNested) {
var distributedChildNodes = getDistributedChildNodes(insertionPoint);
if (distributedChildNodes.length) {
- this.removeAllChildNodes(insertionPoint);
+ this.associateNode(insertionPoint);
distributedChildNodes.forEach(function(child) {
if (isInsertionPoint(child) && isNested)
- this.renderInsertionPoint(visualParent, tree, child, isNested);
+ this.renderInsertionPoint(shadowRoot, renderNode, child, isNested);
else
- this.renderAsAnyDomTree(visualParent, tree, child, isNested);
+ this.renderAsAnyDomTree(shadowRoot, renderNode, child, isNested);
}, this);
} else {
- this.renderFallbackContent(visualParent, insertionPoint);
+ this.renderFallbackContent(shadowRoot, renderNode, insertionPoint);
}
- this.remove(insertionPoint);
+ this.associateNode(insertionPoint.parentNode);
},
- renderShadowInsertionPoint: function(visualParent, tree, shadowInsertionPoint) {
- var nextOlderTree = tree.olderShadowRoot;
+ renderShadowInsertionPoint: function(shadowRoot, renderNode,
+ shadowInsertionPoint) {
+ var nextOlderTree = shadowRoot.olderShadowRoot;
if (nextOlderTree) {
assignToInsertionPoint(nextOlderTree, shadowInsertionPoint);
- this.remove(shadowInsertionPoint);
+ this.associateNode(shadowInsertionPoint.parentNode);
var shadowDOMChildNodes = getChildNodesSnapshot(nextOlderTree);
shadowDOMChildNodes.forEach(function(node) {
- this.renderNode(visualParent, nextOlderTree, node, true);
+ this.renderNode(nextOlderTree, renderNode, node, true);
}, this);
} else {
- this.renderFallbackContent(visualParent, shadowInsertionPoint);
+ this.renderFallbackContent(shadowRoot, renderNode,
+ shadowInsertionPoint);
}
},
- renderFallbackContent: function (visualParent, fallbackHost) {
+ renderFallbackContent: function(shadowRoot, renderNode, fallbackHost) {
var logicalChildNodes = getChildNodesSnapshot(fallbackHost);
this.associateNode(fallbackHost);
- this.remove(fallbackHost);
+ this.associateNode(fallbackHost.parentNode);
logicalChildNodes.forEach(function(node) {
- this.appendChild(visualParent, node);
+ this.renderAsAnyDomTree(shadowRoot, renderNode, node, false);
}, this);
},
@@ -498,23 +562,6 @@
}
},
- appendChild: function(parent, child) {
- // this.associateNode(child);
- this.associateNode(parent);
- appendChild(parent, child);
- },
-
- remove: function(node) {
- // this.associateNode(node);
- this.associateNode(node.parentNode);
- remove(node);
- },
-
- removeAllChildNodes: function(parent) {
- this.associateNode(parent);
- removeAllChildNodes(parent);
- },
-
associateNode: function(node) {
shadowDOMRendererTable.set(node, this);
}
@@ -613,9 +660,8 @@
// Exposed for testing
scope.visual = {
- removeAllChildNodes: removeAllChildNodes,
- appendChild: appendChild,
- removeChild: removeChild
+ insertBefore: insertBefore,
+ remove: remove,
};
})(this.ShadowDOMPolyfill);
\ No newline at end of file
diff --git a/test/js/paralleltrees.js b/test/js/paralleltrees.js
index c062ed8..10a1561 100644
--- a/test/js/paralleltrees.js
+++ b/test/js/paralleltrees.js
@@ -28,7 +28,8 @@ suite('Parallel Trees', function() {
div.textContent = 'a';
var textNode = div.firstChild;
- visual.removeAllChildNodes(div);
+ div.createShadowRoot();
+ div.offsetWidth;
expectStructure(unwrap(div), {});
expectStructure(unwrap(textNode), {});
@@ -50,7 +51,8 @@ suite('Parallel Trees', function() {
var b = a.nextSibling;
var c = div.lastChild;
- visual.removeAllChildNodes(div);
+ div.createShadowRoot();
+ div.offsetWidth;
expectStructure(unwrap(div), {});
expectStructure(unwrap(a), {});
@@ -88,7 +90,7 @@ suite('Parallel Trees', function() {
unwrapAndExpectStructure(div, {});
unwrapAndExpectStructure(textNode, {});
- visual.appendChild(div, textNode);
+ visual.insertBefore(div, textNode, null);
unwrapAndExpectStructure(div, {
firstChild: textNode,
@@ -109,7 +111,7 @@ suite('Parallel Trees', function() {
var a = div.firstChild;
var b = document.createElement('b');
- visual.appendChild(div, b);
+ visual.insertBefore(div, b, null);
unwrapAndExpectStructure(div, {
firstChild: a,
@@ -144,7 +146,7 @@ suite('Parallel Trees', function() {
var b = div.lastChild;
var c = document.createElement('c');
- visual.appendChild(div, c);
+ visual.insertBefore(div, c, null);
unwrapAndExpectStructure(div, {
firstChild: a,
@@ -185,49 +187,6 @@ suite('Parallel Trees', function() {
expectStructure(c, {});
});
- test('appendChild with document fragment', function() {
- var div = document.createElement('div');
- var df = document.createDocumentFragment();
- var a = df.appendChild(document.createElement('a'));
- var b = df.appendChild(document.createElement('b'));
-
- visual.appendChild(div, df);
-
- unwrapAndExpectStructure(div, {
- firstChild: a,
- lastChild: b
- });
-
- unwrapAndExpectStructure(df, {});
-
- unwrapAndExpectStructure(a, {
- parentNode: div,
- nextSibling: b
- });
-
- unwrapAndExpectStructure(b, {
- parentNode: div,
- previousSibling: a
- });
-
- expectStructure(div, {});
-
- expectStructure(df, {
- firstChild: a,
- lastChild: b
- });
-
- expectStructure(a, {
- parentNode: df,
- nextSibling: b
- });
-
- expectStructure(b, {
- parentNode: df,
- previousSibling: a
- });
- });
-
test('appendChild with document fragment again', function() {
var div = document.createElement('div');
div.innerHTML = '';
@@ -314,12 +273,160 @@ suite('Parallel Trees', function() {
});
});
+ test('insertBefore', function() {
+ var a = document.createElement('a');
+ var b = document.createElement('b');
+ a.appendChild(b);
+
+ unwrapAndExpectStructure(a, {
+ firstChild: b,
+ lastChild: b
+ });
+ unwrapAndExpectStructure(b, {
+ parentNode: a
+ });
+
+ expectStructure(a, {
+ firstChild: b,
+ lastChild: b
+ });
+ expectStructure(b, {
+ parentNode: a
+ });
+
+ var c = document.createElement('c');
+ visual.insertBefore(a, c, b);
+
+ unwrapAndExpectStructure(a, {
+ firstChild: c,
+ lastChild: b
+ });
+ unwrapAndExpectStructure(b, {
+ parentNode: a,
+ previousSibling: c
+ });
+ unwrapAndExpectStructure(c, {
+ parentNode: a,
+ nextSibling: b
+ });
+
+ expectStructure(a, {
+ firstChild: b,
+ lastChild: b
+ });
+ expectStructure(b, {
+ parentNode: a
+ });
+ expectStructure(c, {});
+
+ var d = document.createElement('d');
+ visual.insertBefore(a, d, b);
+
+ unwrapAndExpectStructure(a, {
+ firstChild: c,
+ lastChild: b
+ });
+ unwrapAndExpectStructure(b, {
+ parentNode: a,
+ previousSibling: d
+ });
+ unwrapAndExpectStructure(c, {
+ parentNode: a,
+ nextSibling: d
+ });
+ unwrapAndExpectStructure(d, {
+ parentNode: a,
+ nextSibling: b,
+ previousSibling: c
+ });
+
+ expectStructure(a, {
+ firstChild: b,
+ lastChild: b
+ });
+ expectStructure(b, {
+ parentNode: a
+ });
+ expectStructure(c, {});
+ expectStructure(d, {});
+ });
+
+ test('insertBefore 2', function() {
+ var a = document.createElement('a');
+ var b = document.createElement('b');
+ var c = document.createElement('b');
+ a.appendChild(b);
+ a.appendChild(c);
+
+ unwrapAndExpectStructure(a, {
+ firstChild: b,
+ lastChild: c
+ });
+ unwrapAndExpectStructure(b, {
+ parentNode: a,
+ nextSibling: c
+ });
+ unwrapAndExpectStructure(c, {
+ parentNode: a,
+ previousSibling: b
+ });
+
+ expectStructure(a, {
+ firstChild: b,
+ lastChild: c
+ });
+ expectStructure(b, {
+ parentNode: a,
+ nextSibling: c
+ });
+ expectStructure(c, {
+ parentNode: a,
+ previousSibling: b
+ });
+
+ // b d c
+ var d = document.createElement('d');
+ visual.insertBefore(a, d, c);
+
+ unwrapAndExpectStructure(a, {
+ firstChild: b,
+ lastChild: c
+ });
+ unwrapAndExpectStructure(b, {
+ parentNode: a,
+ nextSibling: d
+ });
+ unwrapAndExpectStructure(c, {
+ parentNode: a,
+ previousSibling: d
+ });
+ unwrapAndExpectStructure(d, {
+ parentNode: a,
+ previousSibling: b,
+ nextSibling: c
+ });
+
+ expectStructure(a, {
+ firstChild: b,
+ lastChild: c
+ });
+ expectStructure(b, {
+ parentNode: a,
+ nextSibling: c
+ });
+ expectStructure(c, {
+ parentNode: a,
+ previousSibling: b
+ });
+ expectStructure(d, {});
+ });
+
test('removeChild, start with one child', function() {
var div = document.createElement('div');
div.innerHTML = '';
var a = div.firstChild;
- visual.removeChild(div, a);
+ visual.remove(a);
unwrapAndExpectStructure(div, {});
unwrapAndExpectStructure(a, {});
@@ -340,7 +447,7 @@ suite('Parallel Trees', function() {
var a = div.firstChild;
var b = div.lastChild;
- visual.removeChild(div, a);
+ visual.remove(a);
unwrapAndExpectStructure(div, {
firstChild: b,
@@ -375,7 +482,7 @@ suite('Parallel Trees', function() {
var a = div.firstChild;
var b = div.lastChild;
- visual.removeChild(div, b);
+ visual.remove(b);
unwrapAndExpectStructure(div, {
firstChild: a,
@@ -411,7 +518,7 @@ suite('Parallel Trees', function() {
var b = a.nextSibling;
var c = div.lastChild;
- visual.removeChild(div, b);
+ visual.remove(b);
unwrapAndExpectStructure(div, {
firstChild: a,
@@ -503,7 +610,8 @@ suite('Parallel Trees', function() {
var b = a.nextSibling;
var c = div.lastChild;
- visual.removeAllChildNodes(div);
+ div.createShadowRoot();
+ div.offsetWidth;
unwrapAndExpectStructure(div, {});
unwrapAndExpectStructure(a, {});
@@ -619,7 +727,8 @@ suite('Parallel Trees', function() {
var b = a.nextSibling;
var c = document.createElement('c');
- visual.removeAllChildNodes(div);
+ div.createShadowRoot();
+ div.offsetWidth;
div.appendChild(c);
@@ -753,7 +862,8 @@ suite('Parallel Trees', function() {
var c = a.nextSibling;
var b = document.createElement('b');
- visual.removeAllChildNodes(div);
+ div.createShadowRoot();
+ div.offsetWidth;
div.insertBefore(b, c);
@@ -831,8 +941,8 @@ suite('Parallel Trees', function() {
var a = df.appendChild(document.createElement('a'));
var b = df.appendChild(document.createElement('b'));
- visual.removeAllChildNodes(div);
- visual.removeAllChildNodes(df);
+ div.createShadowRoot();
+ div.offsetWidth;
div.insertBefore(df, c);
@@ -944,7 +1054,8 @@ suite('Parallel Trees', function() {
var a = div.firstChild;
var b = div.lastChild;
- visual.removeAllChildNodes(div);
+ div.createShadowRoot();
+ div.offsetWidth;
expectStructure(div, {
firstChild: a,
@@ -1075,7 +1186,8 @@ suite('Parallel Trees', function() {
var c = a.nextSibling;
var b = document.createElement('b');
- visual.removeAllChildNodes(div);
+ div.createShadowRoot();
+ div.offsetWidth;
div.replaceChild(b, c);
@@ -1207,7 +1319,10 @@ suite('Parallel Trees', function() {
var doc = wrap(document);
var div = doc.createElement('div');
div.innerHTML = '';
- visual.removeAllChildNodes(div);
+
+ div.createShadowRoot();
+ div.offsetWidth;
+
var a = div.firstChild;
div.innerHTML = '';
diff --git a/test/js/rerender.js b/test/js/rerender.js
index fdef024..5afffe9 100644
--- a/test/js/rerender.js
+++ b/test/js/rerender.js
@@ -427,7 +427,6 @@ suite('Shadow DOM rerender', function() {
var b = document.createElement('b');
-
host.insertBefore(b, a);
assert.strictEqual(getVisualInnerHtml(host), 'Hello');
@@ -462,7 +461,6 @@ suite('Shadow DOM rerender', function() {
var b = document.createElement('b');
-
host.replaceChild(b, a);
assert.strictEqual(getVisualInnerHtml(host), '');
@@ -538,4 +536,113 @@ suite('Shadow DOM rerender', function() {
host.offsetWidth;
assert.equal(host.impl.innerHTML, 'wvz');
});
+
+ test('minimal dom changes', function() {
+ var div = document.createElement('div');
+ var OriginalNodePrototype =
+ // Node.prototype
+ Object.getPrototypeOf(
+ // Element.prototype
+ Object.getPrototypeOf(
+ // HTMLElement.prototype
+ Object.getPrototypeOf(
+ // HTMLDivElement.prototype
+ Object.getPrototypeOf(unwrap(div)))));
+
+ var originalInsertBefore = OriginalNodePrototype.insertBefore;
+ var originalRemoveChild = OriginalNodePrototype.removeChild;
+
+ var insertBeforeCount = 0;
+ var removeChildCount = 0;
+
+ OriginalNodePrototype.insertBefore = function(newChild, refChild) {
+ insertBeforeCount++;
+ return originalInsertBefore.call(this, newChild, refChild);
+ };
+
+ OriginalNodePrototype.removeChild = function(child) {
+ removeChildCount++;
+ return originalRemoveChild.call(this, child);
+ };
+
+ function reset() {
+ insertBeforeCount = 0;
+ removeChildCount = 0;
+ }
+
+ try {
+
+ var div = document.createElement('div');
+ var a = div.appendChild(document.createElement('a'));
+
+ var sr = div.createShadowRoot();
+ var content = sr.appendChild(document.createElement('content'));
+ var b = sr.appendChild(document.createElement('b'));
+
+ assert.equal(getVisualInnerHtml(div), '');
+
+ assert.equal(insertBeforeCount, 1);
+ assert.equal(removeChildCount, 1);
+
+ reset();
+
+ // Invalidates but does not change the rendered tree.
+ content.select = '*';
+
+ assert.equal(getVisualInnerHtml(div), '');
+ assert.equal(insertBeforeCount, 0);
+ assert.equal(removeChildCount, 0);
+
+ // Does not use our overridden appendChild
+ var c = div.appendChild(document.createElement('c'));
+ assert.equal(insertBeforeCount, 0);
+ assert.equal(removeChildCount, 0);
+
+ assert.equal(getVisualInnerHtml(div), '');
+ assert.equal(insertBeforeCount, 1);
+ assert.equal(removeChildCount, 1);
+
+ content.select = 'c';
+ reset();
+ assert.equal(getVisualInnerHtml(div), '');
+ assert.equal(insertBeforeCount, 0);
+ assert.equal(removeChildCount, 1);
+
+ content.select = '*';
+ reset();
+ assert.equal(getVisualInnerHtml(div), '');
+ assert.equal(insertBeforeCount, 1);
+ assert.equal(removeChildCount, 0);
+
+ content.select = 'x';
+ reset();
+ assert.equal(getVisualInnerHtml(div), '');
+ assert.equal(insertBeforeCount, 0);
+ assert.equal(removeChildCount, 2);
+
+ content.appendChild(document.createTextNode('fallback'));
+ reset();
+ assert.equal(getVisualInnerHtml(div), 'fallback');
+ assert.equal(insertBeforeCount, 1);
+ assert.equal(removeChildCount, 1); // moved from content
+
+ content.insertBefore(document.createTextNode('more '),
+ content.firstChild);
+ reset();
+ assert.equal(getVisualInnerHtml(div), 'more fallback');
+ assert.equal(insertBeforeCount, 0); // already inserted before "fallback"
+ assert.equal(removeChildCount, 0);
+
+ content.select = '*';
+ reset();
+ assert.equal(getVisualInnerHtml(div), '');
+ assert.equal(insertBeforeCount, 2);
+ assert.equal(removeChildCount, 2);
+
+
+ } finally {
+ OriginalNodePrototype.insertBefore = originalInsertBefore;
+ OriginalNodePrototype.removeChild = originalRemoveChild;
+ }
+ });
});
\ No newline at end of file