From 8b1face967a40d53d9d5d81f7baf531ef16bf3bf Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 13 Oct 2015 18:03:12 -0700 Subject: [PATCH] Add .getDistributedNodes observation. Refactor flush. --- src/lib/dom-api-flush.html | 66 +++++------ .../dom-api-observe-distributed-nodes.html | 4 +- src/lib/dom-api-observe-nodes.html | 23 +++- test/unit/polymer-dom-observeNodes.html | 112 +++++++++++++++++- 4 files changed, 160 insertions(+), 45 deletions(-) diff --git a/src/lib/dom-api-flush.html b/src/lib/dom-api-flush.html index 82449b92ac..a9e1c6ec5b 100644 --- a/src/lib/dom-api-flush.html +++ b/src/lib/dom-api-flush.html @@ -23,59 +23,53 @@ _FLUSH_MAX: 100, _needsTakeRecords: !Polymer.Settings.useNativeCustomElements, _debouncers: [], - _preFlushList: [], + _staticFlushList: [], _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._flushGuard = 0; + this._prepareFlush(); + while (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) { + for (var i=0; i < this._debouncers.length; i++) { + this._debouncers[i].complete(); + } + // clear the list of debouncers + if (this._finishDebouncer) { + this._finishDebouncer.complete(); + } + this._prepareFlush(); + this._flushGuard++; + } + if (this._flushGuard >= this._FLUSH_MAX) { + console.warn('Polymer.dom.flush aborted. Flush may not be complete.') } - this._flush(); }, - _flush: function() { - // flush debouncers - for (var i=0; i < this._debouncers.length; i++) { - this._debouncers[i].complete(); - } - // clear the list of debouncers - if (this._finishDebouncer) { - this._finishDebouncer.complete(); - } + _prepareFlush: function() { + // TODO(sorvell): There is currently not a good way + // to process all custom elements mutations under SD polyfill because + // these mutations may be inside shadowRoots. // again make any pending CE mutations that might trigger debouncer // additions go... - this._flushPolyfills(); - // flush again if there are now any debouncers to process - if (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) { - this._flushGuard++; - this._flush(); - } else { - if (this._flushGuard >= this._FLUSH_MAX) { - console.warn('Polymer.dom.flush aborted. Flush may not be complete.') - } - this._flushGuard = 0; - } - }, - - // TODO(sorvell): There is currently not a good way - // to process all custom elements mutations under SD polyfill because - // these mutations may be inside shadowRoots. - _flushPolyfills: function() { if (this._needsTakeRecords) { CustomElements.takeRecords(); } + for (var i=0; i < this._staticFlushList.length; i++) { + this._staticFlushList[i](); + } }, - addPreflush: function(fn) { - this._preFlushList.push(fn); + // add to the static list of methods to call when flushing + addStaticFlush: function(fn) { + this._staticFlushList.push(fn); }, - // TODO(sorvell): Map when we can? - removePreflush: function(fn) { - var i = this._preFlushList.indexOf(fn); + // remove a function from the static list of methods to call when flushing + removeStaticFlush: function(fn) { + var i = this._staticFlushList.indexOf(fn); if (i >= 0) { - this._preFlushList.splice(i, 1); + this._staticFlushList.splice(i, 1); } }, diff --git a/src/lib/dom-api-observe-distributed-nodes.html b/src/lib/dom-api-observe-distributed-nodes.html index 1cb6438030..e8fe88746f 100644 --- a/src/lib/dom-api-observe-distributed-nodes.html +++ b/src/lib/dom-api-observe-distributed-nodes.html @@ -41,8 +41,8 @@ // (but note that will) _beforeCallListeners: function() {}, - _generateListenerInfo: function() { - return true; + _getEffectiveNodes: function() { + return this.domApi.getDistributedNodes(); } }); diff --git a/src/lib/dom-api-observe-nodes.html b/src/lib/dom-api-observe-nodes.html index b4b27bb884..e8432a0adb 100644 --- a/src/lib/dom-api-observe-nodes.html +++ b/src/lib/dom-api-observe-nodes.html @@ -106,7 +106,9 @@ }, _observeContent: function(content) { - return Polymer.dom(content).observeNodes(this._scheduleNotify.bind(this)); + var h = Polymer.dom(content).observeNodes(this._scheduleNotify.bind(this)); + h._avoidChangeCalculation = true; + return h; }, _unobserveContentElements: function(elements) { @@ -127,7 +129,7 @@ _callListeners: function() { var o$ = this._listeners; - var nodes = this.domApi.getEffectiveChildNodes(); + var nodes = this._getEffectiveNodes(); for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) { var info = this._generateListenerInfo(o, nodes); if (info || o._alwaysCallListener) { @@ -136,7 +138,14 @@ } }, + _getEffectiveNodes: function() { + return this.domApi.getEffectiveChildNodes() + }, + _generateListenerInfo: function(listener, newNodes) { + if (listener._avoidChangeCalculation) { + return true; + } var oldNodes = listener._nodes; var info = { target: this.node, @@ -185,11 +194,13 @@ if (!this._observer) { var self = this; this._mutationHandler = function(mxns) { - self._scheduleNotify(); + if (mxns && mxns.length) { + self._scheduleNotify(); + } }; this._observer = new MutationObserver(this._mutationHandler); - this._preflush = this._flush.bind(this); - Polymer.dom.addPreflush(this._preflush); + this._boundFlush = this._flush.bind(this); + Polymer.dom.addStaticFlush(this._boundFlush); // NOTE: subtree true is way too aggressive, but it easily catches // attribute changes on children. These changes otherwise require // attribute observers on every child. Testing has shown this @@ -205,7 +216,7 @@ this._observer.disconnect(); this._observer = null; this._mutationHandler = null; - Polymer.dom.removePreflush(this._preflush); + Polymer.dom.removeStaticFlush(this._boundFlush); baseCleanup.call(this); }, diff --git a/test/unit/polymer-dom-observeNodes.html b/test/unit/polymer-dom-observeNodes.html index 70b77b11c2..5a2df74606 100644 --- a/test/unit/polymer-dom-observeNodes.html +++ b/test/unit/polymer-dom-observeNodes.html @@ -235,6 +235,31 @@ document.body.removeChild(el); }); + test('observe children changes to distributing element that provoke additional changes', function() { + var el = document.createElement('test-content'); + document.body.appendChild(el); + + var recordedInfo, elAddedInObserver; + var observerCallCount = 0; + var handle = Polymer.dom(el).observeNodes(function(info) { + if (Polymer.dom(info.target).childNodes.length < 5) { + elAddedInObserver = document.createElement('div'); + Polymer.dom(info.target).appendChild(elAddedInObserver); + } + observerCallCount++; + recordedInfo = info; + }); + // add + var d = document.createElement('div'); + Polymer.dom(el).appendChild(d); + Polymer.dom.flush(); + assert.equal(observerCallCount, 5); + assert.equal(recordedInfo.addedNodes.length, 1); + assert.equal(recordedInfo.addedNodes[0], elAddedInObserver); + assert.equal(Polymer.dom(el).childNodes.length, 5); + document.body.removeChild(el); + }); + test('observe children changes to distributing element (async)', function(done) { var el = document.createElement('test-content'); document.body.appendChild(el); @@ -321,7 +346,7 @@ document.body.removeChild(el); }); - test('observe changes to inner node with ', function() { + test('observe changes to inner node wrapping ', function() { var el = document.createElement('test-content'); document.body.appendChild(el); var observedInfo; @@ -335,6 +360,7 @@ Polymer.dom(el).appendChild(d); Polymer.dom(el).appendChild(d1); Polymer.dom.flush(); + assert.equal(observedInfo.target, el.$.contentContainer); assert.equal(observedInfo.addedNodes.length, 2); assert.equal(observedInfo.removedNodes.length, 0); // remove @@ -359,6 +385,45 @@ document.body.removeChild(el); }); + test('observe changes to ', function() { + var el = document.createElement('test-content'); + document.body.appendChild(el); + var observedInfo; + var handle = Polymer.dom(el.$.content).observeNodes(function(info) { + observedInfo = info; + }); + Polymer.dom.flush(); + // add + var d = document.createElement('div'); + var d1 = document.createElement('div'); + Polymer.dom(el).appendChild(d); + Polymer.dom(el).appendChild(d1); + Polymer.dom.flush(); + assert.equal(observedInfo.target, el.$.content); + assert.equal(observedInfo.addedNodes.length, 2); + assert.equal(observedInfo.removedNodes.length, 0); + // remove + Polymer.dom(el).removeChild(d); + Polymer.dom(el).removeChild(d1); + Polymer.dom.flush(); + assert.equal(observedInfo.addedNodes.length, 0); + assert.equal(observedInfo.removedNodes.length, 2); + // add + Polymer.dom(el).appendChild(d); + Polymer.dom(el).appendChild(d1); + Polymer.dom.flush(); + assert.equal(observedInfo.addedNodes.length, 2); + assert.equal(observedInfo.removedNodes.length, 0); + // reset, unobserve and remove + observedInfo = null; + Polymer.dom(el.$.content).unobserveNodes(handle); + Polymer.dom(el).removeChild(d); + Polymer.dom(el).removeChild(d1); + Polymer.dom.flush(); + assert.equal(observedInfo, null); + document.body.removeChild(el); + }); + test('observe effective children inside distributing element', function() { var el = document.createElement('test-content1'); document.body.appendChild(el); @@ -446,6 +511,51 @@ document.body.removeChild(el); }); + test('observe inside deep distributing element', function() { + var el = document.createElement('test-content3'); + document.body.appendChild(el); + + var recorded; + var content = el.$.content.$.content.$.content.$.content; + assert.equal(content.localName, 'content'); + var handle = Polymer.dom(content).observeNodes(function(info) { + recorded = info; + }); + // add + var d = document.createElement('div'); + var d1 = document.createElement('div'); + Polymer.dom(el).appendChild(d); + Polymer.dom(el).appendChild(d1); + Polymer.dom.flush(); + assert.equal(recorded.addedNodes.length, 2); + assert.equal(recorded.removedNodes.length, 0); + assert.equal(recorded.addedNodes[0], d); + assert.equal(recorded.addedNodes[1], d1); + // remove + Polymer.dom(el).removeChild(d); + Polymer.dom(el).removeChild(d1); + Polymer.dom.flush(); + assert.equal(recorded.addedNodes.length, 0); + assert.equal(recorded.removedNodes.length, 2); + assert.equal(recorded.removedNodes[0], d); + assert.equal(recorded.removedNodes[1], d1); + // add + Polymer.dom(el).appendChild(d); + Polymer.dom(el).appendChild(d1); + Polymer.dom.flush(); + assert.equal(recorded.addedNodes.length, 2); + assert.equal(recorded.addedNodes[0], d); + assert.equal(recorded.addedNodes[1], d1); + // reset, unobserve and remove + recorded = null; + Polymer.dom(content).unobserveNodes(handle); + Polymer.dom(el).removeChild(d); + Polymer.dom(el).removeChild(d1); + Polymer.dom.flush(); + assert.equal(recorded, null); + document.body.removeChild(el); + }); + test('observe effective children inside deep distributing element (async)', function(done) { var el = document.createElement('test-content3'); document.body.appendChild(el);