Merge pull request #3112 from Polymer/shady-linked

ShadyDOM: use a linked-list for tree accessors
This commit is contained in:
Kevin Schaaf 2015-12-15 17:09:54 -08:00
commit 273ab0fbe6
22 changed files with 1732 additions and 1092 deletions

View File

@ -57,12 +57,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_initFeatures: function() {
// setup gestures
this._setupGestures();
// manage configuration
this._setupConfigure();
// setup style properties
this._setupStyleProperties();
// setup debouncers
this._setupDebouncers();
// setup shady
this._setupShady();
this._registerHost();
if (this._template) {
// manage local dom

View File

@ -366,18 +366,22 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// instance-time
_localSubTree: function(node, host) {
return (node === host) ? node.childNodes :
(node._lightChildren || node.childNodes);
},
findAnnotatedNode: function(root, annote) {
// recursively ascend tree until we hit root
var parent = annote.parent &&
Polymer.Annotations.findAnnotatedNode(root, annote.parent);
// unwind the stack, returning the indexed node at each level
return !parent ? root :
Polymer.Annotations._localSubTree(parent, root)[annote.index];
if (parent) {
// note: marginally faster than indexing via childNodes
// (http://jsperf.com/childnodes-lookup)
for (var n=parent.firstChild, i=0; n; n=n.nextSibling) {
if (annote.index === i++) {
return n;
}
}
} else {
return root;
}
}
};

View File

@ -33,6 +33,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
createdCallback: function() {
Polymer.telemetry.instanceCount++;
this.isAttached = false;
this.root = this;
this._doBehavior('created'); // abstract
this._initFeatures(); // abstract

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._maybeDistributeParent();
}
},
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>

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

