Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 [RUM-8606] Track First Hidden before init #3391

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/rum-core/src/browser/performanceObservable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export enum RumPerformanceEntryType {
NAVIGATION = 'navigation',
PAINT = 'paint',
RESOURCE = 'resource',
VISIBILITY_STATE = 'visibility-state',
}

export interface RumPerformanceLongTaskTiming {
Expand Down Expand Up @@ -171,6 +172,13 @@ export interface RumPerformanceLongAnimationFrameTiming {
toJSON(): Omit<RumPerformanceLongAnimationFrameTiming, 'toJSON'>
}

export interface RumFirstHiddenTiming {
entryType: RumPerformanceEntryType.VISIBILITY_STATE
name: 'hidden'
startTime: RelativeTime
toJSON(): Omit<RumFirstHiddenTiming, 'toJSON'>
}

export type RumPerformanceEntry =
| RumPerformanceResourceTiming
| RumPerformanceLongTaskTiming
Expand All @@ -181,6 +189,7 @@ export type RumPerformanceEntry =
| RumFirstInputTiming
| RumPerformanceEventTiming
| RumLayoutShiftTiming
| RumFirstHiddenTiming

export type EntryTypeToReturnType = {
[RumPerformanceEntryType.EVENT]: RumPerformanceEventTiming
Expand All @@ -192,6 +201,7 @@ export type EntryTypeToReturnType = {
[RumPerformanceEntryType.LONG_ANIMATION_FRAME]: RumPerformanceLongAnimationFrameTiming
[RumPerformanceEntryType.NAVIGATION]: RumPerformanceNavigationTiming
[RumPerformanceEntryType.RESOURCE]: RumPerformanceResourceTiming
[RumPerformanceEntryType.VISIBILITY_STATE]: RumFirstHiddenTiming
}

export function createPerformanceObservable<T extends RumPerformanceEntryType>(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { RelativeTime } from '@datadog/browser-core'
import { DOM_EVENT } from '@datadog/browser-core'
import { createNewEvent, restorePageVisibility, setPageVisibility } from '@datadog/browser-core/test'
import { mockRumConfiguration } from '../../../../test'
import { mockRumConfiguration, mockGlobalPerformanceBuffer } from '../../../../test'
import type { GlobalPerformanceBufferMock } from '../../../../test'
import { trackFirstHidden } from './trackFirstHidden'

describe('trackFirstHidden', () => {
const configuration = mockRumConfiguration()
let firstHidden: { timeStamp: RelativeTime; stop: () => void }
let performanceBufferMock: GlobalPerformanceBufferMock

afterEach(() => {
restorePageVisibility()
Expand Down Expand Up @@ -82,6 +84,38 @@ describe('trackFirstHidden', () => {
})
})

describe('using visibilityState entries', () => {
let originalSupportedEntryTypes: string[] | undefined
beforeEach(() => {
performanceBufferMock = mockGlobalPerformanceBuffer()
if (typeof PerformanceObserver !== 'undefined') {
originalSupportedEntryTypes = PerformanceObserver.supportedEntryTypes as string[]
Object.defineProperty(PerformanceObserver, 'supportedEntryTypes', {
get: () => [...(originalSupportedEntryTypes || []), 'visibility-state'],
configurable: true,
})
}
})
it('should set timestamp to earliest hidden event from performance entries', () => {
setPageVisibility('visible')

performanceBufferMock.addPerformanceEntry({
entryType: 'visibility-state',
name: 'hidden',
startTime: 23,
} as PerformanceEntry)

performanceBufferMock.addPerformanceEntry({
entryType: 'visibility-state',
name: 'hidden',
startTime: 23219031,
} as PerformanceEntry)

firstHidden = trackFirstHidden(configuration)
expect(firstHidden.timeStamp).toBe(23 as RelativeTime)
})
})

function createWindowEventTarget() {
return document.createElement('div') as unknown as Window
}
Expand Down
48 changes: 26 additions & 22 deletions packages/rum-core/src/domain/view/viewMetrics/trackFirstHidden.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
import type { RelativeTime } from '@datadog/browser-core'
import { addEventListeners, DOM_EVENT } from '@datadog/browser-core'
import { addEventListeners, DOM_EVENT, noop } from '@datadog/browser-core'
import type { RumConfiguration } from '../../configuration'
import { supportPerformanceTimingEvent, RumPerformanceEntryType } from '../../../browser/performanceObservable'

export type FirstHidden = ReturnType<typeof trackFirstHidden>

export function trackFirstHidden(configuration: RumConfiguration, eventTarget: Window = window) {
let timeStamp: RelativeTime
let stopListeners: () => void | undefined

if (document.visibilityState === 'hidden') {
timeStamp = 0 as RelativeTime
} else {
timeStamp = Infinity as RelativeTime
;({ stop: stopListeners } = addEventListeners(
configuration,
eventTarget,
[DOM_EVENT.PAGE_HIDE, DOM_EVENT.VISIBILITY_CHANGE],
(event) => {
if (event.type === DOM_EVENT.PAGE_HIDE || document.visibilityState === 'hidden') {
timeStamp = event.timeStamp as RelativeTime
stopListeners()
}
},
{ capture: true }
))
return { timeStamp: 0 as RelativeTime, stop: noop }
}

if (supportPerformanceTimingEvent(RumPerformanceEntryType.VISIBILITY_STATE)) {
const firstHiddenEntry = performance.getEntriesByType('visibility-state').find((entry) => entry.name === 'hidden')
if (firstHiddenEntry) {
return { timeStamp: firstHiddenEntry.startTime as RelativeTime, stop: noop }
}
}

let timeStamp: RelativeTime = Infinity as RelativeTime

const { stop } = addEventListeners(
configuration,
eventTarget,
[DOM_EVENT.PAGE_HIDE, DOM_EVENT.VISIBILITY_CHANGE],
(event) => {
if (event.type === DOM_EVENT.PAGE_HIDE || document.visibilityState === 'hidden') {
timeStamp = event.timeStamp as RelativeTime
stop()
}
},
{ capture: true }
)

return {
get timeStamp() {
return timeStamp
},
stop() {
stopListeners?.()
},
stop,
}
}