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(); + }); + }); + }); + }); +