@ -0,0 +1,594 @@
<!--
@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 dom = DomApi.factory;
var TreeApi = Polymer.TreeApi;
var getInnerHTML = Polymer.domInnerHTML.getInnerHTML;
var CONTENT = DomApi.CONTENT;
// *************** Configure DomApi for Shady DOM!! ***************
if (Settings.useShadow) {
return;
}
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.insertBefore(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)
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');
}
// remove node from its current position iff it's in a tree.
if (node.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) {
var parent = TreeApi.Logical.getParentNode(node);
// notify existing parent that this node is being removed.
if (parent) {
if (DomApi.hasApi(parent)) {
dom(parent).notifyObserver();
}
this._removeNode(node);
} else {
this._removeOwnerShadyRoot(node);
}
}
if (!this._addNode(node, ref_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) {
TreeApi.Composed.insertBefore(container, node, ref_node);
} else {
TreeApi.Composed.appendChild(container, node);
}
}
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.
*/
removeChild: function(node) {
if (TreeApi.Logical.getParentNode(node) !== this.node) {
throw Error('The node to be removed is not a child of this node: ' +
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.
// undistributed nodes.
if (container === node.parentNode) {
TreeApi.Composed.removeChild(container, node);
}
}
this.notifyObserver();
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) {
// 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)._maybeDistributeParent();
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);
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 = TreeApi.Logical.getParentNode(node);
if (parent) {
root = parent._isShadyRoot ? parent :
this._ownerShadyRootForNode(parent);
} else {
root = null;
}
}
node._ownerShadyRoot = root;
}
return node._ownerShadyRoot;
},
_maybeDistribute: function(node) {
// 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 && dom(node).querySelector(CONTENT);
var wrappedContent = fragContent &&
(TreeApi.Logical.getParentNode(fragContent).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.getOwnerRoot();
if (root) {
// note, insertion point list update is handled after node
// mutations are complete
this._lazyDistribute(root.host);
}
}
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 needsDist || (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$ = dom(node).querySelectorAll(CONTENT);
for (var i=0, n, np, na; (i<c$.length) && (n=c$[i]); i++) {
np = TreeApi.Logical.getParentNode(n);
// 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) {
TreeApi.Composed.removeChild(parent, node);
}
return true;
}
},
_updateInsertionPoints: function(host) {
var i$ = host.shadyRoot._insertionPoints =
dom(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(TreeApi.Logical.getParentNode(c));
}
},
_nodeNeedsDistribution: function(node) {
return node && node.shadyRoot &&
DomApi.hasInsertionPoint(node.shadyRoot);
},
// a node being added is always in this same host as this.node.
_addNodeToHost: function(host, node) {
if (host._elementAdd) {
host._elementAdd(node);
}
},
_removeNodeFromHost: function(host, node) {
if (host._elementRemove) {
host._elementRemove(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$ = dom(content).getDistributedNodes();
for (var j=0; j<dc$.length; j++) {
hostNeedsDist = true;
var node = dc$[j];
var parent = node.parentNode;
if (parent) {
TreeApi.Composed.removeChild(parent, node);
}
}
}
}
return hostNeedsDist;
},
_contains: function(container, node) {
while (node) {
if (node == container) {
return true;
}
node = TreeApi.Logical.getParentNode(node);
}
},
_removeOwnerShadyRoot: function(node) {
// optimization: only reset the tree if node is actually in a root
if (this._hasCachedOwnerRoot(node)) {
var c$ = TreeApi.Logical.getChildNodes(node);
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$ = dom(content).getDistributedNodes();
for (var i=0, l=n$.length, n, p$; (i<l) && (n=n$[i]); i++) {
p$ = dom(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(TreeApi.Logical.getChildNodes(node), 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(TreeApi.Logical.getChildNodes(node), 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._maybeDistributeParent();
},
removeAttribute: function(name) {
this.node.removeAttribute(name);
this._maybeDistributeParent();
},
_maybeDistributeParent: function() {
if (this._nodeNeedsDistribution(this.parentNode)) {
this._lazyDistribute(this.parentNode);
return true;
}
},
cloneNode: function(deep) {
var n = nativeCloneNode.call(this.node, false);
if (deep) {
var c$ = this.childNodes;
var d = dom(n);
for (var i=0, nc; i < c$.length; i++) {
nc = dom(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$ = TreeApi.Logical.getChildNodes(externalNode);
var d = dom(n);
for (var i=0, nc; i < c$.length; i++) {
nc = dom(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() {
if (TreeApi.Logical.hasChildNodes(this.node)) {
return Array.prototype.filter.call(this.childNodes, function(n) {
return (n.nodeType === Node.ELEMENT_NODE);
});
} else {
return TreeApi.arrayCopyChildren(this.node);
}
},
configurable: true
},
parentNode: {
get: function() {
return TreeApi.Logical.getParentNode(this.node);
},
configurable: true
},
firstChild: {
get: function() {
return TreeApi.Logical.getFirstChild(this.node);
},
configurable: true
},
lastChild: {
get: function() {
return TreeApi.Logical.getLastChild(this.node);
},
configurable: true
},
nextSibling: {
get: function() {
return TreeApi.Logical.getNextSibling(this.node);
},
configurable: true
},
previousSibling: {
get: function() {
return TreeApi.Logical.getPreviousSibling(this.node);
},
configurable: true
},
firstElementChild: {
get: function() {
return TreeApi.Logical.getFirstElementChild(this.node);
},
configurable: true
},
lastElementChild: {
get: function() {
return TreeApi.Logical.getLastElementChild(this.node);
},
configurable: true
},
nextElementSibling: {
get: function() {
return TreeApi.Logical.getNextElementSibling(this.node);
},
configurable: true
},
previousElementSibling: {
get: function() {
return TreeApi.Logical.getPreviousElementSibling(this.node);
},
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$ = dom(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,420 +138,37 @@ 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) {
var dom = DomApi.factory = function(node) {
node = node || document;
if (!node.__domApi) {
node.__domApi = new DomApi(node, patch);
node.__domApi = new DomApi.ctor(node);
}
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>
</script>

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

@ -0,0 +1,288 @@
<!--
@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 nativeAppendChild = Element.prototype.appendChild;
var nativeRemoveChild = Element.prototype.removeChild;
/**
* TreeApi is a dom manipulation library used by Shady/Polymer.dom to
* manipulate composed and logical trees.
*/
var TreeApi = 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 = {
hasParentNode: function(node) {
return Boolean(node.__dom && node.__dom.parentNode);
},
hasChildNodes: function(node) {
return Boolean(node.__dom && node.__dom.childNodes !== undefined);
},
getChildNodes: function(node) {
// note: we're distinguishing here between undefined and false-y:
// hasChildNodes uses undefined check to see if this element has logical
// children; the false-y check indicates whether or not we should rebuild
// the cached childNodes array.
return this.hasChildNodes(node) ? this._getChildNodes(node) :
node.childNodes;
},
_getChildNodes: function(node) {
if (!node.__dom.childNodes) {
node.__dom.childNodes = [];
for (var n=node.__dom.firstChild; n; n=n.__dom.nextSibling) {
node.__dom.childNodes.push(n);
}
}
return node.__dom.childNodes;
},
getParentNode: function(node) {
return node.__dom && node.__dom.parentNode || node.parentNode;
},
getFirstChild: function(node) {
return node.__dom && node.__dom.firstChild || node.firstChild;
},
getLastChild: function(node) {
return node.__dom && node.__dom.lastChild || node.lastChild;
},
getNextSibling: function(node) {
return node.__dom && node.__dom.nextSibling || node.nextSibling;
},
getPreviousSibling: function(node) {
return node.__dom && node.__dom.previousSibling || node.previousSibling;
},
getFirstElementChild: function(node) {
return node.__dom && node.__dom.firstChild ?
this._getFirstElementChild(node) : node.firstElementChild;
},
_getFirstElementChild: function(node) {
var n = node.__dom.firstChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.nextSibling;
}
return n;
},
getLastElementChild: function(node) {
return node.__dom && node.__dom.lastChild ?
this._getLastElementChild(node) : node.firstElementChild;
},
_getLastElementChild: function(node) {
var n = node.__dom.lastChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.previousSibling;
}
return n;
},
getNextElementSibling: function(node) {
return node.__dom && node.__dom.nextSibling ?
this._getNextElementSibling(node) : node.nextElementSibling;
},
_getNextElementSibling: function(node) {
var n = node.__dom.nextSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.nextSibling;
}
return n;
},
getPreviousElementSibling: function(node) {
return node.__dom && node.__dom.previousSibling ?
this._getPreviousElementSibling(node) : node.previousElementSibling;
},
_getPreviousElementSibling: function(node) {
var n = node.__dom.previousSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.previousSibling;
}
return n;
},
// 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.__dom = node.__dom || {};
node.__dom.firstChild = node.firstChild;
node.__dom.lastChild = node.lastChild;
node.__dom.childNodes = [];
for (var n=node.firstChild; n; n=n.nextSibling) {
n.__dom = n.__dom || {};
n.__dom.parentNode = node;
node.__dom.childNodes.push(n);
n.__dom.nextSibling = n.nextSibling;
n.__dom.previousSibling = n.previousSibling;
}
}
},
recordInsertBefore: function(node, container, ref_node) {
container.__dom.childNodes = null;
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// TODO(sorvell): remember this for patching:
// the act of setting this info can affect patched nodes
// getters; therefore capture childNodes before patching.
for (var n=node.firstChild; n; n=n.nextSibling) {
this._linkNode(n, container, ref_node);
}
} else {
this._linkNode(node, container, ref_node);
}
},
_linkNode: function(node, container, ref_node) {
node.__dom = node.__dom || {};
container.__dom = container.__dom || {};
if (ref_node) {
ref_node.__dom = ref_node.__dom || {};
}
// update ref_node.previousSibling <-> node
node.__dom.previousSibling = ref_node ? ref_node.__dom.previousSibling :
container.__dom.lastChild;
if (node.__dom.previousSibling) {
node.__dom.previousSibling.__dom.nextSibling = node;
}
// update node <-> ref_node
node.__dom.nextSibling = ref_node;
if (node.__dom.nextSibling) {
node.__dom.nextSibling.__dom.previousSibling = node;
}
// update node <-> container
node.__dom.parentNode = container;
if (ref_node) {
if (ref_node === container.__dom.firstChild) {
container.__dom.firstChild = node;
}
} else {
container.__dom.lastChild = node;
if (!container.__dom.firstChild) {
container.__dom.firstChild = node;
}
}
// remove caching of childNodes
container.__dom.childNodes = null;
},
recordRemoveChild: function(node, container) {
node.__dom = node.__dom || {};
container.__dom = container.__dom || {};
if (node === container.__dom.firstChild) {
container.__dom.firstChild = node.__dom.nextSibling;
}
if (node === container.__dom.lastChild) {
container.__dom.lastChild = node.__dom.previousSibling;
}
var p = node.__dom.previousSibling;
var n = node.__dom.nextSibling;
if (p) {
p.__dom.nextSibling = n;
}
if (n) {
n.__dom.previousSibling = p;
}
node.__dom.parentNode = node.__dom.previousSibling =
node.__dom.nextSibling = null;
// remove caching of childNodes
container.__dom.childNodes = null;
}
}
// 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 = {
getChildNodes: function(node) {
return Polymer.TreeApi.arrayCopyChildNodes(node);
},
getParentNode: function(node) {
return node.parentNode;
},
// 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) {
return nativeInsertBefore.call(parentNode, newChild, refChild || null);
},
appendChild: function(parentNode, newChild) {
return nativeAppendChild.call(parentNode, newChild);
},
removeChild: function(parentNode, node) {
return nativeRemoveChild.call(parentNode, node);
}
};
})();
</script>

View File

@ -22,7 +22,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
(function() {
// ******* Only patch if we're using Shady DOM *******
if (Polymer.Settings.useShadow) {
return;
}
var baseFinishDistribute = Polymer.Base._finishDistribute;
var TreeApi = Polymer.TreeApi;
var DomApi = Polymer.DomApi;
var dom = Polymer.dom;
var nativeInsertBefore = Element.prototype.insertBefore;
var nativeAppendChild = Element.prototype.appendChild;
var nativeRemoveChild = Element.prototype.removeChild;
// NOTE: any manipulation of a node must occur in a patched parent
// so that the parent can cleanup the node's composed and logical
@ -30,13 +42,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// likely to be satisfied.
// Also note that any use of qS/qSA must be done in a patched node.
Polymer.Base._finishDistribute = function() {
var hasDistributed = this.root._hasDistributed;
baseFinishDistribute.call(this);
if (!this.__patched) {
if (!hasDistributed) {
for (var n=this.firstChild; n; n=n.nextSibling) {
Polymer.dom(n);
};
Polymer.dom(this);
Polymer.dom(this.root);
Array.prototype.forEach.call(this.childNodes, function(c) {
Polymer.dom(c);
});
// TODO(sorvell): ensure top element's parents are wrapped (helped A2
// since it uses qSA on the fragment containing stamped custom elements)
// note that getOwnerRoot will patch all parents but there should be an
@ -48,34 +61,31 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded;
var getComposedChildren = Polymer.DomApi.getComposedChildren;
var nativeShadow = Polymer.Settings.useShadow;
var excluded = ['head'];
Polymer.telemetry.patched = 0;
// experimental: support patching selected native api.
Polymer.DomApi.ctor.prototype.patch = function(force) {
if (nativeShadow || this.node.__patched ||
(this.node.localName && excluded.indexOf(this.node.localName) >= 0)) {
return;
}
var ctor = Polymer.DomApi.ctor;
Polymer.DomApi.ctor = function(node) {
Polymer.DomApi.patch(node);
return new ctor(node);
}
getComposedChildren(this.node);
saveLightChildrenIfNeeded(this.node);
if (!this.node._lightParent) {
this.node._lightParent = this.node.parentNode;
}
if (!this.node._composedParent) {
this.node._composedParent = this.node.parentNode;
}
// TODO(sorvell): correctly patch non-element nodes.
if (this.node.nodeType !== Node.TEXT_NODE) {
this.node.__patched = true;
patchImpl.patch(this.node);
Polymer.DomApi.ctor.prototype = ctor.prototype;
Polymer.DomApi.patch = function(node) {
if (!node.__patched &&
(!node.localName || !excluded.indexOf(node.localName) >= 0)) {
TreeApi.Logical.saveChildNodes(node);
if (!TreeApi.Composed.hasParentNode(node)) {
TreeApi.Composed.saveParentNode(node);
}
TreeApi.Composed.saveChildNodes(node);
// TODO(sorvell): correctly patch non-element nodes.
if (node.nodeType !== Node.TEXT_NODE) {
node.__patched = true;
patchImpl.patch(node);
}
}
};
@ -90,9 +100,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this.unpatch();
};
var log = false;
// allows attribute setting to be patched
var nativeSetAttribute = Element.prototype.setAttribute;
Polymer.DomApi.ctor.prototype.setAttribute = function(name, value) {
nativeSetAttribute.call(this.node, name, value);
this._maybeDistributeParent();
};
var factory = Polymer.DomApi.factory;
var nativeRemoveAttribute = Element.prototype.removeAttribute;
Polymer.DomApi.ctor.prototype.removeAttribute = function(name, value) {
nativeRemoveAttribute.call(this.node, name);
this._maybeDistributeParent();
};
var log = false;
var patchImpl = {
@ -101,7 +123,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
methods: ['appendChild', 'insertBefore', 'removeChild', 'replaceChild',
'querySelector', 'querySelectorAll', 'getDestinationInsertionPoints',
'cloneNode', 'importNode'],
'cloneNode', 'setAttribute', 'removeAttribute'],
// <content>: getDistributedNodes
accessors: ['parentNode', 'childNodes',
@ -160,7 +182,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
obj['_$' + name + '$_'] = orig;
obj[name] = function() {
log && console.log(this, name, arguments);
return factory(this)[name].apply(this.__domApi, arguments);
return dom(this)[name].apply(this.__domApi, arguments);
};
},
@ -170,14 +192,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
var info = {
get: function() {
log && console.log(this, name);
return factory(this)[name];
return dom(this)[name];
},
configurable: true
};
if (writable) {
info.set = function(value) {
factory(this)[name] = value;
dom(this)[name] = value;
};
}
Object.defineProperty(obj, name, info);
@ -216,20 +237,365 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
Polymer.DomApi.getLightChildren = function(node) {
var children = node._lightChildren;
return children ? children : Polymer.DomApi.getComposedChildren(node);
}
Polymer.Base.instanceTemplate = function(template) {
var m = document._$importNode$_ || document.importNode;
var dom =
m.call(document, template._content || template.content, true);
return dom;
}
Polymer.DomApi.patchImpl = patchImpl;
// NOTE: patch logical implementations here so we can use
// composed getters
// TODO(sorvell): may need to patch saveChildNodes iff the tree has
// already been distributed.
TreeApi.Logical.recordInsertBefore = function(node, container, ref_node) {
container.__dom.childNodes = null;
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// TODO(sorvell): remember this for patching:
// the act of setting this info can affect patched nodes
// getters; therefore capture childNodes before patching.
for (var n=TreeApi.Composed.getFirstChild(node); n;
n=TreeApi.Composed.getNextSibling(n)) {
this._linkNode(n, container, ref_node);
}
} else {
this._linkNode(node, container, ref_node);
}
}
TreeApi.Logical.getParentNode = function(node) {
return node.__dom && node.__dom.parentNode ||
TreeApi.Composed.getParentNode(node);
};
TreeApi.Logical.getFirstChild = function(node) {
return node.__dom && node.__dom.firstChild ||
TreeApi.Composed.getFirstChild(node);
};
TreeApi.Logical.getLastChild = function(node) {
return node.__dom && node.__dom.lastChild ||
TreeApi.Composed.getLastChild(node);
};
TreeApi.Logical.getNextSibling = function(node) {
return node.__dom && node.__dom.nextSibling ||
TreeApi.Composed.getNextSibling(node);
};
TreeApi.Logical.getPreviousSibling = function(node) {
return node.__dom && node.__dom.previousSibling ||
TreeApi.Composed.getPreviousSibling(node);
};
TreeApi.Logical.getFirstElementChild = function(node) {
return node.__dom && node.__dom.firstChild ?
this._getFirstElementChild(node) :
TreeApi.Composed.getFirstElementChild(node);
};
TreeApi.Logical.getLastElementChild = function(node) {
return node.__dom && node.__dom.lastChild ?
this._getLastElementChild(node) :
TreeApi.Composed.getLastElementChild(node);
};
TreeApi.Logical.getNextElementSibling = function(node) {
return node.__dom && node.__dom.nextSibling ?
this._getNextElementSibling(node) :
TreeApi.Composed.getNextElementSibling(node);
};
TreeApi.Logical.getPreviousElementSibling = function(node) {
return node.__dom && node.__dom.previousSibling ?
this._getPreviousElementSibling(node) :
TreeApi.Composed.getPreviousElementSibling(node);
};
// TODO(sorvell): This is largely copy/pasted from the Logical tree
// implementation. The code could be factored such that it could be shared
// but there are perf trade offs to consider.
TreeApi.Composed = {
hasParentNode: function(node) {
return Boolean(node.__dom && node.__dom.$parentNode !== undefined);
},
hasChildNodes: function(node) {
return Boolean(node.__dom && node.__dom.$childNodes !== undefined);
},
getChildNodes: function(node) {
return this.hasChildNodes(node) ? this._getChildNodes(node) :
(!node.__patched && TreeApi.arrayCopy(node.childNodes));
},
_getChildNodes: function(node) {
if (!node.__dom.$childNodes) {
node.__dom.$childNodes = [];
for (var n=node.__dom.$firstChild; n; n=n.__dom.$nextSibling) {
node.__dom.$childNodes.push(n);
}
}
return node.__dom.$childNodes;
},
getComposedChildNodes: function(node) {
return node.__dom.$childNodes;
},
getParentNode: function(node) {
return this.hasParentNode(node) ? node.__dom.$parentNode :
(!node.__patched && node.parentNode);
},
getFirstChild: function(node) {
return node.__patched ? node.__dom.$firstChild : node.firstChild;
},
getLastChild: function(node) {
return node.__patched ? node.__dom.$lastChild : node.lastChild;
},
getNextSibling: function(node) {
return node.__patched ? node.__dom.$nextSibling : node.nextSibling;
},
getPreviousSibling: function(node) {
return node.__patched ? node.__dom.$previousSibling : node.previousSibling;
},
getFirstElementChild: function(node) {
return node.__patched ? this._getFirstElementChild(node) :
node.firstElementChild;
},
_getFirstElementChild: function(node) {
var n = node.__dom.$firstChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.$nextSibling;
}
return n;
},
getLastElementChild: function(node) {
return node.__patched ? this._getLastElementChild(node) :
node.firstElementChild;
},
_getLastElementChild: function(node) {
var n = node.__dom.$lastChild;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.$previousSibling;
}
return n;
},
getNextElementSibling: function(node) {
return node.__patched ? this._getNextElementSibling(node) :
node.nextElementSibling;
},
_getNextElementSibling: function(node) {
var n = node.__dom.$nextSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.$nextSibling;
}
return n;
},
getPreviousElementSibling: function(node) {
return node.__patched ? this._getPreviousElementSibling(node) :
node.previousElementSibling;
},
_getPreviousElementSibling: function(node) {
var n = node.__dom.$previousSibling;
while (n && n.nodeType !== Node.ELEMENT_NODE) {
n = n.__dom.$previousSibling;
}
return n;
},
saveChildNodes: function(node) {
if (!this.hasChildNodes(node)) {
node.__dom = node.__dom || {};
node.__dom.$firstChild = node.firstChild;
node.__dom.$lastChild = node.lastChild;
node.__dom.$childNodes = [];
for (var n=node.firstChild; n; n=n.nextSibling) {
n.__dom = n.__dom || {};
n.__dom.$parentNode = node;
node.__dom.$childNodes.push(n);
n.__dom.$nextSibling = n.nextSibling;
n.__dom.$previousSibling = n.previousSibling;
}
}
},
recordInsertBefore: function(node, container, ref_node) {
container.__dom.$childNodes = null;
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
// TODO(sorvell): remember this for patching:
// the act of setting this info can affect patched nodes
// getters; therefore capture childNodes before patching.
for (var n=this.getFirstChild(node); n; n=this.getNextSibling(n)) {
this._linkNode(n, container, ref_node);
}
} else {
this._linkNode(node, container, ref_node);
}
},
_linkNode: function(node, container, ref_node) {
node.__dom = node.__dom || {};
container.__dom = container.__dom || {};
if (ref_node) {
ref_node.__dom = ref_node.__dom || {};
}
// update ref_node.previousSibling <-> node
node.__dom.$previousSibling = ref_node ? ref_node.__dom.$previousSibling :
container.__dom.$lastChild;
if (node.__dom.$previousSibling) {
node.__dom.$previousSibling.__dom.$nextSibling = node;
}
// update node <-> ref_node
node.__dom.$nextSibling = ref_node;
if (node.__dom.$nextSibling) {
node.__dom.$nextSibling.__dom.$previousSibling = node;
}
// update node <-> container
node.__dom.$parentNode = container;
if (ref_node) {
if (ref_node === container.__dom.$firstChild) {
container.__dom.$firstChild = node;
}
} else {
container.__dom.$lastChild = node;
if (!container.__dom.$firstChild) {
container.__dom.$firstChild = node;
}
}
// remove caching of childNodes
container.__dom.$childNodes = null;
},
recordRemoveChild: function(node, container) {
node.__dom = node.__dom || {};
container.__dom = container.__dom || {};
if (node === container.__dom.$firstChild) {
container.__dom.$firstChild = node.__dom.$nextSibling;
}
if (node === container.__dom.$lastChild) {
container.__dom.$lastChild = node.__dom.$previousSibling;
}
var p = node.__dom.$previousSibling;
var n = node.__dom.$nextSibling;
if (p) {
p.__dom.$nextSibling = n;
}
if (n) {
n.__dom.$previousSibling = p;
}
node.__dom.$parentNode = node.__dom.$previousSibling =
node.__dom.$nextSibling = null;
// remove caching of childNodes
container.__dom.$childNodes = null;
},
// 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) {
if (node.__dom && node.__dom.$parentNode) {
node.__dom.$parentNode.__dom.$childNodes = [];
}
node.textContent = '';
},
saveParentNode: function(node) {
node.__dom = node.__dom || {};
node.__dom.$parentNode = node.parentNode;
},
insertBefore: function(parentNode, newChild, refChild) {
this.saveChildNodes(parentNode);
// remove from current location.
if (this.hasParentNode(newChild)) {
var oldParent = this.getParentNode(newChild);
if (oldParent) {
this._removeChild(oldParent, newChild);
}
}
this._addChild(parentNode, newChild, refChild);
return nativeInsertBefore.call(parentNode, newChild, refChild || null);
},
appendChild: function(parentNode, newChild) {
this.saveChildNodes(parentNode);
// remove from current location.
if (this.hasParentNode(newChild)) {
var oldParent = this.getParentNode(newChild);
if (oldParent) {
this._removeChild(oldParent, newChild);
}
}
this._addChild(parentNode, newChild);
return nativeAppendChild.call(parentNode, newChild);
},
removeChild: function(parentNode, node) {
var currentParent = this.getParentNode(node);
this.saveChildNodes(parentNode);
this._removeChild(parentNode, node);
if (currentParent === parentNode) {
// TODO(sorvell); abort if the composedParent is not expected...
if (!node.__patched && node.parentNode !== node.__dom.$parentNode) {
return;
}
return nativeRemoveChild.call(parentNode, node);
}
},
_addChild: function(parentNode, newChild, refChild) {
if (newChild.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
var c$ = this.getChildNodes(newChild);
for (var j=0; j < c$.length; j++) {
this._addChild(parentNode, c$[j], refChild);
}
} else {
newChild.__dom = newChild.__dom || {};
newChild.__dom.$parentNode = parentNode;
var c$ = this.getComposedChildNodes(parentNode);
if (c$) {
var i = c$.indexOf(refChild);
i = i === -1 ? c$.length : i;
c$.splice(i, 0, newChild);
}
}
},
_removeChild: function(parentNode, node) {
node.__dom = node.__dom || {};
node.__dom.$parentNode = null;
var c$ = this.getComposedChildNodes(parentNode);
if (c$) {
var i = c$.indexOf(node);
if (i >= 0) {
c$.splice(i, 1);
}
}
}
};
// patch important nodes
if (window.document) {
Polymer.dom(document);
if (document.body) {
Polymer.dom(document.body);
}
}
})();
</script>

