Skip to content

Commit

Permalink
* ensure element cannot return to “disabled” state after upgrading.
Browse files Browse the repository at this point in the history
* ensure nested `beforeNextRender` calls always go before the next render
* ensure nested `afterNextRender` are called after additional renders
  • Loading branch information
Steven Orvell committed Mar 17, 2017
1 parent 50ae3bb commit e9c58ad
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 63 deletions.
22 changes: 12 additions & 10 deletions lib/mixins/element-mixin.html
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,8 @@
proto._bindTemplate(template, propertiesForClass(proto.constructor));
}

function flushPropertiesStub() {}

/**
* @polymerMixinClass
* @unrestricted
Expand Down Expand Up @@ -547,7 +549,6 @@
*/
_initializeProperties() {
Polymer.telemetry.instanceCount++;
this.__upgradeFlush = null;
this.constructor.finalize();
const importPath = this.constructor.importPath;
// note: finalize template when we have access to `localName` to
Expand Down Expand Up @@ -678,16 +679,17 @@
* @override
*/
attributeChangedCallback(name, old, value) {
// process `disable-upgrade` specially
if (name === DISABLED) {
const disabled = value !== null;
if (this.__upgradeFlush && !disabled) {
this._flushProperties = this.__upgradeFlush;
this.__upgradeFlush = null;
// process `disable-upgrade` specially:
// First we see `disable-upgrade` added and disable `_flushProperties`,
// then when it's removed, restore regular flushing and flush.
// This should only be allowed before "readying".
if (name === DISABLED && !this.__dataInitialized) {
if (value !== null) {
this.__flushProperties = this._flushProperties;
this._flushProperties = flushPropertiesStub;
} else {
this._flushProperties = this.__flushProperties;
this._flushProperties();
} else if (disabled) {
this.__upgradeFlush = this._flushProperties;
this._flushProperties = function() {};
}
} else if (old !== value) {
let property = caseMap.dashToCamelCase(name);
Expand Down
39 changes: 23 additions & 16 deletions lib/utils/render-status.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,33 @@
flushQueue(beforeRenderQueue);
// after the render
setTimeout(function() {
flushQueue(afterRenderQueue);
runQueue(afterRenderQueue);
});
});
}

function flushQueue(queue) {
const max = queue.length;
let i=0;
while (queue.length && i < max) {
i++;
const q = queue.shift();
const context = q[0];
const callback = q[1];
const args = q[2];
try {
callback.apply(context, args);
} catch(e) {
setTimeout(() => {
throw e;
})
}
while (queue.length) {
callMethod(queue.shift());
}
}

function runQueue(queue) {
for (let i=0, l=queue.length; i < l; i++) {
callMethod(queue.shift());
}
}

function callMethod(info) {
const context = info[0];
const callback = info[1];
const args = info[2];
try {
callback.apply(context, args);
} catch(e) {
setTimeout(() => {
throw e;
})
}
}

Expand All @@ -55,6 +61,7 @@
flushQueue(beforeRenderQueue);
flushQueue(afterRenderQueue);
}
scheduled = false;
}

/**
Expand Down
98 changes: 61 additions & 37 deletions test/unit/render-status.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,47 @@
class XFoo extends Polymer.LegacyElementMixin(HTMLElement) {
ready() {
super.ready();
sinon.spy(this, 'beforeNextRender');
sinon.spy(this, 'stillBeforeNextRender');
Polymer.RenderStatus.beforeNextRender(this, this.beforeNextRender,
['before']);
}

beforeNextRender() {
Polymer.RenderStatus.beforeNextRender(this, this.stillBeforeNextRender,
['still-before']);
}
stillBeforeNextRender() {
if (this.beforeDone) {
this.beforeDone();
}
}
}
customElements.define('x-foo', XFoo);

class XBar extends Polymer.LegacyElementMixin(HTMLElement) {
ready() {
super.ready();
sinon.spy(this, 'afterNextRender');
sinon.spy(this, 'afterAfterNextRender');
Polymer.RenderStatus.afterNextRender(this, (a) => {
this.afterNextRender(a);
}, ['after']);
}

beforeNextRender() {}
afterNextRender() {}
afterNextRender() {
Polymer.RenderStatus.afterNextRender(this, (a) => {
this.afterAfterNextRender(a);
}, ['after-after']);
}
afterAfterNextRender() {
if (this.afterDone) {
this.afterDone();
}
}

}
customElements.define('x-foo', XFoo);
customElements.define('x-bar', XBar);
});
</script>

Expand All @@ -42,51 +71,46 @@

test('beforeNextRender', function(done) {
let el = document.createElement('x-foo');
let beforeCalled;
el.beforeNextRender = function(a) {
assert.equal(a, 'before', 'before render argument incorrect');
beforeCalled = true;
}
document.body.appendChild(el);
requestAnimationFrame(() => {
setTimeout(() => {
assert.isTrue(beforeCalled);
done();
});
});
el.beforeDone = function() {
assert.ok(el.beforeNextRender.withArgs('before').calledOnce);
assert.ok(el.stillBeforeNextRender.withArgs('still-before').calledOnce);
// break out of this raf so next test is not tainted.
requestAnimationFrame(() => { done() });
};
});

test('afterNextRender', function(done) {
let el = document.createElement('x-foo');
let afterCalled;
let raf;
let el = document.createElement('x-bar');
let raf1 = sinon.spy();
let raf2 = sinon.spy();
requestAnimationFrame(() => {
assert.notOk(afterCalled);
raf = true;
})
el.afterNextRender = function(a) {
assert.equal(a, 'after', 'after render argument incorrect');
afterCalled = true;
assert.ok(raf);
done();
raf1();
requestAnimationFrame(() => {
raf2();
});
});
el.afterDone = function() {
assert.ok(el.afterNextRender.withArgs('after').calledOnce);
assert.ok(el.afterNextRender.calledAfter(raf1));
assert.ok(el.afterAfterNextRender.withArgs('after-after').calledOnce);
assert.ok(el.afterAfterNextRender.calledAfter(raf2));
// break out of this raf so next test is not tainted.
requestAnimationFrame(() => { done() });
}
document.body.appendChild(el);
});

test('flush', function() {
let el = document.createElement('x-foo');
let beforeCalled;
el.beforeNextRender = function() {
beforeCalled = true;
}
let afterCalled;
el.afterNextRender = function() {
afterCalled = true;
}
document.body.appendChild(el);
let el1 = document.createElement('x-foo');
let el2 = document.createElement('x-bar');
document.body.appendChild(el1);
document.body.appendChild(el2);
Polymer.RenderStatus.flush();
assert.isTrue(beforeCalled);
assert.isTrue(afterCalled);
assert.ok(el1.beforeNextRender.withArgs('before').calledOnce);
assert.ok(el1.stillBeforeNextRender.withArgs('still-before').calledOnce);
assert.ok(el2.afterNextRender.withArgs('after').calledOnce);
assert.ok(el2.afterAfterNextRender.withArgs('after-after').calledOnce);
});

});
Expand Down

0 comments on commit e9c58ad

Please sign in to comment.