diff --git a/src/service/resource.js b/src/service/resource.js index 9416ef62e8b8..facce74549c1 100644 --- a/src/service/resource.js +++ b/src/service/resource.js @@ -673,13 +673,16 @@ export class Resource { /** * Whether the resource is displayed, i.e. if it has non-zero width and * height. - * @param {!ClientRect=} opt_premeasuredRect If provided, use this - * premeasured ClientRect instead of using the cached layout box. + * @param {boolean} usePremeasuredRect If true and a premeasured rect is + * available, use it. Otherwise, use the cached layout box. * @return {boolean} */ - isDisplayed(opt_premeasuredRect) { + isDisplayed(usePremeasuredRect = false) { + devAssert(!usePremeasuredRect || !this.intersect_); const isFluid = this.element.getLayout() == Layout.FLUID; - const box = opt_premeasuredRect || this.getLayoutBox(); + const box = usePremeasuredRect + ? devAssert(this.premeasuredRect_) + : this.getLayoutBox(); const hasNonZeroSize = box.height > 0 && box.width > 0; return ( (isFluid || hasNonZeroSize) && diff --git a/src/service/resources-impl.js b/src/service/resources-impl.js index 9b3de8eeceef..b61b94b4fd94 100644 --- a/src/service/resources-impl.js +++ b/src/service/resources-impl.js @@ -1420,14 +1420,28 @@ export class ResourcesImpl { const reschedule = this.reschedule_.bind(this, task); executing.promise.then(reschedule, reschedule); } else { - // With IntersectionObserver, the element's client rect measurement - // is recent so immediate remeasuring shouldn't be necessary. - if (!this.intersectionObserver_) { - task.resource.measure(); + const {resource} = task; + + let stillDisplayed = true; + if (this.intersectionObserver_) { + // With IntersectionObserver, peek at the premeasured rect to see + // if the resource is still displayed (has a non-zero size). + // The premeasured rect is most analogous to an immediate measure. + if (resource.hasBeenPremeasured()) { + stillDisplayed = resource.isDisplayed( + /* usePremeasuredRect */ true + ); + } + } else { + // Remeasure can only update isDisplayed(), not in-viewport state. + resource.measure(); } // Check if the element has exited the viewport or the page has changed // visibility since the layout was scheduled. - if (this.isLayoutAllowed_(task.resource, task.forceOutsideViewport)) { + if ( + stillDisplayed && + this.isLayoutAllowed_(resource, task.forceOutsideViewport) + ) { task.promise = task.callback(); task.startTime = now; dev().fine(TAG_, 'exec:', task.id, 'at', task.startTime); @@ -1439,9 +1453,8 @@ export class ResourcesImpl { ) .catch(/** @type {function (*)} */ (reportError)); } else { - devAssert(!this.intersectionObserver_); dev().fine(TAG_, 'cancelled', task.id); - task.resource.layoutCanceled(); + resource.layoutCanceled(); } } diff --git a/test/unit/test-resources.js b/test/unit/test-resources.js index e0c0341863ca..fdbfcc7c187a 100644 --- a/test/unit/test-resources.js +++ b/test/unit/test-resources.js @@ -23,7 +23,6 @@ import {Signals} from '../../src/utils/signals'; import {VisibilityState} from '../../src/visibility-state'; import {layoutRectLtwh} from '../../src/layout-rect'; import {loadPromise} from '../../src/event-helper'; -import {toggleExperiment} from '../../src/experiments'; /*eslint "google-camelcase/google-camelcase": 0*/ describe('Resources', () => { @@ -42,9 +41,7 @@ describe('Resources', () => { it('should calculate correct calcTaskScore', () => { const viewportRect = layoutRectLtwh(0, 100, 300, 400); - window.sandbox - .stub(resources.viewport_, 'getRect') - .callsFake(() => viewportRect); + window.sandbox.stub(resources.viewport_, 'getRect').returns(viewportRect); // Task 1 is right in the middle of the viewport and priority 0 const task_in_viewport_p0 = { @@ -481,13 +478,15 @@ describes.fakeWin( let clock; let resources; let schedulePassStub; + let sandbox; beforeEach(() => { win = env.win; - clock = env.sandbox.useFakeTimers(); + sandbox = env.sandbox; + clock = sandbox.useFakeTimers(); resources = Services.resourcesForDoc(win.document.body); resources.relayoutAll_ = false; - schedulePassStub = env.sandbox.stub(resources, 'schedulePass'); + schedulePassStub = sandbox.stub(resources, 'schedulePass'); }); it('should run a full reload pass on window.onload', () => { @@ -621,14 +620,14 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { element.getBoundingClientRect = () => rect; element.applySizesAndMediaQuery = () => {}; element.layoutCallback = () => Promise.resolve(); - element.viewportCallback = env.sandbox.spy(); + element.viewportCallback = sandbox.spy(); element.prerenderAllowed = () => true; element.renderOutsideViewport = () => true; element.isRelayoutNeeded = () => true; element.pauseCallback = () => {}; element.unlayoutCallback = () => true; element.unlayoutOnPause = () => true; - element.togglePlaceholder = () => env.sandbox.spy(); + element.togglePlaceholder = () => sandbox.spy(); element.fakeComputedStyle = { marginTop: '0px', marginRight: '0px', @@ -650,17 +649,19 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { let viewportMock; let resources; let resource1, resource2; + let sandbox; beforeEach(() => { - toggleExperiment(window, 'amp-force-prerender-visible-elements', true); + sandbox = env.sandbox; + const viewer = Services.viewerForDoc(env.ampdoc); - env.sandbox.stub(viewer, 'isRuntimeOn').returns(true); + sandbox.stub(viewer, 'isRuntimeOn').returns(true); resources = new ResourcesImpl(env.ampdoc); resources.remeasurePass_.schedule = () => {}; resources.pass_.schedule = () => {}; - viewportMock = env.sandbox.mock(resources.viewport_); + viewportMock = sandbox.mock(resources.viewport_); - env.sandbox.stub(env.win, 'getComputedStyle').callsFake((el) => { + sandbox.stub(env.win, 'getComputedStyle').callsFake((el) => { return el.fakeComputedStyle ? el.fakeComputedStyle : window.getComputedStyle(el); @@ -676,7 +677,6 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { }); afterEach(() => { - toggleExperiment(window, 'amp-force-prerender-visible-elements', false); viewportMock.verify(); }); @@ -684,7 +684,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { resources.isRuntimeOn_ = true; resources.documentReady_ = true; resources.firstPassAfterDocumentReady_ = true; - env.sandbox.stub(resources.visibilityStateMachine_, 'setState'); + sandbox.stub(resources.visibilityStateMachine_, 'setState'); resources.doPass(); expect(resources.ampdoc.signals().get('ready-scan')).to.be.null; resources.ampInitComplete(); @@ -695,15 +695,12 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { it('should measure unbuilt elements', () => { resources.visible_ = true; - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.VISIBLE); viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); resource1.isBuilt = () => false; - const mediaSpy = env.sandbox.stub( - resource1.element, - 'applySizesAndMediaQuery' - ); + const mediaSpy = sandbox.stub(resource1.element, 'applySizesAndMediaQuery'); expect(resource1.hasBeenMeasured()).to.be.false; resource1.isBuilt = () => false; @@ -715,7 +712,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { it('should render two screens when visible', () => { resources.visible_ = true; - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.VISIBLE); viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); @@ -731,7 +728,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { resource1.state_ = ResourceState.LAYOUT_COMPLETE; resource2.state_ = ResourceState.LAYOUT_COMPLETE; resources.visible_ = true; - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.VISIBLE); viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); @@ -751,7 +748,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { resource2.element.getBoundingClientRect = () => layoutRectLtwh(10, 1010, 100, 101); resources.visible_ = true; - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.VISIBLE); resources.relayoutAll_ = false; @@ -769,7 +766,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { it('should prerender only one screen with prerenderSize = 1', () => { resources.visible_ = false; - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.PRERENDER); resources.prerenderSize_ = 1; @@ -783,7 +780,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { it('should NOT prerender anything with prerenderSize = 0', () => { resources.visible_ = false; - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.PRERENDER); resources.prerenderSize_ = 0; @@ -799,19 +796,19 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { resource1.state_ = ResourceState.LAYOUT_COMPLETE; resource2.state_ = ResourceState.LAYOUT_COMPLETE; resources.visible_ = true; - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.VISIBLE); viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); - const resource1MeasureStub = env.sandbox + const resource1MeasureStub = sandbox .stub(resource1, 'measure') .callsFake(resource1.measure.bind(resource1)); - const resource1UnloadStub = env.sandbox.stub(resource1, 'unload'); - const resource2MeasureStub = env.sandbox + const resource1UnloadStub = sandbox.stub(resource1, 'unload'); + const resource2MeasureStub = sandbox .stub(resource2, 'measure') .callsFake(resource2.measure.bind(resource2)); - const resource2UnloadStub = env.sandbox.stub(resource2, 'unload'); + const resource2UnloadStub = sandbox.stub(resource2, 'unload'); // 1st pass: measure for the first time. resources.discoverWork_(); @@ -854,7 +851,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { resource1.layoutCallback = new Promise((unusedResolve) => {}); resource1.unlayoutCallback = () => true; - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.VISIBLE); viewportMock @@ -901,7 +898,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { expect(resources.queue_.getSize()).to.equal(1); expect(resources.queue_.tasks_[0].resource).to.equal(resource1); - const measureSpy = env.sandbox.spy(resource1, 'measure'); + const measureSpy = sandbox.spy(resource1, 'measure'); resources.work_(); expect(resources.exec_.getSize()).to.equal(1); expect(measureSpy).to.be.calledOnce; @@ -916,18 +913,55 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { expect(resource1.element.layoutScheduleTime).to.be.greaterThan(0); }); + describe('intersect-resources', () => { + beforeEach(() => { + // Enables the "intersect-resources" experiment. + resources.intersectionObserver_ = {}; + }); + + it('should not remeasure before layout', () => { + sandbox.stub(resource1, 'hasBeenPremeasured').returns(false); + sandbox.stub(resource1, 'measure'); + + resources.scheduleLayoutOrPreload(resource1, /* layout */ true); + resources.work_(); + + expect(resources.exec_.getSize()).to.equal(1); + expect(resource1.measure).to.not.be.called; + expect(resource1.getState()).to.equal(ResourceState.LAYOUT_SCHEDULED); + }); + + it('should check premeasured rect before layout', () => { + sandbox.stub(resource1, 'hasBeenMeasured').returns(true); + sandbox.stub(resource1, 'hasBeenPremeasured').returns(true); + sandbox.stub(resource1, 'isDisplayed').returns(true); + resource1.isDisplayed + .withArgs(/* usePremeasuredRect */ true) + .returns(false); + sandbox.spy(resource1, 'layoutCanceled'); + + resources.scheduleLayoutOrPreload(resource1, /* layout */ true); + resources.work_(); + + expect(resources.exec_.getSize()).to.equal(0); + expect(resource1.isDisplayed).to.be.calledWith( + /* usePremeasuredRect */ true + ); + expect(resource1.layoutCanceled).to.be.calledOnce; + expect(resource1.getState()).to.equal(ResourceState.READY_FOR_LAYOUT); + }); + }); + it('should not schedule resource execution outside viewport', () => { resources.scheduleLayoutOrPreload(resource1, true); expect(resources.queue_.getSize()).to.equal(1); expect(resources.queue_.tasks_[0].resource).to.equal(resource1); - const measureSpy = env.sandbox.spy(resource1, 'measure'); - const layoutCanceledSpy = env.sandbox.spy(resource1, 'layoutCanceled'); - env.sandbox.stub(resource1, 'isInViewport').callsFake(() => false); - env.sandbox.stub(resource1, 'renderOutsideViewport').callsFake(() => false); - env.sandbox - .stub(resource1, 'idleRenderOutsideViewport') - .callsFake(() => false); + const measureSpy = sandbox.spy(resource1, 'measure'); + const layoutCanceledSpy = sandbox.spy(resource1, 'layoutCanceled'); + sandbox.stub(resource1, 'isInViewport').returns(false); + sandbox.stub(resource1, 'renderOutsideViewport').returns(false); + sandbox.stub(resource1, 'idleRenderOutsideViewport').returns(false); resources.work_(); expect(resources.exec_.getSize()).to.equal(0); expect(measureSpy).to.be.calledOnce; @@ -945,12 +979,10 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { expect(resources.queue_.getSize()).to.equal(1); expect(resources.queue_.tasks_[0].resource).to.equal(resource1); - const measureSpy = env.sandbox.spy(resource1, 'measure'); - env.sandbox.stub(resource1, 'isInViewport').callsFake(() => false); - env.sandbox.stub(resource1, 'renderOutsideViewport').callsFake(() => false); - env.sandbox - .stub(resource1, 'idleRenderOutsideViewport') - .callsFake(() => false); + const measureSpy = sandbox.spy(resource1, 'measure'); + sandbox.stub(resource1, 'isInViewport').returns(false); + sandbox.stub(resource1, 'renderOutsideViewport').returns(false); + sandbox.stub(resource1, 'idleRenderOutsideViewport').returns(false); resources.work_(); expect(resources.exec_.getSize()).to.equal(1); expect(measureSpy).to.be.calledOnce; @@ -963,14 +995,12 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { expect(resources.queue_.tasks_[0].resource).to.equal(resource1); resources.visible_ = false; - env.sandbox - .stub(resources.ampdoc, 'getVisibilityState') - .callsFake(() => 'prerender'); - env.sandbox.stub(resource1, 'isInViewport').callsFake(() => true); - env.sandbox.stub(resource1, 'prerenderAllowed').callsFake(() => true); + sandbox.stub(resources.ampdoc, 'getVisibilityState').returns('prerender'); + sandbox.stub(resource1, 'isInViewport').returns(true); + sandbox.stub(resource1, 'prerenderAllowed').returns(true); - const measureSpy = env.sandbox.spy(resource1, 'measure'); - const layoutCanceledSpy = env.sandbox.spy(resource1, 'layoutCanceled'); + const measureSpy = sandbox.spy(resource1, 'measure'); + const layoutCanceledSpy = sandbox.spy(resource1, 'layoutCanceled'); resources.work_(); expect(resources.exec_.getSize()).to.equal(1); expect(measureSpy).to.be.calledOnce; @@ -984,14 +1014,12 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { expect(resources.queue_.tasks_[0].resource).to.equal(resource1); resources.visible_ = false; - env.sandbox - .stub(resources.ampdoc, 'getVisibilityState') - .callsFake(() => 'prerender'); - env.sandbox.stub(resource1, 'isInViewport').callsFake(() => true); - env.sandbox.stub(resource1, 'prerenderAllowed').callsFake(() => false); + sandbox.stub(resources.ampdoc, 'getVisibilityState').returns('prerender'); + sandbox.stub(resource1, 'isInViewport').returns(true); + sandbox.stub(resource1, 'prerenderAllowed').returns(false); - const measureSpy = env.sandbox.spy(resource1, 'measure'); - const layoutCanceledSpy = env.sandbox.spy(resource1, 'layoutCanceled'); + const measureSpy = sandbox.spy(resource1, 'measure'); + const layoutCanceledSpy = sandbox.spy(resource1, 'layoutCanceled'); resources.work_(); expect(resources.exec_.getSize()).to.equal(0); expect(measureSpy).to.be.calledOnce; @@ -1005,14 +1033,12 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { expect(resources.queue_.tasks_[0].resource).to.equal(resource1); resources.visible_ = false; - env.sandbox - .stub(resources.ampdoc, 'getVisibilityState') - .callsFake(() => 'hidden'); - env.sandbox.stub(resource1, 'isInViewport').callsFake(() => true); - env.sandbox.stub(resource1, 'prerenderAllowed').callsFake(() => true); + sandbox.stub(resources.ampdoc, 'getVisibilityState').returns('hidden'); + sandbox.stub(resource1, 'isInViewport').returns(true); + sandbox.stub(resource1, 'prerenderAllowed').returns(true); - const measureSpy = env.sandbox.spy(resource1, 'measure'); - const layoutCanceledSpy = env.sandbox.spy(resource1, 'layoutCanceled'); + const measureSpy = sandbox.spy(resource1, 'measure'); + const layoutCanceledSpy = sandbox.spy(resource1, 'layoutCanceled'); resources.work_(); expect(resources.exec_.getSize()).to.equal(0); expect(measureSpy).to.be.calledOnce; @@ -1024,12 +1050,12 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { // it.configure().skipSafari().run( it.skip('should update inViewport before scheduling layouts', () => { resources.visible_ = true; - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.VISIBLE); viewportMock.expects('getRect').returns(layoutRectLtwh(0, 0, 300, 400)); - const setInViewport = env.sandbox.spy(resource1, 'setInViewport'); - const schedule = env.sandbox.spy(resources, 'scheduleLayoutOrPreload'); + const setInViewport = sandbox.spy(resource1, 'setInViewport'); + const schedule = sandbox.spy(resources, 'scheduleLayoutOrPreload'); resources.discoverWork_(); @@ -1038,10 +1064,10 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { }); it('should build resource when not built', () => { - const buildResourceSpy = env.sandbox.spy(resources, 'buildResourceUnsafe_'); - env.sandbox.stub(resources, 'schedule_'); + const buildResourceSpy = sandbox.spy(resources, 'buildResourceUnsafe_'); + sandbox.stub(resources, 'schedule_'); resources.documentReady_ = true; - resource1.element.isBuilt = env.sandbox + resource1.element.isBuilt = sandbox .stub() .onFirstCall() .returns(true) @@ -1049,7 +1075,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { .returns(false); resource2.element.idleRenderOutsideViewport = () => false; resource1.state_ = ResourceState.NOT_BUILT; - resource1.build = env.sandbox.spy(); + resource1.build = sandbox.spy(); resources.discoverWork_(); @@ -1061,11 +1087,11 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { }); it('should build resource when not built and before doc ready', () => { - const buildResourceSpy = env.sandbox.spy(resources, 'buildResourceUnsafe_'); - env.sandbox.stub(resources, 'schedule_'); + const buildResourceSpy = sandbox.spy(resources, 'buildResourceUnsafe_'); + sandbox.stub(resources, 'schedule_'); resources.documentReady_ = false; - env.sandbox.stub(resource1.element, 'nextSibling').returns({}); - resource1.element.isBuilt = env.sandbox + sandbox.stub(resource1.element, 'nextSibling').returns({}); + resource1.element.isBuilt = sandbox .stub() .onFirstCall() .returns(false) @@ -1073,7 +1099,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { .returns(true); resource2.element.idleRenderOutsideViewport = () => false; resource1.state_ = ResourceState.NOT_BUILT; - resource1.build = env.sandbox.spy(); + resource1.build = sandbox.spy(); resources.discoverWork_(); @@ -1082,16 +1108,16 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { }); it('should NOT build non-prerenderable resources in prerender', () => { - env.sandbox + sandbox .stub(resources.ampdoc, 'getVisibilityState') .returns(VisibilityState.PRERENDER); - env.sandbox.stub(resources, 'schedule_'); + sandbox.stub(resources, 'schedule_'); resources.documentReady_ = true; resource1.element.isBuilt = () => false; resource1.prerenderAllowed = () => false; resource1.state_ = ResourceState.NOT_BUILT; - resource1.build = env.sandbox.spy(); + resource1.build = sandbox.spy(); resource2.element.idleRenderOutsideViewport = () => false; resources.discoverWork_(); @@ -1100,8 +1126,8 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { }); it('should NOT build when quota reached', () => { - env.sandbox.stub(resources.ampdoc, 'hasBeenVisible').callsFake(() => false); - env.sandbox.stub(resources, 'schedule_'); + sandbox.stub(resources.ampdoc, 'hasBeenVisible').returns(false); + sandbox.stub(resources, 'schedule_'); resources.documentReady_ = true; resources.buildAttemptsCount_ = 21; // quota is 20 @@ -1110,15 +1136,15 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { resource1.prerenderAllowed = () => true; resource1.isBuildRenderBlocking = () => false; resource1.state_ = ResourceState.NOT_BUILT; - resource1.build = env.sandbox.spy(); + resource1.build = sandbox.spy(); resources.buildOrScheduleBuildForResource_(resource1); expect(resource1.build).to.not.be.called; }); it('should build render blocking resource even if quota is reached', () => { - env.sandbox.stub(resources.ampdoc, 'hasBeenVisible').callsFake(() => false); - env.sandbox.stub(resources, 'schedule_'); + sandbox.stub(resources.ampdoc, 'hasBeenVisible').returns(false); + sandbox.stub(resources, 'schedule_'); resources.documentReady_ = true; resources.buildAttemptsCount_ = 21; // quota is 20 @@ -1127,16 +1153,16 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { resource1.prerenderAllowed = () => true; resource1.isBuildRenderBlocking = () => true; resource1.state_ = ResourceState.NOT_BUILT; - resource1.build = env.sandbox.spy(); + resource1.build = sandbox.spy(); resources.buildOrScheduleBuildForResource_(resource1); expect(resource1.build).to.be.called; }); it('should layout resource if outside viewport but idle', () => { - const schedulePassStub = env.sandbox.stub(resources, 'schedulePass'); + const schedulePassStub = sandbox.stub(resources, 'schedulePass'); resources.documentReady_ = true; - env.sandbox.stub(resource1.element, 'nextSibling').returns({}); + sandbox.stub(resource1.element, 'nextSibling').returns({}); resource1.element.isBuilt = () => true; resource1.element.renderOutsideViewport = () => false; resource1.element.idleRenderOutsideViewport = () => true; @@ -1150,14 +1176,14 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { }); it('should force build resources during discoverWork layout phase', () => { - const buildResourceSpy = env.sandbox.spy(resources, 'buildResourceUnsafe_'); - env.sandbox.stub(resources, 'schedule_'); + const buildResourceSpy = sandbox.spy(resources, 'buildResourceUnsafe_'); + sandbox.stub(resources, 'schedule_'); resources.documentReady_ = true; // Emulates a resource not building. - resource1.element.isBuilt = env.sandbox.stub().returns(false); + resource1.element.isBuilt = sandbox.stub().returns(false); resource2.element.idleRenderOutsideViewport = () => false; resource1.state_ = ResourceState.NOT_BUILT; - resource1.build = env.sandbox.spy(); + resource1.build = sandbox.spy(); resources.discoverWork_(); @@ -1180,7 +1206,7 @@ describes.realWin('Resources discoverWork', {amp: true}, (env) => { resources.documentReady_ = true; resources.firstPassAfterDocumentReady_ = true; - const passCallback = env.sandbox.spy(); + const passCallback = sandbox.spy(); resources.onNextPass(passCallback); resources.doPass(); @@ -1203,19 +1229,18 @@ describes.realWin( let win; let resources; let viewerSendMessageStub, viewportContentHeightChangedStub; + let sandbox; beforeEach(() => { win = env.win; + sandbox = env.sandbox; resources = win.__AMP_SERVICES.resources.obj; - viewerSendMessageStub = env.sandbox.stub( - resources.viewer_, - 'sendMessage' - ); - viewportContentHeightChangedStub = env.sandbox.stub( + viewerSendMessageStub = sandbox.stub(resources.viewer_, 'sendMessage'); + viewportContentHeightChangedStub = sandbox.stub( resources.viewport_, 'contentHeightChanged' ); - env.sandbox.stub(resources.vsync_, 'run').callsFake((task) => { + sandbox.stub(resources.vsync_, 'run').callsFake((task) => { task.measure({}); }); }); @@ -1228,11 +1253,7 @@ describes.realWin( }); it('should send contentHeight to viewer if height was changed', () => { - env.sandbox - .stub(resources.viewport_, 'getContentHeight') - .callsFake(() => { - return 200; - }); + sandbox.stub(resources.viewport_, 'getContentHeight').returns(200); resources.maybeChangeHeight_ = true; resources.doPass(); @@ -1259,11 +1280,7 @@ describes.realWin( }); it('should send contentHeight to viewer if viewport resizes', () => { - env.sandbox - .stub(resources.viewport_, 'getContentHeight') - .callsFake(() => { - return 200; - }); + sandbox.stub(resources.viewport_, 'getContentHeight').returns(200); resources.viewport_.changed_(/* relayoutAll */ true, /* velocity */ 0); resources.doPass(); @@ -1286,6 +1303,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { let resource1; let child2; let resource2; + let sandbox; function createElement() { const signals = new Signals(); @@ -1319,7 +1337,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { return signals; }, }; - element.build = env.sandbox.stub().returns(Promise.resolve()); + element.build = sandbox.stub().returns(Promise.resolve()); return element; } @@ -1333,7 +1351,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { function stubBuild(resource) { const origBuild = resource.build; - env.sandbox.stub(resource, 'build').callsFake(() => { + sandbox.stub(resource, 'build').callsFake(() => { resource.buildPromise = origBuild.call(resource); return resource.buildPromise; }); @@ -1341,8 +1359,9 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { } beforeEach(() => { + sandbox = env.sandbox; const infPromise = new Promise(() => {}); - env.sandbox.stub(env.ampdoc, 'whenReady').callsFake(() => infPromise); + sandbox.stub(env.ampdoc, 'whenReady').returns(infPromise); resources = new ResourcesImpl(env.ampdoc); resources.isBuildOn_ = true; resources.pendingBuildResources_ = []; @@ -1355,7 +1374,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { }); it('should enforce that viewport is ready for first add', () => { - const ensureViewportReady = env.sandbox.stub( + const ensureViewportReady = sandbox.stub( resources.viewport_, 'ensureReadyForElements' ); @@ -1368,7 +1387,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { }); it('should build elements immediately if the document is ready', () => { - const schedulePassStub = env.sandbox.stub(resources, 'schedulePass'); + const schedulePassStub = sandbox.stub(resources, 'schedulePass'); child1.isBuilt = () => false; child2.isBuilt = () => false; resources.documentReady_ = false; @@ -1390,9 +1409,9 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { it.configure().skipSafari( 'should not schedule pass when immediate build fails', () => { - const schedulePassStub = env.sandbox.stub(resources, 'schedulePass'); + const schedulePassStub = sandbox.stub(resources, 'schedulePass'); child1.isBuilt = () => false; - const child1BuildSpy = env.sandbox.spy(); + const child1BuildSpy = sandbox.spy(); child1.build = () => { // Emulate an error happening during an element build. child1BuildSpy(); @@ -1422,7 +1441,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { () => { child1.isBuilt = () => false; child2.isBuilt = () => false; - resources.buildReadyResources_ = env.sandbox.spy(); + resources.buildReadyResources_ = sandbox.spy(); resources.documentReady_ = false; resources.add(child1); resources.upgraded(child1); @@ -1446,7 +1465,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { let schedulePassStub; beforeEach(() => { - schedulePassStub = env.sandbox.stub(resources, 'schedulePass'); + schedulePassStub = sandbox.stub(resources, 'schedulePass'); resources.isBuildOn_ = true; resources.documentReady_ = false; resource1 = stubBuild(resource1); @@ -1506,7 +1525,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { child1.parentNode = parent; parent.nextSibling = true; - env.sandbox.stub(resources.ampdoc, 'getRootNode').callsFake(() => parent); + sandbox.stub(resources.ampdoc, 'getRootNode').returns(parent); resources.buildReadyResources_(); expect(child1.build.called).to.be.false; expect(resources.pendingBuildResources_.length).to.be.equal(1); @@ -1523,7 +1542,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { const newChild = createElementWithResource(3)[0]; newChild.nextSibling = true; const newResource = newChild['__AMP__RESOURCE']; - const child1BuildSpy = env.sandbox.spy(); + const child1BuildSpy = sandbox.spy(); child1.nextSibling = child2; child1.build = () => { // Simulate parent elements adding children elements to simulate @@ -1560,7 +1579,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { resource1, resource2, ]; - const child1BuildSpy = env.sandbox.spy(); + const child1BuildSpy = sandbox.spy(); child1.build = () => { // Emulate an error happening during an element build. child1BuildSpy(); @@ -1600,7 +1619,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { () => { resources.documentReady_ = true; resources.pendingBuildResources_ = [resource1]; - const child1BuildSpy = env.sandbox.spy(); + const child1BuildSpy = sandbox.spy(); child1.build = () => { // Emulate an error happening during an element build. child1BuildSpy(); @@ -1629,8 +1648,8 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { child1.isBuilt = () => true; resources.add(child1); const resource = child1['__AMP__RESOURCE']; - const pauseOnRemoveStub = env.sandbox.stub(resource, 'pauseOnRemove'); - const disconnectStub = env.sandbox.stub(resource, 'disconnect'); + const pauseOnRemoveStub = sandbox.stub(resource, 'pauseOnRemove'); + const disconnectStub = sandbox.stub(resource, 'disconnect'); resources.remove(child1); expect(resources.get()).to.not.contain(resource); expect(pauseOnRemoveStub).to.be.calledOnce; @@ -1643,7 +1662,7 @@ describes.fakeWin('Resources.add/upgrade/remove', {amp: true}, (env) => { let resource; beforeEach(() => { - scheduleBuildStub = env.sandbox.stub( + scheduleBuildStub = sandbox.stub( resources, 'buildOrScheduleBuildForResource_' );