diff --git a/lib/mixins/element-mixin.html b/lib/mixins/element-mixin.html index 309f680f5d..2e0e8c86ac 100644 --- a/lib/mixins/element-mixin.html +++ b/lib/mixins/element-mixin.html @@ -423,6 +423,8 @@ proto._bindTemplate(template, propertiesForClass(proto.constructor)); } + function flushPropertiesStub() {} + /** * @polymerMixinClass * @unrestricted @@ -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 @@ -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); diff --git a/lib/utils/render-status.html b/lib/utils/render-status.html index 8ab664ed5b..7c0bd907d8 100644 --- a/lib/utils/render-status.html +++ b/lib/utils/render-status.html @@ -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; + }) } } @@ -55,6 +61,7 @@ flushQueue(beforeRenderQueue); flushQueue(afterRenderQueue); } + scheduled = false; } /** diff --git a/test/unit/render-status.html b/test/unit/render-status.html index 0f1e4509b4..6312c10732 100644 --- a/test/unit/render-status.html +++ b/test/unit/render-status.html @@ -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); }); @@ -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); }); });