From b9e6859cde2b50d0a685ab13afc5aa081b9f44e1 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 11 Dec 2015 11:27:52 -0800 Subject: [PATCH] Updates the patch-don experiment to work with recent changes. --- src/lib/dom-api.html | 4 +- src/lib/experimental/patch-dom.html | 316 ++++++++++++++++++++++++---- test/smoke/patch/patch-dom.html | 18 +- test/smoke/patch/smoke.html | 79 +++++++ 4 files changed, 363 insertions(+), 54 deletions(-) create mode 100644 test/smoke/patch/smoke.html diff --git a/src/lib/dom-api.html b/src/lib/dom-api.html index 237994bb1a..0a45d9e067 100644 --- a/src/lib/dom-api.html +++ b/src/lib/dom-api.html @@ -140,10 +140,10 @@ var CONTENT = DomApi.CONTENT = 'content'; - var dom = DomApi.factory = function(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; }; diff --git a/src/lib/experimental/patch-dom.html b/src/lib/experimental/patch-dom.html index eb78eedc4f..397c40b2f5 100644 --- a/src/lib/experimental/patch-dom.html +++ b/src/lib/experimental/patch-dom.html @@ -22,7 +22,19 @@ (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 @@ // 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 @@ }; - 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 @@ 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 nativeRemoveAttribute = Element.prototype.removeAttribute; + Polymer.DomApi.ctor.prototype.removeAttribute = function(name, value) { + nativeRemoveAttribute.call(this.node, name); + this._maybeDistributeParent(); + }; + - var factory = Polymer.DomApi.factory; + var log = false; var patchImpl = { @@ -101,7 +123,7 @@ methods: ['appendChild', 'insertBefore', 'removeChild', 'replaceChild', 'querySelector', 'querySelectorAll', 'getDestinationInsertionPoints', - 'cloneNode', 'importNode'], + 'cloneNode', /*'importNode',*/ 'setAttribute', 'removeAttribute'], // : getDistributedNodes accessors: ['parentNode', 'childNodes', @@ -160,7 +182,7 @@ 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,17 @@ } var info = { get: function() { - log && console.log(this, name); - return factory(this)[name]; + //log && console.log(this, name); + if (window.nug) { + debugger; + } + 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,19 +241,224 @@ }; - Polymer.DomApi.getLightChildren = function(node) { - var children = node._lightChildren; - return children ? children : Polymer.DomApi.getComposedChildren(node); + Polymer.DomApi.patchImpl = patchImpl; + + Polymer.TreeApi.Logical.saveChildNodes = function(node) { + if (!this.hasChildNodes(node)) { + node.__firstChild = node.firstChild; + node.__lastChild = node.lastChild; + node.__childNodes = []; + for (var n=TreeApi.Composed.getFirstChild(node); n; + n=TreeApi.Composed.getNextSibling(n)) { + n.__parentNode = node; + node.__childNodes.push(n); + n.__nextSibling = TreeApi.Composed.getNextSibling(n); + n.__previousSibling = TreeApi.Composed.getPreviousSibling(n); + } + } } - Polymer.Base.instanceTemplate = function(template) { - var m = document._$importNode$_ || document.importNode; - var dom = - m.call(document, template._content || template.content, true); - return dom; + Polymer.TreeApi.Logical.recordInsertBefore = + function(node, container, ref_node) { + container.__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); + } } - Polymer.DomApi.patchImpl = patchImpl; + Polymer.TreeApi.Composed = { + + + ensureParentNodes: function(parent, children) { + }, + + hasParentNode: function(node) { + return Boolean(node.__composedParent !== undefined); + }, + + hasChildNodes: function(node) { + return Boolean(node.__composedChildNodes !== undefined); + }, + + getChildNodes: function(node) { + // return node.__composedChildNodes || + // (!node.__patched && TreeApi.arrayCopy(node.childNodes)); + return (node.__composedChildNodes && + TreeApi.arrayCopy(node.__composedChildNodes)) || + (!node.__patched && TreeApi.arrayCopy(node.childNodes)); + }, + + getComposedChildNodes: function(node) { + return node.__composedChildNodes; + }, + + getParentNode: function(node) { + return this.hasParentNode(node) ? node.__composedParent : + (!node.__patched && node.parentNode); + }, + + getFirstChild: function(node) { + if (node.__patched) { + return this.getChildNodes(node)[0]; + } else { + return node.firstChild; + } + }, + + getLastChild: function(node) { + if (node.__patched) { + var c$ = this.getChildNodes(node); + return c$[c$.length-1]; + } else { + return node.lastChild; + } + }, + + getNextSibling: function(node) { + // TODO(sorvell): linked list + var parent = this.getParentNode(node); + //if (parent.__patched) { + var c$ = this.getChildNodes(parent); + var i = c$.indexOf(node); + return c$[i+1]; + //} else if (!node.__patched) { + // return node.nextSibling; + //} + }, + + getPreviousSibling: function(node) { + // TODO(sorvell): linked list + var parent = this.getParentNode(node); + //if (parent.__patched) { + var c$ = this.getChildNodes(parent); + var i = c$.indexOf(node); + return c$[i-1]; + //} else if (!node.__patched) { + // return node.previousSibling; + //} + }, + + // 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.__composedParent) { + node.__composedParent.__composedChildNodes = []; + } + node.textContent = ''; + }, + + saveChildNodes: function(node) { + var c$ = node.__composedChildNodes = []; + for (var n=node.firstChild; n; n=n.nextSibling) { + n.__composedParent = node; + c$.push(n); + } + }, + + saveParentNode: function(node) { + node.__composedParent = node.parentNode; + }, + + insertBefore: function(parentNode, newChild, refChild) { + if (!this.hasChildNodes(parentNode)) { + 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) { + if (!this.hasChildNodes(parentNode)) { + 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); + if (!this.hasChildNodes(parentNode)) { + this.saveChildNodes(parentNode); + } + this._removeChild(parentNode, node); + if (currentParent === parentNode) { + if (!node.__patched && node.parentNode !== node.__composedParent) { + //console.warn('composedParent wrong for', node); + 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.__composedParent = 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.__composedParent = null; + var c$ = this.getComposedChildNodes(parentNode); + if (c$) { + var i = c$.indexOf(node); + if (i >= 0) { + c$.splice(i, 1); + } + } + } + + }; + + // TODO(sorvell): only necessary if we allow document.importNode to be patched. + // var nativeImportNode = document.importNode; + // Polymer.Base.instanceTemplate = function(template) { + // return nativeImportNode.call(document, + // template._content || template.content, true); + // } + + // patch important nodes + if (window.document) { + Polymer.dom(document); + if (document.body) { + Polymer.dom(document.body); + } + } })(); diff --git a/test/smoke/patch/patch-dom.html b/test/smoke/patch/patch-dom.html index 1e223e6ab8..9cdc74dfd7 100644 --- a/test/smoke/patch/patch-dom.html +++ b/test/smoke/patch/patch-dom.html @@ -253,19 +253,19 @@ assert.equal(s.parentNode, rere); Polymer.dom.flush(); if (rere.shadyRoot) { - assert.notEqual(s._composedParent, rere); + assert.notEqual(s.__composedParent, rere); } Polymer.dom.flush(); if (rere.shadyRoot) { - assert.equal(s._composedParent, p); + assert.equal(s.__composedParent, p); } rere.removeChild(s); if (rere.shadyRoot) { - assert.equal(s._composedParent, p); + assert.equal(s.__composedParent, p); } Polymer.dom.flush(); if (rere.shadyRoot) { - assert.equal(s._composedParent, null); + assert.equal(s.__composedParent, null); } }); @@ -374,13 +374,13 @@ 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.__composedParent, 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.__composedParent, d.$.testContainer, 'child not rendered in composed dom'); } } // test with x-distribute @@ -395,17 +395,17 @@ testNoAttr(); // set / unset `test` attr and see if it distributes properly child.setAttribute('test', ''); - d.distributeContent(); + //d.distributeContent(); Polymer.dom.flush(); testWithAttr(); // child.removeAttribute('test'); - d.distributeContent(); + //d.distributeContent(); Polymer.dom.flush(); testNoAttr(); // child.setAttribute('test', ''); - d.distributeContent(); + //d.distributeContent(); Polymer.dom.flush(); testWithAttr(); }); diff --git a/test/smoke/patch/smoke.html b/test/smoke/patch/smoke.html new file mode 100644 index 0000000000..e8d59c175b --- /dev/null +++ b/test/smoke/patch/smoke.html @@ -0,0 +1,79 @@ + + + + + + + + + + + + + +
header 1
+
footer 1
+
+ + + + + + + + + +