Skip to content

Commit

Permalink
🐛 [RUM-3708] slidingSessionWindow clear window after 5 seconds
Browse files Browse the repository at this point in the history
  • Loading branch information
thomas-lebeau committed May 9, 2024
1 parent b0d638c commit 6d48530
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { resetExperimentalFeatures } from '@datadog/browser-core'
import type { Clock } from 'packages/core/test'
import { mockClock } from 'packages/core/test'
import type { TestSetupBuilder } from '../../../../test'
import { appendElement, appendText, createPerformanceEntry, setup } from '../../../../test'
import { LifeCycleEventType } from '../../lifeCycle'
import { RumPerformanceEntryType } from '../../../browser/performanceCollection'
import type { CumulativeLayoutShift } from './trackCumulativeLayoutShift'
import { trackCumulativeLayoutShift } from './trackCumulativeLayoutShift'
import { slidingSessionWindow, trackCumulativeLayoutShift } from './trackCumulativeLayoutShift'

describe('trackCumulativeLayoutShift', () => {
let setupBuilder: TestSetupBuilder
Expand Down Expand Up @@ -215,3 +217,117 @@ describe('trackCumulativeLayoutShift', () => {
})
})
})

fdescribe('slidingSessionWindow', () => {
let clock: Clock

beforeEach(() => {
clock = mockClock()
})

afterEach(() => {
clock.cleanup()
})

it('should return 0 if no layout shift happen', () => {
const window = slidingSessionWindow()

expect(window.value()).toEqual(0)
})

it('should accumulate layout shift values', () => {
const window = slidingSessionWindow()

window.update(createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.2 }))
window.update(createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.5 }))

expect(window.value()).toEqual(0.7)
})

it('should return the element with the largest layout shift', () => {
const window = slidingSessionWindow()

const textNode = appendText('text')
const divElement = appendElement('<div id="div-element"></div>')

window.update(
createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, {
value: 0.2,
sources: [{ node: textNode }, { node: divElement }, { node: textNode }],
})
)

expect(window.largestLayoutShiftTarget()).toEqual(divElement)
})

it('should create a new session window if the gap is more than 1 second', () => {
const window = slidingSessionWindow()

window.update(createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.2 }))

clock.tick(1001)

window.update(createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1 }))

expect(window.value()).toEqual(0.1)
})

it('should create a new session window if the current session window is more than 5 second', () => {
const window = slidingSessionWindow()

window.update(createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0 }))

for (let i = 0; i < 6; i += 1) {
clock.tick(999)
window.update(createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1 }))
} // window 1: 0.5 | window 2: 0.1

expect(window.value()).toEqual(0.1)
})

it('should return largest layout shift target element', () => {
const window = slidingSessionWindow()
const firstElement = appendElement('<div id="first-element"></div>')
const secondElement = appendElement('<div id="second-element"></div>')
const thirdElement = appendElement('<div id="third-element"></div>')

window.update(
createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, {
value: 0.2,
sources: [{ node: firstElement }],
})
)

window.update(
createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, {
value: 0.3,
sources: [{ node: secondElement }],
})
)

window.update(
createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, {
value: 0.1,
sources: [{ node: thirdElement }],
})
)

expect(window.largestLayoutShiftTarget()).toEqual(secondElement)
})

it('should not retain the largest layout shift target element after 5 seconds', () => {
const window = slidingSessionWindow()
const divElement = appendElement('<div id="div-element"></div>')

window.update(
createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, {
value: 0.2,
sources: [{ node: divElement }],
})
)

clock.tick(5001)

expect(window.largestLayoutShiftTarget()).toBeUndefined()
})
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { round, find, ONE_SECOND, noop } from '@datadog/browser-core'
import type { RelativeTime } from '@datadog/browser-core'
import { round, find, ONE_SECOND, noop, setTimeout, clearTimeout } from '@datadog/browser-core'
import type { RelativeTime, TimeoutId } from '@datadog/browser-core'
import { isElementNode } from '../../../browser/htmlDomUtils'
import type { LifeCycle } from '../../lifeCycle'
import { LifeCycleEventType } from '../../lifeCycle'
Expand Down Expand Up @@ -58,19 +58,19 @@ export function trackCumulativeLayoutShift(
maxClsValue = window.value()
const cls = round(maxClsValue, 4)
const clsTarget = window.largestLayoutShiftTarget()
let cslTargetSelector
let clsTargetSelector

if (
clsTarget &&
// Check if the CLS target have been removed from the DOM between the time we collect the target reference and when we compute the selector
clsTarget.isConnected
) {
cslTargetSelector = getSelectorFromElement(clsTarget, configuration.actionNameAttribute)
clsTargetSelector = getSelectorFromElement(clsTarget, configuration.actionNameAttribute)
}

callback({
value: cls,
targetSelector: cslTargetSelector,
targetSelector: clsTargetSelector,
})
}
}
Expand All @@ -82,26 +82,39 @@ export function trackCumulativeLayoutShift(
}
}

function slidingSessionWindow() {
const MAX_WINDOW_LENGTH = 5 * ONE_SECOND
const MAX_UPDATE_GAP = ONE_SECOND

export function slidingSessionWindow() {
let value = 0
let startTime: RelativeTime
let endTime: RelativeTime

let largestLayoutShift = 0
let largestLayoutShiftTarget: HTMLElement | undefined
let largestLayoutShiftTime: RelativeTime
let timeoutId: TimeoutId

function resetWindow(entry: RumLayoutShiftTiming) {
startTime = endTime = entry.startTime
value = entry.value
largestLayoutShift = 0
largestLayoutShiftTarget = undefined
largestLayoutShiftTime = entry.startTime
}

return {
update: (entry: RumLayoutShiftTiming) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => resetWindow(entry), MAX_WINDOW_LENGTH)

const shouldCreateNewWindow =
startTime === undefined ||
entry.startTime - endTime >= ONE_SECOND ||
entry.startTime - startTime >= 5 * ONE_SECOND
entry.startTime - endTime >= MAX_UPDATE_GAP ||
entry.startTime - startTime >= MAX_WINDOW_LENGTH

if (shouldCreateNewWindow) {
startTime = endTime = entry.startTime
value = entry.value
largestLayoutShift = 0
largestLayoutShiftTarget = undefined
resetWindow(entry)
} else {
value += entry.value
endTime = entry.startTime
Expand Down

0 comments on commit 6d48530

Please sign in to comment.