Skip to content

Commit

Permalink
ensure distribution observers see all changes that can come from attr…
Browse files Browse the repository at this point in the history
…ibutes under native Shadow DOM; +minor factoring
  • Loading branch information
Steven Orvell committed Oct 14, 2015
1 parent 8b1face commit 344f5cc
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@
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.
Expand All @@ -49,7 +49,7 @@

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.
Expand All @@ -60,9 +60,13 @@
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;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@
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) {
Expand Down Expand Up @@ -132,7 +133,7 @@
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);
}
}
Expand Down Expand Up @@ -182,13 +183,13 @@

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) {
Expand Down Expand Up @@ -228,6 +229,10 @@

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,
Expand All @@ -240,6 +245,13 @@
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;
}
}

});
Expand Down
28 changes: 15 additions & 13 deletions src/lib/dom-api.html
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,7 @@
if (addedInsertionPoint) {
this._updateInsertionPoints(root.host);
}
if (this.observer) {
this.observer.notify();
}
this.notifyObserver();
return node;
},

Expand All @@ -142,9 +140,7 @@
nativeRemoveChild.call(container, node);
}
}
if (this.observer) {
this.observer.notify();
}
this.notifyObserver();
return node;
},

Expand Down Expand Up @@ -280,10 +276,7 @@
_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);
},
Expand Down Expand Up @@ -478,7 +471,10 @@
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);
}
Expand Down Expand Up @@ -552,8 +548,8 @@
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);
}
Expand All @@ -569,6 +565,12 @@
if (this.observer) {
this.observer.removeListener(handle);
}
},

notifyObserver: function() {
if (this.observer) {
this.observer.notify();
}
}

};
Expand Down
13 changes: 5 additions & 8 deletions src/mini/shady.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
<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() {
Expand Down Expand Up @@ -480,17 +480,14 @@
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();
}
}

Expand Down
90 changes: 90 additions & 0 deletions test/unit/polymer-dom-observeNodes.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,19 @@
});
</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>
Expand Down Expand Up @@ -467,6 +480,38 @@
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);
Expand Down Expand Up @@ -651,6 +696,51 @@
});
});

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);
Expand Down

0 comments on commit 344f5cc

Please sign in to comment.