Index: third_party/polymer/v0_8/components/polymer/src/lib/dom-api.html |
diff --git a/third_party/polymer/v0_8/components/polymer/src/lib/dom-api.html b/third_party/polymer/v0_8/components/polymer/src/lib/dom-api.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f24dada2d3658eb6b881bae85c9b606e3021c262 |
--- /dev/null |
+++ b/third_party/polymer/v0_8/components/polymer/src/lib/dom-api.html |
@@ -0,0 +1,471 @@ |
+<!-- |
+@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="event-api.html"> |
+<script> |
+ |
+ Polymer.DomApi = (function() { |
+ |
+ var Debounce = Polymer.Debounce; |
+ var Settings = Polymer.Settings; |
+ |
+ var nativeInsertBefore = Element.prototype.insertBefore; |
+ var nativeRemoveChild = Element.prototype.removeChild; |
+ var nativeAppendChild = Element.prototype.appendChild; |
+ |
+ var dirtyRoots = []; |
+ |
+ var DomApi = function(node, patch) { |
+ this.node = node; |
+ if (patch) { |
+ this.patch(); |
+ } |
+ }; |
+ |
+ DomApi.prototype = { |
+ |
+ // experimental: support patching selected native api. |
+ patch: function() { |
+ var self = this; |
+ this.node.appendChild = function(node) { |
+ return self.appendChild(node); |
+ }; |
+ this.node.insertBefore = function(node, ref_node) { |
+ return self.insertBefore(node, ref_node); |
+ }; |
+ this.node.removeChild = function(node) { |
+ return self.removeChild(node); |
+ }; |
+ }, |
+ |
+ get childNodes() { |
+ var c$ = getLightChildren(this.node); |
+ return Array.isArray(c$) ? c$ : Array.prototype.slice.call(c$); |
+ }, |
+ |
+ get children() { |
+ return Array.prototype.filter.call(this.childNodes, function(n) { |
+ return (n.nodeType === Node.ELEMENT_NODE); |
+ }); |
+ }, |
+ |
+ get parentNode() { |
+ return this.node.lightParent || this.node.parentNode; |
+ }, |
+ |
+ flush: function() { |
+ for (var i=0, host; i<dirtyRoots.length; i++) { |
+ host = dirtyRoots[i]; |
+ host.flushDebouncer('_distribute'); |
+ } |
+ dirtyRoots = []; |
+ }, |
+ |
+ _lazyDistribute: function(host) { |
+ if (host.shadyRoot) { |
+ host.shadyRoot._distributionClean = false; |
+ } |
+ // TODO(sorvell): optimize debounce so it does less work by default |
+ // and then remove these checks... |
+ // need to dirty distribution once. |
+ if (!host.isDebouncerActive('_distribute')) { |
+ host.debounce('_distribute', host._distributeContent); |
+ dirtyRoots.push(host); |
+ } |
+ }, |
+ |
+ // cases in which we may not be able to just do standard appendChild |
+ // 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) |
+ appendChild: function(node) { |
+ var distributed; |
+ this._removeNodeFromHost(node); |
+ if (this._nodeIsInLogicalTree(this.node)) { |
+ var host = this._hostForNode(this.node); |
+ this._addLogicalInfo(node, this.node, host && host.shadyRoot); |
+ this._addNodeToHost(node); |
+ if (host) { |
+ distributed = this._maybeDistribute(node, this.node, host); |
+ } |
+ } |
+ if (!distributed) { |
+ // if adding to a shadyRoot, add to host instead |
+ var container = this.node._isShadyRoot ? this.node.host : this.node; |
+ nativeAppendChild.call(container, node); |
+ } |
+ return node; |
+ }, |
+ |
+ insertBefore: function(node, ref_node) { |
+ if (!ref_node) { |
+ return this.appendChild(node); |
+ } |
+ var distributed; |
+ this._removeNodeFromHost(node); |
+ if (this._nodeIsInLogicalTree(this.node)) { |
+ saveLightChildrenIfNeeded(this.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'); |
+ } |
+ var host = this._hostForNode(this.node); |
+ this._addLogicalInfo(node, this.node, host && host.shadyRoot, index); |
+ this._addNodeToHost(node); |
+ if (host) { |
+ distributed = this._maybeDistribute(node, this.node, host); |
+ } |
+ } |
+ if (!distributed) { |
+ // 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; |
+ nativeInsertBefore.call(container, node, ref_node); |
+ } |
+ return node; |
+ }, |
+ |
+ /** |
+ Removes the given `node` from the element's `lightChildren`. |
+ This method also performs dom composition. |
+ */ |
+ removeChild: function(node) { |
+ var distributed; |
+ if (this._nodeIsInLogicalTree(this.node)) { |
+ var host = this._hostForNode(this.node); |
+ distributed = this._maybeDistribute(node, this.node, host); |
+ this._removeNodeFromHost(node); |
+ } |
+ if (!distributed) { |
+ // if removing from a shadyRoot, remove form host instead |
+ var container = this.node._isShadyRoot ? this.node.host : this.node; |
+ nativeRemoveChild.call(container, node); |
+ } |
+ return node; |
+ }, |
+ |
+ replaceChild: function(node, ref_node) { |
+ this.insertBefore(node, ref_node); |
+ this.removeChild(ref_node); |
+ return node; |
+ }, |
+ |
+ getOwnerRoot: function() { |
+ return this._ownerShadyRootForNode(this.node); |
+ }, |
+ |
+ _ownerShadyRootForNode: function(node) { |
+ 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, host) { |
+ var nodeNeedsDistribute = this._nodeNeedsDistribution(node); |
+ var distribute = this._parentNeedsDistribution(parent) || |
+ nodeNeedsDistribute; |
+ if (nodeNeedsDistribute) { |
+ this._updateInsertionPoints(host); |
+ } |
+ if (distribute) { |
+ this._lazyDistribute(host); |
+ } |
+ return distribute; |
+ }, |
+ |
+ _updateInsertionPoints: function(host) { |
+ host.shadyRoot._insertionPoints = |
+ factory(host.shadyRoot).querySelectorAll(CONTENT); |
+ }, |
+ |
+ _nodeIsInLogicalTree: function(node) { |
+ return Boolean(node._isShadyRoot || |
+ this._ownerShadyRootForNode(node) || |
+ node.shadyRoot); |
+ }, |
+ |
+ // note: a node is its own host |
+ _hostForNode: function(node) { |
+ var root = node.shadyRoot || (node._isShadyRoot ? |
+ node : this._ownerShadyRootForNode(node)); |
+ return root && root.host; |
+ }, |
+ |
+ _parentNeedsDistribution: function(parent) { |
+ return parent.shadyRoot && hasInsertionPoint(parent.shadyRoot); |
+ }, |
+ |
+ // TODO(sorvell): technically we should check non-fragment nodes for |
+ // <content> children but since this case is assumed to be exceedingly |
+ // rare, we avoid the cost and will address with some specific api |
+ // when the need arises. |
+ _nodeNeedsDistribution: function(node) { |
+ return (node.localName === CONTENT) || |
+ ((node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) && |
+ node.querySelector(CONTENT)); |
+ }, |
+ |
+ _removeNodeFromHost: function(node) { |
+ if (node.lightParent) { |
+ var root = this._ownerShadyRootForNode(node); |
+ if (root) { |
+ root.host._elementRemove(node); |
+ } |
+ this._removeLogicalInfo(node, node.lightParent); |
+ } |
+ this._removeOwnerShadyRoot(node); |
+ }, |
+ |
+ _addNodeToHost: function(node) { |
+ var checkNode = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? |
+ node.firstChild : node; |
+ var root = this._ownerShadyRootForNode(checkNode); |
+ if (root) { |
+ root.host._elementAdd(node); |
+ } |
+ }, |
+ |
+ _addLogicalInfo: function(node, container, root, index) { |
+ saveLightChildrenIfNeeded(container); |
+ var children = factory(container).childNodes; |
+ index = index === undefined ? children.length : index; |
+ // handle document fragments |
+ if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { |
+ var n = node.firstChild; |
+ while (n) { |
+ children.splice(index++, 0, n); |
+ n.lightParent = container; |
+ n = n.nextSibling; |
+ } |
+ } 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) { |
+ // TODO(sorvell): need to clear any children of element? |
+ 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) { |
+ 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 |
+ points. |
+ */ |
+ queryDistributedElements: function(selector) { |
+ var c$ = this.childNodes; |
+ var list = []; |
+ this._distributedFilter(selector, c$, list); |
+ for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) { |
+ if (c.localName === CONTENT) { |
+ this._distributedFilter(selector, factory(c).getDistributedNodes(), |
+ list); |
+ } |
+ } |
+ return list; |
+ }, |
+ |
+ _distributedFilter: function(selector, list, results) { |
+ results = results || []; |
+ for (var i=0, l=list.length, d; (i<l) && (d=list[i]); i++) { |
+ if ((d.nodeType === Node.ELEMENT_NODE) && |
+ (d.localName !== CONTENT) && |
+ matchesSelector.call(d, selector)) { |
+ results.push(d); |
+ } |
+ } |
+ return results; |
+ } |
+ |
+ }; |
+ |
+ if (Settings.useShadow) { |
+ |
+ DomApi.prototype.querySelectorAll = function(selector) { |
+ return Array.prototype.slice.call(this.node.querySelectorAll(selector)); |
+ }; |
+ |
+ DomApi.prototype.patch = function() {}; |
+ |
+ 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.getDestinationInsertionPoints = function() { |
+ var n$ = this.node.getDestinationInsertionPoints(); |
+ return n$ ? Array.prototype.slice.call(n$) : []; |
+ }; |
+ |
+ DomApi.prototype.getDistributedNodes = function() { |
+ var n$ = this.node.getDistributedNodes(); |
+ return n$ ? Array.prototype.slice.call(n$) : []; |
+ }; |
+ |
+ |
+ } |
+ |
+ var CONTENT = 'content'; |
+ |
+ var factory = function(node, patch) { |
+ node = node || document; |
+ if (!node.__domApi) { |
+ node.__domApi = new DomApi(node, patch); |
+ } |
+ return node.__domApi; |
+ }; |
+ |
+ Polymer.dom = function(obj, patch) { |
+ if (obj instanceof Event) { |
+ return Polymer.EventApi.factory(obj); |
+ } else { |
+ return factory(obj, patch); |
+ } |
+ }; |
+ |
+ // make flush available directly. |
+ Polymer.dom.flush = DomApi.prototype.flush; |
+ |
+ function getLightChildren(node) { |
+ var children = node.lightChildren; |
+ return children ? children : node.childNodes; |
+ } |
+ |
+ 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 children = []; |
+ for (var child = node.firstChild; child; child = child.nextSibling) { |
+ children.push(child); |
+ child.lightParent = child.lightParent || node; |
+ } |
+ node.lightChildren = children; |
+ } |
+ } |
+ |
+ function hasInsertionPoint(root) { |
+ return Boolean(root._insertionPoints.length); |
+ } |
+ |
+ var p = Element.prototype; |
+ var matchesSelector = p.matches || p.matchesSelector || |
+ p.mozMatchesSelector || p.msMatchesSelector || |
+ p.oMatchesSelector || p.webkitMatchesSelector; |
+ |
+ return { |
+ getLightChildren: getLightChildren, |
+ saveLightChildrenIfNeeded: saveLightChildrenIfNeeded, |
+ matchesSelector: matchesSelector, |
+ hasInsertionPoint: hasInsertionPoint, |
+ factory: factory |
+ }; |
+ |
+ })(); |
+ |
+</script> |