|
26 | 26 |
|
27 | 27 | DomApi.Mutation.prototype = {
|
28 | 28 |
|
29 |
| - addListener: function(callback) { |
30 |
| - this._ensureObserver(); |
31 |
| - return this._listeners.push(callback); |
| 29 | + addListener: function(callback, options) { |
| 30 | + this._ensureSetup(options); |
| 31 | + return this._listeners.push({fn: callback, options: options}); |
32 | 32 | },
|
33 | 33 |
|
34 | 34 | removeListener: function(handle) {
|
35 | 35 | this._listeners.splice(handle - 1, 1);
|
36 | 36 | if (!this._hasListeners()) {
|
37 |
| - this._cleanupObserver(); |
| 37 | + this._ensureCleanup(); |
38 | 38 | }
|
39 | 39 | },
|
40 | 40 |
|
41 |
| - _ensureObserver: function() { |
| 41 | + _ensureSetup: function(options) { |
42 | 42 | this._observeContentElements(this.domApi.childNodes);
|
43 | 43 | },
|
44 | 44 |
|
45 |
| - // TODO(sorvell): unobserver content? |
46 |
| - _cleanupObserver: function() {}, |
| 45 | + _ensureCleanup: function() {}, // abstract |
47 | 46 |
|
48 | 47 | _hasListeners: function() {
|
49 | 48 | return Boolean(this._listeners.length);
|
|
83 | 82 | },
|
84 | 83 |
|
85 | 84 | _notify: function(mxns) {
|
86 |
| - var info = { |
| 85 | + var info = mxns || { |
87 | 86 | target: this.node,
|
88 | 87 | addedNodes: this._addedNodes,
|
89 | 88 | removedNodes: this._removedNodes
|
|
103 | 102 | for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) {
|
104 | 103 | if (this._isContent(n)) {
|
105 | 104 | n.__observeNodesMap = n.__observeNodesMap || new WeakMap();
|
106 |
| - if (n.__observeNodesMap.get(this) === undefined) { |
107 |
| - h = Polymer.dom(n).observeNodes( |
108 |
| - this._callListeners.bind(this), true); |
| 105 | + if (n.__observeNodesMap.get(this) == null) { |
| 106 | + h = this._observeContent(n); |
109 | 107 | n.__observeNodesMap.set(this, h);
|
110 | 108 | }
|
111 | 109 | }
|
112 | 110 | }
|
113 | 111 | },
|
114 | 112 |
|
| 113 | + _observeContent: function(content) { |
| 114 | + return Polymer.dom(content).observeNodes(this._notify.bind(this), { |
| 115 | + changes: true |
| 116 | + }); |
| 117 | + }, |
| 118 | + |
115 | 119 | _unobserveContentElements: function(elements) {
|
116 | 120 | for (var i=0, n, h; (i < elements.length) && (n=elements[i]); i++) {
|
117 | 121 | if (this._isContent(n)) {
|
118 | 122 | h = n.__observeNodesMap.get(this);
|
119 | 123 | if (h) {
|
| 124 | + n.__observeNodesMap.set(this, null); |
120 | 125 | Polymer.dom(n).unobserveNodes(h);
|
121 | 126 | }
|
122 | 127 | }
|
|
127 | 132 | return (node.localName === 'content');
|
128 | 133 | },
|
129 | 134 |
|
130 |
| - _callListeners: function(info) { |
| 135 | + _callListeners: function(info, filter) { |
131 | 136 | var o$ = this._listeners;
|
132 | 137 | for (var i=0, o; (i < o$.length) && (o=o$[i]); i++) {
|
133 |
| - o.call(this.node, info); |
| 138 | + if (!filter || filter(o.options)) { |
| 139 | + o.fn.call(this.node, info); |
| 140 | + } |
134 | 141 | }
|
135 | 142 | }
|
136 | 143 |
|
|
140 | 147 |
|
141 | 148 | Polymer.Base.extend(DomApi.Mutation.prototype, {
|
142 | 149 |
|
143 |
| - _ensureObserver: function() { |
| 150 | + _ensureSetup: function(options) { |
| 151 | + this._trackAttributes = this._trackAttributes || |
| 152 | + options && options.attributes; |
144 | 153 | if (!this._observer) {
|
145 | 154 | this._observer =
|
146 | 155 | new MutationObserver(this._notify.bind(this));
|
|
165 | 174 | }
|
166 | 175 | },
|
167 | 176 |
|
168 |
| - _cleanupObserver: function() { |
| 177 | + _ensureCleanup: function() { |
169 | 178 | this._observer.disconnect();
|
170 | 179 | Polymer.dom.removePreflush(this._preflush);
|
171 | 180 | },
|
|
176 | 185 | addedNodes: [],
|
177 | 186 | removedNodes: []
|
178 | 187 | };
|
179 |
| - mxns.forEach(function(m) { |
180 |
| - if (m.addedNodes) { |
181 |
| - for (var i=0; i < m.addedNodes.length; i++) { |
182 |
| - info.addedNodes.push(m.addedNodes[i]); |
| 188 | + // collapse multiple mutations into one view |
| 189 | + if (Array.isArray(mxns)) { |
| 190 | + Array.prototype.forEach.call(mxns, function(m) { |
| 191 | + if (m.addedNodes) { |
| 192 | + for (var i=0; i < m.addedNodes.length; i++) { |
| 193 | + info.addedNodes.push(m.addedNodes[i]); |
| 194 | + } |
183 | 195 | }
|
184 |
| - } |
185 |
| - if (m.removedNodes) { |
186 |
| - for (var i=0; i < m.removedNodes.length; i++) { |
187 |
| - info.removedNodes.push(m.removedNodes[i]); |
| 196 | + if (m.removedNodes) { |
| 197 | + for (var i=0; i < m.removedNodes.length; i++) { |
| 198 | + info.removedNodes.push(m.removedNodes[i]); |
| 199 | + } |
188 | 200 | }
|
189 |
| - } |
190 |
| - }); |
| 201 | + }); |
| 202 | + } else if (mxns) { |
| 203 | + info = mxns; |
| 204 | + } |
191 | 205 | if (info.addedNodes.length || info.removedNodes.length) {
|
192 | 206 | this._updateContentElements(info);
|
| 207 | + // attribute tracking helps us notice distributed nodes changes |
| 208 | + // needed only with shadow dom. |
| 209 | + if (this._trackAttributes) { |
| 210 | + this._updateAttributeObservers(info); |
| 211 | + } |
193 | 212 | this._callListeners(info);
|
194 | 213 | }
|
195 | 214 | },
|
|
198 | 217 | if (this._observer) {
|
199 | 218 | this._notify(this._observer.takeRecords());
|
200 | 219 | }
|
| 220 | + }, |
| 221 | + |
| 222 | + _observeContent: function(content) { |
| 223 | + return Polymer.dom(content).observeNodes(this._notify.bind(this), { |
| 224 | + changes: true, |
| 225 | + attributes: this._trackAttributes |
| 226 | + }); |
| 227 | + }, |
| 228 | + |
| 229 | + _updateAttributeObservers: function(info) { |
| 230 | + this._observeAttributes(info.addedNodes); |
| 231 | + this._unobserveAttributes(info.removedNodes); |
| 232 | + }, |
| 233 | + |
| 234 | + _observeAttributes: function(elements) { |
| 235 | + for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) { |
| 236 | + if (n.nodeType === Node.ELEMENT_NODE) { |
| 237 | + this._ensureElementAttrObserver(n); |
| 238 | + } |
| 239 | + } |
| 240 | + }, |
| 241 | + |
| 242 | + _ensureElementAttrObserver: function(element) { |
| 243 | + this._attrObservers = this._attrObservers || new WeakMap(); |
| 244 | + if (!this._attrObservers.get(element)) { |
| 245 | + var self = this; |
| 246 | + this._attrObservers.set(element, new MutationObserver( |
| 247 | + function(mxns) { |
| 248 | + self._callListeners(mxns, function(options) { |
| 249 | + return Boolean(options && options.attributes); |
| 250 | + }); |
| 251 | + } |
| 252 | + )); |
| 253 | + this._attrObservers.get(element) |
| 254 | + .observe(element, {attributes: true}); |
| 255 | + } |
| 256 | + }, |
| 257 | + |
| 258 | + _unobserveAttributes: function(elements) { |
| 259 | + for (var i=0, h, n; (i < elements.length) && (n=elements[i]); i++) { |
| 260 | + if (n.nodeType === Node.ELEMENT_NODE) { |
| 261 | + h = this._attrObservers.get(n); |
| 262 | + if (h) { |
| 263 | + h.disconnect(n); |
| 264 | + } |
| 265 | + this._attrObservers.set(n, null); |
| 266 | + } |
| 267 | + } |
201 | 268 | }
|
202 | 269 |
|
203 | 270 | });
|
|
0 commit comments