From b11f86b2c88fc13f1d4ddf8312431a45bb6761a0 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Thu, 13 Aug 2015 10:44:46 -0700 Subject: [PATCH] Add mutation tracking for distributedNodes. --- src/lib/dom-api-flush.html | 22 +- src/lib/dom-api-mutation-content.html | 172 +++++++-------- src/lib/dom-api-mutation.html | 196 ++++++++---------- src/lib/dom-api.html | 27 ++- src/mini/shady.html | 19 +- test/smoke/observeContent.html | 84 ++++++++ ...observeChildren.html => observeNodes.html} | 18 +- test/smoke/observeReNodes.html | 98 +++++++++ 8 files changed, 413 insertions(+), 223 deletions(-) create mode 100644 test/smoke/observeContent.html rename test/smoke/{observeChildren.html => observeNodes.html} (75%) create mode 100644 test/smoke/observeReNodes.html diff --git a/src/lib/dom-api-flush.html b/src/lib/dom-api-flush.html index 8c0ae43b53..1ecc1821e6 100644 --- a/src/lib/dom-api-flush.html +++ b/src/lib/dom-api-flush.html @@ -16,10 +16,18 @@ _FLUSH_MAX: 100, _needsTakeRecords: !Polymer.Settings.useNativeCustomElements, _debouncers: [], + _preFlushList: [], _finishDebouncer: null, // flush and debounce exposed as statics on Polymer.dom flush: function() { + for (var i=0; i < this._preFlushList.length; i++) { + this._preFlushList[i](); + } + this._flush(); + }, + + _flush: function() { // flush debouncers for (var i=0; i < this._debouncers.length; i++) { this._debouncers[i].complete(); @@ -34,7 +42,7 @@ // flush again if there are now any debouncers to process if (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) { this._flushGuard++; - this.flush(); + this._flush(); } else { if (this._flushGuard >= this._FLUSH_MAX) { console.warn('Polymer.dom.flush aborted. Flush may not be complete.') @@ -52,6 +60,18 @@ } }, + addPreflush: function(fn) { + this._preFlushList.push(fn); + }, + + // TODO(sorvell): Map when we can? + removePreflush: function(fn) { + var i = this._preFlushList.indexOf(fn); + if (i >= 0) { + this._preFlushList.splice(i, 1); + } + }, + addDebouncer: function(debouncer) { this._debouncers.push(debouncer); // ensure the list of active debouncers is cleared when done. diff --git a/src/lib/dom-api-mutation-content.html b/src/lib/dom-api-mutation-content.html index 438f5fc9b7..bf13de0130 100644 --- a/src/lib/dom-api-mutation-content.html +++ b/src/lib/dom-api-mutation-content.html @@ -15,113 +15,85 @@ var DomApi = Polymer.DomApi.ctor; var Settings = Polymer.Settings; - DomApi.prototype.observeDistributedNodes = function(callback) { - if (!this._observer) { - this._observer = new DomApi.Mutation(this); - } - return this._observer.addObserver(callback); - }; - - DomApi.prototype.unobserveDistributedNodes = function(handle) { - if (this._observer) { - this._observer.removeObserver(handle); - } - } - DomApi.MutationContent = function(domApi) { - this.domApi = domApi; - this.node = this.domApi.node; - this._observers = []; - this._addedNodes = []; - this._removedNodes = []; + DomApi.Mutation.call(this, domApi); }; - DomApi.MutationContent.prototype = { - - // addObserver: function(callback) { - // return this._observers.push(callback); - // }, - - // removeObserver: function(handle) { - // this._observers.splice(handle - 1, 1); - // }, - - // hasObservers: function() { - // return Boolean(this._observers.length); - // }, - - // _scheduleMutationNotify: function() { - // this._mutationDebouncer = Polymer.Debounce(this._mutationDebouncer, - // this._notifyObservers); - // this._mutationDebouncer.context = this; - // Polymer.dom.addDebouncer(this._mutationDebouncer); - // }, - - // _notifyObservers: function(mxns) { - // var info = { - // target: this.node, - // addedNodes: this._addedNodes, - // removedNodes: this._removedNodes - // } - // var o$ = this._observers; - // for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) { - // o.call(null, info); - // } - // this._addedNodes = []; - // this._removedNodes = []; - // } - - // observeDistributedNodes: function(callback) { - // if (this.node.localName !== 'content') { - // console.warn('Must call `observeDistributedNodes` on a element.'); - // return; - // } - // // setup .getDistributedNodes observation - // if (!this.hasObservers()) { - // this._startObserveDistributedNodes(); - // } - // this._addObserver(callback); - // }, - - // unobserveDistributedNodes: function(handle) { - // if (this.node.localName !== 'content') { - // return; - // } - // this._removeObserver(handle); - // if (!this.hasObservers()) { - // this._stopObservingDistributedNodes(); - // } - // }, - - // _startObservingDistributedNodes: function() { - // this._distributedObservers = []; - // var root = this.getOwnerRoot(); - // var host = root && root.host; - // if (host) { - // var h = Polymer.dom(host) - // .observeChildren(this._scheduleDistributedNodesNotify); - // this._distributedObservers.push(h); - // } - // }, - - // _stopObservingDistributedNodes: function() { - - // }, - - // _scheduleDistributedNodesNotify: function() { - // this._distributedNodesDebouncer = - // Polymer.Debounce(this._distributedNodesDebouncer, - // this._notifyObservers); - // this._distributedNodesDebouncer.context = this.node; - // Polymer.dom.addDebouncer(this._distributedNodesDebouncer); - // }, + DomApi.MutationContent.prototype = Object.create(DomApi.Mutation.prototype); + + Polymer.Base.extend(DomApi.MutationContent.prototype, { + + addListener: function(callback, includeChanges) { + this._includeChanges = includeChanges; + var h = DomApi.Mutation.prototype.addListener.call(this, callback); + this._scheduleNotify(); + return h; + }, + + notifyIfNeeded: function() { + if (this._hasListeners()) { + this._scheduleNotify(); + } + }, + + _notify: function() { + var info = this._includeChanges ? this._calcChanges() : {}; + if (info) { + info.target = this.node; + this._callListeners(info); + } + }, + + _calcChanges: function() { + var changes = { + addedNodes: [], + removedNodes: [] + }; + var o$ = this.node.__distributedNodes = this.node.__distributedNodes || + []; + var n$ = this.domApi.getDistributedNodes(); + this.node.__distributedNodes = n$; + var splices = Polymer.ArraySplice.calculateSplices(n$, o$); + // process removals + for (var i=0, s; (i element.'); - // return; - // } - // // setup .getDistributedNodes observation - // if (!this.hasObservers()) { - // this._startObserveDistributedNodes(); - // } - // this._addObserver(callback); - // }, - - // unobserveDistributedNodes: function(handle) { - // if (this.node.localName !== 'content') { - // return; - // } - // this._removeObserver(handle); - // if (!this.hasObservers()) { - // this._stopObservingDistributedNodes(); - // } - // }, - - // _startObservingDistributedNodes: function() { - // this._distributedObservers = []; - // var root = this.getOwnerRoot(); - // var host = root && root.host; - // if (host) { - // var h = Polymer.dom(host) - // .observeChildren(this._scheduleDistributedNodesNotify); - // this._distributedObservers.push(h); - // } - // }, - - // _stopObservingDistributedNodes: function() { - - // }, + _updateContentElements: function(info) { + this._observeContentElements(info.addedNodes); + this._unobserveContentElements(info.removedNodes); + }, - // _scheduleDistributedNodesNotify: function() { - // this._distributedNodesDebouncer = - // Polymer.Debounce(this._distributedNodesDebouncer, - // this._notifyObservers); - // this._distributedNodesDebouncer.context = this.node; - // Polymer.dom.addDebouncer(this._distributedNodesDebouncer); - // }, + _observeContentElements: function(elements) { + for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) { + if (n.localName === 'content') { + n.__observeNodesMap = n.__observeNodesMap || new WeakMap(); + if (n.__observeNodesMap.get(this) === undefined) { + h = Polymer.dom(n).observeNodes( + this._callListeners.bind(this), true); + n.__observeNodesMap.set(this, h); + } + } + } + }, + + _unobserveContentElements: function(elements) { + for (var i=0, n, h; (i < elements.length) && (n=elements[i]); i++) { + if (n.localName === 'content') { + h = n.__observeNodesMap.get(this); + if (h) { + Polymer.dom(n).unobserveNodes(h); + } + } + } + }, + + _callListeners: function(info) { + var o$ = this._listeners; + for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) { + o.call(this.node, info); + } + } }; @@ -145,31 +135,34 @@ Polymer.Base.extend(DomApi.Mutation.prototype, { - addObserver: function(callback) { - this._ensureObserver(); - return this._observers.push(callback); - }, - _ensureObserver: function() { if (!this._observer) { this._observer = - new MutationObserver(this._notifyObservers.bind(this)); - this._observer.observe(this.node, {childList: true}); + new MutationObserver(this._notify.bind(this)); // make sure to notify initial state... - this._mutationDebouncer = Polymer.Debounce(this._mutationDebouncer, + this._debouncer = Polymer.Debounce(this._debouncer, function() { - this._notifyObservers([{ + this._notify([{ target: this.node, addedNodes: this.domApi.childNodes.slice() }]); } ); - this._mutationDebouncer.context = this; - Polymer.dom.addDebouncer(this._mutationDebouncer); + this._debouncer.context = this; + Polymer.dom.addDebouncer(this._debouncer); + this._preflush = this._flush.bind(this); + Polymer.dom.addPreflush(this._preflush); } + // note: doing this > 1x is a no-op + this._observer.observe(this.node, {childList: true}); }, - _notifyObservers: function(mxns) { + _cleanupObserver: function() { + this._observer.disconnect(); + Polymer.dom.removePreflush(this._preflush); + }, + + _notify: function(mxns) { var info = { target: this.node, addedNodes: [], @@ -188,26 +181,19 @@ } }); if (info.addedNodes.length || info.removedNodes.length) { - var o$ = this._observers; - for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) { - o.call(null, info); - } + this._updateContentElements(info); + this._callListeners(info); } }, _flush: function() { - this._notifyObservers(this._observer.takeRecords()); + if (this._observer) { + this._notify(this._observer.takeRecords()); + } } }); - DomApi.prototype.flush = function() { - if (this._observer) { - this._observer._flush(); - } - Polymer.dom.flush(); - } - } })(); diff --git a/src/lib/dom-api.html b/src/lib/dom-api.html index a89ea1a6d6..906ca167dc 100644 --- a/src/lib/dom-api.html +++ b/src/lib/dom-api.html @@ -81,8 +81,8 @@ addToComposedParent(container, node); nativeAppendChild.call(container, node); } - if (this._observer) { - this._observer.addNode(node); + if (this.observer) { + this.observer.addNode(node); } return node; }, @@ -118,8 +118,8 @@ addToComposedParent(container, node, ref_node); nativeInsertBefore.call(container, node, ref_node); } - if (this._observer) { - this._observer.addNode(node); + if (this.observer) { + this.observer.addNode(node); } return node; }, @@ -150,8 +150,8 @@ nativeRemoveChild.call(container, node); } } - if (this._observer) { - this._observer.removeNode(node); + if (this.observer) { + this.observer.removeNode(node); } return node; }, @@ -523,6 +523,21 @@ } } return n; + }, + + observeNodes: function(callback) { + if (!this.observer) { + this.observer = this.node.localName === CONTENT ? + new DomApi.MutationContent(this) : + new DomApi.Mutation(this); + } + return this.observer.addListener.apply(this.observer, arguments); + }, + + unobserveNodes: function(handle) { + if (this.observer) { + this.observer.removeListener(handle); + } } }; diff --git a/src/mini/shady.html b/src/mini/shady.html index 02cc11ed89..1529229346 100644 --- a/src/mini/shady.html +++ b/src/mini/shady.html @@ -13,6 +13,7 @@ + + + + + + + + + + + + + + + + + +
content A
+
content B
+
+ +

