Use linked-list for element tree traversal. Factor Polymer.DomApi into shadow/shady modules.

This commit is contained in:
Steven Orvell 2015-11-18 17:00:15 -08:00
parent cfa6d51479
commit 306cc8130e
12 changed files with 1127 additions and 963 deletions

View File

@ -14,6 +14,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var DomApi = Polymer.DomApi.ctor;
var useShadow = Polymer.Settings.useShadow;
/**
* DomApi.classList allows maniuplation of `classList` compatible with
* Polymer.dom. The general usage is
@ -36,20 +38,28 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
DomApi.ClassList.prototype = {
add: function() {
this.node.classList.add.apply(this.node.classList, arguments);
this.domApi._distributeParent();
this._distributeParent();
},
remove: function() {
this.node.classList.remove.apply(this.node.classList, arguments);
this.domApi._distributeParent();
this._distributeParent();
},
toggle: function() {
this.node.classList.toggle.apply(this.node.classList, arguments);
this.domApi._distributeParent();
this._distributeParent();
},
_distributeParent: function() {
if (!useShadow) {
this.domApi._distributeParent();
}
},
contains: function() {
return this.node.classList.contains.apply(this.node.classList,
arguments);

134
src/lib/dom-api-shadow.html Normal file
View File

@ -0,0 +1,134 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<script>
(function() {
'use strict';
var Settings = Polymer.Settings;
var TreeApi = Polymer.TreeApi;
var DomApi = Polymer.DomApi;
// *************** Configure DomApi for Shadow DOM!! ***************
if (!Settings.useShadow) {
return;
}
Polymer.Base.extend(DomApi.prototype, {
querySelectorAll: function(selector) {
return TreeApi.arrayCopy(this.node.querySelectorAll(selector));
},
getOwnerRoot: function() {
var n = this.node;
while (n) {
if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) {
return n;
}
n = n.parentNode;
}
},
importNode: function(externalNode, deep) {
var doc = this.node instanceof Document ? this.node :
this.node.ownerDocument;
return doc.importNode(externalNode, deep);
},
getDestinationInsertionPoints: function() {
var n$ = this.node.getDestinationInsertionPoints &&
this.node.getDestinationInsertionPoints();
return n$ ? TreeApi.arrayCopy(n$) : [];
},
getDistributedNodes: function() {
var n$ = this.node.getDistributedNodes &&
this.node.getDistributedNodes();
return n$ ? TreeApi.arrayCopy(n$) : [];
}
});
Object.defineProperties(DomApi.prototype, {
childNodes: {
get: function() {
return TreeApi.arrayCopyChildNodes(this.node);
},
configurable: true
},
children: {
get: function() {
return TreeApi.arrayCopyChildren(this.node);
},
configurable: true
},
// textContent / innerHTML
textContent: {
get: function() {
return this.node.textContent;
},
set: function(value) {
return this.node.textContent = value;
},
configurable: true
},
innerHTML: {
get: function() {
return this.node.innerHTML;
},
set: function(value) {
return this.node.innerHTML = value;
},
configurable: true
}
});
var forwardMethods = function(m$) {
for (var i=0; i < m$.length; i++) {
forwardMethod(m$[i]);
}
};
var forwardMethod = function(method) {
DomApi.prototype[method] = function() {
return this.node[method].apply(this.node, arguments);
}
};
forwardMethods(['cloneNode', 'appendChild', 'insertBefore',
'removeChild', 'replaceChild', 'setAttribute', 'removeAttribute',
'querySelector']);
var forwardProperties = function(f$) {
for (var i=0; i < f$.length; i++) {
forwardProperty(f$[i]);
}
};
var forwardProperty = function(name) {
Object.defineProperty(DomApi.prototype, name, {
get: function() {
return this.node[name];
},
configurable: true
});
};
forwardProperties(['parentNode', 'firstChild', 'lastChild',
'nextSibling', 'previousSibling', 'firstElementChild',
'lastElementChild', 'nextElementSibling', 'previousElementSibling']);
})();
</script>

690
src/lib/dom-api-shady.html Normal file
View File

@ -0,0 +1,690 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<link rel="import" href="dom-innerHTML.html">
<script>
(function() {
'use strict';
var Settings = Polymer.Settings;
var DomApi = Polymer.DomApi;
var TreeApi = Polymer.TreeApi;
var getInnerHTML = Polymer.domInnerHTML.getInnerHTML;
var CONTENT = DomApi.CONTENT;
// *************** Configure DomApi for Shady DOM!! ***************
if (Settings.useShadow) {
return;
}
var nativeInsertBefore = Element.prototype.insertBefore;
var nativeRemoveChild = Element.prototype.removeChild;
var nativeAppendChild = Element.prototype.appendChild;
var nativeCloneNode = Element.prototype.cloneNode;
var nativeImportNode = Document.prototype.importNode;
Polymer.Base.extend(DomApi.prototype, {
_lazyDistribute: function(host) {
// note: only try to distribute if the root is not clean; this ensures
// we don't distribute before initial distribution
if (host.shadyRoot && host.shadyRoot._distributionClean) {
host.shadyRoot._distributionClean = false;
Polymer.dom.addDebouncer(host.debounce('_distribute',
host._distributeContent));
}
},
appendChild: function(node) {
return this._addNode(node);
},
insertBefore: function(node, ref_node) {
return this._addNode(node, ref_node);
},
// cases in which we may not be able to just do standard native call
// 1. container has a shadyRoot (needsDistribution IFF the shadyRoot
// has an insertion point)
// 2. container is a shadyRoot (don't distribute, instead set
// container to container.host.
// 3. node is <content> (host of container needs distribution)
_addNode: function(node, ref_node) {
this._removeNodeFromParent(node);
var addedInsertionPoint;
var root = this.getOwnerRoot();
// if a <content> is added, make sure it's parent has logical info.
if (root) {
addedInsertionPoint = this._maybeAddInsertionPoint(node, 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');
}
this._addLogicalInfo(node, this.node, ref_node);
}
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)) {
if (ref_node) {
// if ref_node is <content> replace with first distributed node
ref_node = ref_node.localName === CONTENT ?
this._firstComposedNode(ref_node) : ref_node;
}
// if adding to a shadyRoot, add to host instead
var container = this.node._isShadyRoot ? this.node.host : this.node;
if (ref_node) {
nativeInsertBefore.call(container, node, ref_node);
} else {
nativeAppendChild.call(container, node);
}
TreeApi.Composed.recordInsertBefore(container, node, ref_node);
}
if (addedInsertionPoint) {
this._updateInsertionPoints(root.host);
}
this.notifyObserver();
return node;
},
/**
Removes the given `node` from the element's `lightChildren`.
This method also performs dom composition.
*/
removeChild: function(node) {
if (DomApi.factory(node).parentNode !== this.node) {
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 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.
// undistributed nodes.
if (container === node.parentNode) {
nativeRemoveChild.call(container, node);
TreeApi.Composed.recordRemoveChild(container, node);
}
}
this.notifyObserver();
return node;
},
replaceChild: function(node, ref_node) {
this.insertBefore(node, ref_node);
this.removeChild(ref_node);
return node;
},
_hasCachedOwnerRoot: function(node) {
return Boolean(node._ownerShadyRoot !== undefined);
},
getOwnerRoot: function() {
return this._ownerShadyRootForNode(this.node);
},
_ownerShadyRootForNode: function(node) {
if (!node) {
return;
}
if (node._ownerShadyRoot === undefined) {
var root;
if (node._isShadyRoot) {
root = node;
} else {
var parent = Polymer.dom(node).parentNode;
if (parent) {
root = parent._isShadyRoot ? parent :
this._ownerShadyRootForNode(parent);
} else {
root = null;
}
}
node._ownerShadyRoot = root;
}
return node._ownerShadyRoot;
},
_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. For now, the user must call
// distributeContent(true), which updates insertion points manually
// and forces distribution.
var fragContent = (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) &&
!node.__noContent && Polymer.dom(node).querySelector(CONTENT);
var wrappedContent = fragContent &&
(Polymer.dom(fragContent).parentNode.nodeType !==
Node.DOCUMENT_FRAGMENT_NODE);
var hasContent = fragContent || (node.localName === CONTENT);
// There are 2 possible cases where a distribution may need to occur:
// 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)
if (hasContent) {
var root = this._ownerShadyRootForNode(parent);
if (root) {
var host = root.host;
// note, insertion point list update is handled after node
// mutations are complete
this._lazyDistribute(host);
}
}
var parentNeedsDist = this._parentNeedsDistribution(parent);
if (parentNeedsDist) {
this._lazyDistribute(parent);
}
// 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);
},
/* note: parent argument is required since node may have an out
of date parent at this point; returns true if a <content> is being added */
_maybeAddInsertionPoint: function(node, parent) {
var added;
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE &&
!node.__noContent) {
var c$ = DomApi.factory(node).querySelectorAll(CONTENT);
for (var i=0, n, np, na; (i<c$.length) && (n=c$[i]); i++) {
np = DomApi.factory(n).parentNode;
// don't allow node's parent to be fragment itself
if (np === node) {
np = parent;
}
na = this._maybeAddInsertionPoint(n, np);
added = added || na;
}
} else if (node.localName === CONTENT) {
TreeApi.Logical.saveChildNodes(parent);
TreeApi.Logical.saveChildNodes(node);
added = true;
}
return added;
},
_tryRemoveUndistributedNode: function(node) {
if (this.node.shadyRoot) {
var parent = TreeApi.Composed.getParentNode(node);
if (parent) {
nativeRemoveChild.call(parent, node);
}
return true;
}
},
_updateInsertionPoints: function(host) {
var i$ = host.shadyRoot._insertionPoints =
DomApi.factory(host.shadyRoot).querySelectorAll(CONTENT);
// ensure <content>'s and their parents have logical dom info.
for (var i=0, c; i < i$.length; i++) {
c = i$[i];
TreeApi.Logical.saveChildNodes(c);
TreeApi.Logical.saveChildNodes(DomApi.factory(c).parentNode);
}
},
_parentNeedsDistribution: function(parent) {
return parent && parent.shadyRoot &&
DomApi.hasInsertionPoint(parent.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;
if (parent && DomApi.hasApi(parent)) {
DomApi.factory(parent).notifyObserver();
}
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;
var parent = node.__parentNode;
if (parent) {
// distribute node's parent iff needed
DomApi.factory(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);
}
this._removeLogicalInfo(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);
}
},
_removeDistributedChildren: function(root, container) {
var hostNeedsDist;
var ip$ = root._insertionPoints;
for (var i=0; i<ip$.length; i++) {
var content = ip$[i];
if (this._contains(container, content)) {
var dc$ = DomApi.factory(content).getDistributedNodes();
for (var j=0; j<dc$.length; j++) {
hostNeedsDist = true;
var node = dc$[j];
var parent = node.parentNode;
if (parent) {
nativeRemoveChild.call(parent, node);
TreeApi.Composed.recordRemoveChild(parent, node);
}
}
}
}
return hostNeedsDist;
},
_contains: function(container, node) {
while (node) {
if (node == container) {
return true;
}
node = DomApi.factory(node).parentNode;
}
},
// 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);
}
},
_addLogicalInfo: function(node, container, ref_node) {
container.__childNodes = null;
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// NOTE: the act of setting this info can affect patched nodes
// getters; therefore capture childNodes before patching.
var c$ = TreeApi.arrayCopyChildNodes(node);
for (var i=0, n; (i<c$.length) && (n=c$[i]); i++) {
this._linkNode(n, container, ref_node);
}
} else {
this._linkNode(node, container, ref_node);
}
},
_linkNode: function(node, container, ref_node) {
// update node <-> ref_node
node.__previousSibling = ref_node ? ref_node.__previousSibling :
container.__lastChild;
if (node.__previousSibling) {
node.__previousSibling.__nextSibling = node;
}
node.__nextSibling = ref_node;
if (ref_node) {
ref_node.__previousSibling = node;
}
// update node <-> container
node.__parentNode = container;
if (ref_node && ref_node === container.__firstChild) {
container.__firstChild = node;
} else {
container.__lastChild = node;
if (!container.__firstChild) {
container.__firstChild = node;
}
}
// remove caching of childNodes
container.__childNodes = null;
},
// NOTE: in general, we expect contents of the lists here to be small-ish
// and therefore indexOf to be nbd. Other optimizations can be made
// for larger lists (linked list)
_removeLogicalInfo: function(node, container) {
if (node === container.__firstChild) {
container.__firstChild = node.__nextSibling;
}
if (node === container.__lastChild) {
container.__lastChild = node.__previousSibling;
}
var p = node.__previousSibling;
var n = node.__nextSibling;
if (p) {
p.__nextSibling = n;
}
if (n) {
n.__previousSibling = p;
}
node.__parentNode = node.__previousSibling = node.__nextSibling = null;
// remove caching of childNodes
container.__childNodes = null;
},
_removeOwnerShadyRoot: function(node) {
// optimization: only reset the tree if node is actually in a root
if (this._hasCachedOwnerRoot(node)) {
var c$ = DomApi.factory(node).childNodes;
for (var i=0, l=c$.length, n; (i<l) && (n=c$[i]); i++) {
this._removeOwnerShadyRoot(n);
}
}
node._ownerShadyRoot = undefined;
},
// TODO(sorvell): This will fail if distribution that affects this
// question is pending; this is expected to be exceedingly rare, but if
// the issue comes up, we can force a flush in this case.
_firstComposedNode: function(content) {
var n$ = DomApi.factory(content).getDistributedNodes();
for (var i=0, l=n$.length, n, p$; (i<l) && (n=n$[i]); i++) {
p$ = DomApi.factory(n).getDestinationInsertionPoints();
// means that we're composed to this spot.
if (p$[p$.length-1] === content) {
return n;
}
}
},
// TODO(sorvell): consider doing native QSA and filtering results.
querySelector: function(selector) {
return this.querySelectorAll(selector)[0];
},
querySelectorAll: function(selector) {
return this._query(function(n) {
return DomApi.matchesSelector.call(n, selector);
}, this.node);
},
_query: function(matcher, node) {
node = node || this.node;
var list = [];
this._queryElements(DomApi.factory(node).childNodes, matcher, list);
return list;
},
_queryElements: function(elements, matcher, list) {
for (var i=0, l=elements.length, c; (i<l) && (c=elements[i]); i++) {
if (c.nodeType === Node.ELEMENT_NODE) {
this._queryElement(c, matcher, list);
}
}
},
_queryElement: function(node, matcher, list) {
if (matcher(node)) {
list.push(node);
}
this._queryElements(DomApi.factory(node).childNodes, matcher, list);
},
getDestinationInsertionPoints: function() {
return this.node._destinationInsertionPoints || [];
},
getDistributedNodes: function() {
return this.node._distributedNodes || [];
},
_clear: function() {
while (this.childNodes.length) {
this.removeChild(this.childNodes[0]);
}
},
setAttribute: function(name, value) {
this.node.setAttribute(name, value);
this._distributeParent();
},
removeAttribute: function(name) {
this.node.removeAttribute(name);
this._distributeParent();
},
_distributeParent: function() {
if (this._parentNeedsDistribution(this.parentNode)) {
this._lazyDistribute(this.parentNode);
}
},
cloneNode: function(deep) {
var n = nativeCloneNode.call(this.node, false);
if (deep) {
var c$ = this.childNodes;
var d = DomApi.factory(n);
for (var i=0, nc; i < c$.length; i++) {
nc = DomApi.factory(c$[i]).cloneNode(true);
d.appendChild(nc);
}
}
return n;
},
importNode: function(externalNode, deep) {
// for convenience use this node's ownerDoc if the node isn't a document
var doc = this.node instanceof Document ? this.node :
this.node.ownerDocument;
var n = nativeImportNode.call(doc, externalNode, false);
if (deep) {
var c$ = DomApi.factory(externalNode).childNodes;
var d = DomApi.factory(n);
for (var i=0, nc; i < c$.length; i++) {
nc = DomApi.factory(doc).importNode(c$[i], true);
d.appendChild(nc);
}
}
return n;
},
_getComposedInnerHTML: function() {
return getInnerHTML(this.node, true);
}
});
Object.defineProperties(DomApi.prototype, {
childNodes: {
get: function() {
var c$ = TreeApi.Logical.getChildNodes(this.node);
return Array.isArray(c$) ? c$ : TreeApi.arrayCopyChildNodes(this.node);
},
configurable: true
},
children: {
get: function() {
return Array.prototype.filter.call(this.childNodes, function(n) {
return (n.nodeType === Node.ELEMENT_NODE);
});
},
configurable: true
},
parentNode: {
get: function() {
return this.node.__parentNode ||
TreeApi.Composed.getParentNode(this.node);
},
configurable: true
},
firstChild: {
get: function() {
return this.node.__firstChild || this.node.firstChild;
},
configurable: true
},
lastChild: {
get: function() {
return this.node.__lastChild || this.node.lastChild;
},
configurable: true
},
nextSibling: {
get: function() {
return this.node.__nextSibling || this.node.nextSibling;
},
configurable: true
},
previousSibling: {
get: function() {
return this.node.__previousSibling || this.node.previousSibling;
},
configurable: true
},
firstElementChild: {
get: function() {
if (this.node.__firstChild) {
var n = this.node.__firstChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__nextSibling;
}
return n;
} else {
return this.node.firstElementChild;
}
},
configurable: true
},
lastElementChild: {
get: function() {
if (this.node.__lastChild) {
var n = this.node.__lastChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__previousSibling;
}
return n;
} else {
return this.node.lastElementChild;
}
},
configurable: true
},
nextElementSibling: {
get: function() {
if (this.node.__nextSibling) {
var n = this.node.__nextSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__nextSibling;
}
return n;
} else {
return this.node.nextElementSibling;
}
},
configurable: true
},
previousElementSibling: {
get: function() {
if (this.node.__previousSibling) {
var n = this.node.__previousSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__previousSibling;
}
return n;
} else {
return this.node.previousElementSibling;
}
},
configurable: true
},
// textContent / innerHTML
textContent: {
get: function() {
var nt = this.node.nodeType;
if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
return this.node.textContent;
} else {
var tc = [];
for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) {
if (c.nodeType !== Node.COMMENT_NODE) {
tc.push(c.textContent);
}
}
return tc.join('');
}
},
set: function(text) {
var nt = this.node.nodeType;
if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
this.node.textContent = text;
} else {
this._clear();
if (text) {
this.appendChild(document.createTextNode(text));
}
}
},
configurable: true
},
innerHTML: {
get: function() {
var nt = this.node.nodeType;
if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
return null;
} else {
return getInnerHTML(this.node);
}
},
set: function(text) {
var nt = this.node.nodeType;
if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) {
this._clear();
var d = document.createElement('div');
d.innerHTML = text;
// here, appendChild may move nodes async so we cannot rely
// on node position when copying
var c$ = TreeApi.arrayCopyChildNodes(d);
for (var i=0; i < c$.length; i++) {
this.appendChild(c$[i]);
}
}
},
configurable: true
}
});
DomApi.hasInsertionPoint = function(root) {
return Boolean(root && root._insertionPoints.length);
};
})();
</script>

