Skip to content

Commit 344f5cc

Browse files
author
Steven Orvell
committed
ensure distribution observers see all changes that can come from attributes under native Shadow DOM; +minor factoring
1 parent 8b1face commit 344f5cc

File tree

5 files changed

+143
-38
lines changed

5 files changed

+143
-38
lines changed

src/lib/dom-api-observe-distributed-nodes.html renamed to src/lib/dom-api-distributed-nodes-observer.html

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@
1616
var Settings = Polymer.Settings;
1717

1818
/**
19-
* DomApi.ObserveDistributedNodes notifies when the list returned by
19+
* DomApi.DistributedNodesObserver notifies when the list returned by
2020
* a <content> element's `getDistributedNodes()` may have changed.
2121
* It is not meant to be used directly; it is used by
2222
* `Polymer.dom(node).observeNodes(callback)` to observe changes to
2323
* `<content>.getDistributedNodes()`.
2424
*/
25-
DomApi.ObserveDistributedNodes = function(domApi) {
26-
DomApi.ObserveNodes.call(this, domApi);
25+
DomApi.DistributedNodesObserver = function(domApi) {
26+
DomApi.EffectiveNodesObserver.call(this, domApi);
2727
};
2828

29-
DomApi.ObserveDistributedNodes.prototype =
30-
Object.create(DomApi.ObserveNodes.prototype);
29+
DomApi.DistributedNodesObserver.prototype =
30+
Object.create(DomApi.EffectiveNodesObserver.prototype);
3131

32-
Polymer.Base.extend(DomApi.ObserveDistributedNodes.prototype, {
32+
Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, {
3333

3434
// NOTE: ShadyDOM distribute provokes notification of these observers
3535
// so no setup is required.
@@ -49,7 +49,7 @@
4949

5050
if (Settings.useShadow) {
5151

52-
Polymer.Base.extend(DomApi.ObserveDistributedNodes.prototype, {
52+
Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, {
5353

5454
// NOTE: Under ShadowDOM we must observe the host element for
5555
// changes.
@@ -60,9 +60,13 @@
6060
if (host) {
6161
this._observer = Polymer.dom(host).observeNodes(
6262
this._scheduleNotify.bind(this));
63+
// NOTE: we identify this listener as needed for <content>
64+
// notification so that enableShadowAttributeTracking
65+
// can find these observers an ensure that we pass always
66+
// pass notifications down.
67+
this._observer._isContentListener = true;
6368
if (this._hasAttrSelect()) {
6469
Polymer.dom(host).observer.enableShadowAttributeTracking();
65-
this._observer._alwaysCallListener = true;
6670
}
6771
}
6872
}

src/lib/dom-api-observe-nodes.html renamed to src/lib/dom-api-effective-nodes-observer.html

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,19 @@
1717
var hasDomApi = Polymer.DomApi.hasDomApi;
1818

1919
/**
20-
* DomApi.ObserveNodes tracks changes to an element's effective child nodes,
21-
* the same list returned from `Polymer.dom(node).getEffectiveChildNodes()`.
20+
* DomApi.EffectiveNodesObserver tracks changes to an element's
21+
* effective child nodes, the same list returned from
22+
* `Polymer.dom(node).getEffectiveChildNodes()`.
2223
* It is not meant to be used directly; it is used by
2324
* `Polymer.dom(node).observeNodes(callback)` to observe changes.
2425
*/
25-
DomApi.ObserveNodes = function(domApi) {
26+
DomApi.EffectiveNodesObserver = function(domApi) {
2627
this.domApi = domApi;
2728
this.node = this.domApi.node;
2829
this._listeners = [];
2930
};
3031

31-
DomApi.ObserveNodes.prototype = {
32+
DomApi.EffectiveNodesObserver.prototype = {
3233

3334
addListener: function(callback) {
3435
if (!this._isSetup) {
@@ -132,7 +133,7 @@
132133
var nodes = this._getEffectiveNodes();
133134
for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) {
134135
var info = this._generateListenerInfo(o, nodes);
135-
if (info || o._alwaysCallListener) {
136+
if (info || o._alwaysNotify) {
136137
this._callListener(o, info);
137138
}
138139
}
@@ -182,13 +183,13 @@
182183

183184
if (Settings.useShadow) {
184185

185-
var baseSetup = DomApi.ObserveNodes.prototype._setup;
186-
var baseCleanup = DomApi.ObserveNodes.prototype._cleanup;
186+
var baseSetup = DomApi.EffectiveNodesObserver.prototype._setup;
187+
var baseCleanup = DomApi.EffectiveNodesObserver.prototype._cleanup;
187188

188-
var beforeCallListeners = DomApi.ObserveNodes
189+
var beforeCallListeners = DomApi.EffectiveNodesObserver
189190
.prototype._beforeCallListeners;
190191

191-
Polymer.Base.extend(DomApi.ObserveNodes.prototype, {
192+
Polymer.Base.extend(DomApi.EffectiveNodesObserver.prototype, {
192193

193194
_setup: function() {
194195
if (!this._observer) {
@@ -228,6 +229,10 @@
228229

229230
enableShadowAttributeTracking: function() {
230231
if (this._observer) {
232+
// provoke all listeners needed for <content> observation
233+
// to always call listeners when no-op changes occur (which may
234+
// affect lower distributions.
235+
this._makeContentListenersAlwaysNotify();
231236
this._observer.disconnect();
232237
this._observer.observe(this.node, {
233238
childList: true,
@@ -240,6 +245,13 @@
240245
Polymer.dom(host).observer.enableShadowAttributeTracking();
241246
}
242247
}
248+
},
249+
250+
_makeContentListenersAlwaysNotify: function() {
251+
for (var i=0, h; i < this._listeners.length ; i++) {
252+
h = this._listeners[i];
253+
h._alwaysNotify = h._isContentListener;
254+
}
243255
}
244256

245257
});

src/lib/dom-api.html

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,7 @@
116116
if (addedInsertionPoint) {
117117
this._updateInsertionPoints(root.host);
118118
}
119-
if (this.observer) {
120-
this.observer.notify();
121-
}
119+
this.notifyObserver();
122120
return node;
123121
},
124122

@@ -142,9 +140,7 @@
142140
nativeRemoveChild.call(container, node);
143141
}
144142
}
145-
if (this.observer) {
146-
this.observer.notify();
147-
}
143+
this.notifyObserver();
148144
return node;
149145
},
150146

@@ -280,10 +276,7 @@
280276
_removeNodeFromParent: function(node) {
281277
var parent = node._lightParent;
282278
if (parent && hasDomApi(parent)) {
283-
var d = factory(parent);
284-
if (d._observer) {
285-
d._observer.removeNode(node);
286-
}
279+
factory(parent).notifyObserver();
287280
}
288281
this._removeNodeFromHost(node, true);
289282
},
@@ -478,7 +471,10 @@
478471
var c$ = this.childNodes;
479472
for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
480473
if (c.localName === CONTENT) {
481-
list = list.concat(factory(c).getDistributedNodes().slice());
474+
var d$ = factory(c).getDistributedNodes();
475+
for (var j=0; j < d$.length; j++) {
476+
list.push(d$[j]);
477+
}
482478
} else {
483479
list.push(c);
484480
}
@@ -552,8 +548,8 @@
552548
if (callback) {
553549
if (!this.observer) {
554550
this.observer = this.node.localName === CONTENT ?
555-
new DomApi.ObserveDistributedNodes(this) :
556-
new DomApi.ObserveNodes(this);
551+
new DomApi.DistributedNodesObserver(this) :
552+
new DomApi.EffectiveNodesObserver(this);
557553
}
558554
return this.observer.addListener(callback);
559555
}
@@ -569,6 +565,12 @@
569565
if (this.observer) {
570566
this.observer.removeListener(handle);
571567
}
568+
},
569+
570+
notifyObserver: function() {
571+
if (this.observer) {
572+
this.observer.notify();
573+
}
572574
}
573575

574576
};

src/mini/shady.html

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
<link rel="import" href="../lib/dom-api-flush.html">
1313
<link rel="import" href="../lib/dom-api-event.html">
1414
<link rel="import" href="../lib/dom-api-classlist.html">
15-
<link rel="import" href="../lib/dom-api-observe-nodes.html">
16-
<link rel="import" href="../lib/dom-api-observe-distributed-nodes.html">
15+
<link rel="import" href="../lib/dom-api-effective-nodes-observer.html">
16+
<link rel="import" href="../lib/dom-api-distributed-nodes-observer.html">
1717
<script>
1818

1919
(function() {
@@ -480,17 +480,14 @@
480480
for (var i=0, c; i < root._insertionPoints.length; i++) {
481481
c = root._insertionPoints[i];
482482
if (hasDomApi(c)) {
483-
var dc = Polymer.dom(c);
484-
if (dc.observer) {
485-
dc.observer.notify();
486-
}
483+
Polymer.dom(c).notifyObserver();
487484
}
488485
}
489486
}
490487

491488
function notifyInitialDistribution(host) {
492-
if (hasDomApi(host) && Polymer.dom(host).observer) {
493-
Polymer.dom(host).observer.notify();
489+
if (hasDomApi(host)) {
490+
Polymer.dom(host).notifyObserver();
494491
}
495492
}
496493

test/unit/polymer-dom-observeNodes.html

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,19 @@
146146
});
147147
</script>
148148
</dom-module>
149+
150+
<dom-module id='test-content-attr-inside'>
151+
<template>
152+
<test-content-attr3 id="content"><content></content></test-content-attr3>
153+
</template>
154+
<script>
155+
HTMLImports.whenReady(function() {
156+
Polymer({
157+
is:'test-content-attr-inside'
158+
});
159+
});
160+
</script>
161+
</dom-module>
149162

150163

151164
<test-content><div>A</div><div>B</div></test-content>
@@ -467,6 +480,38 @@
467480
document.body.removeChild(el);
468481
});
469482

483+
test('observe effective children changes when adding to another host', function() {
484+
var el = document.createElement('test-content1');
485+
document.body.appendChild(el);
486+
var recorded;
487+
var handle = Polymer.dom(el.$.content).observeNodes(function(info) {
488+
recorded = info;
489+
});
490+
Polymer.dom.flush();
491+
// add
492+
var d = document.createElement('div');
493+
var d1 = document.createElement('div');
494+
Polymer.dom(el).appendChild(d);
495+
Polymer.dom(el).appendChild(d1);
496+
Polymer.dom.flush();
497+
assert.equal(recorded.addedNodes.length, 2);
498+
assert.equal(recorded.removedNodes.length, 0);
499+
assert.equal(recorded.addedNodes[0], d);
500+
assert.equal(recorded.addedNodes[1], d1);
501+
// add somewhere else... we should see these as removes
502+
Polymer.dom(document.body).appendChild(d);
503+
Polymer.dom(document.body).appendChild(d1);
504+
Polymer.dom.flush();
505+
assert.equal(recorded.addedNodes.length, 0);
506+
assert.equal(recorded.removedNodes.length, 2);
507+
assert.equal(recorded.removedNodes[0], d);
508+
assert.equal(recorded.removedNodes[1], d1);
509+
// cleanup
510+
Polymer.dom(document.body).removeChild(d);
511+
Polymer.dom(document.body).removeChild(d1);
512+
document.body.removeChild(el);
513+
});
514+
470515
test('observe effective children inside deep distributing element', function() {
471516
var el = document.createElement('test-content3');
472517
document.body.appendChild(el);
@@ -651,6 +696,51 @@
651696
});
652697
});
653698

699+
test('observe effective children attr changes inside deep distributing element without outer select (async)', function(done) {
700+
var el = document.createElement('test-content-attr-inside');
701+
document.body.appendChild(el);
702+
703+
var recorded;
704+
var content = el.$.content.$.content.$.content.$.content;
705+
var handle = Polymer.dom(content).observeNodes(function(info) {
706+
recorded = info;
707+
}, {attributes: true});
708+
Polymer.dom.flush();
709+
recorded = null;
710+
// add
711+
var d = document.createElement('div');
712+
var d1 = document.createElement('div');
713+
Polymer.dom(el).appendChild(d);
714+
Polymer.dom(el).appendChild(d1);
715+
Polymer.dom.flush();
716+
assert.equal(recorded, null);
717+
Polymer.dom(d).setAttribute('a', '');
718+
Polymer.dom(d).setAttribute('b', '');
719+
setTimeout(function() {
720+
assert.equal(recorded, null);
721+
Polymer.dom(d).setAttribute('c', '');
722+
setTimeout(function() {
723+
assert.equal(recorded.addedNodes.length, 1);
724+
assert.equal(recorded.removedNodes.length, 0);
725+
assert.equal(recorded.addedNodes[0], d);
726+
Polymer.dom(d).removeAttribute('c');
727+
setTimeout(function() {
728+
assert.equal(recorded.addedNodes.length, 0);
729+
assert.equal(recorded.removedNodes.length, 1);
730+
assert.equal(recorded.removedNodes[0], d);
731+
recorded = null;
732+
Polymer.dom(content).unobserveNodes(handle);
733+
Polymer.dom(d).setAttribute('c', '');
734+
setTimeout(function() {
735+
assert.equal(recorded, null);
736+
document.body.removeChild(el);
737+
done();
738+
});
739+
});
740+
});
741+
});
742+
});
743+
654744
test('add/remove multiple observers', function() {
655745
var el = document.createElement('test-content1');
656746
document.body.appendChild(el);

0 commit comments

Comments
 (0)