+ +
static A
static B
+ + + + + diff --git a/test/smoke/observeChildren.html b/test/smoke/observeNodes.html similarity index 75% rename from test/smoke/observeChildren.html rename to test/smoke/observeNodes.html index 0b9624f4b1..957a17cc6c 100644 --- a/test/smoke/observeChildren.html +++ b/test/smoke/observeNodes.html @@ -2,7 +2,7 @@ - observeChildren + observeNodes @@ -21,7 +21,7 @@ Polymer({ is:'test-content', created: function() { - Polymer.dom(this).observeChildren(function(info) { + Polymer.dom(this).observeNodes(function(info) { console.log('test-content', info); }); } @@ -37,7 +37,7 @@ Polymer({ is:'test-static', created: function() { - Polymer.dom(this).observeChildren(function(info) { + Polymer.dom(this).observeNodes(function(info) { console.log('test-static', info); }); } @@ -58,11 +58,11 @@ Polymer.dom(content).flush(); Polymer.dom(stat).flush(); console.group('test dynamic'); - function obs(mxns) { - console.log('custom observer', mxns); + function obs(info) { + console.log('custom observer', info); } - var hc = Polymer.dom(content).observeChildren(obs); - var hs = Polymer.dom(stat).observeChildren(obs); + var hc = Polymer.dom(content).observeNodes(obs); + var hs = Polymer.dom(stat).observeNodes(obs); var d = document.createElement('div'); d.id = 'foo'; @@ -71,8 +71,8 @@ Polymer.dom(stat).appendChild(d); Polymer.dom(content).flush(); Polymer.dom(stat).flush(); - Polymer.dom(content).unobserveChildren(hc); - Polymer.dom(stat).unobserveChildren(hs); + Polymer.dom(content).unobserveNodes(hc); + Polymer.dom(stat).unobserveNodes(hs); Polymer.dom(stat).removeChild(d); Polymer.dom(stat).flush(); console.groupEnd('test dynamic'); diff --git a/test/smoke/observeReNodes.html b/test/smoke/observeReNodes.html new file mode 100644 index 0000000000..336d04d9ab --- /dev/null +++ b/test/smoke/observeReNodes.html @@ -0,0 +1,98 @@ + + + + + observeReNodes + + + + + + + + + + + + + + + + + + + + + +
content A
+
content B
+
+ +

+ + + + +