diff --git a/src/lib/dom-api-shady.html b/src/lib/dom-api-shady.html
index 584d5bc6cc..61888b3894 100644
--- a/src/lib/dom-api-shady.html
+++ b/src/lib/dom-api-shady.html
@@ -44,11 +44,7 @@
},
appendChild: function(node) {
- return this._addNode(node);
- },
-
- insertBefore: function(node, ref_node) {
- return this._addNode(node, ref_node);
+ return this.insertBefore(node);
},
// cases in which we may not be able to just do standard native call
@@ -57,25 +53,18 @@
// 2. container is a shadyRoot (don't distribute, instead set
// container to container.host.
// 3. node is (host of container needs distribution)
- _addNode: function(node, ref_node) {
- this._removeNodeFromParent(node);
- var addedInsertionPoint;
- var root = this.getOwnerRoot();
- // if a is added, make sure it's parent has logical info.
- if (root) {
- addedInsertionPoint = this._maybeAddInsertionPoint(node, this.node);
+ insertBefore: function(node, ref_node) {
+ if (ref_node && TreeApi.Logical.getParentNode(ref_node) !== this.node) {
+ throw Error('The ref_node to be inserted before is not a child ' +
+ 'of this node');
}
- if (TreeApi.Logical.hasChildNodes(this.node)) {
- if (ref_node && ref_node.__parentNode !== this.node) {
- throw Error('The ref_node to be inserted before is not a child ' +
- 'of this node');
- }
- TreeApi.Logical.recordInsertBefore(node, this.node, ref_node);
+ // notify existing parent that this node is being removed.
+ var parent = TreeApi.Logical.getParentNode(node);
+ if (parent && DomApi.hasApi(parent)) {
+ dom(parent).notifyObserver();
}
- this._addNodeToHost(node);
- // if not distributing and not adding to host, do a fast path addition
- if (!this._maybeDistribute(node, this.node) &&
- !this._tryRemoveUndistributedNode(node)) {
+ this._removeNode(node);
+ if (!this._addNode(node, ref_node)) {
if (ref_node) {
// if ref_node is replace with first distributed node
ref_node = ref_node.localName === CONTENT ?
@@ -90,13 +79,27 @@
}
TreeApi.Composed.recordInsertBefore(container, node, ref_node);
}
- if (addedInsertionPoint) {
- this._updateInsertionPoints(root.host);
- }
this.notifyObserver();
return node;
},
+ // Try to add node. Record logical info, track insertion points, perform
+ // distribution iff needed. Return true if the add is handled.
+ _addNode: function(node, ref_node) {
+ var root = this.getOwnerRoot();
+ if (root) {
+ root._invalidInsertionPoints =
+ this._maybeAddInsertionPoint(node, this.node);
+ this._addNodeToHost(root.host, node);
+ }
+ if (TreeApi.Logical.hasChildNodes(this.node)) {
+ TreeApi.Logical.recordInsertBefore(node, this.node, ref_node);
+ }
+ // if not distributing and not adding to host, do a fast path addition
+ return (this._maybeDistribute(node) ||
+ this._tryRemoveUndistributedNode(node));
+ },
+
/**
Removes the given `node` from the element's `lightChildren`.
This method also performs dom composition.
@@ -106,8 +109,7 @@
console.warn('The node to be removed is not a child of this node',
node);
}
- this._removeNodeFromHost(node);
- if (!this._maybeDistribute(node, this.node)) {
+ if (!this._removeNode(node)) {
// 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.
@@ -121,6 +123,35 @@
return node;
},
+ // Try to remove node: update logical info and perform distribution iff
+ // needed. Return true if the removal has been handled.
+ // note that it's possible for both the node's host and its parent
+ // to require distribution... both cases are handled here.
+ _removeNode: function(node) {
+ TreeApi.Composed.recordRemoveChild(
+ TreeApi.Composed.getParentNode(node), node);
+ // important that we want to do this only if the node has a logical parent
+ var logicalParent = TreeApi.Logical.hasParentNode(node) &&
+ TreeApi.Logical.getParentNode(node);
+ var distributed;
+ var root = this._ownerShadyRootForNode(node);
+ if (logicalParent) {
+ // distribute node's parent iff needed
+ distributed = dom(node)._distributeParent();
+ TreeApi.Logical.recordRemoveChild(node, logicalParent);
+ // remove node from root and distribute it iff needed
+ if (root && this._removeDistributedChildren(root, node)) {
+ root._invalidInsertionPoints = true;
+ this._lazyDistribute(root.host);
+ }
+ }
+ this._removeOwnerShadyRoot(node);
+ if (root) {
+ this._removeNodeFromHost(root.host, node);
+ }
+ return distributed;
+ },
+
replaceChild: function(node, ref_node) {
this.insertBefore(node, ref_node);
this.removeChild(ref_node);
@@ -157,7 +188,7 @@
return node._ownerShadyRoot;
},
- _maybeDistribute: function(node, parent) {
+ _maybeDistribute: function(node) {
// TODO(sorvell): technically we should check non-fragment nodes for
// children but since this case is assumed to be exceedingly
// rare, we avoid the cost and will address with some specific api
@@ -176,24 +207,23 @@
// 2. children being inserted into parent with a shady root (parent
// needs distribution)
if (hasContent) {
- var root = this._ownerShadyRootForNode(parent);
+ var root = this.getOwnerRoot();
if (root) {
- var host = root.host;
// note, insertion point list update is handled after node
// mutations are complete
- this._lazyDistribute(host);
+ this._lazyDistribute(root.host);
}
}
- var parentNeedsDist = this._parentNeedsDistribution(parent);
- if (parentNeedsDist) {
- this._lazyDistribute(parent);
+ var needsDist = this._nodeNeedsDistribution(this.node);
+ if (needsDist) {
+ this._lazyDistribute(this.node);
}
// 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)
- return parentNeedsDist || (hasContent && !wrappedContent);
+ return needsDist || (hasContent && !wrappedContent);
},
/* note: parent argument is required since node may have an out
@@ -241,51 +271,21 @@
}
},
- _parentNeedsDistribution: function(parent) {
- return parent && parent.shadyRoot &&
- DomApi.hasInsertionPoint(parent.shadyRoot);
+ _nodeNeedsDistribution: function(node) {
+ return node && node.shadyRoot &&
+ DomApi.hasInsertionPoint(node.shadyRoot);
},
- _removeNodeFromParent: function(node) {
- // note: we may need to notify and not have logical info so fallback
- // to composed parentNode.
-
- var parent = node.__parentNode || node.parentNode;
- //var parent = dom(node).parentNode;
- // TODO(sorvell): hasDomApi doesn't make sense now
- if (parent && DomApi.hasApi(parent)) {
- dom(parent).notifyObserver();
+ // a node being added is always in this same host as this.node.
+ _addNodeToHost: function(host, node) {
+ if (host._elementAdd) {
+ host._elementAdd(node);
}
- this._removeNodeFromHost(node, true);
},
- // NOTE: if `ensureComposedRemoval` is true then the node should be
- // removed from its composed parent.
- _removeNodeFromHost: function(node, ensureComposedRemoval) {
- // note that it's possible for both the node's host and its parent
- // to require distribution... both cases are handled here.
- var hostNeedsDist;
- var root;
- // important that we want to do this only if the node has a logical parent
- var parent = node.__parentNode;
- if (parent) {
- // distribute node's parent iff needed
- dom(node)._distributeParent();
- root = this._ownerShadyRootForNode(node);
- // remove node from root and distribute it iff needed
- if (root) {
- root.host._elementRemove(node);
- hostNeedsDist = this._removeDistributedChildren(root, node);
- }
- TreeApi.Logical.recordRemoveChild(node, parent);
- }
- this._removeOwnerShadyRoot(node);
- if (root && hostNeedsDist) {
- this._updateInsertionPoints(root.host);
- this._lazyDistribute(root.host);
- } else if (ensureComposedRemoval) {
- TreeApi.Composed.recordRemoveChild(
- TreeApi.Composed.getParentNode(node), node);
+ _removeNodeFromHost: function(host, node) {
+ if (host._elementRemove) {
+ host._elementRemove(node);
}
},
@@ -319,14 +319,6 @@
}
},
- // a node being added is always in this same host as this.node.
- _addNodeToHost: function(node) {
- var root = this.getOwnerRoot();
- if (root) {
- root.host._elementAdd(node);
- }
- },
-
_removeOwnerShadyRoot: function(node) {
// optimization: only reset the tree if node is actually in a root
if (this._hasCachedOwnerRoot(node)) {
@@ -410,8 +402,9 @@
},
_distributeParent: function() {
- if (this._parentNeedsDistribution(this.parentNode)) {
+ if (this._nodeNeedsDistribution(this.parentNode)) {
this._lazyDistribute(this.parentNode);
+ return true;
}
},
diff --git a/src/lib/dom-tree-api.html b/src/lib/dom-tree-api.html
index 339a56a177..2d48a4b7a2 100644
--- a/src/lib/dom-tree-api.html
+++ b/src/lib/dom-tree-api.html
@@ -58,6 +58,10 @@
return Boolean(node.__childNodes !== undefined);
},
+ hasParentNode: function(node) {
+ return Boolean(node.__parentNode);
+ },
+
getChildNodes: function(node) {
if (this.hasChildNodes(node)) {
if (!node.__childNodes) {
@@ -75,6 +79,10 @@
}
},
+ getParentNode: function(node) {
+ return node.__parentNode || node.parentNode;
+ },
+
// Capture the list of light children. It's important to do this before we
// start transforming the DOM into "rendered" state.
// Children may be added to this list dynamically. It will be treated as the
diff --git a/src/mini/shady.html b/src/mini/shady.html
index 4e7883ca0c..5d446a31c9 100644
--- a/src/mini/shady.html
+++ b/src/mini/shady.html
@@ -134,6 +134,11 @@
_distributeContent: function() {
if (this._useContent && !this.shadyRoot._distributionClean) {
+ if (this.shadyRoot._invalidInsertionPoints) {
+ var dom = Polymer.dom(this);
+ dom._updateInsertionPoints(this);
+ this.shadyRoot._invalidInsertionPoints = false;
+ }
// logically distribute self
this._beginDistribute();
this._distributeDirtyRoots();