-
Notifications
You must be signed in to change notification settings - Fork 141
/
pageStateHistory.ts
128 lines (108 loc) · 3.97 KB
/
pageStateHistory.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import type { Duration, RelativeTime } from '@datadog/browser-core'
import {
elapsed,
ValueHistory,
SESSION_TIME_OUT_DELAY,
toServerDuration,
addEventListeners,
relativeNow,
DOM_EVENT,
} from '@datadog/browser-core'
import type { PageStateServerEntry } from '../../rawRumEvent.types'
// Arbitrary value to cap number of element for memory consumption in the browser
export const MAX_PAGE_STATE_ENTRIES = 4000
// Arbitrary value to cap number of element for backend & to save bandwidth
export const MAX_PAGE_STATE_ENTRIES_SELECTABLE = 500
export const PAGE_STATE_CONTEXT_TIME_OUT_DELAY = SESSION_TIME_OUT_DELAY
export const enum PageState {
ACTIVE = 'active',
PASSIVE = 'passive',
HIDDEN = 'hidden',
FROZEN = 'frozen',
TERMINATED = 'terminated',
}
export type PageStateEntry = { state: PageState; startTime: RelativeTime }
export interface PageStateHistory {
findAll: (startTime: RelativeTime, duration: Duration) => PageStateServerEntry[] | undefined
addPageState(nextPageState: PageState, startTime?: RelativeTime): void
stop: () => void
}
export function startPageStateHistory(
maxPageStateEntriesSelectable = MAX_PAGE_STATE_ENTRIES_SELECTABLE
): PageStateHistory {
const pageStateHistory = new ValueHistory<PageStateEntry>(PAGE_STATE_CONTEXT_TIME_OUT_DELAY, MAX_PAGE_STATE_ENTRIES)
let currentPageState: PageState
addPageState(getPageState(), relativeNow())
const { stop: stopEventListeners } = addEventListeners(
window,
[
DOM_EVENT.PAGE_SHOW,
DOM_EVENT.FOCUS,
DOM_EVENT.BLUR,
DOM_EVENT.VISIBILITY_CHANGE,
DOM_EVENT.RESUME,
DOM_EVENT.FREEZE,
DOM_EVENT.PAGE_HIDE,
],
(event) => {
// Only get events fired by the browser to avoid false currentPageState changes done with custom events
// cf: developer extension auto flush: https://github.com/DataDog/browser-sdk/blob/2f72bf05a672794c9e33965351964382a94c72ba/developer-extension/src/panel/flushEvents.ts#L11-L12
if (event.isTrusted) {
addPageState(computePageState(event), event.timeStamp as RelativeTime)
}
},
{ capture: true }
)
function addPageState(nextPageState: PageState, startTime = relativeNow()) {
if (nextPageState === currentPageState) {
return
}
currentPageState = nextPageState
pageStateHistory.closeActive(startTime)
pageStateHistory.add({ state: currentPageState, startTime }, startTime)
}
return {
findAll: (eventStartTime: RelativeTime, duration: Duration): PageStateServerEntry[] | undefined => {
const pageStateEntries = pageStateHistory.findAll(eventStartTime, duration)
if (pageStateEntries.length === 0) {
return
}
const pageStateServerEntries = []
// limit the number of entries to return
const limit = Math.max(0, pageStateEntries.length - maxPageStateEntriesSelectable)
// loop page state entries backward to return the selected ones in desc order
for (let index = pageStateEntries.length - 1; index >= limit; index--) {
const pageState = pageStateEntries[index]
// compute the start time relative to the event start time (ex: to be relative to the view start time)
const relativeStartTime = elapsed(eventStartTime, pageState.startTime)
pageStateServerEntries.push({
state: pageState.state,
start: toServerDuration(relativeStartTime),
})
}
return pageStateServerEntries
},
addPageState,
stop: () => {
stopEventListeners()
pageStateHistory.stop()
},
}
}
function computePageState(event: Event) {
if (event.type === DOM_EVENT.FREEZE) {
return PageState.FROZEN
} else if (event.type === DOM_EVENT.PAGE_HIDE) {
return (event as PageTransitionEvent).persisted ? PageState.FROZEN : PageState.TERMINATED
}
return getPageState()
}
function getPageState() {
if (document.visibilityState === 'hidden') {
return PageState.HIDDEN
}
if (document.hasFocus()) {
return PageState.ACTIVE
}
return PageState.PASSIVE
}