diff --git a/src/lib/dom-api-mutation-content.html b/src/lib/dom-api-mutation-content.html
index b603d91ca8..f458046eb4 100644
--- a/src/lib/dom-api-mutation-content.html
+++ b/src/lib/dom-api-mutation-content.html
@@ -23,13 +23,17 @@
Polymer.Base.extend(DomApi.MutationContent.prototype, {
- addListener: function(callback, includeChanges) {
- this._includeChanges = includeChanges;
- var h = DomApi.Mutation.prototype.addListener.call(this, callback);
+ addListener: function(callback, options) {
+ var h = DomApi.Mutation.prototype.addListener.call(this, callback,
+ options);
this._scheduleNotify();
return h;
},
+ _ensureSetup: function(options) {
+ this._describeChanges = options && options.changes;
+ },
+
notify: function() {
if (this._hasListeners()) {
this._scheduleNotify();
@@ -37,7 +41,7 @@
},
_notify: function() {
- var info = this._includeChanges ? this._calcChanges() : {};
+ var info = this._describeChanges ? this._calcChanges() : {};
if (info) {
info.target = this.node;
this._callListeners(info);
@@ -75,20 +79,29 @@
if (Settings.useShadow) {
+ var ensureSetup = DomApi.MutationContent.prototype._ensureSetup;
+
Polymer.Base.extend(DomApi.MutationContent.prototype, {
- _ensureObserver: function() {
+ _ensureSetup: function(options) {
+ ensureSetup.call(this, options);
+ this._trackAttributes = this._trackAttributes ||
+ options && options.attributes;
var root = this.domApi.getOwnerRoot();
var host = root && root.host;
if (host) {
this._observer = Polymer.dom(host).observeNodes(
- this.notify.bind(this));
+ this.notify.bind(this), {attributes: this._trackAttributes});
}
},
- _cleanupObserver: function() {
+ _ensureCleanup: function() {
if (this._observer) {
- Polymer.dom(host).unobserveNodes(this._observer);
+ var root = this.domApi.getOwnerRoot();
+ var host = root && root.host;
+ if (host) {
+ Polymer.dom(host).unobserveNodes(this._observer);
+ }
}
}
diff --git a/src/lib/dom-api-mutation.html b/src/lib/dom-api-mutation.html
index 1d3b16fd8f..802355c693 100644
--- a/src/lib/dom-api-mutation.html
+++ b/src/lib/dom-api-mutation.html
@@ -26,24 +26,23 @@
DomApi.Mutation.prototype = {
- addListener: function(callback) {
- this._ensureObserver();
- return this._listeners.push(callback);
+ addListener: function(callback, options) {
+ this._ensureSetup(options);
+ return this._listeners.push({fn: callback, options: options});
},
removeListener: function(handle) {
this._listeners.splice(handle - 1, 1);
if (!this._hasListeners()) {
- this._cleanupObserver();
+ this._ensureCleanup();
}
},
- _ensureObserver: function() {
+ _ensureSetup: function(options) {
this._observeContentElements(this.domApi.childNodes);
},
- // TODO(sorvell): unobserver content?
- _cleanupObserver: function() {},
+ _ensureCleanup: function() {}, // abstract
_hasListeners: function() {
return Boolean(this._listeners.length);
@@ -83,7 +82,7 @@
},
_notify: function(mxns) {
- var info = {
+ var info = mxns || {
target: this.node,
addedNodes: this._addedNodes,
removedNodes: this._removedNodes
@@ -103,20 +102,26 @@
for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) {
if (this._isContent(n)) {
n.__observeNodesMap = n.__observeNodesMap || new WeakMap();
- if (n.__observeNodesMap.get(this) === undefined) {
- h = Polymer.dom(n).observeNodes(
- this._callListeners.bind(this), true);
+ if (n.__observeNodesMap.get(this) == null) {
+ h = this._observeContent(n);
n.__observeNodesMap.set(this, h);
}
}
}
},
+ _observeContent: function(content) {
+ return Polymer.dom(content).observeNodes(this._notify.bind(this), {
+ changes: true
+ });
+ },
+
_unobserveContentElements: function(elements) {
for (var i=0, n, h; (i < elements.length) && (n=elements[i]); i++) {
if (this._isContent(n)) {
h = n.__observeNodesMap.get(this);
if (h) {
+ n.__observeNodesMap.set(this, null);
Polymer.dom(n).unobserveNodes(h);
}
}
@@ -127,10 +132,12 @@
return (node.localName === 'content');
},
- _callListeners: function(info) {
+ _callListeners: function(info, filter) {
var o$ = this._listeners;
for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) {
- o.call(this.node, info);
+ if (!filter || filter(o.options)) {
+ o.fn.call(this.node, info);
+ }
}
}
@@ -140,7 +147,9 @@
Polymer.Base.extend(DomApi.Mutation.prototype, {
- _ensureObserver: function() {
+ _ensureSetup: function(options) {
+ this._trackAttributes = this._trackAttributes ||
+ options && options.attributes;
if (!this._observer) {
this._observer =
new MutationObserver(this._notify.bind(this));
@@ -165,7 +174,7 @@
}
},
- _cleanupObserver: function() {
+ _ensureCleanup: function() {
this._observer.disconnect();
Polymer.dom.removePreflush(this._preflush);
},
@@ -176,20 +185,30 @@
addedNodes: [],
removedNodes: []
};
- mxns.forEach(function(m) {
- if (m.addedNodes) {
- for (var i=0; i < m.addedNodes.length; i++) {
- info.addedNodes.push(m.addedNodes[i]);
+ // collapse multiple mutations into one view
+ if (Array.isArray(mxns)) {
+ Array.prototype.forEach.call(mxns, function(m) {
+ if (m.addedNodes) {
+ for (var i=0; i < m.addedNodes.length; i++) {
+ info.addedNodes.push(m.addedNodes[i]);
+ }
}
- }
- if (m.removedNodes) {
- for (var i=0; i < m.removedNodes.length; i++) {
- info.removedNodes.push(m.removedNodes[i]);
+ if (m.removedNodes) {
+ for (var i=0; i < m.removedNodes.length; i++) {
+ info.removedNodes.push(m.removedNodes[i]);
+ }
}
- }
- });
+ });
+ } else if (mxns) {
+ info = mxns;
+ }
if (info.addedNodes.length || info.removedNodes.length) {
this._updateContentElements(info);
+ // attribute tracking helps us notice distributed nodes changes
+ // needed only with shadow dom.
+ if (this._trackAttributes) {
+ this._updateAttributeObservers(info);
+ }
this._callListeners(info);
}
},
@@ -198,6 +217,54 @@
if (this._observer) {
this._notify(this._observer.takeRecords());
}
+ },
+
+ _observeContent: function(content) {
+ return Polymer.dom(content).observeNodes(this._notify.bind(this), {
+ changes: true,
+ attributes: this._trackAttributes
+ });
+ },
+
+ _updateAttributeObservers: function(info) {
+ this._observeAttributes(info.addedNodes);
+ this._unobserveAttributes(info.removedNodes);
+ },
+
+ _observeAttributes: function(elements) {
+ for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) {
+ if (n.nodeType === Node.ELEMENT_NODE) {
+ this._ensureElementAttrObserver(n);
+ }
+ }
+ },
+
+ _ensureElementAttrObserver: function(element) {
+ this._attrObservers = this._attrObservers || new WeakMap();
+ if (!this._attrObservers.get(element)) {
+ var self = this;
+ this._attrObservers.set(element, new MutationObserver(
+ function(mxns) {
+ self._callListeners(mxns, function(options) {
+ return Boolean(options && options.attributes);
+ });
+ }
+ ));
+ this._attrObservers.get(element)
+ .observe(element, {attributes: true});
+ }
+ },
+
+ _unobserveAttributes: function(elements) {
+ for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) {
+ if (n.nodeType === Node.ELEMENT_NODE) {
+ h = this._attrObservers.get(n);
+ if (h) {
+ h.disconnect(n);
+ }
+ this._attrObservers.set(n, null);
+ }
+ }
}
});
diff --git a/src/lib/dom-api.html b/src/lib/dom-api.html
index 388f84b900..f697088e5f 100644
--- a/src/lib/dom-api.html
+++ b/src/lib/dom-api.html
@@ -525,7 +525,7 @@
return n;
},
- observeNodes: function(callback) {
+ observeNodes: function(callback, options) {
if (!this.observer) {
this.observer = this.node.localName === CONTENT ?
new DomApi.MutationContent(this) :
diff --git a/test/smoke/observeReNodes2.html b/test/smoke/observeReNodes-attr.html
similarity index 74%
rename from test/smoke/observeReNodes2.html
rename to test/smoke/observeReNodes-attr.html
index e9477e1fc2..d53a509725 100644
--- a/test/smoke/observeReNodes2.html
+++ b/test/smoke/observeReNodes-attr.html
@@ -25,6 +25,7 @@
},
observe: function() {
+ console.warn('observeNodes', this.localName);
this._childObserver = Polymer.dom(this).observeNodes(function(info) {
info.addedNodes.forEach(function(n) {
if (n.nodeType === Node.ELEMENT_NODE) {
@@ -36,7 +37,7 @@
console.log('removed:', n.localName, n.textContent);
}
});
- });
+ }, {changes: true, attributes: true});
this._contentObserver = Polymer.dom(this.$.c).observeNodes(function(info) {
info.addedNodes.forEach(function(n) {
if (n.nodeType === Node.ELEMENT_NODE) {
@@ -48,12 +49,13 @@
console.log('%c content removed:', 'color: blue;', n.localName, n.textContent);
}
});
- }, true);
+ }, {changes: true, attributes: true});
},
unobserve: function() {
+ console.warn('unobserveNodes', this.localName);
Polymer.dom(this).unobserveNodes(this._childObserver);
- Polymer.dom(this).unobserveNodes(this._contentObserver);
+ Polymer.dom(this.$.c).unobserveNodes(this._contentObserver);
}
});
@@ -90,7 +92,7 @@
Polymer.dom.flush();
- // setTimeout(function() {
+ function test(done) {
console.group('test dynamic');
var d = makeNode('dynamic!');
Polymer.dom.flush();
@@ -103,7 +105,7 @@
Polymer.dom(content).appendChild(d);
Polymer.dom.flush();
Polymer.dom(d).classList.add('a');
- Polymer.dom(Polymer.dom(d).parentNode).notifyObservers();
+ //Polymer.dom(Polymer.dom(d).parentNode).notifyObservers();
Polymer.dom(content.$.inner).appendChild(makeNode('2'));
Polymer.dom.flush();
d = makeNode('-1');
@@ -118,8 +120,38 @@
Polymer.dom(content).removeChild(Polymer.dom(content).firstChild);
Polymer.dom(content).removeChild(Polymer.dom(content).lastChild);
Polymer.dom.flush();
- console.groupEnd('test dynamic');
- // }, 1000);
+
+ var d1 = makeNode('dynamic2!');
+ Polymer.dom(content).appendChild(d1);
+
+ setTimeout(function() {
+ Polymer.dom(d1).classList.add('a');
+ Polymer.dom(d1).classList.add('b');
+ setTimeout(function() {
+ //Polymer.dom(content).notifyObservers();
+ console.groupEnd('test dynamic');
+ if (done) {
+ done();
+ }
+ });
+ });
+ }
+
+
+ test(function() {
+ content.$.inner.unobserve();
+ test(function() {
+ content.$.inner.observe();
+ test(function() {
+ content.$.inner.unobserve();
+ test(function() {
+ content.$.inner.observe();
+ test();
+ });
+ });
+ });
+ });
+