diff --git a/src/lib/dom-api-mutation-content.html b/src/lib/dom-api-mutation-content.html index 5683c4b52e..220e1ff2b7 100644 --- a/src/lib/dom-api-mutation-content.html +++ b/src/lib/dom-api-mutation-content.html @@ -23,54 +23,18 @@ Polymer.Base.extend(DomApi.MutationContent.prototype, { - addListener: function(callback, options) { - var h = DomApi.Mutation.prototype.addListener.call(this, callback, - options); - this._scheduleNotify(); - return h; - }, + // NOTE: ShadyDOM distribute provokes notification of these observers + // so no setup is required. + _setup: function() {}, - _ensureSetup: function(options) {}, + _cleanup: function() {}, - notify: function() { - if (this._hasListeners()) { - this._scheduleNotify(); - } - }, - - _notify: function() { - var info = this._suspendChangeInfo ? {} : this._calcChanges(); - if (info) { - info.target = this.node; - this._callListeners(info); - } - }, + // no need to update sub-elements since does not nest + // (but note that will) + _beforeCallListeners: function() {}, - _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= 0) { + this._listeners.splice(i, 1); + handle._nodes = []; + } if (!this._hasListeners()) { - this._ensureCleanup(); + this._cleanup(); + this._isSetup = false; } }, - _ensureSetup: function(options) { - if (!this._isSetup) { - this._isSetup = true; - this._observeContentElements(this.domApi.childNodes); - } + _setup: function() { + this._observeContentElements(this.domApi.childNodes); }, - _ensureCleanup: function() {}, // abstract + _cleanup: function() { + this._unobserveContentElements(this.domApi.childNodes); + }, _hasListeners: function() { return Boolean(this._listeners.length); }, - addNode: function(node) { - if (this._hasListeners()) { - this._addedNodes.push(node); - this._scheduleNotify(); + _scheduleNotify: function() { + if (this._debouncer) { + this._debouncer.stop(); } + this._debouncer = Polymer.Debounce(this._debouncer, + this._notify); + this._debouncer.context = this; + Polymer.dom.addDebouncer(this._debouncer); }, - removeNode: function(node) { + notify: function() { if (this._hasListeners()) { - this._removedNodes.push(node); this._scheduleNotify(); } }, - addAllNodes: function(force) { - if (this._hasListeners() || force) { - var c$ = this.domApi.childNodes; - if (c$.length) { - for (var i=0, c; (i < c$.length) && (c=c$[i]); i++) { - this._addedNodes.push(c); - } - this._scheduleNotify(); - } - } - }, - - _scheduleNotify: function() { - this._debouncer = Polymer.Debounce(this._debouncer, - this._notify); - this._debouncer.context = this; - Polymer.dom.addDebouncer(this._debouncer); - }, - _notify: function(mxns) { - var info = mxns || { - target: this.node, - addedNodes: this._addedNodes, - removedNodes: this._removedNodes - } - this._updateContentElements(info); - this._callListeners(info); - this._addedNodes = []; - this._removedNodes = []; + this._beforeCallListeners(); + this._callListeners(); }, - _notifyInitial: function(listener) { - var info = { - target: this.node, - addedNodes: this.domApi.childNodes, - removedNodes: [] - }; - this._callListener(listener, info); + _beforeCallListeners: function() { + this._updateContentElements(); }, - _updateContentElements: function(info) { - this._observeContentElements(info.addedNodes); - this._unobserveContentElements(info.removedNodes); + _updateContentElements: function() { + this._observeContentElements(this.domApi.childNodes); }, _observeContentElements: function(elements) { - for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) { + for (var i=0, n; (i < elements.length) && (n=elements[i]); i++) { if (this._isContent(n)) { n.__observeNodesMap = n.__observeNodesMap || new WeakMap(); - if (n.__observeNodesMap.get(this) == null) { - h = this._observeContent(n); - n.__observeNodesMap.set(this, h); + if (!n.__observeNodesMap.has(this)) { + n.__observeNodesMap.set(this, this._observeContent(n)); } } } }, _observeContent: function(content) { - return Polymer.dom(content).observeNodes(this._notify.bind(this)); + return Polymer.dom(content).observeNodes(this._scheduleNotify.bind(this)); }, _unobserveContentElements: function(elements) { @@ -134,8 +108,8 @@ if (this._isContent(n)) { h = n.__observeNodesMap.get(this); if (h) { - n.__observeNodesMap.set(this, null); Polymer.dom(n).unobserveNodes(h); + n.__observeNodesMap.delete(this); } } } @@ -145,15 +119,44 @@ return (node.localName === 'content'); }, - _callListeners: function(info, filter) { + _callListeners: function() { var o$ = this._listeners; + var nodes = this.domApi.getEffectiveChildNodes(); for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) { - if (!filter || filter(o.options)) { + var info = this._generateListenerInfo(o, nodes); + if (info || o._alwaysCallListener) { this._callListener(o, info); } } }, + _generateListenerInfo: function(listener, newNodes) { + var oldNodes = listener._nodes; + var info = { + target: this.node, + addedNodes: [], + removedNodes: [] + }; + var splices = Polymer.ArraySplice.calculateSplices(newNodes, oldNodes); + // process removals + for (var i=0, s; (i 1x is a no-op - this._observer.observe(this.node, {childList: true}); + // 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 + // approach to be more efficient. + // TODO(sorvell): do we still need to include an option to defeat + // attribute tracking? + this._observer.observe(this.node, { + childList: true, + attributes: true, + subtree: true + }); } - ensureSetup.call(this, options); + baseSetup.call(this); }, - _ensureCleanup: function() { + _cleanup: function() { this._observer.disconnect(); + this._observer = null; + this._mutationHandler = null; Polymer.dom.removePreflush(this._preflush); - }, - - _notify: function(mxns) { - var info = { - target: this.node, - addedNodes: [], - removedNodes: [] - }; - // 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]); - } - } - }); - } 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); - } + baseCleanup.call(this); }, _flush: function() { if (this._observer) { - this._notify(this._observer.takeRecords()); - } - }, - - _observeContent: function(content) { - return Polymer.dom(content).observeNodes(this._notify.bind(this), { - 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.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); - } + this._mutationHandler(this._observer.takeRecords()); } } diff --git a/src/lib/dom-api.html b/src/lib/dom-api.html index faa8d4ffa7..9d4067eb4f 100644 --- a/src/lib/dom-api.html +++ b/src/lib/dom-api.html @@ -111,7 +111,7 @@ this._updateInsertionPoints(root.host); } if (this.observer) { - this.observer.addNode(node); + this.observer.notify(); } return node; }, @@ -137,7 +137,7 @@ } } if (this.observer) { - this.observer.removeNode(node); + this.observer.notify(); } return node; }, @@ -526,23 +526,20 @@ return n; }, - observeNodes: function(callback, options) { + 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); + return this.observer.addListener(callback); }, unobserveNodes: function(handle) { if (this.observer) { this.observer.removeListener(handle); } - }, - - // abstract, intended as public 'kick' mehanism - notifyObservers: function() {} + } }; @@ -752,25 +749,6 @@ DomApi.prototype._distributeParent = function() {}; - DomApi.prototype.notifyObservers = function() { - if (this.node.shadowRoot) { - var ip$ = this.node.shadowRoot.querySelectorAll('content'); - for (var i=0, c; (i + + + + +