Skip to content

Commit

Permalink
Move ensureLoaded to CustomElement from owners (#32416)
Browse files Browse the repository at this point in the history
* Move ensureLoaded to CustomElement from owners

* fix presubmits

* fix presubmits

* remove empty_func

* More notes

* reload a previously failed element
  • Loading branch information
Dima Voytenko committed Feb 9, 2021
1 parent 770086d commit 0a3f18a
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 117 deletions.
2 changes: 1 addition & 1 deletion build-system/tasks/presubmit-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ const forbiddenTerms = {
},
'\\.scheduleLayoutOrPreload\\(': {
message: 'scheduleLayoutOrPreload is a restricted API.',
allowlist: ['src/service/owners-impl.js', 'src/service/resources-impl.js'],
allowlist: ['src/custom-element.js', 'src/service/resources-impl.js'],
},
'(win|Win)(dow)?(\\(\\))?\\.open\\W': {
message: 'Use dom.openWindowDialog',
Expand Down
40 changes: 40 additions & 0 deletions src/custom-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,46 @@ function createBaseCustomElementClass(win) {
));
}

/**
* @return {!Promise}
* @final
*/
whenLoaded() {
return this.signals_.whenSignal(CommonSignals.LOAD_END);
}

/**
* Ensure that element is eagerly loaded.
*
* @param {number=} opt_parentPriority
* @return {!Promise}
* @final
*/
ensureLoaded(opt_parentPriority) {
return this.whenBuilt().then(() => {
const resource = this.getResource_();
if (resource.getState() == ResourceState.LAYOUT_COMPLETE) {
return;
}
if (
resource.getState() != ResourceState.LAYOUT_SCHEDULED ||
resource.isMeasureRequested()
) {
resource.measure();
}
if (!resource.isDisplayed()) {
return;
}
this.getResources().scheduleLayoutOrPreload(
resource,
/* layout */ true,
opt_parentPriority,
/* forceOutsideViewport */ true
);
return this.whenLoaded();
});
}

/**
* Called to instruct the element to preconnect to hosts it uses during
* layout.
Expand Down
64 changes: 3 additions & 61 deletions src/service/owners-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@
* limitations under the License.
*/

