Fix dynamic insertion of wrapped or redistributing content.

This commit is contained in:
Kevin Schaaf
2015-06-10 00:42:48 -07:00
parent a0257028c2
commit 49ed47c403
6 changed files with 179 additions and 73 deletions

View File

@@ -58,18 +58,15 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// container to container.host.
// 3. node is <content> (host of container needs distribution)
appendChild: function(node) {
var distributed;
var handled;
this._removeNodeFromHost(node);
if (this._nodeIsInLogicalTree(this.node)) {
var host = this._hostForNode(this.node);
this._addLogicalInfo(node, this.node, host && host.shadyRoot);
this._addLogicalInfo(node, this.node);
this._addNodeToHost(node);
if (host) {
distributed = this._maybeDistribute(node, this.node, host);
}
handled = this._maybeDistribute(node, this.node);
}
// if not distributing and not adding to host, do a fast path addition
if (!distributed && !this._tryRemoveUndistributedNode(node)) {
if (!handled && !this._tryRemoveUndistributedNode(node)) {
// if adding to a shadyRoot, add to host instead
var container = this.node._isShadyRoot ? this.node.host : this.node;
nativeAppendChild.call(container, node);
@@ -82,7 +79,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (!ref_node) {
return this.appendChild(node);
}
var distributed;
var handled;
this._removeNodeFromHost(node);
if (this._nodeIsInLogicalTree(this.node)) {
saveLightChildrenIfNeeded(this.node);
@@ -92,15 +89,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
throw Error('The ref_node to be inserted before is not a child ' +
'of this node');
}
var host = this._hostForNode(this.node);
this._addLogicalInfo(node, this.node, host && host.shadyRoot, index);
this._addLogicalInfo(node, this.node, index);
this._addNodeToHost(node);
if (host) {
distributed = this._maybeDistribute(node, this.node, host);
}
handled = this._maybeDistribute(node, this.node);
}
// if not distributing and not adding to host, do a fast path addition
if (!distributed && !this._tryRemoveUndistributedNode(node)) {
if (!handled && !this._tryRemoveUndistributedNode(node)) {
// if ref_node is <content> replace with first distributed node
ref_node = ref_node.localName === CONTENT ?
this._firstComposedNode(ref_node) : ref_node;
@@ -118,19 +112,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
removeChild: function(node) {
if (factory(node).parentNode !== this.node) {
console.warn('The node to be removed is not a child of this node',
console.warn('The node to be removed is not a child of this node',
node);
}
var distributed;
var handled;
if (this._nodeIsInLogicalTree(this.node)) {
var host = this._hostForNode(this.node);
distributed = this._maybeDistribute(node, this.node, host);
handled = this._maybeDistribute(node, this.node);
this._removeNodeFromHost(node);
}
if (!distributed) {
if (!handled) {
// if removing from a shadyRoot, remove form host instead
var container = this.node._isShadyRoot ? this.node.host : this.node;
// not guaranteed to physically be in container; e.g.
// not guaranteed to physically be in container; e.g.
// undistributed nodes.
if (container === node.parentNode) {
nativeRemoveChild.call(container, node);
@@ -173,17 +166,39 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_maybeDistribute: function(node, parent, host) {
var nodeNeedsDistribute = this._nodeNeedsDistribution(node);
var distribute = this._parentNeedsDistribution(parent) ||
nodeNeedsDistribute;
if (nodeNeedsDistribute) {
_maybeDistribute: function(node, parent) {
// TODO(sorvell): technically we should check non-fragment nodes for
// <content> children but since this case is assumed to be exceedingly
// rare, we avoid the cost and will address with some specific api
// when the need arises.
var isContent = (node.localName === CONTENT);
var hasContent = (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) &&
node.querySelector(CONTENT);
// There are 2 possible cases where a host may need distribution:
// 1. <content> being inserted (the host of the shady root where
// content is inserted needs distribution)
// 2. children being inserted into parent with a shady root (parent
// needs distribution)
// Note that when 1 & 2 are both true (re-distribution), distributing the
// <content>'s host (1) will cause the parent to distribute as well (2)
var host;
if (isContent || hasContent) {
host = this._ownerShadyRootForNode(parent).host;
this._updateInsertionPoints(host);
} else if (this._parentNeedsDistribution(parent)) {
host = parent;
}
if (distribute) {
if (host) {
this._lazyDistribute(host);
}
return distribute;
// Return true when distribution will fully handle the composition
// Note that if a content was being inserted that was wrapped by a node,
// and the parent does not need distribution, return false to allow
// the nodes to be added directly, after which children may be
// distributed and composed into the wrapping node(s)
var wrappedContent = hasContent &&
(hasContent.parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE);
return host && (!wrappedContent || host == parent);
},
_tryRemoveUndistributedNode: function(node) {
@@ -200,7 +215,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
factory(host.shadyRoot).querySelectorAll(CONTENT);
},
// a node is in a shadyRoot, is a shadyRoot,
// a node is in a shadyRoot, is a shadyRoot,
// or has a lightParent
_nodeIsInLogicalTree: function(node) {
return Boolean(node._lightParent || node._isShadyRoot ||
@@ -208,27 +223,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
node.shadyRoot);
},
// note: a node is its own host
_hostForNode: function(node) {
var root = node.shadyRoot || (node._isShadyRoot ?
node : this._ownerShadyRootForNode(node));
return root && root.host;
},
_parentNeedsDistribution: function(parent) {
return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot);
},
// TODO(sorvell): technically we should check non-fragment nodes for
// <content> children but since this case is assumed to be exceedingly
// rare, we avoid the cost and will address with some specific api
// when the need arises.
_nodeNeedsDistribution: function(node) {
return (node.localName === CONTENT) ||
((node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) &&
node.querySelector(CONTENT));
},
_removeNodeFromHost: function(node) {
if (node._lightParent) {
var root = this._ownerShadyRootForNode(node);
@@ -249,7 +247,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
},
_addLogicalInfo: function(node, container, root, index) {
_addLogicalInfo: function(node, container, index) {
saveLightChildrenIfNeeded(container);
var children = factory(container).childNodes;
index = index === undefined ? children.length : index;
@@ -453,23 +451,23 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return (n.nodeType === Node.ELEMENT_NODE);
});
},
configurable: true
configurable: true
},
parentNode: {
get: function() {
return this.node._lightParent ||
return this.node._lightParent ||
(this.node.__patched ? this.node._composedParent :
this.node.parentNode);
},
configurable: true
configurable: true
},
firstChild: {
get: function() {
return this.childNodes[0];
},
configurable: true
configurable: true
},
lastChild: {
@@ -477,7 +475,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var c$ = this.childNodes;
return c$[c$.length-1];
},
configurable: true
configurable: true
},
nextSibling: {
@@ -487,7 +485,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
}
},
configurable: true
configurable: true
},
previousSibling: {
@@ -497,14 +495,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
}
},
configurable: true
configurable: true
},
firstElementChild: {
get: function() {
return this.children[0];
},
configurable: true
configurable: true
},
lastElementChild: {
@@ -512,7 +510,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var c$ = this.children;
return c$[c$.length-1];
},
configurable: true
configurable: true
},
nextElementSibling: {
@@ -522,7 +520,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
}
},
configurable: true
configurable: true
},
previousElementSibling: {
@@ -532,7 +530,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
}
},
configurable: true
configurable: true
},
// textContent / innerHTML
@@ -552,7 +550,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this.appendChild(document.createTextNode(text));
}
},
configurable: true
configurable: true
},
innerHTML: {
@@ -573,7 +571,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
}
},
configurable: true
configurable: true
}
});
@@ -624,7 +622,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
get: function() {
return Array.prototype.slice.call(this.node.children);
},
configurable: true
configurable: true
},
// textContent / innerHTML
@@ -635,7 +633,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
set: function(value) {
return this.node.textContent = value;
},
configurable: true
configurable: true
},
innerHTML: {
@@ -645,13 +643,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
set: function(value) {
return this.node.innerHTML = value;
},
configurable: true
configurable: true
}
});
var forwards = ['parentNode', 'firstChild', 'lastChild', 'nextSibling',
'previousSibling', 'firstElementChild', 'lastElementChild',
var forwards = ['parentNode', 'firstChild', 'lastChild', 'nextSibling',
'previousSibling', 'firstElementChild', 'lastElementChild',
'nextElementSibling', 'previousElementSibling'];
forwards.forEach(function(name) {
@@ -659,7 +657,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
get: function() {
return this.node[name];
},
configurable: true
configurable: true
});
});
@@ -709,7 +707,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
} else {
addNodeToComposedChildren(node, parent, children, i);
}
}
function addNodeToComposedChildren(node, parent, children, i) {

View File

@@ -353,11 +353,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
function clearDistributedDestinationInsertionPoints(content) {
var e$ = content._distributedNodes;
for (var i=0; i < e$.length; i++) {
var d = e$[i]._destinationInsertionPoints;
if (d) {
// this is +1 because these insertion points are *not* in this scope
d.splice(d.indexOf(content)+1, d.length);
if (e$) {
for (var i=0; i < e$.length; i++) {
var d = e$[i]._destinationInsertionPoints;
if (d) {
// this is +1 because these insertion points are *not* in this scope
d.splice(d.indexOf(content)+1, d.length);
}
}
}
}

View File

@@ -76,7 +76,7 @@
<script>
Polymer({
is: "x-distribute"
is: "x-distribute"
});
</script>
@@ -178,4 +178,38 @@
<script>Polymer({is: 'x-compose-select-attr'});</script>
</dom-module>
<dom-module id="x-dynamic-content">
<template>
<div id="container">
<template is="dom-if" id="domif">
<content select=".insert" id="dynamicContent"></content>
</template>
</div>
</template>
<script>Polymer({is: 'x-dynamic-content'});</script>
</dom-module>
<dom-module id="x-dynamic-content-wrapped">
<template>
<div>
<template is="dom-if" id="domif">
<div id="container">
<content select=".insert"></content>
</div>
</template>
</div>
</template>
<script>Polymer({is: 'x-dynamic-content-wrapped'});</script>
</dom-module>
<dom-module id="x-dynamic-content-redist">
<template>
<x-dynamic-content id="redistContainer">
<template is="dom-if" id="redistDomif">
<content select=".insert" id="redistContent"></content>
</template>
</x-dynamic-content>
</template>
<script>Polymer({is: 'x-dynamic-content-redist'});</script>
</dom-module>

View File

@@ -47,6 +47,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<x-redistribute-a-b></x-redistribute-a-b>
<x-dynamic-content>
<div class="insert"></div>
</x-dynamic-content>
<x-dynamic-content-wrapped>
<div class="insert"></div>
</x-dynamic-content-wrapped>
<x-dynamic-content-redist>
<div class="insert"></div>
</x-dynamic-content-redist>
<script src="polymer-dom.js"></script>
</body>

View File

@@ -18,6 +18,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
</head>
<body>
<x-test></x-test>
<div class="accessors">
@@ -46,6 +47,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<x-redistribute-a-b></x-redistribute-a-b>
<x-dynamic-content>
<div class="insert"></div>
</x-dynamic-content>
<x-dynamic-content-wrapped>
<div class="insert"></div>
</x-dynamic-content-wrapped>
<x-dynamic-content-redist>
<div class="insert"></div>
</x-dynamic-content-redist>
<script src="polymer-dom.js"></script>
</body>

View File

@@ -1,3 +1,4 @@
suite('Polymer.dom', function() {
var testElement;
@@ -494,7 +495,7 @@ suite('Polymer.dom', function() {
test('Polymer.dom.childNodes is an array', function() {
assert.isTrue(Array.isArray(Polymer.dom(document.body).childNodes));
});
});
});
@@ -744,4 +745,50 @@ suite('Polymer.dom non-distributed elements', function() {
assert.notOk(Polymer.dom(test).getOwnerRoot(), 'getOwnerRoot incorrect for child moved from a root to no root');
});
});
suite('dynamic content insertion', function() {
test('x-dynamic-content', function() {
var el = document.querySelector('x-dynamic-content');
if (el.shadyRoot) {
assert(!el.querySelector('#container .insert'));
}
el.$.domif.if = true;
el.$.domif.render();
Polymer.dom.flush();
if (el.shadyRoot) {
assert(!!el.querySelector('#container .insert'));
}
});
test('x-dynamic-content-wrapped', function() {
var el = document.querySelector('x-dynamic-content-wrapped');
if (el.shadyRoot) {
assert(!el.querySelector('#container'));
}
el.$.domif.if = true;
el.$.domif.render();
Polymer.dom.flush();
if (el.shadyRoot) {
assert(!!el.querySelector('#container .insert'));
}
});
test('x-dynamic-content-redist', function() {
var el = document.querySelector('x-dynamic-content-redist');
window.e = el;
if (el.shadyRoot) {
assert(!el.querySelector('#redistContainer .insert'));
}
el.$.redistDomif.if = true;
el.$.redistContainer.$.domif.if = true;
el.$.redistDomif.render();
Polymer.dom.flush();
if (el.shadyRoot) {
assert(!!el.querySelector('#redistContainer .insert'));
}
});
});