Skip to content

Commit

Permalink
Defer full upgrade until the element is connected (ampproject#5908)
Browse files Browse the repository at this point in the history
* Defer full upgrade until the element is connected

* docs
  • Loading branch information
Dima Voytenko authored and Vanessa Pasque committed Dec 22, 2016
1 parent 82f1aa7 commit 4c19988
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 37 deletions.
16 changes: 11 additions & 5 deletions src/custom-element.js
Expand Up @@ -573,7 +573,14 @@ function createBaseCustomElementClass(win) {
if (this.isInTemplate_) {
return;
}
this.tryUpgrade_(new newImplClass(this));
if (this.upgradeState_ != UpgradeState.NOT_UPGRADED) {
// Already upgraded or in progress or failed.
return;
}
this.implementation_ = new newImplClass(this);
if (this.everAttached) {
this.tryUpgrade_();
}
}

/**
Expand Down Expand Up @@ -832,7 +839,7 @@ function createBaseCustomElementClass(win) {
}
if (!this.everAttached) {
if (!isStub(this.implementation_)) {
this.tryUpgrade_(this.implementation_);
this.tryUpgrade_();
}
if (!this.isUpgraded()) {
this.classList.add('amp-unresolved');
Expand Down Expand Up @@ -871,11 +878,10 @@ function createBaseCustomElementClass(win) {

/**
* Try to upgrade the element with the provided implementation.
* @param {!./base-element.BaseElement=} opt_impl
* @private @final @this {!Element}
*/
tryUpgrade_(opt_impl) {
const impl = opt_impl || this.implementation_;
tryUpgrade_() {
const impl = this.implementation_;
dev().assert(!isStub(impl), 'Implementation must not be a stub');
if (this.upgradeState_ != UpgradeState.NOT_UPGRADED) {
// Already upgraded or in progress or failed.
Expand Down
86 changes: 54 additions & 32 deletions test/functional/test-custom-element.js
Expand Up @@ -346,45 +346,42 @@ describe('CustomElement', () => {
expect(element.implementation_.layoutWidth_).to.equal(111);
});

it('StubElement - upgrade', () => {
it('StubElement - upgrade after attached', () => {
const element = new StubElementClass();
expect(element.isUpgraded()).to.equal(false);
expect(testElementCreatedCallback.callCount).to.equal(0);

element.layout_ = Layout.FILL;
element.setAttribute('layout', 'fill');
element.updateLayoutBox({top: 0, left: 0, width: 111, height: 51});
resourcesMock.expects('upgraded').withExactArgs(element).never();
container.appendChild(element);
resourcesMock.expects('upgraded').withExactArgs(element).once();

element.upgrade(TestElement);

expect(element.isUpgraded()).to.equal(true);
expect(element.implementation_ instanceof TestElement).to.equal(true);
expect(element.implementation_).to.be.instanceOf(TestElement);
expect(element.implementation_.layout_).to.equal(Layout.FILL);
expect(element.implementation_.layoutWidth_).to.equal(111);
expect(testElementCreatedCallback.callCount).to.equal(1);
expect(testElementFirstAttachedCallback.callCount).to.equal(0);
expect(testElementFirstAttachedCallback.callCount).to.equal(1);
expect(element.isBuilt()).to.equal(false);
});

it('StubElement - upgrade previously attached', () => {
it('StubElement - upgrade before attached', () => {
const element = new StubElementClass();
expect(element.isUpgraded()).to.equal(false);
expect(testElementCreatedCallback.callCount).to.equal(0);

element.layout_ = Layout.FILL;
element.setAttribute('layout', 'fill');
element.updateLayoutBox({top: 0, left: 0, width: 111, height: 51});
element.everAttached = true;
element.resources_ = resources;
resourcesMock.expects('upgraded').withExactArgs(element).once();
resourcesMock.expects('upgraded').withExactArgs(element).never();

element.upgrade(TestElement);

expect(element.isUpgraded()).to.equal(true);
expect(element.implementation_ instanceof TestElement).to.equal(true);
expect(element.implementation_.layout_).to.equal(Layout.FILL);
expect(element.implementation_.layoutWidth_).to.equal(111);
expect(testElementCreatedCallback.callCount).to.equal(1);
expect(testElementFirstAttachedCallback.callCount).to.equal(1);
expect(element.isUpgraded()).to.equal(false);
expect(element.implementation_).to.be.instanceOf(TestElement);
expect(testElementCreatedCallback.callCount).to.equal(0);
expect(testElementFirstAttachedCallback.callCount).to.equal(0);
expect(element.isBuilt()).to.equal(false);
});

Expand Down Expand Up @@ -492,9 +489,8 @@ describe('CustomElement', () => {

element.upgrade(TestElementWithReUpgrade);

expect(element.isUpgraded()).to.equal(true);
expect(element.implementation_ instanceof TestElement).to.equal(true);
expect(testElementCreatedCallback.callCount).to.equal(1);
expect(element.isUpgraded()).to.equal(false);
expect(testElementCreatedCallback.callCount).to.equal(0);
});


Expand Down Expand Up @@ -694,6 +690,26 @@ describe('CustomElement', () => {
resourcesMock.expects('upgraded').withExactArgs(element).never();
element.upgrade(TestElement);

expect(element.isUpgraded()).to.equal(false);
expect(element.isBuilt()).to.equal(false);
expect(() => {
element.layoutCallback();
}).to.throw(/Must be built to receive viewport events/);

expect(testElementLayoutCallback.callCount).to.equal(0);
});

it('StubElement - layoutCallback after upgrade but before build', () => {
const element = new StubElementClass();
element.setAttribute('layout', 'fill');
expect(testElementLayoutCallback.callCount).to.equal(0);
expect(element.isUpgraded()).to.equal(false);
expect(element.isBuilt()).to.equal(false);

resourcesMock.expects('upgraded').withExactArgs(element).once();
container.appendChild(element);
element.upgrade(TestElement);

expect(element.isUpgraded()).to.equal(true);
expect(element.isBuilt()).to.equal(false);
expect(() => {
Expand Down Expand Up @@ -760,24 +776,18 @@ describe('CustomElement', () => {
}).to.throw(/Must never be called in template/);
});

it('StubElement - layoutCallback', () => {
it('StubElement - layoutCallback should fail before attach', () => {
const element = new StubElementClass();
element.setAttribute('layout', 'fill');
resourcesMock.expects('upgraded').withExactArgs(element).never();
element.upgrade(TestElement);
element.build();
expect(element.isUpgraded()).to.equal(true);
expect(element.isBuilt()).to.equal(true);
expect(() => element.build()).to.throw(/Cannot build unupgraded element/);
expect(element.isUpgraded()).to.equal(false);
expect(element.isBuilt()).to.equal(false);
expect(testElementLayoutCallback.callCount).to.equal(0);

const p = element.layoutCallback();
expect(testElementLayoutCallback.callCount).to.equal(1);
return p.then(() => {
expect(element.readyState).to.equal('complete');
});
});

it('StubElement - layoutCallback previously attached', () => {
it('StubElement - layoutCallback after attached', () => {
const element = new StubElementClass();
element.setAttribute('layout', 'fill');
element.everAttached = true;
Expand Down Expand Up @@ -1091,7 +1101,7 @@ describe('CustomElement', () => {
resourcesMock.expects('upgraded').withExactArgs(element).never();
element.upgrade(TestElement);

expect(element.isUpgraded()).to.equal(true);
expect(element.isUpgraded()).to.equal(false);
expect(element.isBuilt()).to.equal(false);
element.viewportCallback(false);
expect(element.isInViewport_).to.equal(false);
Expand All @@ -1114,7 +1124,8 @@ describe('CustomElement', () => {
it('StubElement - should be called once upgraded', () => {
const element = new StubElementClass();
element.setAttribute('layout', 'fill');
resourcesMock.expects('upgraded').withExactArgs(element).never();
resourcesMock.expects('upgraded').withExactArgs(element).once();
container.appendChild(element);
element.upgrade(TestElement);
element.build();
expect(element.isUpgraded()).to.equal(true);
Expand All @@ -1126,6 +1137,17 @@ describe('CustomElement', () => {
expect(testElementViewportCallback.callCount).to.equal(1);
});

it('StubElement - should not upgrade before attach', () => {
const element = new StubElementClass();
element.setAttribute('layout', 'fill');
resourcesMock.expects('upgraded').withExactArgs(element).never();
element.upgrade(TestElement);
expect(element.isUpgraded()).to.equal(false);
expect(element.isBuilt()).to.equal(false);
expect(element.implementation_).to.be.instanceOf(TestElement);
expect(testElementViewportCallback.callCount).to.equal(0);
});

it('StubElement - should be called once upgraded, attached', () => {
const element = new StubElementClass();
element.setAttribute('layout', 'fill');
Expand Down

0 comments on commit 4c19988

Please sign in to comment.