Skip to content

Commit

Permalink
* Improve clarity: change __dataInitialized to __dataReady
Browse files Browse the repository at this point in the history
* When `_flushClients` is called, ensure that clients are always enabled or flushed as appropriate. This ensures that (1) clients that are enabled before the host is enabled flush properly, and (2) clients that are stamped but not enabled properly enable when the host flushes.
  • Loading branch information
Steven Orvell committed May 23, 2017
1 parent 06df53d commit 8e8692f
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 32 deletions.
6 changes: 3 additions & 3 deletions lib/mixins/property-accessors.html
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
this.__serializing = false;
this.__dataCounter = 0;
this.__dataEnabled = false;
this.__dataInitialized = false;
this.__dataReady = false;
this.__dataInvalid = false;
// initialize data with prototype values saved when creating accessors
this.__data = {};
Expand Down Expand Up @@ -465,7 +465,7 @@
* @protected
*/
_invalidateProperties() {
if (!this.__dataInvalid && this.__dataInitialized) {
if (!this.__dataInvalid && this.__dataReady) {
this.__dataInvalid = true;
microtask.run(() => {
if (this.__dataInvalid) {
Expand Down Expand Up @@ -529,7 +529,7 @@
* @public
*/
ready() {
this.__dataInitialized = true;
this.__dataReady = true;
// Run normal flush
this._flushProperties();
}
Expand Down
50 changes: 22 additions & 28 deletions lib/mixins/property-effects.html
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,7 @@
_initializeProperties() {
super._initializeProperties();
hostStack.registerHost(this);
this.__dataClientsInitialized = false;
this.__dataClientsReady = false;
this.__dataPendingClients = null;
this.__dataToNotify = null;
this.__dataLinkedPaths = null;
Expand Down Expand Up @@ -1395,14 +1395,14 @@

/**
* Overrides `PropertyAccessor`'s default async queuing of
* `_propertiesChanged`: if `__dataInitialized` is false (has not yet been
* `_propertiesChanged`: if `__dataReady` is false (has not yet been
* manually flushed), the function no-ops; otherwise flushes
* `_propertiesChanged` synchronously.
*
* @override
*/
_invalidateProperties() {
if (this.__dataInitialized) {
if (this.__dataReady) {
this._flushProperties();
}
}
Expand All @@ -1429,50 +1429,44 @@
* @protected
*/
_flushClients() {
if (!this.__dataClientsInitialized) {
this.__dataClientsInitialized = true;
if (!this.__dataClientsReady) {
this.__dataClientsReady = true;
this._readyClients();
// Override point where accessors are turned on; importantly,
// this is after clients have fully readied, providing a guarantee
// that any property effects occur only after all clients are ready.
this.__dataInitialized = true;
this.__dataReady = true;
} else {
// Flush all clients
let clients = this.__dataPendingClients;
if (clients) {
this.__dataPendingClients = null;
for (let i=0; i < clients.length; i++) {
let client = clients[i];
if (client.__dataPending) {
client._flushProperties();
}
}
}
this.__enableOrFlushClients();
}
}

/**
* Perform any initial setup on client dom. Called before the first
* `_flushProperties` call on client dom and before any element
* observers are called.
*
* @protected
*/
_readyClients() {
__enableOrFlushClients() {
let clients = this.__dataPendingClients;
if (clients) {
this.__dataPendingClients = null;
for (let i=0; i < clients.length; i++) {
let client = clients[i];
if (!client.__dataEnabled) {
client._enableProperties();
} else {
} else if (client.__dataPending) {
client._flushProperties();
}
}
}
}

/**
* Perform any initial setup on client dom. Called before the first
* `_flushProperties` call on client dom and before any element
* observers are called.
*
* @protected
*/
_readyClients() {
this.__enableOrFlushClients();
}

/**
* Sets a bag of property changes to this instance, and
* synchronously processes all effects of the properties as a batch.
Expand Down Expand Up @@ -1516,7 +1510,7 @@
this._flushProperties();
// If no data was pending, `_flushProperties` will not `flushClients`
// so ensure this is done.
if (!this.__dataClientsInitialized) {
if (!this.__dataClientsReady) {
this._flushClients();
}
// Before ready, client notifications do not trigger _flushProperties.
Expand Down Expand Up @@ -2247,7 +2241,7 @@
// Setup compound storage, 2-way listeners, and dataHost for bindings
setupBindings(this, templateInfo);
// Flush properties into template nodes if already booted
if (this.__dataInitialized) {
if (this.__dataReady) {
runEffects(this, templateInfo.propertyEffects, this.__data, null,
false, templateInfo.nodeList);
}
Expand Down
92 changes: 91 additions & 1 deletion test/unit/property-effects-template.html
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
</template>
<!-- Nested template in Shadow DOM for runtime stamping -->
<template id="templateFromShadowDom">
<x-element id="first"></x-element>
<x-element early="[[earlyProp]]" id="first"></x-element>
<x-element id="events" on-click="handleClick" on-tap="handleTap"></x-element>
<template id="domIf" is="dom-if" if="[[prop]]">
<x-element id="ifElementSD" prop="{{prop}}" path="{{obj.path}}"></x-element>
Expand Down Expand Up @@ -131,6 +131,12 @@
this.shadowRoot.appendChild(dom);
return dom;
}
stampTemplateAndSetPropFromShadow() {
let dom = this._stampTemplate(this.$.templateFromShadowDom);
this.earlyProp = 'early';
this.shadowRoot.appendChild(dom);
return dom;
}
stampTemplateFromLight() {
let dom = this._stampTemplate(Polymer.DomModule.import(this.localName, '#templateFromLightDom'));
this.shadowRoot.appendChild(dom);
Expand Down Expand Up @@ -430,6 +436,90 @@
assert.equal(stamped[0], el.$.first);
});

test('runtime stamp template and set prop before attaching (from shadow dom)', () => {
let dom = el.stampTemplateAndSetPropFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom.$, 'SD');
let stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom.$.first);
assert.deepEqual(Polymer.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime',
'x-element#shadow|x-element-child#noBinding',
'x-element#shadow|x-element-child#hasBinding',
'x-element#shadow'
]);
});

test('runtime stamp and remove multiple templates and set prop before attaching (from shadow dom)', () => {
let stamped;
// Stamp template
let dom1 = el.stampTemplateAndSetPropFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom1.$, 'SD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom1.$.first);
// Unstamp
el._removeBoundDom(dom1);
for (let n in dom1.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
assert.equal(stamped[0], el.$.first);
// Stamp again
let dom2 = el.stampTemplateAndSetPropFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom2.$, 'SD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 2);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom2.$.first);
// Stamp again
let dom3 = el.stampTemplateAndSetPropFromShadow();
assertStampingCorrect(el, el.$);
assertStampingCorrect(el, dom2.$, 'SD');
assertStampingCorrect(el, dom3.$, 'SD');
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 3);
assert.equal(stamped[0], el.$.first);
assert.equal(stamped[1], dom2.$.first);
assert.equal(stamped[2], dom3.$.first);
assert.deepEqual(Polymer.lifecycleOrder.ready, [
'x-runtime|x-element#proto|x-element-child#noBinding',
'x-runtime|x-element#proto|x-element-child#hasBinding',
'x-runtime|x-element#proto',
'x-runtime',
'x-element#shadow|x-element-child#noBinding',
'x-element#shadow|x-element-child#hasBinding',
'x-element#shadow',
'x-runtime|x-element#shadow|x-element-child#noBinding',
'x-runtime|x-element#shadow|x-element-child#hasBinding',
'x-runtime|x-element#shadow',
'x-runtime|x-element#shadow|x-element-child#noBinding',
'x-runtime|x-element#shadow|x-element-child#hasBinding',
'x-runtime|x-element#shadow'
]);
// Unstamp
el._removeBoundDom(dom2);
el._removeBoundDom(dom3);
for (let n in dom2.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
for (let n in dom3.$) {
assert.notOk(dom1.$[n].parentNode, null);
}
stamped = el.shadowRoot.querySelectorAll('x-element#first');
assert.equal(stamped.length, 1);
assert.equal(stamped[0], el.$.first);
});

test('runtime stamp template (from light dom)', () => {
let dom = el.stampTemplateFromLight();
assertStampingCorrect(el, el.$);
Expand Down

0 comments on commit 8e8692f

Please sign in to comment.