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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ User headers...
+
+ Element content...
+ User footers...
+
+
+
+
+
+
+
+
+