diff --git a/packages/core/src/sessionManagement.ts b/packages/core/src/sessionManagement.ts index 360b003f08..131be54144 100644 --- a/packages/core/src/sessionManagement.ts +++ b/packages/core/src/sessionManagement.ts @@ -34,7 +34,7 @@ export function startSessionManagement( const renewObservable = new Observable() let currentSessionId = retrieveActiveSession(sessionCookie).id - const expandOrRenewSession = utils.throttle(() => { + const { throttled: expandOrRenewSession } = utils.throttle(() => { const session = retrieveActiveSession(sessionCookie) const { type, isTracked } = computeSessionState(session[sessionTypeKey]) session[sessionTypeKey] = type diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 2e41265a42..ad73808470 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -34,30 +34,41 @@ export function throttle( fn: () => void, wait: number, options?: { leading?: boolean; trailing?: boolean } -): () => void { +): { throttled: () => void; stop: () => void } { const needLeadingExecution = options && options.leading !== undefined ? options.leading : true const needTrailingExecution = options && options.trailing !== undefined ? options.trailing : true let inWaitPeriod = false let hasPendingExecution = false + let pendingTimeoutId: number + let isStopped = false - return function(this: any) { - if (inWaitPeriod) { - hasPendingExecution = true - return - } - if (needLeadingExecution) { - fn.apply(this) - } else { - hasPendingExecution = true - } - inWaitPeriod = true - setTimeout(() => { - if (needTrailingExecution && hasPendingExecution) { + return { + throttled(this: any) { + if (isStopped) { + return + } + if (inWaitPeriod) { + hasPendingExecution = true + return + } + if (needLeadingExecution) { fn.apply(this) + } else { + hasPendingExecution = true } - inWaitPeriod = false - hasPendingExecution = false - }, wait) + inWaitPeriod = true + pendingTimeoutId = window.setTimeout(() => { + if (needTrailingExecution && hasPendingExecution) { + fn.apply(this) + } + inWaitPeriod = false + hasPendingExecution = false + }, wait) + }, + stop() { + window.clearTimeout(pendingTimeoutId) + isStopped = true + }, } } diff --git a/packages/core/test/utils.spec.ts b/packages/core/test/utils.spec.ts index 6acde543a6..aebcd51597 100644 --- a/packages/core/test/utils.spec.ts +++ b/packages/core/test/utils.spec.ts @@ -4,6 +4,7 @@ describe('utils', () => { describe('throttle', () => { let spy: jasmine.Spy let throttled: () => void + let stop: () => void beforeEach(() => { jasmine.clock().install() @@ -17,7 +18,7 @@ describe('utils', () => { describe('when {leading: false, trailing:false}', () => { beforeEach(() => { - throttled = throttle(spy, 2, { leading: false, trailing: false }) + throttled = throttle(spy, 2, { leading: false, trailing: false }).throttled }) it('should not call throttled function', () => { @@ -61,7 +62,7 @@ describe('utils', () => { describe('when {leading: false, trailing:true}', () => { beforeEach(() => { - throttled = throttle(spy, 2, { leading: false }) + throttled = throttle(spy, 2, { leading: false }).throttled }) it('should call throttled function after the wait period', () => { @@ -105,7 +106,7 @@ describe('utils', () => { describe('when {leading: true, trailing:false}', () => { beforeEach(() => { - throttled = throttle(spy, 2, { trailing: false }) + throttled = throttle(spy, 2, { trailing: false }).throttled }) it('should call throttled function immediately', () => { @@ -149,7 +150,7 @@ describe('utils', () => { describe('when {leading: true, trailing:true}', () => { beforeEach(() => { - throttled = throttle(spy, 2) + throttled = throttle(spy, 2).throttled }) it('should call throttled function immediately', () => { @@ -190,6 +191,32 @@ describe('utils', () => { expect(spy).toHaveBeenCalledTimes(2) }) }) + + describe('stop', () => { + beforeEach(() => { + const result = throttle(spy, 2) + stop = result.stop + throttled = result.throttled + }) + + it('should abort pending execution', () => { + throttled() + throttled() + expect(spy).toHaveBeenCalledTimes(1) + + stop() + + jasmine.clock().tick(2) + expect(spy).toHaveBeenCalledTimes(1) + }) + + it('should dismiss future calls', () => { + stop() + throttled() + jasmine.clock().tick(2) + expect(spy).toHaveBeenCalledTimes(0) + }) + }) }) describe('format', () => { diff --git a/packages/rum/src/viewCollection.ts b/packages/rum/src/viewCollection.ts index 5a0cecdec2..2d829eccfd 100644 --- a/packages/rum/src/viewCollection.ts +++ b/packages/rum/src/viewCollection.ts @@ -81,9 +81,13 @@ function newView( viewContext = { id, location, sessionId: session.getId() } // Update the view every time the measures are changing - const scheduleViewUpdate = throttle(monitor(updateView), THROTTLE_VIEW_UPDATE_PERIOD, { - leading: false, - }) + const { throttled: scheduleViewUpdate, stop: stopScheduleViewUpdate } = throttle( + monitor(updateView), + THROTTLE_VIEW_UPDATE_PERIOD, + { + leading: false, + } + ) function updateMeasures(newMeasures: Partial) { measures = { ...measures, ...newMeasures } scheduleViewUpdate() @@ -110,6 +114,8 @@ function newView( end() { stopTimingsTracking() stopEventCountsTracking() + // prevent pending view updates execution + stopScheduleViewUpdate() // Make a final view update updateView() }, diff --git a/packages/rum/test/viewCollection.spec.ts b/packages/rum/test/viewCollection.spec.ts index 51ae3d26a4..096f9e8aed 100644 --- a/packages/rum/test/viewCollection.spec.ts +++ b/packages/rum/test/viewCollection.spec.ts @@ -297,4 +297,25 @@ describe('rum view measures', () => { userActionCount: 0, }) }) + + it('should not update measures after ending a view', () => { + jasmine.clock().install() + expect(getRumEventCount()).toEqual(1) + + lifeCycle.notify(LifeCycleEventType.RESOURCE_ADDED_TO_BATCH) + + expect(getRumEventCount()).toEqual(1) + + history.pushState({}, '', '/bar') + + expect(getRumEventCount()).toEqual(3) + expect(getViewEvent(1).id).toEqual(getViewEvent(0).id) + expect(getViewEvent(2).id).not.toEqual(getViewEvent(0).id) + + jasmine.clock().tick(THROTTLE_VIEW_UPDATE_PERIOD) + + expect(getRumEventCount()).toEqual(3) + + jasmine.clock().uninstall() + }) })