From 82dc565b8488d2684970bae3a53d60faf4ef4fdb Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Sun, 10 May 2015 10:03:52 -0700 Subject: [PATCH 1/5] Adds node mutation apis to Polymer.dom: `setAttribute`, `removeAttribute`, `classList`. These methods automatically provoke distribution if necessary. Adds redistribution support to `distributeContent`. --- src/lib/dom-api.html | 45 +++++ src/mini/shady.html | 64 +++++- src/standard/utils.html | 16 +- test/smoke/nested-ip.html | 118 +++++++++++ test/unit/polymer-dom-elements.html | 33 +++- test/unit/polymer-dom-shadow.html | 8 + test/unit/polymer-dom.html | 8 + test/unit/polymer-dom.js | 297 ++++++++++++++++++++-------- test/unit/shady.html | 75 +++---- 9 files changed, 529 insertions(+), 135 deletions(-) create mode 100644 test/smoke/nested-ip.html diff --git a/src/lib/dom-api.html b/src/lib/dom-api.html index d022275fcf..0372a318bf 100644 --- a/src/lib/dom-api.html +++ b/src/lib/dom-api.html @@ -13,6 +13,7 @@ diff --git a/src/standard/utils.html b/src/standard/utils.html index 02ccbfd7de..c2354c06f1 100644 --- a/src/standard/utils.html +++ b/src/standard/utils.html @@ -34,9 +34,9 @@ bool = !node.classList.contains(name); } if (bool) { - node.classList.add(name); + Polymer.dom(node).classList.add(name); } else { - node.classList.remove(name); + Polymer.dom(node).classList.remove(name); } }, @@ -55,9 +55,9 @@ bool = !node.hasAttribute(name); } if (bool) { - node.setAttribute(name, ''); + Polymer.dom(node).setAttribute(name, ''); } else { - node.removeAttribute(name); + Polymer.dom(node).removeAttribute(name); } }, @@ -71,10 +71,10 @@ */ classFollows: function(name, toElement, fromElement) { if (fromElement) { - fromElement.classList.remove(name); + Polymer.dom(fromElement).classList.remove(name); } if (toElement) { - toElement.classList.add(name); + Polymer.dom(toElement).classList.add(name); } }, @@ -88,10 +88,10 @@ */ attributeFollows: function(name, toElement, fromElement) { if (fromElement) { - fromElement.removeAttribute(name); + Polymer.dom(fromElement).removeAttribute(name); } if (toElement) { - toElement.setAttribute(name, ''); + Polymer.dom(toElement).setAttribute(name, ''); } }, diff --git a/test/smoke/nested-ip.html b/test/smoke/nested-ip.html new file mode 100644 index 0000000000..d3be023c46 --- /dev/null +++ b/test/smoke/nested-ip.html @@ -0,0 +1,118 @@ + + + + polymer + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/unit/polymer-dom-elements.html b/test/unit/polymer-dom-elements.html index c60317f0fa..8e8a617ff9 100644 --- a/test/unit/polymer-dom-elements.html +++ b/test/unit/polymer-dom-elements.html @@ -87,4 +87,35 @@ Polymer({ is: 'x-compose' }); - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/unit/polymer-dom-shadow.html b/test/unit/polymer-dom-shadow.html index 1f896f788b..10a2462e92 100644 --- a/test/unit/polymer-dom-shadow.html +++ b/test/unit/polymer-dom-shadow.html @@ -31,6 +31,14 @@
Foo
+ +
+
+ + +
+
+ diff --git a/test/unit/polymer-dom.html b/test/unit/polymer-dom.html index 2fd90d3b1d..46ca281786 100644 --- a/test/unit/polymer-dom.html +++ b/test/unit/polymer-dom.html @@ -30,6 +30,14 @@
Foo
+ +
+
+ + +
+
+ diff --git a/test/unit/polymer-dom.js b/test/unit/polymer-dom.js index 96fba0c589..d22b5429de 100644 --- a/test/unit/polymer-dom.js +++ b/test/unit/polymer-dom.js @@ -1,12 +1,17 @@ suite('Polymer.dom', function() { + var testElement; + + suiteSetup(function() { + testElement = document.querySelector('x-test'); + }) + test('querySelector (local)', function() { - var test = document.querySelector('x-test'); - var projected = Polymer.dom(test.root).querySelector('#projected'); + var projected = Polymer.dom(testElement.root).querySelector('#projected'); assert.equal(projected.textContent, 'projected'); - var p2 = Polymer.dom(test).querySelector('#projected'); + var p2 = Polymer.dom(testElement).querySelector('#projected'); assert.notOk(p2); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); assert.equal(rere.is, 'x-rereproject'); var re = Polymer.dom(rere.root).querySelector('x-reproject'); assert.equal(re.is, 'x-reproject'); @@ -15,8 +20,7 @@ suite('Polymer.dom', function() { }); test('querySelectorAll (local)', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var re = Polymer.dom(rere.root).querySelector('x-reproject'); var p = Polymer.dom(re.root).querySelector('x-project'); var rereList = Polymer.dom(rere.root).querySelectorAll('*'); @@ -30,9 +34,8 @@ suite('Polymer.dom', function() { }); test('querySelector (light)', function() { - var test = document.querySelector('x-test'); - var projected = Polymer.dom(test.root).querySelector('#projected'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var projected = Polymer.dom(testElement.root).querySelector('#projected'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var re = Polymer.dom(rere.root).querySelector('x-reproject'); var p = Polymer.dom(re.root).querySelector('x-project'); assert.equal(Polymer.dom(rere).querySelector('#projected'), projected); @@ -41,9 +44,8 @@ suite('Polymer.dom', function() { }); test('querySelectorAll (light)', function() { - var test = document.querySelector('x-test'); - var projected = Polymer.dom(test.root).querySelector('#projected'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var projected = Polymer.dom(testElement.root).querySelector('#projected'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var re = Polymer.dom(rere.root).querySelector('x-reproject'); var p = Polymer.dom(re.root).querySelector('x-project'); assert.equal(Polymer.dom(rere).querySelectorAll('#projected')[0], projected); @@ -52,10 +54,9 @@ suite('Polymer.dom', function() { }); test('projection', function() { - var test = document.querySelector('x-test'); - var projected = Polymer.dom(test.root).querySelector('#projected'); + var projected = Polymer.dom(testElement.root).querySelector('#projected'); assert.equal(projected.textContent, 'projected'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); assert.equal(rere.is, 'x-rereproject'); var re = Polymer.dom(rere.root).querySelector('x-reproject'); assert.equal(re.is, 'x-reproject'); @@ -72,56 +73,196 @@ suite('Polymer.dom', function() { }); test('distributeContent', function() { - var test = document.querySelector('x-test'); - test._distributionClean = false; - test._distributeContent(); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var projected = Polymer.dom(testElement.root).querySelector('#projected'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); + var c1 = Polymer.dom(rere.root).querySelector('content'); + var re = Polymer.dom(rere.root).querySelector('x-reproject'); + var c2 = Polymer.dom(re.root).querySelector('content'); + var p = Polymer.dom(re.root).querySelector('x-project'); + var c3 = Polymer.dom(p.root).querySelector('content'); + var ip$ = [c1, c2, c3]; + testElement.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(projected).getDestinationInsertionPoints(), ip$); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); assert.equal(rere.is, 'x-rereproject'); - rere._distributionClean = false; - rere._distributeContent(); + rere.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(projected).getDestinationInsertionPoints(), ip$); var re = Polymer.dom(rere.root).querySelector('x-reproject'); assert.equal(re.is, 'x-reproject'); - re._distributionClean = false; - re._distributeContent(); + re.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(projected).getDestinationInsertionPoints(), ip$); var p = Polymer.dom(re.root).querySelector('x-project'); assert.equal(p.is, 'x-project'); }); + test('distributeContent (reproject)', function() { + var select = document.querySelector('x-select1'); + var child = Polymer.dom(select).firstElementChild; + var c1 = Polymer.dom(select.root).querySelector('content'); + var c2 = Polymer.dom(select.$.select.root).querySelector('content'); + var c3 = Polymer.dom(select.$.select.$.select.root).querySelector('content'); + assert.equal(c1.getAttribute('select'), '[s1]'); + assert.equal(c2.getAttribute('select'), '[s2]'); + assert.equal(c3.getAttribute('select'), '[s3]'); + var ip$ = [c1, c2, c3]; + assert.equal(child.className, 'select-child'); + assert.equal(Polymer.dom(child).getDestinationInsertionPoints().length, 0); + child.setAttribute('s1', ''); + select.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1]); + child.setAttribute('s2', ''); + select.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2]); + child.setAttribute('s3', ''); + select.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2, c3]); + child.removeAttribute('s1'); + select.$.select.$.select.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), []); + child.setAttribute('s1', ''); + select.$.select.$.select.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2, c3]); + child.removeAttribute('s2'); + select.$.select.$.select.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1]); + child.setAttribute('s2', ''); + select.$.select.$.select.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2, c3]); + child.removeAttribute('s3'); + select.$.select.$.select.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2]); + child.removeAttribute('s2'); + child.removeAttribute('s1'); + select.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), []); + }); + + test('Polymer.dom.setAttribute (reproject)', function() { + var select = document.querySelector('x-select1'); + var child = Polymer.dom(select).firstElementChild; + var c1 = Polymer.dom(select.root).querySelector('content'); + var c2 = Polymer.dom(select.$.select.root).querySelector('content'); + var c3 = Polymer.dom(select.$.select.$.select.root).querySelector('content'); + assert.equal(c1.getAttribute('select'), '[s1]'); + assert.equal(c2.getAttribute('select'), '[s2]'); + assert.equal(c3.getAttribute('select'), '[s3]'); + var ip$ = [c1, c2, c3]; + assert.equal(child.className, 'select-child'); + assert.equal(Polymer.dom(child).getDestinationInsertionPoints().length, 0); + Polymer.dom(child).setAttribute('s1', ''); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1]); + Polymer.dom(child).setAttribute('s2', ''); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2]); + Polymer.dom(child).setAttribute('s3', ''); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2, c3]); + Polymer.dom(child).removeAttribute('s1'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), []); + Polymer.dom(child).setAttribute('s1', ''); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2, c3]); + Polymer.dom(child).removeAttribute('s2'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1]); + Polymer.dom(child).setAttribute('s2', ''); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2, c3]); + Polymer.dom(child).removeAttribute('s3'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2]); + Polymer.dom(child).removeAttribute('s2'); + Polymer.dom(child).removeAttribute('s1'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), []); + }); + + test('Polymer.dom.classListAdd/Remove/Toggle (reproject)', function() { + var select = document.querySelector('x-select-class1'); + var child = Polymer.dom(select).firstElementChild; + var c1 = Polymer.dom(select.root).querySelector('content'); + var c2 = Polymer.dom(select.$.select.root).querySelector('content'); + var c3 = Polymer.dom(select.$.select.$.select.root).querySelector('content'); + assert.equal(c1.getAttribute('select'), '.s1'); + assert.equal(c2.getAttribute('select'), '.s2'); + assert.equal(c3.getAttribute('select'), '.s3'); + var ip$ = [c1, c2, c3]; + assert.equal(Polymer.dom(child).getDestinationInsertionPoints().length, 0); + Polymer.dom(child).classList.add('s1'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1]); + Polymer.dom(child).classList.add('s2'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2]); + Polymer.dom(child).classList.add('s3'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2, c3]); + Polymer.dom(child).classList.toggle('s1'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), []); + Polymer.dom(child).classList.toggle('s1'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2, c3]); + Polymer.dom(child).classList.remove('s2'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1]); + Polymer.dom(child).classList.toggle('s2'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2, c3]); + Polymer.dom(child).classList.remove('s3'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [c1, c2]); + Polymer.dom(child).classList.remove('s2'); + Polymer.dom(child).classList.remove('s1'); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), []); + }); + test('appendChild (light)', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var s = document.createElement('span'); s.id = 'added'; s.textContent = 'Added'; Polymer.dom(rere).appendChild(s); - assert.equal(Polymer.dom(test.root).querySelector('#added'), s); + assert.equal(Polymer.dom(testElement.root).querySelector('#added'), s); }); test('insertBefore (light)', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); - var ref = Polymer.dom(test.root).querySelector('#added'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); + var ref = Polymer.dom(testElement.root).querySelector('#added'); var s = document.createElement('span'); s.id = 'added2'; s.textContent = 'Added2'; Polymer.dom(rere).insertBefore(s, ref); - assert.equal(Polymer.dom(test.root).querySelector('#added2'), s); + assert.equal(Polymer.dom(testElement.root).querySelector('#added2'), s); }); test('removeChild (light)', function() { - var test = document.querySelector('x-test'); - var added = Polymer.dom(test.root).querySelector('#added'); - var added2 = Polymer.dom(test.root).querySelector('#added2'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); - assert.equal(Polymer.dom(test.root).querySelectorAll('*').length, 4); + var added = Polymer.dom(testElement.root).querySelector('#added'); + var added2 = Polymer.dom(testElement.root).querySelector('#added2'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); + assert.equal(Polymer.dom(testElement.root).querySelectorAll('*').length, 4); Polymer.dom(rere).removeChild(added); Polymer.dom(rere).removeChild(added2); - assert.equal(Polymer.dom(test.root).querySelectorAll('*').length, 2); + assert.equal(Polymer.dom(testElement.root).querySelectorAll('*').length, 2); }); test('appendChild (local)', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var s = document.createElement('span'); s.id = 'local'; s.textContent = 'Local'; @@ -130,9 +271,8 @@ suite('Polymer.dom', function() { }); test('insertBefore (local)', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); - var ref = Polymer.dom(test.root).querySelector('#local'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); + var ref = Polymer.dom(testElement.root).querySelector('#local'); var s = document.createElement('span'); s.id = 'local2'; s.textContent = 'Local2'; @@ -141,8 +281,7 @@ suite('Polymer.dom', function() { }); test('removeChild (local)', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var local = Polymer.dom(rere.root).querySelector('#local'); var local2 = Polymer.dom(rere.root).querySelector('#local2'); Polymer.dom(rere.root).removeChild(local); @@ -151,23 +290,21 @@ suite('Polymer.dom', function() { }); test('localDom.insertBefore first element results in minimal change', function() { - var test = document.querySelector('x-test'); - var children = Polymer.dom(test.root).childNodes; - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var children = Polymer.dom(testElement.root).childNodes; + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); assert.equal(rere.attachedCount, 1); var s = document.createElement('span'); s.id = 'local-first'; s.textContent = 'Local First'; - Polymer.dom(test.root).insertBefore(s, children[0]); - assert.equal(Polymer.dom(test.root).querySelector('#local-first'), s); + Polymer.dom(testElement.root).insertBefore(s, children[0]); + assert.equal(Polymer.dom(testElement.root).querySelector('#local-first'), s); assert.equal(rere.attachedCount, 1); - Polymer.dom(test.root).removeChild(s); + Polymer.dom(testElement.root).removeChild(s); assert.equal(rere.attachedCount, 1); }); test('appendChild (fragment, local)', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var fragment = document.createDocumentFragment(); var childCount = 5; for (var i=0; i < childCount; i++) { @@ -185,8 +322,7 @@ suite('Polymer.dom', function() { }); test('insertBefore (fragment, local)', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var fragment = document.createDocumentFragment(); var childCount = 5; for (var i=0; i < childCount; i++) { @@ -208,8 +344,7 @@ suite('Polymer.dom', function() { }); test('distribute (forced)', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var re = Polymer.dom(rere.root).querySelector('x-reproject'); var p = Polymer.dom(re.root).querySelector('x-project'); var s = document.createElement('span'); @@ -221,7 +356,7 @@ suite('Polymer.dom', function() { if (rere.shadyRoot) { assert.notEqual(s._composedParent, rere); } - Polymer.dom(test).flush(); + Polymer.dom(testElement).flush(); if (rere.shadyRoot) { assert.equal(s._composedParent, p); } @@ -229,18 +364,17 @@ suite('Polymer.dom', function() { if (rere.shadyRoot) { assert.equal(s._composedParent, p); } - Polymer.dom(test).flush(); + Polymer.dom(testElement).flush(); if (rere.shadyRoot) { assert.equal(s._composedParent, null); } }); test('queryDistributedElements', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var re = Polymer.dom(rere.root).querySelector('x-reproject'); var p = Polymer.dom(re.root).querySelector('x-project'); - var projected = Polymer.dom(test.root).querySelector('#projected'); + var projected = Polymer.dom(testElement.root).querySelector('#projected'); var d$ = Polymer.dom(p.root).queryDistributedElements('*'); assert.equal(d$.length, 1); assert.equal(d$[0], projected); @@ -251,21 +385,20 @@ suite('Polymer.dom', function() { var test = Polymer.dom().querySelector('x-test'); var rere = Polymer.dom().querySelector('x-rereproject'); var projected = Polymer.dom().querySelector('#projected'); - assert.ok(test); + assert.ok(testElement); assert.notOk(rere); assert.notOk(projected); }); test('Polymer.dom event', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var re = Polymer.dom(rere.root).querySelector('x-reproject'); var p = Polymer.dom(re.root).querySelector('x-project'); var eventHandled = 0; - test.addEventListener('test-event', function(e) { + testElement.addEventListener('test-event', function(e) { eventHandled++; assert.equal(Polymer.dom(e).rootTarget, p); - assert.equal(Polymer.dom(e).localTarget, test); + assert.equal(Polymer.dom(e).localTarget, testElement); var path = Polymer.dom(e).path; // path includes window only on more recent Shadow DOM implementations // account for that here. @@ -273,7 +406,7 @@ suite('Polymer.dom', function() { assert.equal(path[0], p); assert.equal(path[2], re); assert.equal(path[4], rere); - assert.equal(path[6], test); + assert.equal(path[6], testElement); }); rere.addEventListener('test-event', function(e) { @@ -286,10 +419,9 @@ suite('Polymer.dom', function() { }); test('parentNode', function() { - var test = document.querySelector('x-test'); - var rere = Polymer.dom(test.root).querySelector('x-rereproject'); - var projected = Polymer.dom(test.root).querySelector('#projected'); - assert.equal(Polymer.dom(test).parentNode, wrap(document.body)); + var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); + var projected = Polymer.dom(testElement.root).querySelector('#projected'); + assert.equal(Polymer.dom(testElement).parentNode, wrap(document.body)); assert.equal(Polymer.dom(projected).parentNode, rere); }); @@ -370,24 +502,24 @@ suite('Polymer.dom accessors', function() { }); test('Polymer.dom textContent', function() { - var test = document.createElement('x-project'); - Polymer.dom(test).textContent = 'Hello World'; - assert.equal(Polymer.dom(test).textContent, 'Hello World', 'textContent getter incorrect'); - if (test.shadyRoot) { + var testElement = document.createElement('x-project'); + Polymer.dom(testElement).textContent = 'Hello World'; + assert.equal(Polymer.dom(testElement).textContent, 'Hello World', 'textContent getter incorrect'); + if (testElement.shadyRoot) { Polymer.dom.flush(); - assert.equal(test._composedChildren[1].textContent, 'Hello World', 'text content setter incorrect'); + assert.equal(testElement._composedChildren[1].textContent, 'Hello World', 'text content setter incorrect'); } }); test('Polymer.dom innerHTML', function() { - var test = document.createElement('x-project'); - Polymer.dom(test).innerHTML = '
Hello World
'; - var added = Polymer.dom(test).firstChild; + var testElement = document.createElement('x-project'); + Polymer.dom(testElement).innerHTML = '
Hello World
'; + var added = Polymer.dom(testElement).firstChild; assert(added.textContent , 'Hello World', 'innerHTML setter incorrect'); - assert(Polymer.dom(test).innerHTML , '
Hello World
', 'innerHTML getter incorrect'); - if (test.shadyRoot) { + assert(Polymer.dom(testElement).innerHTML , '
Hello World
', 'innerHTML getter incorrect'); + if (testElement.shadyRoot) { Polymer.dom.flush(); - assert.equal(test._composedChildren[1], added, 'innerHTML setter composed incorrectly'); + assert.equal(testElement._composedChildren[1], added, 'innerHTML setter composed incorrectly'); } }); }); @@ -455,14 +587,17 @@ suite('Polymer.dom non-distributed elements', function() { // 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(); }); diff --git a/test/unit/shady.html b/test/unit/shady.html index 70737aa8d5..94dda5c62f 100644 --- a/test/unit/shady.html +++ b/test/unit/shady.html @@ -49,7 +49,7 @@ // Pretend we're stamping the template contents. setRootInnerHTML(host.shadyRoot, shadowRootHtml); // Invoke distribution and verify the resulting tree. - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), expectedHtml); }); } @@ -128,10 +128,10 @@ function testRender() { // Simulate the correct ordering as "ready" would fire. - host.distributeContent(); + distributeContentNow(host); // NOTE: needed only for this imperative test that needs // to simulate distribution from `shadyRoot` - p.distributeContent(); + distributeContentNow(p); assert.strictEqual(getComposedHTML(host), 'a: b: '); @@ -147,7 +147,7 @@ } testRender(); - //testRender(); + testRender(); }); @@ -158,15 +158,15 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, 'fallback'); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); host.firstChild.textContent = ''; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), ''); host.lightChildren = []; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'fallback'); }); @@ -177,15 +177,15 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, 'after'); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Helloafter'); host.shadyRoot.lightChildren[1].textContent = ''; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); host.shadyRoot.lightChildren = []; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), ''); }); @@ -196,19 +196,19 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, 'fallback'); var b = host.shadyRoot.firstChild.firstChild; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'fallback'); b.textContent = ''; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), ''); host.shadyRoot.firstChild.lightChildren = []; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), ''); host.shadyRoot.lightChildren = []; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), ''); }); @@ -218,15 +218,15 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, 'fallback'); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); host.firstChild.removeChild(host.firstChild.firstChild); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), ''); host.lightChildren = []; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'fallback'); }); @@ -238,7 +238,7 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, 'fallback'); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), ''); Polymer.dom(host).removeChild(b); @@ -258,19 +258,19 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, 'after'); var b = host.shadyRoot.lastChild; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Helloafter'); b.removeChild(b.firstChild); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); host.shadyRoot.lightChildren.splice(1, 1); // remove b - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); host.shadyRoot.lightChildren = []; // remove a - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), ''); }); @@ -282,15 +282,15 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, 'fallback b' + 'fallback a'); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'WorldHello'); host.shadyRoot.firstChild.setAttribute('select', 'xxx'); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'fallback bHello'); host.shadyRoot.firstChild.setAttribute('select', ''); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'HelloWorldfallback a'); }); @@ -300,7 +300,7 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, ''); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); var b = document.createElement('b'); @@ -315,13 +315,13 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, ''); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); var b = document.createElement('b'); Polymer.dom(host.shadyRoot).appendChild(b); Polymer.dom.flush(); - //host.distributeContent(); + //distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); }); @@ -332,7 +332,7 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, ''); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); var b = document.createElement('b'); @@ -348,7 +348,7 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, ''); var content = host.shadyRoot.firstChild; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); var b = document.createElement('b'); @@ -364,12 +364,12 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, ''); - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); var b = document.createElement('b'); host.lightChildren[0] = b; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), ''); }); @@ -380,7 +380,7 @@ syncLightDOM(host); setRootInnerHTML(host.shadyRoot, ''); var content = host.shadyRoot.firstChild; - host.distributeContent(); + distributeContentNow(host); assert.strictEqual(getComposedHTML(host), 'Hello'); var b = document.createElement('b'); @@ -404,7 +404,7 @@ syncLightDOM(child); setRootInnerHTML(child.shadyRoot, ''); var childLocalSub = child.shadyRoot.lastChild; - host.distributeContent(); + distributeContentNow(host); // NOTE: needed only for this imperative test that needs // to simulate distribution from `shadyRoot` child._distributeContent(); @@ -428,7 +428,7 @@ var childLightSub = getComposedChildAtIndex(child, 100) ; syncLightDOM(child); setRootInnerHTML(child.shadyRoot, ''); - host.distributeContent(); + distributeContentNow(host); assert.deepEqual(Polymer.dom(host).querySelectorAll('div#main'), [hostLightMain]); assert.deepEqual(Polymer.dom(host).querySelectorAll('#sub'), []); assert.deepEqual(Polymer.dom(child).querySelectorAll('div#sub'), [childLightSub]); @@ -496,6 +496,11 @@ } } +function distributeContentNow(node) { + node.distributeContent(); + Polymer.dom.flush(); +} + From 1deb578c3b07e77f8c912cccef4cc81863a4346f Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Mon, 11 May 2015 18:01:24 -0700 Subject: [PATCH 2/5] Make classList a getter so it can be lazy; dom mutation api's only cause distribution on parent. --- src/lib/dom-api.html | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/lib/dom-api.html b/src/lib/dom-api.html index 0372a318bf..bba37586ac 100644 --- a/src/lib/dom-api.html +++ b/src/lib/dom-api.html @@ -29,7 +29,6 @@ if (this.patch) { this.patch(); } - this.classList = new DomApi.ClassList(this); }; DomApi.prototype = { @@ -369,8 +368,6 @@ } }, - // TODO(sorvell): make Polymer utils use these... - setAttribute: function(name, value) { this.node.setAttribute(name, value); this._distributeParent(); @@ -383,12 +380,22 @@ _distributeParent: function() { if (this.parentNode && this.parentNode.shadyRoot) { - this.parentNode.distributeContent(); + this._lazyDistribute(this.parentNode); } } }; + Object.defineProperty(DomApi.prototype, 'classList', { + get: function() { + if (!this._classList) { + this._classList = new DomApi.ClassList(this); + } + return this._classList; + }, + configurable: true + }); + DomApi.ClassList = function(host) { this.domApi = host; this.node = host.node; From da0cddf5e61b620eec22e52d5038ff6dda91def3 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Mon, 11 May 2015 19:59:30 -0700 Subject: [PATCH 3/5] Always provoke distribution on any hosts that are redistribution candidates. This prevents invalid logical dom state. --- src/mini/shady.html | 34 +++++++++++++++++------------ test/smoke/nested-ip.html | 25 +++++++++++++-------- test/unit/polymer-dom-elements.html | 15 +++++++++++++ test/unit/polymer-dom-shadow.html | 2 ++ test/unit/polymer-dom.html | 2 ++ test/unit/polymer-dom.js | 32 +++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 23 deletions(-) diff --git a/src/mini/shady.html b/src/mini/shady.html index a58e5dafad..37b3872838 100644 --- a/src/mini/shady.html +++ b/src/mini/shady.html @@ -191,6 +191,10 @@ var p$ = node._insertionPoints; for (var i=0, l=p$.length, p; (i + diff --git a/test/unit/polymer-dom-elements.html b/test/unit/polymer-dom-elements.html index 8e8a617ff9..3b67c99744 100644 --- a/test/unit/polymer-dom-elements.html +++ b/test/unit/polymer-dom-elements.html @@ -118,4 +118,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/unit/polymer-dom-shadow.html b/test/unit/polymer-dom-shadow.html index 10a2462e92..ec177124b5 100644 --- a/test/unit/polymer-dom-shadow.html +++ b/test/unit/polymer-dom-shadow.html @@ -39,6 +39,8 @@
+ + diff --git a/test/unit/polymer-dom.html b/test/unit/polymer-dom.html index 46ca281786..76606b8c79 100644 --- a/test/unit/polymer-dom.html +++ b/test/unit/polymer-dom.html @@ -38,6 +38,8 @@
+ + diff --git a/test/unit/polymer-dom.js b/test/unit/polymer-dom.js index d22b5429de..bd2652ec18 100644 --- a/test/unit/polymer-dom.js +++ b/test/unit/polymer-dom.js @@ -232,6 +232,38 @@ suite('Polymer.dom', function() { assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), []); }); + test('re-distribution results in correct logical tree when outer host remove a node from pool of inner host', function() { + var r = document.querySelector('x-redistribute-a-b'); + var rc = Polymer.dom(r.root).querySelectorAll('content'); + var ec1 = Polymer.dom(r.$.echo1.root).querySelector('content'); + var ec2 = Polymer.dom(r.$.echo2.root).querySelector('content'); + var child = document.createElement('div'); + child.className = 'a'; + Polymer.dom(r).appendChild(child); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [rc[0], ec1]); + assert.deepEqual(Polymer.dom(rc[0]).getDistributedNodes(), [child]); + assert.deepEqual(Polymer.dom(rc[1]).getDistributedNodes(), []); + assert.deepEqual(Polymer.dom(ec1).getDistributedNodes(), [child]); + assert.deepEqual(Polymer.dom(ec2).getDistributedNodes(), []); + child.className = 'b'; + r.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [rc[1], ec2]); + assert.deepEqual(Polymer.dom(rc[0]).getDistributedNodes(), []); + assert.deepEqual(Polymer.dom(rc[1]).getDistributedNodes(), [child]); + assert.deepEqual(Polymer.dom(ec1).getDistributedNodes(), []); + assert.deepEqual(Polymer.dom(ec2).getDistributedNodes(), [child]); + child.className = 'a'; + r.distributeContent(); + Polymer.dom.flush(); + assert.deepEqual(Polymer.dom(child).getDestinationInsertionPoints(), [rc[0], ec1]); + assert.deepEqual(Polymer.dom(rc[0]).getDistributedNodes(), [child]); + assert.deepEqual(Polymer.dom(rc[1]).getDistributedNodes(), []); + assert.deepEqual(Polymer.dom(ec1).getDistributedNodes(), [child]); + assert.deepEqual(Polymer.dom(ec2).getDistributedNodes(), []); + }); + test('appendChild (light)', function() { var rere = Polymer.dom(testElement.root).querySelector('x-rereproject'); var s = document.createElement('span'); From 5d59eb7a22f732af09d9ef93e9a43379ec5c241f Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 12 May 2015 10:50:16 -0700 Subject: [PATCH 4/5] `distributeContent` guarantees that an element distributes with an up to date distribution pool. This means it must distribute from the top of an element's distribution tree. --- src/mini/shady.html | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/mini/shady.html b/src/mini/shady.html index 37b3872838..e774619d47 100644 --- a/src/mini/shady.html +++ b/src/mini/shady.html @@ -80,12 +80,10 @@ */ distributeContent: function() { if (this.shadyRoot) { - var dom = Polymer.dom(this); - var list = getRedistributingHosts(this); - list.push(this); - for (var i=0; i < list.length; i++) { - dom._lazyDistribute(list[i]); - } + // 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); + Polymer.dom(this)._lazyDistribute(host); } }, @@ -404,17 +402,13 @@ return node.__patched ? node._composedParent : node.parentNode; } - // return this host's like of redistributing hosts. - function getRedistributingHosts(host) { - var list = []; + // returns the host that's the top of this host's distribution tree + function getTopDistributingHost(host) { while (host && hostNeedsRedistribution(host)) { - host = host.domHost; - // order from top-bottom for efficiency - if (host) { - list.unshift(host); - } + var last = host; + var host = host.domHost; } - return list; + return host || last; } // Return true if a host's children includes From f5cdedec87a2ef1f7496bae4119657b7cbfafba1 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 12 May 2015 11:17:23 -0700 Subject: [PATCH 5/5] simplify slightly. --- src/mini/shady.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/mini/shady.html b/src/mini/shady.html index e774619d47..d8e6aa3433 100644 --- a/src/mini/shady.html +++ b/src/mini/shady.html @@ -405,10 +405,9 @@ // returns the host that's the top of this host's distribution tree function getTopDistributingHost(host) { while (host && hostNeedsRedistribution(host)) { - var last = host; - var host = host.domHost; + host = host.domHost; } - return host || last; + return host; } // Return true if a host's children includes @@ -418,7 +417,7 @@ for (var i=0, c; i < c$.length; i++) { c = c$[i]; if (c.localName === 'content') { - return true; + return host.domHost; } } }