View File

@ -21,24 +21,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'use strict';
var Settings = Polymer.Settings;
var getInnerHTML = Polymer.domInnerHTML.getInnerHTML;
var nativeInsertBefore = Element.prototype.insertBefore;
var nativeRemoveChild = Element.prototype.removeChild;
var nativeAppendChild = Element.prototype.appendChild;
var nativeCloneNode = Element.prototype.cloneNode;
var nativeImportNode = Document.prototype.importNode;
var DomApi = function(node) {
this.node = needsToWrap ? DomApi.wrap(node) : node;
};
// ensure nodes are wrapped if SD polyfill is present
var needsToWrap = Settings.hasShadow && !Settings.nativeShadow;
var wrap = window.wrap ? window.wrap : function(node) { return node; };
var DomApi = function(node) {
this.node = needsToWrap ? wrap(node) : node;
if (this.patch) {
this.patch();
}
};
DomApi.wrap = window.wrap ? window.wrap : function(node) { return node; };
DomApi.prototype = {
@ -68,400 +58,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return n === this.node;
},
_lazyDistribute: function(host) {
// note: only try to distribute if the root is not clean; this ensures
// we don't distribute before initial distribution
if (host.shadyRoot && host.shadyRoot._distributionClean) {
host.shadyRoot._distributionClean = false;
Polymer.dom.addDebouncer(host.debounce('_distribute',
host._distributeContent));
}
},
appendChild: function(node) {
return this._addNode(node);
},
insertBefore: function(node, ref_node) {
return this._addNode(node, ref_node);
},
// cases in which we may not be able to just do standard native call
// 1. container has a shadyRoot (needsDistribution IFF the shadyRoot
// has an insertion point)
// 2. container is a shadyRoot (don't distribute, instead set
// container to container.host.
// 3. node is <content> (host of container needs distribution)
_addNode: function(node, ref_node) {
this._removeNodeFromParent(node);
var addedInsertionPoint;
var root = this.getOwnerRoot();
// if a <content> is added, make sure it's parent has logical info.
if (root) {
addedInsertionPoint = this._maybeAddInsertionPoint(node, this.node);
}
if (this._nodeHasLogicalChildren(this.node)) {
if (ref_node) {
var children = this.childNodes;
var index = children.indexOf(ref_node);
if (index < 0) {
throw Error('The ref_node to be inserted before is not a child ' +
'of this node');
}
}
this._addLogicalInfo(node, this.node, index);
}
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)) {
if (ref_node) {
// if ref_node is <content> replace with first distributed node
ref_node = ref_node.localName === CONTENT ?
this._firstComposedNode(ref_node) : ref_node;
}
// if adding to a shadyRoot, add to host instead
var container = this.node._isShadyRoot ? this.node.host : this.node;
addToComposedParent(container, node, ref_node);
if (ref_node) {
nativeInsertBefore.call(container, node, ref_node);
} else {
nativeAppendChild.call(container, node);
}
}
if (addedInsertionPoint) {
this._updateInsertionPoints(root.host);
}
this.notifyObserver();
return node;
},
/**
Removes the given `node` from the element's `lightChildren`.
This method also performs dom composition.
*/
removeChild: function(node) {
if (factory(node).parentNode !== this.node) {
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 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.
// undistributed nodes.
if (container === node.parentNode) {
removeFromComposedParent(container, node);
nativeRemoveChild.call(container, node);
}
}
this.notifyObserver();
return node;
},
replaceChild: function(node, ref_node) {
this.insertBefore(node, ref_node);
this.removeChild(ref_node);
return node;
},
_hasCachedOwnerRoot: function(node) {
return Boolean(node._ownerShadyRoot !== undefined);
},
getOwnerRoot: function() {
return this._ownerShadyRootForNode(this.node);
},
_ownerShadyRootForNode: function(node) {
if (!node) {
return;
}
if (node._ownerShadyRoot === undefined) {
var root;
if (node._isShadyRoot) {
root = node;
} else {
var parent = Polymer.dom(node).parentNode;
if (parent) {
root = parent._isShadyRoot ? parent :
this._ownerShadyRootForNode(parent);
} else {
root = null;
}
}
node._ownerShadyRoot = root;
}
return node._ownerShadyRoot;
},
_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. For now, the user must call
// distributeContent(true), which updates insertion points manually
// and forces distribution.
var fragContent = (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) &&
!node.__noContent && Polymer.dom(node).querySelector(CONTENT);
var wrappedContent = fragContent &&
(Polymer.dom(fragContent).parentNode.nodeType !==
Node.DOCUMENT_FRAGMENT_NODE);
var hasContent = fragContent || (node.localName === CONTENT);
// There are 2 possible cases where a distribution may need to occur:
// 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)
if (hasContent) {
var root = this._ownerShadyRootForNode(parent);
if (root) {
var host = root.host;
// note, insertion point list update is handled after node
// mutations are complete
this._lazyDistribute(host);
}
}
var parentNeedsDist = this._parentNeedsDistribution(parent);
if (parentNeedsDist) {
this._lazyDistribute(parent);
}
// 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);
},
/* note: parent argument is required since node may have an out
of date parent at this point; returns true if a <content> is being added */
_maybeAddInsertionPoint: function(node, parent) {
var added;
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE &&
!node.__noContent) {
var c$ = factory(node).querySelectorAll(CONTENT);
for (var i=0, n, np, na; (i<c$.length) && (n=c$[i]); i++) {
np = factory(n).parentNode;
// don't allow node's parent to be fragment itself
if (np === node) {
np = parent;
}
na = this._maybeAddInsertionPoint(n, np);
added = added || na;
}
} else if (node.localName === CONTENT) {
saveLightChildrenIfNeeded(parent);
saveLightChildrenIfNeeded(node);
added = true;
}
return added;
},
_tryRemoveUndistributedNode: function(node) {
if (this.node.shadyRoot) {
var parent = getComposedParent(node);
if (parent) {
nativeRemoveChild.call(parent, node);
}
return true;
}
},
_updateInsertionPoints: function(host) {
var i$ = host.shadyRoot._insertionPoints =
factory(host.shadyRoot).querySelectorAll(CONTENT);
// ensure <content>'s and their parents have logical dom info.
for (var i=0, c; i < i$.length; i++) {
c = i$[i];
saveLightChildrenIfNeeded(c);
saveLightChildrenIfNeeded(factory(c).parentNode);
}
},
// a node has logical children
_nodeHasLogicalChildren: function(node) {
return Boolean(node._lightChildren !== undefined);
},
_parentNeedsDistribution: function(parent) {
return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot);
},
_removeNodeFromParent: function(node) {
// note: we may need to notify and not have logical info so fallback
// to composed parentNode.
var parent = node._lightParent || node.parentNode;
if (parent && hasDomApi(parent)) {
factory(parent).notifyObserver();
}
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;
var parent = node._lightParent;
if (parent) {
// distribute node's parent iff needed
factory(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);
}
this._removeLogicalInfo(node, parent);
}
this._removeOwnerShadyRoot(node);
if (root && hostNeedsDist) {
this._updateInsertionPoints(root.host);
this._lazyDistribute(root.host);
} else if (ensureComposedRemoval) {
removeFromComposedParent(getComposedParent(node), node);
}
},
_removeDistributedChildren: function(root, container) {
var hostNeedsDist;
var ip$ = root._insertionPoints;
for (var i=0; i<ip$.length; i++) {
var content = ip$[i];
if (this._contains(container, content)) {
var dc$ = factory(content).getDistributedNodes();
for (var j=0; j<dc$.length; j++) {
hostNeedsDist = true;
var node = dc$[j];
var parent = node.parentNode;
if (parent) {
removeFromComposedParent(parent, node);
nativeRemoveChild.call(parent, node);
}
}
}
}
return hostNeedsDist;
},
_contains: function(container, node) {
while (node) {
if (node == container) {
return true;
}
node = factory(node).parentNode;
}
},
// 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);
}
},
_addLogicalInfo: function(node, container, index) {
var children = factory(container).childNodes;
index = index === undefined ? children.length : index;
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// NOTE: the act of setting this info can affect patched nodes
// getters; therefore capture childNodes before patching.
var c$ = arrayCopyChildNodes(node);
for (var i=0, n; (i<c$.length) && (n=c$[i]); i++) {
children.splice(index++, 0, n);
n._lightParent = container;
}
} else {
children.splice(index, 0, node);
node._lightParent = container;
}
},
// NOTE: in general, we expect contents of the lists here to be small-ish
// and therefore indexOf to be nbd. Other optimizations can be made
// for larger lists (linked list)
_removeLogicalInfo: function(node, container) {
var children = factory(container).childNodes;
var index = children.indexOf(node);
if ((index < 0) || (container !== node._lightParent)) {
throw Error('The node to be removed is not a child of this node');
}
children.splice(index, 1);
node._lightParent = null;
},
_removeOwnerShadyRoot: function(node) {
// optimization: only reset the tree if node is actually in a root
if (this._hasCachedOwnerRoot(node)) {
var c$ = factory(node).childNodes;
for (var i=0, l=c$.length, n; (i<l) && (n=c$[i]); i++) {
this._removeOwnerShadyRoot(n);
}
}
node._ownerShadyRoot = undefined;
},
// TODO(sorvell): This will fail if distribution that affects this
// question is pending; this is expected to be exceedingly rare, but if
// the issue comes up, we can force a flush in this case.
_firstComposedNode: function(content) {
var n$ = factory(content).getDistributedNodes();
for (var i=0, l=n$.length, n, p$; (i<l) && (n=n$[i]); i++) {
p$ = factory(n).getDestinationInsertionPoints();
// means that we're composed to this spot.
if (p$[p$.length-1] === content) {
return n;
}
}
},
// TODO(sorvell): consider doing native QSA and filtering results.
querySelector: function(selector) {
return this.querySelectorAll(selector)[0];
},
querySelectorAll: function(selector) {
return this._query(function(n) {
return matchesSelector.call(n, selector);
}, this.node);
},
_query: function(matcher, node) {
node = node || this.node;
var list = [];
this._queryElements(factory(node).childNodes, matcher, list);
return list;
},
_queryElements: function(elements, matcher, list) {
for (var i=0, l=elements.length, c; (i<l) && (c=elements[i]); i++) {
if (c.nodeType === Node.ELEMENT_NODE) {
this._queryElement(c, matcher, list);
}
}
},
_queryElement: function(node, matcher, list) {
if (matcher(node)) {
list.push(node);
}
this._queryElements(factory(node).childNodes, matcher, list);
},
getDestinationInsertionPoints: function() {
return this.node._destinationInsertionPoints || [];
},
getDistributedNodes: function() {
return this.node._distributedNodes || [];
},
/*
Returns a list of nodes distributed within this element. These can be
dom children or elements distributed to children that are insertion
@ -472,7 +68,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var list = [];
for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
if ((c.nodeType === Node.ELEMENT_NODE) &&
matchesSelector.call(c, selector)) {
DomApi.matchesSelector.call(c, selector)) {
list.push(c);
}
}
@ -489,7 +85,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var c$ = this.childNodes;
for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
if (c.localName === CONTENT) {
var d$ = factory(c).getDistributedNodes();
var d$ = DomApi.factory(c).getDistributedNodes();
for (var j=0; j < d$.length; j++) {
list.push(d$[j]);
}
@ -500,57 +96,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return list;
},
_clear: function() {
while (this.childNodes.length) {
this.removeChild(this.childNodes[0]);
}
},
setAttribute: function(name, value) {
this.node.setAttribute(name, value);
this._distributeParent();
},
removeAttribute: function(name) {
this.node.removeAttribute(name);
this._distributeParent();
},
_distributeParent: function() {
if (this._parentNeedsDistribution(this.parentNode)) {
this._lazyDistribute(this.parentNode);
}
},
cloneNode: function(deep) {
var n = nativeCloneNode.call(this.node, false);
if (deep) {
var c$ = this.childNodes;
var d = factory(n);
for (var i=0, nc; i < c$.length; i++) {
nc = factory(c$[i]).cloneNode(true);
d.appendChild(nc);
}
}
return n;
},
importNode: function(externalNode, deep) {
// for convenience use this node's ownerDoc if the node isn't a document
var doc = this.node instanceof Document ? this.node :
this.node.ownerDocument;
var n = nativeImportNode.call(doc, externalNode, false);
if (deep) {
var c$ = factory(externalNode).childNodes;
var d = factory(n);
for (var i=0, nc; i < c$.length; i++) {
nc = factory(doc).importNode(c$[i], true);
d.appendChild(nc);
}
}
return n;
},
/**
* Notifies callers about changes to the element's effective child nodes,
* the same list as returned by `getEffectiveChildNodes`.
@ -593,283 +138,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
// changes and accessors...
if (!Settings.useShadow) {
var CONTENT = DomApi.CONTENT = 'content';
Object.defineProperties(DomApi.prototype, {
childNodes: {
get: function() {
var c$ = getLightChildren(this.node);
return Array.isArray(c$) ? c$ : arrayCopyChildNodes(this.node);
},
configurable: true
},
children: {
get: function() {
return Array.prototype.filter.call(this.childNodes, function(n) {
return (n.nodeType === Node.ELEMENT_NODE);
});
},
configurable: true
},
parentNode: {
get: function() {
return this.node._lightParent ||
getComposedParent(this.node);
},
configurable: true
},
firstChild: {
get: function() {
return this.childNodes[0];
},
configurable: true
},
lastChild: {
get: function() {
var c$ = this.childNodes;
return c$[c$.length-1];
},
configurable: true
},
nextSibling: {
get: function() {
var c$ = this.parentNode && factory(this.parentNode).childNodes;
if (c$) {
return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
}
},
configurable: true
},
previousSibling: {
get: function() {
var c$ = this.parentNode && factory(this.parentNode).childNodes;
if (c$) {
return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
}
},
configurable: true
},
firstElementChild: {
get: function() {
return this.children[0];
},
configurable: true
},
lastElementChild: {
get: function() {
var c$ = this.children;
return c$[c$.length-1];
},
configurable: true
},
nextElementSibling: {
get: function() {
var c$ = this.parentNode && factory(this.parentNode).children;
if (c$) {
return c$[Array.prototype.indexOf.call(c$, this.node) + 1];
}
},
configurable: true
},
previousElementSibling: {
get: function() {
var c$ = this.parentNode && factory(this.parentNode).children;
if (c$) {
return c$[Array.prototype.indexOf.call(c$, this.node) - 1];
}
},
configurable: true
},
// textContent / innerHTML
textContent: {
get: function() {
var nt = this.node.nodeType;
if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
return this.node.textContent;
} else {
var tc = [];
for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) {
if (c.nodeType !== Node.COMMENT_NODE) {
tc.push(c.textContent);
}
}
return tc.join('');
}
},
set: function(text) {
var nt = this.node.nodeType;
if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
this.node.textContent = text;
} else {
this._clear();
if (text) {
this.appendChild(document.createTextNode(text));
}
}
},
configurable: true
},
innerHTML: {
get: function() {
var nt = this.node.nodeType;
if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) {
return null;
} else {
return getInnerHTML(this.node);
}
},
set: function(text) {
var nt = this.node.nodeType;
if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) {
this._clear();
var d = document.createElement('div');
d.innerHTML = text;
// here, appendChild may move nodes async so we cannot rely
// on node position when copying
var c$ = arrayCopyChildNodes(d);
for (var i=0; i < c$.length; i++) {
this.appendChild(c$[i]);
}
}
},
configurable: true
}
});
DomApi.prototype._getComposedInnerHTML = function() {
return getInnerHTML(this.node, true);
};
} else {
var forwardMethods = function(m$) {
for (var i=0; i < m$.length; i++) {
forwardMethod(m$[i]);
}
};
var forwardMethod = function(method) {
DomApi.prototype[method] = function() {
return this.node[method].apply(this.node, arguments);
}
};
forwardMethods(['cloneNode', 'appendChild', 'insertBefore',
'removeChild', 'replaceChild']);
DomApi.prototype.querySelectorAll = function(selector) {
return arrayCopy(this.node.querySelectorAll(selector));
};
DomApi.prototype.getOwnerRoot = function() {
var n = this.node;
while (n) {
if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) {
return n;
}
n = n.parentNode;
}
};
DomApi.prototype.importNode = function(externalNode, deep) {
var doc = this.node instanceof Document ? this.node :
this.node.ownerDocument;
return doc.importNode(externalNode, deep);
}
DomApi.prototype.getDestinationInsertionPoints = function() {
var n$ = this.node.getDestinationInsertionPoints &&
this.node.getDestinationInsertionPoints();
return n$ ? arrayCopy(n$) : [];
};
DomApi.prototype.getDistributedNodes = function() {
var n$ = this.node.getDistributedNodes &&
this.node.getDistributedNodes();
return n$ ? arrayCopy(n$) : [];
};
DomApi.prototype._distributeParent = function() {};
Object.defineProperties(DomApi.prototype, {
childNodes: {
get: function() {
return arrayCopyChildNodes(this.node);
},
configurable: true
},
children: {
get: function() {
return arrayCopyChildren(this.node);
},
configurable: true
},
// textContent / innerHTML
textContent: {
get: function() {
return this.node.textContent;
},
set: function(value) {
return this.node.textContent = value;
},
configurable: true
},
innerHTML: {
get: function() {
return this.node.innerHTML;
},
set: function(value) {
return this.node.innerHTML = value;
},
configurable: true
}
});
var forwardProperties = function(f$) {
for (var i=0; i < f$.length; i++) {
forwardProperty(f$[i]);
}
};
var forwardProperty = function(name) {
Object.defineProperty(DomApi.prototype, name, {
get: function() {
return this.node[name];
},
configurable: true
});
};
forwardProperties(['parentNode', 'firstChild', 'lastChild',
'nextSibling', 'previousSibling', 'firstElementChild',
'lastElementChild', 'nextElementSibling', 'previousElementSibling']);
}
var CONTENT = 'content';
function factory(node, patch) {
DomApi.factory = function(node, patch) {
node = node || document;
if (!node.__domApi) {
node.__domApi = new DomApi(node, patch);
@ -877,136 +148,27 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return node.__domApi;
};
function hasDomApi(node) {
DomApi.hasApi = function(node) {
return Boolean(node.__domApi);
};
DomApi.ctor = DomApi;
Polymer.dom = function(obj, patch) {
if (obj instanceof Event) {
return Polymer.EventApi.factory(obj);
} else {
return factory(obj, patch);
return DomApi.factory(obj, patch);
}
};
function getLightChildren(node) {
var children = node._lightChildren;
// TODO(sorvell): it's more correct to use _composedChildren instead of
// childNodes here but any trivial failure to use Polymer.dom
// will result in an error so we avoid using _composedChildren
return children ? children : node.childNodes;
}
function getComposedChildren(node) {
if (!node._composedChildren) {
node._composedChildren = arrayCopyChildNodes(node);
}
return node._composedChildren;
}
function addToComposedParent(parent, node, ref_node) {
var children = getComposedChildren(parent);
var i = ref_node ? children.indexOf(ref_node) : -1;
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
var fragChildren = getComposedChildren(node);
for (var j=0; j < fragChildren.length; j++) {
addNodeToComposedChildren(fragChildren[j], parent, children, i + j);
}
node._composedChildren = null;
} else {
addNodeToComposedChildren(node, parent, children, i);
}
}
function getComposedParent(node) {
return node.__patched ? node._composedParent : node.parentNode;
}
function addNodeToComposedChildren(node, parent, children, i) {
node._composedParent = parent;
children.splice(i >= 0 ? i : children.length, 0, node);
}
function removeFromComposedParent(parent, node) {
node._composedParent = null;
if (parent) {
var children = getComposedChildren(parent);
var i = children.indexOf(node);
if (i >= 0) {
children.splice(i, 1);
}
}
}
function saveLightChildrenIfNeeded(node) {
// 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
// source of truth for the light children of the element. This element's
// actual children will be treated as the rendered state once lightChildren
// is populated.
if (!node._lightChildren) {
var c$ = arrayCopyChildNodes(node);
for (var i=0, l=c$.length, child; (i<l) && (child=c$[i]); i++) {
child._lightParent = child._lightParent || node;
}
node._lightChildren = c$;
}
}
// sad but faster than slice...
function arrayCopyChildNodes(parent) {
var copy=[], i=0;
for (var n=parent.firstChild; n; n=n.nextSibling) {
copy[i++] = n;
}
return copy;
}
function arrayCopyChildren(parent) {
var copy=[], i=0;
for (var n=parent.firstElementChild; n; n=n.nextElementSibling) {
copy[i++] = n;
}
return copy;
}
function arrayCopy(a$) {
var l = a$.length;
var copy = new Array(l);
for (var i=0; i < l; i++) {
copy[i] = a$[i];
}
return copy;
}
function hasInsertionPoint(root) {
return Boolean(root && root._insertionPoints.length);
}
var p = Element.prototype;
var matchesSelector = p.matches || p.matchesSelector ||
p.mozMatchesSelector || p.msMatchesSelector ||
p.oMatchesSelector || p.webkitMatchesSelector;
return {
getLightChildren: getLightChildren,
getComposedParent: getComposedParent,
getComposedChildren: getComposedChildren,
removeFromComposedParent: removeFromComposedParent,
saveLightChildrenIfNeeded: saveLightChildrenIfNeeded,
matchesSelector: matchesSelector,
hasInsertionPoint: hasInsertionPoint,
ctor: DomApi,
factory: factory,
hasDomApi: hasDomApi,
arrayCopy: arrayCopy,
arrayCopyChildNodes: arrayCopyChildNodes,
arrayCopyChildren: arrayCopyChildren,
wrap: wrap
};
DomApi.matchesSelector = p.matches || p.matchesSelector ||
p.mozMatchesSelector || p.msMatchesSelector ||
p.oMatchesSelector || p.webkitMatchesSelector;
return DomApi;
})();
</script>

158
src/lib/dom-tree-api.html Normal file
View File

@ -0,0 +1,158 @@
<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="settings.html">
<link rel="import" href="dom-innerHTML.html">
<script>
(function() {
'use strict';
// native add/remove
var nativeInsertBefore = Element.prototype.insertBefore;
var nativeRemoveChild = Element.prototype.removeChild;
/**
* TreeApi is a dom manipulation library used by Shady/Polymer.dom to
* manipulate composed and logical trees.
*/
Polymer.TreeApi = {
// sad but faster than slice...
arrayCopyChildNodes: function(parent) {
var copy=[], i=0;
for (var n=parent.firstChild; n; n=n.nextSibling) {
copy[i++] = n;
}
return copy;
},
arrayCopyChildren: function(parent) {
var copy=[], i=0;
for (var n=parent.firstElementChild; n; n=n.nextElementSibling) {
copy[i++] = n;
}
return copy;
},
arrayCopy: function(a$) {
var l = a$.length;
var copy = new Array(l);
for (var i=0; i < l; i++) {
copy[i] = a$[i];
}
return copy;
}
};
Polymer.TreeApi.Logical = {
hasChildNodes: function(node) {
return Boolean(node.__childNodes !== undefined);
},
getChildNodes: function(node) {
if (this.hasChildNodes(node)) {
if (!node.__childNodes) {
node.__childNodes = [];
for (var n=node.__firstChild; n; n=n.__nextSibling) {
node.__childNodes.push(n);
}
}
return node.__childNodes;
} else {
// TODO(sorvell): it's more correct to `Composed.getChildNodes`
// instead of `childNodes` here but any trivial failure
//to use Polymer.dom will result in an error.
return node.childNodes;
}
},
// 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
// source of truth for the light children of the element. This element's
// actual children will be treated as the rendered state once this function
// has been called.
saveChildNodes: function(node) {
if (!this.hasChildNodes(node)) {
node.__firstChild = node.firstChild;
node.__lastChild = node.lastChild;
node.__childNodes = [];
for (var n=node.firstChild; n; n=n.nextSibling) {
n.__parentNode = node;
node.__childNodes.push(n);
n.__nextSibling = n.nextSibling;
n.__previousSibling = n.previousSibling;
}
}
}
}
// TODO(sorvell): composed tree manipulation is made available
// (1) to maninpulate the composed tree, and (2) to track changes
// to the tree for optional patching pluggability.
Polymer.TreeApi.Composed = {
ensureParentNodes: function(parent, children) {
},
getChildNodes: function(node) {
return Polymer.TreeApi.arrayCopyChildNodes(node);
},
getParentNode: function(node) {
return node.parentNode;
},
recordInsertBefore: function(parent, node, ref_node) {
},
recordRemoveChild: function(parent, node) {
},
recordParentNode: function(node, parent) {
},
// composed tracking needs to reset composed children here in case
// they may have already been set (this shouldn't happen but can
// if dependency ordering is incorrect and as a result upgrade order
// is unexpected)
clearChildNodes: function(node) {
node.textContent = '';
},
insertBefore: function(parentNode, newChild, refChild) {
var newChildParent = this.getParentNode(newChild);
if (newChildParent !== parentNode) {
this.recordRemoveChild(newChildParent, newChild);
}
// remove child from its old parent first
this.removeChild(newChild);
// insert it into the real DOM
nativeInsertBefore.call(parentNode, newChild, refChild || null);
this.recordParentNode(newChild, parentNode);
},
removeChild: function(node) {
var parentNode = this.getParentNode(node);
if (parentNode) {
this.recordParentNode(node, null);
// remove it from the real DOM
nativeRemoveChild.call(parentNode, node);
}
}
};
})();
</script>