View File

@ -107,7 +107,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

@ -69,6 +69,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (host && host._clients) {
host._clients.push(this);
}
this._clients = null;
this._clientsReadied = false;
},
// establish this element as the current hosting element (allows
@ -86,6 +88,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_tryReady: function() {
this._readied = false;
if (this._canReady()) {
this._ready();
}

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({
@ -30,11 +34,25 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
this._useContent = this._useContent || Boolean(this._template);
},
_setupShady: function() {
// object shaping...
this.shadyRoot = null;
if (!this.__domApi) {
this.__domApi = null;
}
if (!this.__dom) {
this.__dom = null;
}
if (!this._ownerShadyRoot) {
this._ownerShadyRoot = undefined;
}
},
// called as part of content initialization, prior to template stamping
_poolContent: function() {
if (this._useContent) {
// capture lightChildren to help reify dom scoping
saveLightChildrenIfNeeded(this);
TreeApi.Logical.saveChildNodes(this);
}
},
@ -47,7 +65,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 +84,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;
},
@ -101,19 +119,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
*/
distributeContent: function(updateInsertionPoints) {
if (this.shadyRoot) {
var dom = Polymer.dom(this);
if (updateInsertionPoints) {
dom._updateInsertionPoints(this);
}
this.shadyRoot._invalidInsertionPoints =
this.shadyRoot._invalidInsertionPoints || updateInsertionPoints;
// Distribute the host that's the top of this element's distribution
// tree. Distributing that host will *always* distibute this element.
var host = getTopDistributingHost(this);
dom._lazyDistribute(host);
Polymer.dom(this)._lazyDistribute(host);
}
},
_distributeContent: function() {
if (this._useContent && !this.shadyRoot._distributionClean) {
if (this.shadyRoot._invalidInsertionPoints) {
Polymer.dom(this)._updateInsertionPoints(this);
this.shadyRoot._invalidInsertionPoints = false;
}
// logically distribute self
this._beginDistribute();
this._distributeDirtyRoots();
@ -122,7 +142,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 +167,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 +204,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 +212,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 +234,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 +280,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 +293,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 = TreeApi.Logical.getParentNode(p);
if (!parent._useContent && (parent !== this) &&
(parent !== this.shadyRoot)) {
this._updateChildNodes(parent, this._composeNode(parent));
@ -288,7 +304,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 +324,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 +334,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(container, n);
}
composed.splice(s.index + d, 1);
}
@ -330,12 +346,11 @@ 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);
},
_matchesContentSelect: function(node, contentElement) {
@ -375,14 +390,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 +415,10 @@ 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;
// only get logical parent.
var parent = TreeApi.Logical.getParentNode(content);
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 +435,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; 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)) {
@ -468,10 +446,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// Return true if a host's children includes
// an insertion point that selects selectively
function hostNeedsRedistribution(host) {
var c$ = Polymer.dom(host).children;
var c$ = TreeApi.Logical.getChildNodes(host);
for (var i=0, c; i < c$.length; i++) {
c = c$[i];
if (c.localName === 'content') {
if (c.localName && c.localName === 'content') {
return host.domHost;
}
}
@ -480,21 +458,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

@ -47,6 +47,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_setupConfigure: function(initialConfig) {
this._config = {};
this._handlers = [];
this._aboveConfig = null;
if (initialConfig) {
// don't accept undefined values in intialConfig
for (var i in initialConfig) {

View File

@ -676,6 +676,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
};
Polymer.Base._addFeature({
_setupGestures: function() {
this.__polymerGestures = null;
},
// override _listen to handle gestures
_listen: function(node, eventName, handler) {
if (Gestures.gestures[eventName]) {

View File

@ -55,6 +55,11 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
// here we have an instance time spot to put custom property data
_setupStyleProperties: function() {
this.customStyle = {};
this._styleCache = null;
this._styleProperties = null;
this._scopeSelector = null;
this._ownStyleProperties = null;
this._customStyle = null;
},
_needsStyleProperties: function() {

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

@ -247,25 +247,23 @@ suite('Polymer.dom (patch)', function() {
s.id = 'light';
s.textContent = 'Light';
rere.appendChild(s);
// TODO(sorvell); patch
Polymer.dom(s);
assert.equal(rere.querySelector('#light'), s);
assert.equal(s.parentNode, rere);
Polymer.dom.flush();
if (rere.shadyRoot) {
assert.notEqual(s._composedParent, rere);
assert.notEqual(s.__dom.$parentNode, rere);
}
Polymer.dom.flush();
if (rere.shadyRoot) {
assert.equal(s._composedParent, p);
assert.equal(s.__dom.$parentNode, p);
}
rere.removeChild(s);
if (rere.shadyRoot) {
assert.equal(s._composedParent, p);
assert.equal(s.__dom.$parentNode, p);
}
Polymer.dom.flush();
if (rere.shadyRoot) {
assert.equal(s._composedParent, null);
assert.equal(s.__dom.$parentNode, null);
}
});
@ -298,35 +296,6 @@ suite('Polymer.dom (patch)', function() {
assert.notOk(projected);
});
test('Polymer.dom event', function() {
var test = document.querySelector('x-test');
var rere = test.root.querySelector('x-rereproject');
var re = rere.root.querySelector('x-reproject');
var p = re.root.querySelector('x-project');
var eventHandled = 0;
test.addEventListener('test-event', function(e) {
eventHandled++;
assert.equal(Polymer.dom(e).rootTarget, p);
assert.equal(Polymer.dom(e).localTarget, test);
var path = Polymer.dom(e).path;
// path includes window only on more recent Shadow DOM implementations
// account for that here.
assert.ok(path.length >= 10);
assert.equal(path[0], p);
assert.equal(path[2], re);
assert.equal(path[4], rere);
assert.equal(path[6], test);
});
rere.addEventListener('test-event', function(e) {
eventHandled++;
assert.equal(Polymer.dom(e).localTarget, rere);
});
p.fire('test-event');
assert.equal(eventHandled, 2);
});
test('Polymer.dom.childNodes is an array', function() {
assert.isTrue(Array.isArray(Polymer.dom(document.body).childNodes));
});
@ -374,13 +343,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(child.__dom.$parentNode, 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(child.__dom.$parentNode, d.$.testContainer, 'child not rendered in composed dom');
}
}
// test with x-distribute
@ -395,17 +364,12 @@ suite('Polymer.dom non-distributed elements', function() {
testNoAttr();
// set / unset `test` attr and see if it distributes properly
child.setAttribute('test', '');
d.distributeContent();
Polymer.dom.flush();
testWithAttr();
//
child.removeAttribute('test');
d.distributeContent();
Polymer.dom.flush();
testNoAttr();
//
child.setAttribute('test', '');
d.distributeContent();
Polymer.dom.flush();
testWithAttr();
});

View File

@ -0,0 +1,79 @@
<!doctype html>
<!--
@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
-->
<html>
<head>
<meta charset="utf-8">
<script src="../../../../webcomponentsjs/webcomponents-lite.js"></script>
<script src="../../../../web-component-tester/browser.js"></script>
<link rel="import" href="../../../polymer.html">
<link rel="import" href="../../../src/lib/experimental/patch-dom.html">
</head>
<body>
<x-test>
<header>header 1</header>
<footer>footer 1</footer>
</x-test>
<dom-module id="x-test">
<template>
<style>
:host {
display: block;
border: 2px solid black;
padding: 8px;
margin: 8px;
}
.header {
background: steelblue;
}
.content {
margin: 10px;
background: beige;
}
.footer {
background: tomato;
}
</style>
<div>User headers...</div>
<div class="header"><content select="header"></content></div>
<div class="content">Element content...</div>
<div>User footers...</div>
<div class="footer"><content select="footer"></content></div>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({is: 'x-test'});
});
</script>
</dom-module>
<script>
addEventListener('WebComponentsReady', function() {
var test = document.querySelector('x-test');
console.log('should not find anything:',
document.querySelector('.content'),
test.querySelector('.content'));
var f = document.createElement('footer');
f.textContent = 'dynamic footer';
test.insertBefore(f, test.lastElementChild);
var h = document.createElement('header');
h.textContent = 'dynamic header';
test.appendChild(h);
})
</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);
}
@ -1100,7 +1100,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
Polymer.dom(h1).appendChild(d);
@ -1112,7 +1112,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
document.body.removeChild(h1);
@ -1134,7 +1134,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
Polymer.dom(h1).appendChild(d);
@ -1146,7 +1146,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
document.body.removeChild(h1);
@ -1246,7 +1246,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
Polymer.dom(h1).appendChild(d);
@ -1258,7 +1258,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
document.body.removeChild(h1);
@ -1280,7 +1280,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
Polymer.dom(h1).appendChild(d);
@ -1292,7 +1292,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
document.body.removeChild(h1);
@ -1313,7 +1313,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
Polymer.dom(h1).appendChild(d);
@ -1325,7 +1325,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
document.body.removeChild(h1);
@ -1335,7 +1335,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var div = document.createElement('div');
div.innerHTML = '<x-dist-simple><div></div></x-dist-simple>';
var h1 = div.firstChild;
var h2 = document.createDocumentFragment();;
var h2 = document.createDocumentFragment();
document.body.appendChild(h1);
Polymer.dom.flush();
var d = Polymer.dom(h1).firstElementChild;
@ -1347,7 +1347,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
Polymer.dom(h1).appendChild(d);
@ -1359,7 +1359,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(h2).appendChild(d);
Polymer.dom.flush();
assert.equal(Polymer.dom(h2).childNodes.length, 1);
assert.equal(Polymer.dom(h2).firstElementChild, d);
assert.equal(Polymer.dom(h2).firstChild, d);
assert.equal(Polymer.dom(h1).childNodes.length, 0);
assert.deepEqual(Polymer.dom(h1.$.content).getDistributedNodes().length, 0);
document.body.removeChild(h1);

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);
}
});
@ -760,7 +760,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');
@ -784,9 +784,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');
}
});
@ -851,13 +852,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.__dom.childNodes, [a]);
assert.strictEqual(a.__dom.parentNode, host);
assertArrayEqual(host.shadyRoot.__dom.childNodes, [p]);
assert.strictEqual(p.__dom.parentNode, host.shadyRoot);
assertArrayEqual(p.__dom.childNodes, [b, content]);
assert.strictEqual(b.__dom.parentNode, p);
assert.strictEqual(content.__dom.parentNode, p);
assertArrayEqual(p.shadyRoot.__dom.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.__dom.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.__dom.childNodes[1].textContent = '';
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<a>Hello</a><b></b>');
host.shadyRoot._lightChildren = [];
host.shadyRoot.__dom.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.__dom.childNodes = [];
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '');
host.shadyRoot._lightChildren = [];
host.shadyRoot.__dom.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.__dom.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.__dom.childNodes.splice(1, 1); // remove b
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<a>Hello</a>');
host.shadyRoot._lightChildren = []; // remove a
host.shadyRoot.__dom.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.__dom.childNodes[0] = b;
distributeContentNow(host);
assert.strictEqual(getComposedHTML(host), '<b></b>');
});
@ -437,12 +437,17 @@ suite('Mutate light DOM', function() {
});
function syncLightDOM(n) {
if (n._lightChildren) {
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);
if (n.__dom.childNodes !== undefined) {
var c$ = Array.prototype.slice.call(n.childNodes);
n.__dom.firstChild = c$[0];
n.__dom.lastChild = c$[c$.length-1];
c$.forEach(function(c, i) {
if (n.__dom.childNodes.indexOf(c) < 0) {
c.__dom = c.__dom || {};
c.__dom.parentNode = n;
c.__dom.previousSibling = c$[i-1];
c.__dom.nextSibling = c$[i+1];
n.__dom.childNodes.push(c);
}
});
}
@ -452,17 +457,11 @@ var nativeAppendChild = Element.prototype.appendChild;
function setInnerHTML(node, value) {
node.textContent = '';
if (node._composedChildren) {
node._composedChildren = [];
}
var temp = node.ownerDocument.createElement('div');
temp.innerHTML = value;
var firstChild;
while (firstChild = temp.firstChild) {
nativeAppendChild.call(node, firstChild);
if (node._composedChildren) {
node._composedChildren.push(firstChild);
}
}
}
@ -477,11 +476,11 @@ function updateRootInsertionPoints(root) {
}
function getComposedHTML(node) {
return node.__patched ? Polymer.domInnerHTML.getInnerHTML(node, true) : node.innerHTML;
return node.innerHTML;
}
function getComposedChildAtIndex(node, index) {
var c$ = node._composedChildren || node.childNodes;
var c$ = node.childNodes;
if (c$) {
index = index || 0;
index = Math.max(0, Math.min(index, c$.length-1));