Index: third_party/polymer/v0_8/components/polymer/src/mini/shady.html |
diff --git a/third_party/polymer/v0_8/components/polymer/src/mini/shady.html b/third_party/polymer/v0_8/components/polymer/src/mini/shady.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..79b7829c37e6ff684ff6392e8677b2dc9b6bdf53 |
--- /dev/null |
+++ b/third_party/polymer/v0_8/components/polymer/src/mini/shady.html |
@@ -0,0 +1,376 @@ |
+<!-- |
+@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="../lib/array-splice.html"> |
+<link rel="import" href="../lib/dom-api.html"> |
+<script> |
+ |
+ (function() { |
+ /** |
+ |
+ Implements a pared down version of ShadowDOM's scoping, which is easy to |
+ polyfill across browsers. |
+ |
+ */ |
+ Polymer.Base._addFeature({ |
+ |
+ _prepShady: function() { |
+ // Use this system iff localDom is needed. |
+ this._useContent = this._useContent || Boolean(this._template); |
+ if (this._useContent) { |
+ this._template._hasInsertionPoint = |
+ this._template.content.querySelector('content'); |
+ } |
+ }, |
+ |
+ // called as part of content initialization, prior to template stamping |
+ _poolContent: function() { |
+ if (this._useContent) { |
+ // capture lightChildren to help reify dom scoping |
+ saveLightChildrenIfNeeded(this); |
+ } |
+ }, |
+ |
+ // called as part of content initialization, after template stamping |
+ _setupRoot: function() { |
+ if (this._useContent) { |
+ this._createLocalRoot(); |
+ } |
+ }, |
+ |
+ _createLocalRoot: function() { |
+ this.shadyRoot = this.root; |
+ this.shadyRoot._distributionClean = false; |
+ this.shadyRoot._isShadyRoot = true; |
+ this.shadyRoot._dirtyRoots = []; |
+ // capture insertion point list |
+ // TODO(sorvell): it's faster to do this via native qSA than annotator. |
+ this.shadyRoot._insertionPoints = this._template._hasInsertionPoint ? |
+ this.shadyRoot.querySelectorAll('content') : []; |
+ // save logical tree info for shadyRoot. |
+ saveLightChildrenIfNeeded(this.shadyRoot); |
+ this.shadyRoot.host = this; |
+ }, |
+ |
+ /** |
+ * Return the element whose local dom within which this element |
+ * is contained. This is a shorthand for |
+ * `Polymer.dom(this).getOwnerRoot().host`. |
+ */ |
+ get domHost() { |
+ var root = Polymer.dom(this).getOwnerRoot(); |
+ return root && root.host; |
+ }, |
+ |
+ /** |
+ * Force this element to distribute its children to its local dom. |
+ * A user should call `distributeContent` if distribution has been |
+ * invalidated due to changes to selectors on child elements that |
+ * effect distribution. For example, if an element contains an |
+ * insertion point with <content select=".foo"> and a `foo` class is |
+ * added to a child, then `distributeContent` must be called to update |
+ * local dom distribution. |
+ */ |
+ distributeContent: function() { |
+ if (this._useContent) { |
+ this.shadyRoot._distributionClean = false; |
+ this._distributeContent(); |
+ } |
+ }, |
+ |
+ _distributeContent: function() { |
+ if (this._useContent && !this.shadyRoot._distributionClean) { |
+ // logically distribute self |
+ this._beginDistribute(); |
+ this._distributeDirtyRoots(); |
+ this._finishDistribute(); |
+ } |
+ }, |
+ |
+ _beginDistribute: function() { |
+ if (this._useContent && hasInsertionPoint(this.shadyRoot)) { |
+ // reset distributions |
+ this._resetDistribution(); |
+ // compute which nodes should be distributed where |
+ // TODO(jmesserly): this is simplified because we assume a single |
+ // ShadowRoot per host and no `<shadow>`. |
+ this._distributePool(this.shadyRoot, this._collectPool()); |
+ } |
+ }, |
+ |
+ _distributeDirtyRoots: function() { |
+ var c$ = this.shadyRoot._dirtyRoots; |
+ for (var i=0, l= c$.length, c; (i<l) && (c=c$[i]); i++) { |
+ c._distributeContent(); |
+ } |
+ this.shadyRoot._dirtyRoots = []; |
+ }, |
+ |
+ _finishDistribute: function() { |
+ // compose self |
+ if (this._useContent) { |
+ if (hasInsertionPoint(this.shadyRoot)) { |
+ this._composeTree(); |
+ } else { |
+ if (!this.shadyRoot._hasDistributed) { |
+ this.textContent = ''; |
+ this.appendChild(this.shadyRoot); |
+ } else { |
+ // simplified non-tree walk composition |
+ var children = this._composeNode(this); |
+ this._updateChildNodes(this, children); |
+ } |
+ } |
+ this.shadyRoot._hasDistributed = true; |
+ this.shadyRoot._distributionClean = true; |
+ } |
+ }, |
+ |
+ // This is a polyfill for Element.prototype.matches, which is sometimes |
+ // still prefixed. Alternatively we could just polyfill it somewhere. |
+ // Note that the arguments are reversed from what you might expect. |
+ elementMatches: function(selector, node) { |
+ if (node === undefined) { |
+ node = this; |
+ } |
+ return matchesSelector.call(node, selector); |
+ }, |
+ |
+ // Many of the following methods are all conceptually static, but they are |
+ // included here as "protected" methods to allow overriding. |
+ |
+ _resetDistribution: function() { |
+ // light children |
+ var children = getLightChildren(this); |
+ for (var i = 0; i < children.length; i++) { |
+ var child = children[i]; |
+ if (child._destinationInsertionPoints) { |
+ child._destinationInsertionPoints = undefined; |
+ } |
+ } |
+ // insertion points |
+ var root = this.shadyRoot; |
+ var p$ = root._insertionPoints; |
+ for (var j = 0; j < p$.length; j++) { |
+ p$[j]._distributedNodes = []; |
+ } |
+ }, |
+ |
+ // Gather the pool of nodes that should be distributed. We will combine |
+ // these with the "content root" to arrive at the composed tree. |
+ _collectPool: function() { |
+ var pool = []; |
+ var children = getLightChildren(this); |
+ for (var i = 0; i < children.length; i++) { |
+ var child = children[i]; |
+ if (isInsertionPoint(child)) { |
+ pool.push.apply(pool, child._distributedNodes); |
+ } else { |
+ pool.push(child); |
+ } |
+ } |
+ return pool; |
+ }, |
+ |
+ // perform "logical" distribution; note, no actual dom is moved here, |
+ // instead elements are distributed into a `content._distributedNodes` |
+ // array where applicable. |
+ _distributePool: function(node, pool) { |
+ var p$ = node._insertionPoints; |
+ for (var i=0, l=p$.length, p; (i<l) && (p=p$[i]); i++) { |
+ this._distributeInsertionPoint(p, pool); |
+ } |
+ }, |
+ |
+ _distributeInsertionPoint: function(content, pool) { |
+ // distribute nodes from the pool that this selector matches |
+ var anyDistributed = false; |
+ for (var i=0, l=pool.length, node; i < l; i++) { |
+ node=pool[i]; |
+ // skip nodes that were already used |
+ if (!node) { |
+ continue; |
+ } |
+ // distribute this node if it matches |
+ if (this._matchesContentSelect(node, content)) { |
+ distributeNodeInto(node, content); |
+ // remove this node from the pool |
+ pool[i] = undefined; |
+ // since at least one node matched, we won't need fallback content |
+ anyDistributed = true; |
+ var parent = content.lightParent; |
+ // dirty a shadyRoot if a change may trigger reprojection! |
+ if (parent && parent.shadyRoot && |
+ hasInsertionPoint(parent.shadyRoot)) { |
+ parent.shadyRoot._distributionClean = false; |
+ this.shadyRoot._dirtyRoots.push(parent); |
+ } |
+ } |
+ } |
+ // Fallback content if nothing was distributed here |
+ if (!anyDistributed) { |
+ var children = getLightChildren(content); |
+ for (var j = 0; j < children.length; j++) { |
+ distributeNodeInto(children[j], content); |
+ } |
+ } |
+ }, |
+ |
+ // Reify dom such that it is at its correct rendering position |
+ // based on logical distribution. |
+ _composeTree: function() { |
+ 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; |
+ if (!parent._useContent && (parent !== this) && |
+ (parent !== this.shadyRoot)) { |
+ this._updateChildNodes(parent, this._composeNode(parent)); |
+ } |
+ } |
+ }, |
+ |
+ // Returns the list of nodes which should be rendered inside `node`. |
+ _composeNode: function(node) { |
+ var children = []; |
+ var c$ = getLightChildren(node.shadyRoot || node); |
+ for (var i = 0; i < c$.length; i++) { |
+ var child = c$[i]; |
+ if (isInsertionPoint(child)) { |
+ var distributedNodes = child._distributedNodes; |
+ for (var j = 0; j < distributedNodes.length; j++) { |
+ var distributedNode = distributedNodes[j]; |
+ if (isFinalDestination(child, distributedNode)) { |
+ children.push(distributedNode); |
+ } |
+ } |
+ } else { |
+ children.push(child); |
+ } |
+ } |
+ return children; |
+ }, |
+ |
+ // Ensures that the rendered node list inside `node` is `children`. |
+ _updateChildNodes: function(node, children) { |
+ var splices = |
+ Polymer.ArraySplice.calculateSplices(children, node.childNodes); |
+ for (var i=0; i<splices.length; i++) { |
+ var s = splices[i]; |
+ // remove |
+ for (var j=0, c; j < s.removed.length; j++) { |
+ c = s.removed[j]; |
+ if (c.previousSibling == children[s.index-1]) { |
+ remove(c); |
+ } |
+ } |
+ // insert |
+ for (var idx=s.index, ch, o; idx < s.index + s.addedCount; idx++) { |
+ ch = children[idx]; |
+ o = node.childNodes[idx]; |
+ while (o && o === ch) { |
+ o = o.nextSibling; |
+ } |
+ insertBefore(node, ch, o); |
+ } |
+ } |
+ }, |
+ |
+ _matchesContentSelect: function(node, contentElement) { |
+ var select = contentElement.getAttribute('select'); |
+ // no selector matches all nodes (including text) |
+ if (!select) { |
+ return true; |
+ } |
+ select = select.trim(); |
+ // same thing if it had only whitespace |
+ if (!select) { |
+ return true; |
+ } |
+ // selectors can only match Elements |
+ if (!(node instanceof Element)) { |
+ return false; |
+ } |
+ // only valid selectors can match: |
+ // TypeSelector |
+ // * |
+ // ClassSelector |
+ // IDSelector |
+ // AttributeSelector |
+ // negation |
+ var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/; |
+ if (!validSelectors.test(select)) { |
+ return false; |
+ } |
+ return this.elementMatches(select, node); |
+ }, |
+ |
+ // system override point |
+ _elementAdd: function() {}, |
+ |
+ // system override point |
+ _elementRemove: function() {} |
+ |
+ }); |
+ |
+ var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded; |
+ var getLightChildren = Polymer.DomApi.getLightChildren; |
+ var matchesSelector = Polymer.DomApi.matchesSelector; |
+ var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint; |
+ |
+ function distributeNodeInto(child, insertionPoint) { |
+ insertionPoint._distributedNodes.push(child); |
+ var points = child._destinationInsertionPoints; |
+ if (!points) { |
+ child._destinationInsertionPoints = [insertionPoint]; |
+ // TODO(sorvell): _destinationInsertionPoints may not be cleared when |
+ // nodes are dynamically added/removed, therefore test before adding |
+ // insertion points. |
+ } else if (points.indexOf(insertionPoint) < 0) { |
+ points.push(insertionPoint); |
+ } |
+ } |
+ |
+ function isFinalDestination(insertionPoint, node) { |
+ var points = node._destinationInsertionPoints; |
+ return points && points[points.length - 1] === insertionPoint; |
+ } |
+ |
+ function isInsertionPoint(node) { |
+ // TODO(jmesserly): we could add back 'shadow' support here. |
+ return node.localName == 'content'; |
+ } |
+ |
+ var nativeInsertBefore = Element.prototype.insertBefore; |
+ var nativeRemoveChild = Element.prototype.removeChild; |
+ |
+ function insertBefore(parentNode, newChild, refChild) { |
+ // remove child from its old parent first |
+ remove(newChild); |
+ // make sure we never lose logical DOM information: |
+ // if the parentNode doesn't have lightChildren, save that information now. |
+ saveLightChildrenIfNeeded(parentNode); |
+ // insert it into the real DOM |
+ nativeInsertBefore.call(parentNode, newChild, refChild || null); |
+ } |
+ |
+ function remove(node) { |
+ var parentNode = node.parentNode; |
+ if (parentNode) { |
+ // make sure we never lose logical DOM information: |
+ // if the parentNode doesn't have lightChildren, save that information now. |
+ saveLightChildrenIfNeeded(parentNode); |
+ // remove it from the real DOM |
+ nativeRemoveChild.call(parentNode, node); |
+ } |
+ } |
+ |
+ })(); |
+ |
+</script> |