View File

@ -112,7 +112,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var cssText = '';
// if element is a template, get content from its .content
var content = element.content || element;
var e$ = Polymer.DomApi.arrayCopy(
var e$ = Polymer.TreeApi.arrayCopy(
content.querySelectorAll(this.MODULE_STYLES_SELECTOR));
for (var i=0, e; i < e$.length; i++) {
e = e$[i];

View File

@ -154,7 +154,7 @@ elements to the template itself as the binding scope.
this._prepBindings();
this._prepPropertyInfo();
Polymer.Base._initFeatures.call(this);
this._children = Polymer.DomApi.arrayCopyChildNodes(this.root);
this._children = Polymer.TreeApi.arrayCopyChildNodes(this.root);
}
this._insertChildren();
this.fire('dom-change');

View File

@ -8,7 +8,10 @@ Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<link rel="import" href="../lib/array-splice.html">
<link rel="import" href="../lib/dom-tree-api.html">
<link rel="import" href="../lib/dom-api.html">
<link rel="import" href="../lib/dom-api-shady.html">
<link rel="import" href="../lib/dom-api-shadow.html">
<link rel="import" href="../lib/dom-api-flush.html">
<link rel="import" href="../lib/dom-api-event.html">
<link rel="import" href="../lib/dom-api-classlist.html">
@ -21,7 +24,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Implements a pared down version of ShadowDOM's scoping, which is easy to
polyfill across browsers.
*/
var hasDomApi = Polymer.DomApi.hasDomApi;
var DomApi = Polymer.DomApi;
var TreeApi = Polymer.TreeApi;
Polymer.Base._addFeature({
@ -34,7 +38,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_poolContent: function() {
if (this._useContent) {
// capture lightChildren to help reify dom scoping
saveLightChildrenIfNeeded(this);
TreeApi.Logical.saveChildNodes(this);
}
},
@ -47,7 +51,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// removed from document by shadyDOM distribution
// so we ensure this here
if (!this.dataHost) {
upgradeLightChildren(this._lightChildren);
upgradeLogicalChildren(TreeApi.Logical.getChildNodes(this));
}
}
},
@ -66,11 +70,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// a. for shadyRoot
// b. for insertion points (fallback)
// c. for parents of insertion points
saveLightChildrenIfNeeded(this.shadyRoot);
TreeApi.Logical.saveChildNodes(this.shadyRoot);
for (var i=0, c; i < i$.length; i++) {
c = i$[i];
saveLightChildrenIfNeeded(c);
saveLightChildrenIfNeeded(c.parentNode);
TreeApi.Logical.saveChildNodes(c);
TreeApi.Logical.saveChildNodes(c.parentNode);
}
this.shadyRoot.host = this;
},
@ -122,7 +126,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_beginDistribute: function() {
if (this._useContent && hasInsertionPoint(this.shadyRoot)) {
if (this._useContent && DomApi.hasInsertionPoint(this.shadyRoot)) {
// reset distributions
this._resetDistribution();
// compute which nodes should be distributed where
@ -147,18 +151,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// so that attachment that provokes additional distribution (e.g.
// adding something to your parentNode) works
this.shadyRoot._distributionClean = true;
if (hasInsertionPoint(this.shadyRoot)) {
if (DomApi.hasInsertionPoint(this.shadyRoot)) {
this._composeTree();
// NOTE: send a signal to insertion points that we have distributed
// which informs effective children observers
notifyContentObservers(this.shadyRoot);
} else {
if (!this.shadyRoot._hasDistributed) {
this.textContent = '';
// reset composed children here in case they may have already
// been set (this shouldn't happen but can if dependency ordering
// is incorrect and as a result upgrade order is unexpected)
this._composedChildren = null;
TreeApi.Composed.clearChildNodes(this);
this.appendChild(this.shadyRoot);
} else {
// simplified non-tree walk composition
@ -188,7 +188,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Alternatively we could just polyfill it somewhere.
// Note that the arguments are reversed from what you might expect.
node = node || this;
return matchesSelector.call(node, selector);
return DomApi.matchesSelector.call(node, selector);
},
// Many of the following methods are all conceptually static, but they are
@ -196,7 +196,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_resetDistribution: function() {
// light children
var children = getLightChildren(this);
var children = TreeApi.Logical.getChildNodes(this);
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child._destinationInsertionPoints) {
@ -218,7 +218,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// these with the "content root" to arrive at the composed tree.
_collectPool: function() {
var pool = [];
var children = getLightChildren(this);
var children = TreeApi.Logical.getChildNodes(this);
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (isInsertionPoint(child)) {
@ -264,7 +264,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
// Fallback content if nothing was distributed here
if (!anyDistributed) {
var children = getLightChildren(content);
var children = TreeApi.Logical.getChildNodes(content);
for (var j = 0; j < children.length; j++) {
distributeNodeInto(children[j], content);
}
@ -277,7 +277,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._updateChildNodes(this, this._composeNode(this));
var p$ = this.shadyRoot._insertionPoints;
for (var i=0, l=p$.length, p, parent; (i<l) && (p=p$[i]); i++) {
parent = p._lightParent || p.parentNode;
parent = p.__parentNode || p.parentNode;
if (!parent._useContent && (parent !== this) &&
(parent !== this.shadyRoot)) {
this._updateChildNodes(parent, this._composeNode(parent));
@ -288,7 +288,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Returns the list of nodes which should be rendered inside `node`.
_composeNode: function(node) {
var children = [];
var c$ = getLightChildren(node.shadyRoot || node);
var c$ = TreeApi.Logical.getChildNodes(node.shadyRoot || node);
for (var i = 0; i < c$.length; i++) {
var child = c$[i];
if (isInsertionPoint(child)) {
@ -308,7 +308,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Ensures that the rendered node list inside `container` is `children`.
_updateChildNodes: function(container, children) {
var composed = getComposedChildren(container);
var composed = TreeApi.Composed.getChildNodes(container);
var splices =
Polymer.ArraySplice.calculateSplices(children, composed);
// process removals
@ -318,8 +318,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// to remove it; this can happen if Polymer.dom moves a node and
// then schedules its previous host for distribution resulting in
// the node being removed here.
if (getComposedParent(n) === container) {
remove(n);
if (TreeApi.Composed.getParentNode(n) === container) {
TreeApi.Composed.removeChild(n);
}
composed.splice(s.index + d, 1);
}
@ -330,12 +330,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
next = composed[s.index];
for (var j=s.index, n; j < s.index + s.addedCount; j++) {
n = children[j];
insertBefore(container, n, next);
TreeApi.Composed.insertBefore(container, n, next);
// TODO(sorvell): is this splice strictly needed?
composed.splice(j, 0, n);
}
}
// ensure composed parent is set
ensureComposedParent(container, children);
TreeApi.Composed.ensureParentNodes(container, children);
},
_matchesContentSelect: function(node, contentElement) {
@ -375,14 +376,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded;
var getLightChildren = Polymer.DomApi.getLightChildren;
var matchesSelector = Polymer.DomApi.matchesSelector;
var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint;
var getComposedChildren = Polymer.DomApi.getComposedChildren;
var getComposedParent = Polymer.DomApi.getComposedParent;
var removeFromComposedParent = Polymer.DomApi.removeFromComposedParent;
function distributeNodeInto(child, insertionPoint) {
insertionPoint._distributedNodes.push(child);
var points = child._destinationInsertionPoints;
@ -408,9 +401,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// dirty a shadyRoot if a change may trigger reprojection!
function maybeRedistributeParent(content, host) {
var parent = content._lightParent;
var parent = content.__parentNode;
if (parent && parent.shadyRoot &&
hasInsertionPoint(parent.shadyRoot) &&
DomApi.hasInsertionPoint(parent.shadyRoot) &&
parent.shadyRoot._distributionClean) {
parent.shadyRoot._distributionClean = false;
host.shadyRoot._dirtyRoots.push(parent);
@ -427,36 +420,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
return node.localName == 'content';
}
var nativeInsertBefore = Element.prototype.insertBefore;
var nativeRemoveChild = Element.prototype.removeChild;
function insertBefore(parentNode, newChild, refChild) {
var newChildParent = getComposedParent(newChild);
if (newChildParent !== parentNode) {
removeFromComposedParent(newChildParent, newChild);
}
// remove child from its old parent first
remove(newChild);
// insert it into the real DOM
nativeInsertBefore.call(parentNode, newChild, refChild || null);
newChild._composedParent = parentNode;
}
function remove(node) {
var parentNode = getComposedParent(node);
if (parentNode) {
node._composedParent = null;
// remove it from the real DOM
nativeRemoveChild.call(parentNode, node);
}
}
function ensureComposedParent(parent, children) {
for (var i=0, n; i < children.length; i++) {
children[i]._composedParent = parent;
}
}
// returns the host that's the top of this host's distribution tree
function getTopDistributingHost(host) {
while (host && hostNeedsRedistribution(host)) {
@ -480,21 +443,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
function notifyContentObservers(root) {
for (var i=0, c; i < root._insertionPoints.length; i++) {
c = root._insertionPoints[i];
if (hasDomApi(c)) {
if (DomApi.hasApi(c)) {
Polymer.dom(c).notifyObserver();
}
}
}
function notifyInitialDistribution(host) {
if (hasDomApi(host)) {
if (DomApi.hasApi(host)) {
Polymer.dom(host).notifyObserver();
}
}
var needsUpgrade = window.CustomElements && !CustomElements.useNative;
function upgradeLightChildren(children) {
function upgradeLogicalChildren(children) {
if (needsUpgrade && children) {
for (var i=0; i < children.length; i++) {
CustomElements.upgrade(children[i]);

View File

@ -0,0 +1,42 @@
<!doctype html>
<html>
<head>
<title>annotations</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../../../webcomponentsjs/webcomponents-lite.js"></script>
<script>//Polymer = {dom: 'shadow'}</script>
<link rel="import" href="../../polymer.html">
</head>
<body>
<script>
var ul = document.createElement("ul");
for (i=0; i < 1000; i++) {
var li = document.createElement("li");
ul.appendChild(li);
}
var start = window.performance.now();
var e = ul.firstElementChild;
for (i=0; i <999; i++)
e = e.nextSibling;
console.log(e);
console.log("Native performance: "+(window.performance.now()-start)+"ms");
var start = window.performance.now();
var e = ul.firstElementChild;
for (i=0; i <999; i++)
e = Polymer.dom(e).nextSibling;
console.log(e);
console.log("Polymer performance: "+(window.performance.now()-start)+"ms");
</script>
</body>
</html>

View File

@ -948,7 +948,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assert.equal(host.$.distWrapper.children[1], s2);
assert.equal(host.$.distWrapper.children[2], s3);
assert.equal(host.$.distWrapper.children[3], s0);
var composedChildren = host.$.distWrapper._composedChildren;
var composedChildren = Polymer.TreeApi.Composed.getChildNodes(host.$.distWrapper);
assert.equal(composedChildren.length, 4);
assert.equal(composedChildren[0], s1);
assert.equal(composedChildren[1], s2);
@ -961,7 +961,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom.flush();
if (host.shadyRoot) {
assert.equal(host.$.distWrapper.children.length, 1);
var composedChildren = host.$.distWrapper._composedChildren;
var composedChildren = Polymer.TreeApi.Composed.getChildNodes(host.$.distWrapper);
assert.equal(composedChildren.length, 1);
assert.equal(composedChildren[0], s0);
}
@ -987,7 +987,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
assert.equal(host.$.distWrapper.children[1], s2);
assert.equal(host.$.distWrapper.children[2], s3);
assert.equal(host.$.distWrapper.children[3], s0);
var composedChildren = host.$.distWrapper._composedChildren;
var composedChildren = Polymer.TreeApi.Composed.getChildNodes(host.$.distWrapper);
assert.equal(composedChildren.length, 4);
assert.equal(composedChildren[0], s1);
assert.equal(composedChildren[1], s2);
@ -1000,7 +1000,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom.flush();
if (host.shadyRoot) {
assert.equal(host.$.distWrapper.children.length, 1);
var composedChildren = host.$.distWrapper._composedChildren;
var composedChildren = Polymer.TreeApi.Composed.getChildNodes(host.$.distWrapper);
assert.equal(composedChildren.length, 1);
assert.equal(composedChildren[0], s0);
}

View File

@ -489,19 +489,19 @@ suite('Polymer.dom', function() {
assert.equal(Polymer.dom(rere).querySelector('#light'), s);
assert.equal(Polymer.dom(s).parentNode, rere);
if (rere.shadyRoot) {
assert.notEqual(s._composedParent, rere);
assert.notEqual(Polymer.TreeApi.Composed.getParentNode(s), rere);
}
Polymer.dom(testElement).flush();
if (rere.shadyRoot) {
assert.equal(s._composedParent, p);
assert.equal(Polymer.TreeApi.Composed.getParentNode(s), p);
}
Polymer.dom(rere).removeChild(s);
if (rere.shadyRoot) {
assert.equal(s._composedParent, p);
assert.equal(Polymer.TreeApi.Composed.getParentNode(s), p);
}
Polymer.dom(testElement).flush();
if (rere.shadyRoot) {
assert.equal(s._composedParent, null);
assert.equal(Polymer.TreeApi.Composed.getParentNode(s), null);
}
});
@ -713,7 +713,7 @@ suite('Polymer.dom accessors', function() {
assert.equal(Polymer.dom(testElement).textContent, 'Hello World', 'textContent getter incorrect');
if (testElement.shadyRoot) {
Polymer.dom.flush();
assert.equal(testElement._composedChildren[1].textContent, 'Hello World', 'text content setter incorrect');
assert.equal(Polymer.TreeApi.Composed.getChildNodes(testElement)[1].textContent, 'Hello World', 'text content setter incorrect');
}
testElement = document.createElement('x-commented');
assert.equal(Polymer.dom(testElement.root).textContent, '[]', 'text content getter with comment incorrect');
@ -737,9 +737,10 @@ suite('Polymer.dom accessors', function() {
assert.equal(Polymer.dom(testElement).innerHTML , '<div>Hello World</div><div>2</div><div>3</div>', 'innerHTML getter incorrect');
if (testElement.shadyRoot) {
Polymer.dom.flush();
assert.equal(testElement._composedChildren[1], added, 'innerHTML setter composed incorrectly');
assert.equal(testElement._composedChildren[2].textContent, '2', 'innerHTML setter composed incorrectly');
assert.equal(testElement._composedChildren[3].textContent, '3', 'innerHTML setter composed incorrectly');
var children = Polymer.TreeApi.Composed.getChildNodes(testElement);
assert.equal(children[1], added, 'innerHTML setter composed incorrectly');
assert.equal(children[2].textContent, '2', 'innerHTML setter composed incorrectly');
assert.equal(children[3].textContent, '3', 'innerHTML setter composed incorrectly');
}
});
@ -804,13 +805,13 @@ suite('Polymer.dom non-distributed elements', function() {
function testNoAttr() {
assert.equal(Polymer.dom(child).getDestinationInsertionPoints()[0], d.$.notTestContent, 'child not distributed logically');
if (shady) {
assert.equal(child._composedParent, d.$.notTestContainer, 'child not rendered in composed dom');
assert.equal(Polymer.TreeApi.Composed.getParentNode(child), d.$.notTestContainer, 'child not rendered in composed dom');
}
}
function testWithAttr() {
assert.equal(Polymer.dom(child).getDestinationInsertionPoints()[0], d.$.testContent, 'child not distributed logically');
if (shady) {
assert.equal(child._composedParent, d.$.testContainer, 'child not rendered in composed dom');
assert.equal(Polymer.TreeApi.Composed.getParentNode(child), d.$.testContainer, 'child not rendered in composed dom');
}
}
// test with x-distribute

View File

@ -135,14 +135,14 @@ test('Reproject', function() {
assert.strictEqual(getComposedHTML(host),
'<x-content-test id="p">a: <a></a>b: <b></b></x-content-test>');
assertArrayEqual(host._lightChildren, [a]);
assert.strictEqual(a._lightParent, host);
assertArrayEqual(host.shadyRoot._lightChildren, [p]);
assert.strictEqual(p._lightParent, host.shadyRoot);
assertArrayEqual(p._lightChildren, [b, content]);
assert.strictEqual(b._lightParent, p);
assert.strictEqual(content._lightParent, p);
assertArrayEqual(p.shadyRoot._lightChildren,
assertArrayEqual(host.__childNodes, [a]);
assert.strictEqual(a.__parentNode, host);
assertArrayEqual(host.shadyRoot.__childNodes, [p]);
assert.strictEqual(p.__parentNode, host.shadyRoot);
assertArrayEqual(p.__childNodes, [b, content]);
assert.strictEqual(b.__parentNode, p);
assert.strictEqual(content.__parentNode, p);
assertArrayEqual(p.shadyRoot.__childNodes,
[textNodeA, contentA, textNodeB, contentB]);
}
@ -165,7 +165,7 @@ suite('Mutate light DOM', function() {
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<a></a>');
host._lightChildren = [];
host.__childNodes = [];
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), 'fallback');
});
@ -180,11 +180,11 @@ suite('Mutate light DOM', function() {
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b>after</b>');
host.shadyRoot._lightChildren[1].textContent = '';
host.shadyRoot.__childNodes[1].textContent = '';
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');
host.shadyRoot._lightChildren = [];
host.shadyRoot.__childNodes = [];
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '');
});
@ -203,11 +203,11 @@ suite('Mutate light DOM', function() {
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<b></b>');
host.shadyRoot.firstChild._lightChildren = [];
host.shadyRoot.firstChild.__childNodes = [];
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '');
host.shadyRoot._lightChildren = [];
host.shadyRoot.__childNodes = [];
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '');
});
@ -225,7 +225,7 @@ suite('Mutate light DOM', function() {
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<a></a>');
host._lightChildren = [];
host.__childNodes = [];
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), 'fallback');
});
@ -265,11 +265,11 @@ suite('Mutate light DOM', function() {
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');
host.shadyRoot._lightChildren.splice(1, 1); // remove b
host.shadyRoot.__childNodes.splice(1, 1); // remove b
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
host.shadyRoot._lightChildren = []; // remove a
host.shadyRoot.__childNodes = []; // remove a
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '');
});
@ -368,7 +368,7 @@ suite('Mutate light DOM', function() {
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
var b = document.createElement('b');
host._lightChildren[0] = b;
host.__childNodes[0] = b;
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<b></b>');
});
@ -437,12 +437,16 @@ suite('Mutate light DOM', function() {
});
function syncLightDOM(n) {
if (n._lightChildren) {
if (n.__childNodes) {
var c$ = n.__patched ? n._composedChildren || [] : Array.prototype.slice.call(n.childNodes);
c$.forEach(function(c) {
if (n._lightChildren.indexOf(c) < 0) {
c._lightParent = n;
n._lightChildren.push(c);
n.__firstChild = c$[0];
n.__lastChild = c$[c$.length-1];
c$.forEach(function(c, i) {
if (n.__childNodes.indexOf(c) < 0) {
c.__parentNode = n;
c.__previousSibling = c$[i-1];
c.__nextSibling = c$[i+1];
n.__childNodes.push(c);
}
});
}