Skip to content

Commit 8242a98

Browse files
author
Steven Orvell
committed
Add optional attribute tracking to support better distributed node notifications under shadow dom.
1 parent 1774f57 commit 8242a98

File tree

4 files changed

+153
-41
lines changed

4 files changed

+153
-41
lines changed

src/lib/dom-api-mutation-content.html

+21-8
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,25 @@
2323

2424
Polymer.Base.extend(DomApi.MutationContent.prototype, {
2525

26-
addListener: function(callback, includeChanges) {
27-
this._includeChanges = includeChanges;
28-
var h = DomApi.Mutation.prototype.addListener.call(this, callback);
26+
addListener: function(callback, options) {
27+
var h = DomApi.Mutation.prototype.addListener.call(this, callback,
28+
options);
2929
this._scheduleNotify();
3030
return h;
3131
},
3232

33+
_ensureSetup: function(options) {
34+
this._describeChanges = options && options.changes;
35+
},
36+
3337
notify: function() {
3438
if (this._hasListeners()) {
3539
this._scheduleNotify();
3640
}
3741
},
3842

3943
_notify: function() {
40-
var info = this._includeChanges ? this._calcChanges() : {};
44+
var info = this._describeChanges ? this._calcChanges() : {};
4145
if (info) {
4246
info.target = this.node;
4347
this._callListeners(info);
@@ -75,20 +79,29 @@
7579

7680
if (Settings.useShadow) {
7781

82+
var ensureSetup = DomApi.MutationContent.prototype._ensureSetup;
83+
7884
Polymer.Base.extend(DomApi.MutationContent.prototype, {
7985

80-
_ensureObserver: function() {
86+
_ensureSetup: function(options) {
87+
ensureSetup.call(this, options);
88+
this._trackAttributes = this._trackAttributes ||
89+
options && options.attributes;
8190
var root = this.domApi.getOwnerRoot();
8291
var host = root && root.host;
8392
if (host) {
8493
this._observer = Polymer.dom(host).observeNodes(
85-
this.notify.bind(this));
94+
this.notify.bind(this), {attributes: this._trackAttributes});
8695
}
8796
},
8897

89-
_cleanupObserver: function() {
98+
_ensureCleanup: function() {
9099
if (this._observer) {
91-
Polymer.dom(host).unobserveNodes(this._observer);
100+
var root = this.domApi.getOwnerRoot();
101+
var host = root && root.host;
102+
if (host) {
103+
Polymer.dom(host).unobserveNodes(this._observer);
104+
}
92105
}
93106
}
94107

src/lib/dom-api-mutation.html

+92-25
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,23 @@
2626

2727
DomApi.Mutation.prototype = {
2828

29-
addListener: function(callback) {
30-
this._ensureObserver();
31-
return this._listeners.push(callback);
29+
addListener: function(callback, options) {
30+
this._ensureSetup(options);
31+
return this._listeners.push({fn: callback, options: options});
3232
},
3333

3434
removeListener: function(handle) {
3535
this._listeners.splice(handle - 1, 1);
3636
if (!this._hasListeners()) {
37-
this._cleanupObserver();
37+
this._ensureCleanup();
3838
}
3939
},
4040

41-
_ensureObserver: function() {
41+
_ensureSetup: function(options) {
4242
this._observeContentElements(this.domApi.childNodes);
4343
},
4444

45-
// TODO(sorvell): unobserver content?
46-
_cleanupObserver: function() {},
45+
_ensureCleanup: function() {}, // abstract
4746

4847
_hasListeners: function() {
4948
return Boolean(this._listeners.length);
@@ -83,7 +82,7 @@
8382
},
8483

8584
_notify: function(mxns) {
86-
var info = {
85+
var info = mxns || {
8786
target: this.node,
8887
addedNodes: this._addedNodes,
8988
removedNodes: this._removedNodes
@@ -103,20 +102,26 @@
103102
for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) {
104103
if (this._isContent(n)) {
105104
n.__observeNodesMap = n.__observeNodesMap || new WeakMap();
106-
if (n.__observeNodesMap.get(this) === undefined) {
107-
h = Polymer.dom(n).observeNodes(
108-
this._callListeners.bind(this), true);
105+
if (n.__observeNodesMap.get(this) == null) {
106+
h = this._observeContent(n);
109107
n.__observeNodesMap.set(this, h);
110108
}
111109
}
112110
}
113111
},
114112

113+
_observeContent: function(content) {
114+
return Polymer.dom(content).observeNodes(this._notify.bind(this), {
115+
changes: true
116+
});
117+
},
118+
115119
_unobserveContentElements: function(elements) {
116120
for (var i=0, n, h; (i < elements.length) && (n=elements[i]); i++) {
117121
if (this._isContent(n)) {
118122
h = n.__observeNodesMap.get(this);
119123
if (h) {
124+
n.__observeNodesMap.set(this, null);
120125
Polymer.dom(n).unobserveNodes(h);
121126
}
122127
}
@@ -127,10 +132,12 @@
127132
return (node.localName === 'content');
128133
},
129134

130-
_callListeners: function(info) {
135+
_callListeners: function(info, filter) {
131136
var o$ = this._listeners;
132137
for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) {
133-
o.call(this.node, info);
138+
if (!filter || filter(o.options)) {
139+
o.fn.call(this.node, info);
140+
}
134141
}
135142
}
136143

@@ -140,7 +147,9 @@
140147

141148
Polymer.Base.extend(DomApi.Mutation.prototype, {
142149

143-
_ensureObserver: function() {
150+
_ensureSetup: function(options) {
151+
this._trackAttributes = this._trackAttributes ||
152+
options && options.attributes;
144153
if (!this._observer) {
145154
this._observer =
146155
new MutationObserver(this._notify.bind(this));
@@ -165,7 +174,7 @@
165174
}
166175
},
167176

168-
_cleanupObserver: function() {
177+
_ensureCleanup: function() {
169178
this._observer.disconnect();
170179
Polymer.dom.removePreflush(this._preflush);
171180
},
@@ -176,20 +185,30 @@
176185
addedNodes: [],
177186
removedNodes: []
178187
};
179-
mxns.forEach(function(m) {
180-
if (m.addedNodes) {
181-
for (var i=0; i < m.addedNodes.length; i++) {
182-
info.addedNodes.push(m.addedNodes[i]);
188+
// collapse multiple mutations into one view
189+
if (Array.isArray(mxns)) {
190+
Array.prototype.forEach.call(mxns, function(m) {
191+
if (m.addedNodes) {
192+
for (var i=0; i < m.addedNodes.length; i++) {
193+
info.addedNodes.push(m.addedNodes[i]);
194+
}
183195
}
184-
}
185-
if (m.removedNodes) {
186-
for (var i=0; i < m.removedNodes.length; i++) {
187-
info.removedNodes.push(m.removedNodes[i]);
196+
if (m.removedNodes) {
197+
for (var i=0; i < m.removedNodes.length; i++) {
198+
info.removedNodes.push(m.removedNodes[i]);
199+
}
188200
}
189-
}
190-
});
201+
});
202+
} else if (mxns) {
203+
info = mxns;
204+
}
191205
if (info.addedNodes.length || info.removedNodes.length) {
192206
this._updateContentElements(info);
207+
// attribute tracking helps us notice distributed nodes changes
208+
// needed only with shadow dom.
209+
if (this._trackAttributes) {
210+
this._updateAttributeObservers(info);
211+
}
193212
this._callListeners(info);
194213
}
195214
},
@@ -198,6 +217,54 @@
198217
if (this._observer) {
199218
this._notify(this._observer.takeRecords());
200219
}
220+
},
221+
222+
_observeContent: function(content) {
223+
return Polymer.dom(content).observeNodes(this._notify.bind(this), {
224+
changes: true,
225+
attributes: this._trackAttributes
226+
});
227+
},
228+
229+
_updateAttributeObservers: function(info) {
230+
this._observeAttributes(info.addedNodes);
231+
this._unobserveAttributes(info.removedNodes);
232+
},
233+
234+
_observeAttributes: function(elements) {
235+
for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) {
236+
if (n.nodeType === Node.ELEMENT_NODE) {
237+
this._ensureElementAttrObserver(n);
238+
}
239+
}
240+
},
241+
242+
_ensureElementAttrObserver: function(element) {
243+
this._attrObservers = this._attrObservers || new WeakMap();
244+
if (!this._attrObservers.get(element)) {
245+
var self = this;
246+
this._attrObservers.set(element, new MutationObserver(
247+
function(mxns) {
248+
self._callListeners(mxns, function(options) {
249+
return Boolean(options && options.attributes);
250+
});
251+
}
252+
));
253+
this._attrObservers.get(element)
254+
.observe(element, {attributes: true});
255+
}
256+
},
257+
258+
_unobserveAttributes: function(elements) {
259+
for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) {
260+
if (n.nodeType === Node.ELEMENT_NODE) {
261+
h = this._attrObservers.get(n);
262+
if (h) {
263+
h.disconnect(n);
264+
}
265+
this._attrObservers.set(n, null);
266+
}
267+
}
201268
}
202269

203270
});

src/lib/dom-api.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -525,7 +525,7 @@
525525
return n;
526526
},
527527

528-
observeNodes: function(callback) {
528+
observeNodes: function(callback, options) {
529529
if (!this.observer) {
530530
this.observer = this.node.localName === CONTENT ?
531531
new DomApi.MutationContent(this) :

test/smoke/observeReNodes2.html renamed to test/smoke/observeReNodes-attr.html

+39-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
},
2626

2727
observe: function() {
28+
console.warn('observeNodes', this.localName);
2829
this._childObserver = Polymer.dom(this).observeNodes(function(info) {
2930
info.addedNodes.forEach(function(n) {
3031
if (n.nodeType === Node.ELEMENT_NODE) {
@@ -36,7 +37,7 @@
3637
console.log('removed:', n.localName, n.textContent);
3738
}
3839
});
39-
});
40+
}, {changes: true, attributes: true});
4041
this._contentObserver = Polymer.dom(this.$.c).observeNodes(function(info) {
4142
info.addedNodes.forEach(function(n) {
4243
if (n.nodeType === Node.ELEMENT_NODE) {
@@ -48,12 +49,13 @@
4849
console.log('%c content removed:', 'color: blue;', n.localName, n.textContent);
4950
}
5051
});
51-
}, true);
52+
}, {changes: true, attributes: true});
5253
},
5354

5455
unobserve: function() {
56+
console.warn('unobserveNodes', this.localName);
5557
Polymer.dom(this).unobserveNodes(this._childObserver);
56-
Polymer.dom(this).unobserveNodes(this._contentObserver);
58+
Polymer.dom(this.$.c).unobserveNodes(this._contentObserver);
5759
}
5860
});
5961
</script>
@@ -90,7 +92,7 @@
9092

9193
Polymer.dom.flush();
9294

93-
// setTimeout(function() {
95+
function test(done) {
9496
console.group('test dynamic');
9597
var d = makeNode('dynamic!');
9698
Polymer.dom.flush();
@@ -103,7 +105,7 @@
103105
Polymer.dom(content).appendChild(d);
104106
Polymer.dom.flush();
105107
Polymer.dom(d).classList.add('a');
106-
Polymer.dom(Polymer.dom(d).parentNode).notifyObservers();
108+
//Polymer.dom(Polymer.dom(d).parentNode).notifyObservers();
107109
Polymer.dom(content.$.inner).appendChild(makeNode('2'));
108110
Polymer.dom.flush();
109111
d = makeNode('-1');
@@ -118,8 +120,38 @@
118120
Polymer.dom(content).removeChild(Polymer.dom(content).firstChild);
119121
Polymer.dom(content).removeChild(Polymer.dom(content).lastChild);
120122
Polymer.dom.flush();
121-
console.groupEnd('test dynamic');
122-
// }, 1000);
123+
124+
var d1 = makeNode('dynamic2!');
125+
Polymer.dom(content).appendChild(d1);
126+
127+
setTimeout(function() {
128+
Polymer.dom(d1).classList.add('a');
129+
Polymer.dom(d1).classList.add('b');
130+
setTimeout(function() {
131+
//Polymer.dom(content).notifyObservers();
132+
console.groupEnd('test dynamic');
133+
if (done) {
134+
done();
135+
}
136+
});
137+
});
138+
}
139+
140+
141+
test(function() {
142+
content.$.inner.unobserve();
143+
test(function() {
144+
content.$.inner.observe();
145+
test(function() {
146+
content.$.inner.unobserve();
147+
test(function() {
148+
content.$.inner.observe();
149+
test();
150+
});
151+
});
152+
});
153+
});
154+
123155
</script>
124156

125157
</body>

0 commit comments

Comments
 (0)