// TODO(powerivq)
// Resource.setOwner, Resource.getOwner should be moved here.
// ResourceState.NOT_BUILT might not be needed here.
import {OwnersInterface} from './owners-interface';
import {Resource, ResourceState} from './resource';
import {Resource} from './resource';
import {Services} from '../services';
import {devAssert} from '../log';
import {isArray} from '../types';
Expand Down Expand Up @@ -104,28 +101,7 @@ export class OwnersImpl {
requireLayout(element, opt_parentPriority) {
const promises = [];
this.discoverResourcesForElement_(element, (resource) => {
if (resource.getState() == ResourceState.LAYOUT_COMPLETE) {
return;
}
if (resource.getState() != ResourceState.LAYOUT_SCHEDULED) {
promises.push(
resource.whenBuilt().then(() => {
resource.measure();
if (!resource.isDisplayed()) {
return;
}
this.resources_.scheduleLayoutOrPreload(
resource,
/* layout */ true,
opt_parentPriority,
/* forceOutsideViewport */ true
);
return resource.loadedOnce();
})
);
} else if (resource.isDisplayed()) {
promises.push(resource.loadedOnce());
}
promises.push(resource.element.ensureLoaded());
});
return Promise.all(promises);
}
Expand Down Expand Up @@ -187,43 +163,9 @@ export class OwnersImpl {
*/
scheduleLayoutOrPreloadForSubresources_(parentResource, layout, subElements) {
this.findResourcesInElements_(parentResource, subElements, (resource) => {
if (resource.getState() === ResourceState.NOT_BUILT) {
resource.whenBuilt().then(() => {
this.measureAndTryScheduleLayout_(
resource,
/* isPreload */ !layout,
parentResource.getLayoutPriority()
);
});
} else {
this.measureAndTryScheduleLayout_(
resource,
/* isPreload */ !layout,
parentResource.getLayoutPriority()
);
}
resource.element.ensureLoaded(parentResource.getLayoutPriority());
});
}

/**
* @param {!Resource} resource
* @param {boolean} isPreload
* @param {number=} opt_parentPriority
* @private
*/
measureAndTryScheduleLayout_(resource, isPreload, opt_parentPriority) {
resource.measure();
if (
resource.getState() === ResourceState.READY_FOR_LAYOUT &&
resource.isDisplayed()
) {
this.resources_.scheduleLayoutOrPreload(
resource,
/* layout */ !isPreload,
opt_parentPriority
);
}
}
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/service/resources-interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ export class ResourcesInterface {
* @param {boolean} layout
* @param {number=} opt_parentPriority
* @param {boolean=} opt_forceOutsideViewport
* @package
*/
scheduleLayoutOrPreload(
resource,
Expand Down
140 changes: 140 additions & 0 deletions test/unit/test-custom-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -1574,6 +1574,146 @@ describes.realWin('CustomElement', {amp: true}, (env) => {
expect(element.prerenderAllowed()).to.be.false;
});
});

describe('ensureLoaded', () => {
it('should build and load', async () => {
const element = new ElementClass();
element.setAttribute('layout', 'fixed');
element.setAttribute('width', '10');
element.setAttribute('height', '10');
container.appendChild(element);
const resource = element.getResource_();

expect(element.isBuilt()).to.be.false;

const parentPriority = 1;
resourcesMock
.expects('scheduleLayoutOrPreload')
.withExactArgs(
resource,
/* layout */ true,
parentPriority,
/* forceOutsideViewport */ true
)
.once();

const promise = element.ensureLoaded(parentPriority);
await element.buildInternal();
await element.layoutCallback();

await promise;
expect(element.isBuilt()).to.be.true;
});

it('should load pre-built element', async () => {
const element = new ElementClass();
element.setAttribute('layout', 'fixed');
element.setAttribute('width', '10');
element.setAttribute('height', '10');
container.appendChild(element);
const resource = element.getResource_();

await element.buildInternal();
expect(element.isBuilt()).to.be.true;

const parentPriority = 1;
resourcesMock
.expects('scheduleLayoutOrPreload')
.withExactArgs(
resource,
/* layout */ true,
parentPriority,
/* forceOutsideViewport */ true
)
.once();

const promise = element.ensureLoaded(parentPriority);
await element.layoutCallback();
await promise;
});

it('should do nothing for already-loaded element', async () => {
const element = new ElementClass();
element.setAttribute('layout', 'fixed');
element.setAttribute('width', '10');
element.setAttribute('height', '10');
container.appendChild(element);
const resource = element.getResource_();

await element.buildInternal();
resource.measure();
resource.layoutScheduled(Date.now());
await resource.startLayout();

resourcesMock.expects('scheduleLayoutOrPreload').never();

await element.ensureLoaded();
});

it('should reload a previously failed element', async () => {
const element = new ElementClass();
element.setAttribute('layout', 'fixed');
element.setAttribute('width', '10');
element.setAttribute('height', '10');
container.appendChild(element);
const resource = element.getResource_();

await element.buildInternal();
resource.measure();
resource.layoutScheduled(Date.now());
const layoutCallbackStub = env.sandbox.stub(
element,
'layoutCallback'
);
layoutCallbackStub.returns(Promise.reject(new Error('intentional')));
try {
await resource.startLayout();
} catch (e) {
// Expected.
}

layoutCallbackStub./*OK*/ restore();
resourcesMock
.expects('scheduleLayoutOrPreload')
.withExactArgs(
resource,
/* layout */ true,
/* parentPriority */ undefined,
/* forceOutsideViewport */ true
)
.once();

const promise = element.ensureLoaded();
await element.layoutCallback();
await promise;
});

it('should do nothing for a non-displayed element', async () => {
const element = new ElementClass();
element.setAttribute('layout', 'nodisplay');
container.appendChild(element);
await element.buildInternal();

resourcesMock.expects('scheduleLayoutOrPreload').never();

await element.ensureLoaded();
});

it('should remeasure if needed', async () => {
const element = new ElementClass();
element.setAttribute('layout', 'nodisplay');
container.appendChild(element);
const resource = element.getResource_();
await element.buildInternal();

const measureSpy = env.sandbox.spy(resource, 'measure');
resourcesMock.expects('scheduleLayoutOrPreload').never();

resource.requestMeasure();
await element.ensureLoaded();
expect(measureSpy).to.be.calledOnce;
});
});
});
});

Expand Down

0 comments on commit 0a3f18a

Please sign in to comment.