ensure distribution observers see all changes that can come from attributes under native Shadow DOM; +minor factoring

This commit is contained in:
Steven Orvell
2015-10-14 11:50:11 -07:00
parent 8b1face967
commit 344f5cc1b2
5 changed files with 143 additions and 38 deletions

View File

@@ -16,20 +16,20 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var Settings = Polymer.Settings;
/**
* DomApi.ObserveDistributedNodes notifies when the list returned by
* DomApi.DistributedNodesObserver notifies when the list returned by
* a <content> element's `getDistributedNodes()` may have changed.
* It is not meant to be used directly; it is used by
* `Polymer.dom(node).observeNodes(callback)` to observe changes to
* `<content>.getDistributedNodes()`.
*/
DomApi.ObserveDistributedNodes = function(domApi) {
DomApi.ObserveNodes.call(this, domApi);
DomApi.DistributedNodesObserver = function(domApi) {
DomApi.EffectiveNodesObserver.call(this, domApi);
};
DomApi.ObserveDistributedNodes.prototype =
Object.create(DomApi.ObserveNodes.prototype);
DomApi.DistributedNodesObserver.prototype =
Object.create(DomApi.EffectiveNodesObserver.prototype);
Polymer.Base.extend(DomApi.ObserveDistributedNodes.prototype, {
Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, {
// NOTE: ShadyDOM distribute provokes notification of these observers
// so no setup is required.
@@ -49,7 +49,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (Settings.useShadow) {
Polymer.Base.extend(DomApi.ObserveDistributedNodes.prototype, {
Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, {
// NOTE: Under ShadowDOM we must observe the host element for
// changes.
@@ -60,9 +60,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (host) {
this._observer = Polymer.dom(host).observeNodes(
this._scheduleNotify.bind(this));
// NOTE: we identify this listener as needed for <content>
// notification so that enableShadowAttributeTracking
// can find these observers an ensure that we pass always
// pass notifications down.
this._observer._isContentListener = true;
if (this._hasAttrSelect()) {
Polymer.dom(host).observer.enableShadowAttributeTracking();
this._observer._alwaysCallListener = true;
}
}
}

View File

@@ -17,18 +17,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var hasDomApi = Polymer.DomApi.hasDomApi;
/**
* DomApi.ObserveNodes tracks changes to an element's effective child nodes,
* the same list returned from `Polymer.dom(node).getEffectiveChildNodes()`.
* DomApi.EffectiveNodesObserver tracks changes to an element's
* effective child nodes, the same list returned from
* `Polymer.dom(node).getEffectiveChildNodes()`.
* It is not meant to be used directly; it is used by
* `Polymer.dom(node).observeNodes(callback)` to observe changes.
*/
DomApi.ObserveNodes = function(domApi) {
DomApi.EffectiveNodesObserver = function(domApi) {
this.domApi = domApi;
this.node = this.domApi.node;
this._listeners = [];
};
DomApi.ObserveNodes.prototype = {
DomApi.EffectiveNodesObserver.prototype = {
addListener: function(callback) {
if (!this._isSetup) {
@@ -132,7 +133,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
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) {
if (info || o._alwaysNotify) {
this._callListener(o, info);
}
}
@@ -182,13 +183,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (Settings.useShadow) {
var baseSetup = DomApi.ObserveNodes.prototype._setup;
var baseCleanup = DomApi.ObserveNodes.prototype._cleanup;
var baseSetup = DomApi.EffectiveNodesObserver.prototype._setup;
var baseCleanup = DomApi.EffectiveNodesObserver.prototype._cleanup;
var beforeCallListeners = DomApi.ObserveNodes
var beforeCallListeners = DomApi.EffectiveNodesObserver
.prototype._beforeCallListeners;
Polymer.Base.extend(DomApi.ObserveNodes.prototype, {
Polymer.Base.extend(DomApi.EffectiveNodesObserver.prototype, {
_setup: function() {
if (!this._observer) {
@@ -228,6 +229,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
enableShadowAttributeTracking: function() {
if (this._observer) {
// provoke all listeners needed for <content> observation
// to always call listeners when no-op changes occur (which may
// affect lower distributions.
this._makeContentListenersAlwaysNotify();
this._observer.disconnect();
this._observer.observe(this.node, {
childList: true,
@@ -240,6 +245,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom(host).observer.enableShadowAttributeTracking();
}
}
},
_makeContentListenersAlwaysNotify: function() {
for (var i=0, h; i < this._listeners.length ; i++) {
h = this._listeners[i];
h._alwaysNotify = h._isContentListener;
}
}
});

View File

@@ -116,9 +116,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (addedInsertionPoint) {
this._updateInsertionPoints(root.host);
}
if (this.observer) {
this.observer.notify();
}
this.notifyObserver();
return node;
},
@@ -142,9 +140,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
nativeRemoveChild.call(container, node);
}
}
if (this.observer) {
this.observer.notify();
}
this.notifyObserver();
return node;
},
@@ -280,10 +276,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
_removeNodeFromParent: function(node) {
var parent = node._lightParent;
if (parent && hasDomApi(parent)) {
var d = factory(parent);
if (d._observer) {
d._observer.removeNode(node);
}
factory(parent).notifyObserver();
}
this._removeNodeFromHost(node, true);
},
@@ -478,7 +471,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
var c$ = this.childNodes;
for (var i=0, l=c$.length, c; (i<l) && (c=c$[i]); i++) {
if (c.localName === CONTENT) {
list = list.concat(factory(c).getDistributedNodes().slice());
var d$ = factory(c).getDistributedNodes();
for (var j=0; j < d$.length; j++) {
list.push(d$[j]);
}
} else {
list.push(c);
}
@@ -552,8 +548,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (callback) {
if (!this.observer) {
this.observer = this.node.localName === CONTENT ?
new DomApi.ObserveDistributedNodes(this) :
new DomApi.ObserveNodes(this);
new DomApi.DistributedNodesObserver(this) :
new DomApi.EffectiveNodesObserver(this);
}
return this.observer.addListener(callback);
}
@@ -569,6 +565,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
if (this.observer) {
this.observer.removeListener(handle);
}
},
notifyObserver: function() {
if (this.observer) {
this.observer.notify();
}
}
};

View File

@@ -12,8 +12,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
<link rel="import" href="../lib/dom-api-flush.html">
<link rel="import" href="../lib/dom-api-event.html">
<link rel="import" href="../lib/dom-api-classlist.html">
<link rel="import" href="../lib/dom-api-observe-nodes.html">
<link rel="import" href="../lib/dom-api-observe-distributed-nodes.html">
<link rel="import" href="../lib/dom-api-effective-nodes-observer.html">
<link rel="import" href="../lib/dom-api-distributed-nodes-observer.html">
<script>
(function() {
@@ -480,17 +480,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
for (var i=0, c; i < root._insertionPoints.length; i++) {
c = root._insertionPoints[i];
if (hasDomApi(c)) {
var dc = Polymer.dom(c);
if (dc.observer) {
dc.observer.notify();
}
Polymer.dom(c).notifyObserver();
}
}
}
function notifyInitialDistribution(host) {
if (hasDomApi(host) && Polymer.dom(host).observer) {
Polymer.dom(host).observer.notify();
if (hasDomApi(host)) {
Polymer.dom(host).notifyObserver();
}
}

View File

@@ -146,6 +146,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
</script>
</dom-module>
<dom-module id='test-content-attr-inside'>
<template>
<test-content-attr3 id="content"><content></content></test-content-attr3>
</template>
<script>
HTMLImports.whenReady(function() {
Polymer({
is:'test-content-attr-inside'
});
});
</script>
</dom-module>
<test-content><div>A</div><div>B</div></test-content>
@@ -467,6 +480,38 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
document.body.removeChild(el);
});
test('observe effective children changes when adding to another host', function() {
var el = document.createElement('test-content1');
document.body.appendChild(el);
var recorded;
var handle = Polymer.dom(el.$.content).observeNodes(function(info) {
recorded = 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(recorded.addedNodes.length, 2);
assert.equal(recorded.removedNodes.length, 0);
assert.equal(recorded.addedNodes[0], d);
assert.equal(recorded.addedNodes[1], d1);
// add somewhere else... we should see these as removes
Polymer.dom(document.body).appendChild(d);
Polymer.dom(document.body).appendChild(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);
// cleanup
Polymer.dom(document.body).removeChild(d);
Polymer.dom(document.body).removeChild(d1);
document.body.removeChild(el);
});
test('observe effective children inside deep distributing element', function() {
var el = document.createElement('test-content3');
document.body.appendChild(el);
@@ -651,6 +696,51 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
});
test('observe effective children attr changes inside deep distributing element without outer select (async)', function(done) {
var el = document.createElement('test-content-attr-inside');
document.body.appendChild(el);
var recorded;
var content = el.$.content.$.content.$.content.$.content;
var handle = Polymer.dom(content).observeNodes(function(info) {
recorded = info;
}, {attributes: true});
Polymer.dom.flush();
recorded = null;
// 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, null);
Polymer.dom(d).setAttribute('a', '');
Polymer.dom(d).setAttribute('b', '');
setTimeout(function() {
assert.equal(recorded, null);
Polymer.dom(d).setAttribute('c', '');
setTimeout(function() {
assert.equal(recorded.addedNodes.length, 1);
assert.equal(recorded.removedNodes.length, 0);
assert.equal(recorded.addedNodes[0], d);
Polymer.dom(d).removeAttribute('c');
setTimeout(function() {
assert.equal(recorded.addedNodes.length, 0);
assert.equal(recorded.removedNodes.length, 1);
assert.equal(recorded.removedNodes[0], d);
recorded = null;
Polymer.dom(content).unobserveNodes(handle);
Polymer.dom(d).setAttribute('c', '');
setTimeout(function() {
assert.equal(recorded, null);
document.body.removeChild(el);
done();
});
});
});
});
});
test('add/remove multiple observers', function() {
var el = document.createElement('test-content1');
document.body.appendChild(el);