Skip to content

Commit ca3ffe0

Browse files
Integrate romanG/bf-cache-views (#3527) into staging-20
Integrated commit sha: 707f265 Co-authored-by: RomanGaignault <[email protected]>
2 parents b3faac8 + 707f265 commit ca3ffe0

File tree

10 files changed

+187
-1
lines changed

10 files changed

+187
-1
lines changed

packages/core/src/domain/telemetry/telemetryEvent.types.ts

+12
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ export type TelemetryConfigurationEvent = CommonTelemetryProperties & {
161161
* Whether long tasks are tracked
162162
*/
163163
track_long_task?: boolean
164+
/**
165+
* Whether views loaded from the bfcache are tracked
166+
*/
167+
track_bfcache_views?: boolean
164168
/**
165169
* Whether a secure cross-site session cookie is used (deprecated)
166170
*/
@@ -257,6 +261,10 @@ export type TelemetryConfigurationEvent = CommonTelemetryProperties & {
257261
* Whether console.error logs, uncaught exceptions and network errors are tracked
258262
*/
259263
forward_errors_to_logs?: boolean
264+
/**
265+
* The number of displays available to the device
266+
*/
267+
number_of_displays?: number
260268
/**
261269
* The console.* tracked
262270
*/
@@ -407,6 +415,10 @@ export type TelemetryConfigurationEvent = CommonTelemetryProperties & {
407415
* Whether the anonymous users are tracked
408416
*/
409417
track_anonymous_user?: boolean
418+
/**
419+
* Whether a list of allowed origins is used to control SDK execution in browser extension contexts. When enabled, the SDK will check if the current origin matches the allowed origins list before running.
420+
*/
421+
use_allowed_tracking_origins?: boolean
410422
[k: string]: unknown
411423
}
412424
[k: string]: unknown

packages/rum-core/src/domain/configuration/configuration.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ describe('serializeRumConfiguration', () => {
535535
trackViewsManually: true,
536536
trackResources: true,
537537
trackLongTasks: true,
538+
trackBfcacheViews: true,
538539
remoteConfigurationId: '123',
539540
plugins: [{ name: 'foo', getConfigurationTelemetry: () => ({ bar: true }) }],
540541
trackFeatureFlagsForEvents: ['vital'],
@@ -578,6 +579,7 @@ describe('serializeRumConfiguration', () => {
578579
enable_privacy_for_action_name: false,
579580
track_resources: true,
580581
track_long_task: true,
582+
track_bfcache_views: true,
581583
use_worker_url: true,
582584
compress_intake_requests: true,
583585
plugins: [{ name: 'foo', bar: true }],

packages/rum-core/src/domain/configuration/configuration.ts

+8
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ export interface RumInitConfiguration extends InitConfiguration {
122122
* Allows you to control RUM views creation. See [Override default RUM view names](https://docs.datadoghq.com/real_user_monitoring/browser/advanced_configuration/?tab=npm#override-default-rum-view-names) for further information.
123123
*/
124124
trackViewsManually?: boolean | undefined
125+
/**
126+
* Enable the creation of dedicated views for pages restored from the Back-Forward cache.
127+
* @default false
128+
*/
129+
trackBfcacheViews?: boolean | undefined
125130
/**
126131
* Enables collection of resource events.
127132
* @default true
@@ -175,6 +180,7 @@ export interface RumConfiguration extends Configuration {
175180
trackViewsManually: boolean
176181
trackResources: boolean
177182
trackLongTasks: boolean
183+
trackBfcacheViews: boolean
178184
version?: string
179185
subdomain?: string
180186
customerDataTelemetrySampleRate: number
@@ -245,6 +251,7 @@ export function validateAndBuildRumConfiguration(
245251
trackViewsManually: !!initConfiguration.trackViewsManually,
246252
trackResources: !!(initConfiguration.trackResources ?? true),
247253
trackLongTasks: !!(initConfiguration.trackLongTasks ?? true),
254+
trackBfcacheViews: !!initConfiguration.trackBfcacheViews,
248255
subdomain: initConfiguration.subdomain,
249256
defaultPrivacyLevel: objectHasValue(DefaultPrivacyLevel, initConfiguration.defaultPrivacyLevel)
250257
? initConfiguration.defaultPrivacyLevel
@@ -337,6 +344,7 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration) {
337344
track_user_interactions: configuration.trackUserInteractions,
338345
track_resources: configuration.trackResources,
339346
track_long_task: configuration.trackLongTasks,
347+
track_bfcache_views: configuration.trackBfcacheViews,
340348
plugins: configuration.plugins?.map((plugin) => ({
341349
name: plugin.name,
342350
...plugin.getConfigurationTelemetry?.(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { addEventListener, DOM_EVENT } from '@datadog/browser-core'
2+
3+
export function onBFCacheRestore(callback: (event: PageTransitionEvent) => void): () => void {
4+
const { stop } = addEventListener(
5+
{ allowUntrustedEvents: true },
6+
window,
7+
DOM_EVENT.PAGE_SHOW,
8+
(event: PageTransitionEvent) => {
9+
if (event.persisted) {
10+
callback(event)
11+
}
12+
},
13+
{ capture: true }
14+
)
15+
return stop
16+
}

packages/rum-core/src/domain/view/trackViews.spec.ts

+33
Original file line numberDiff line numberDiff line change
@@ -1029,3 +1029,36 @@ describe('service and version', () => {
10291029
expect(getViewUpdate(0).version).toEqual('view version')
10301030
})
10311031
})
1032+
1033+
describe('BFCache views', () => {
1034+
const lifeCycle = new LifeCycle()
1035+
let clock: Clock
1036+
let viewTest: ViewTest
1037+
1038+
beforeEach(() => {
1039+
clock = mockClock()
1040+
1041+
viewTest = setupViewTest({ lifeCycle, partialConfig: { trackBfcacheViews: true } })
1042+
1043+
registerCleanupTask(() => {
1044+
viewTest.stop()
1045+
clock.cleanup()
1046+
})
1047+
})
1048+
1049+
it('should create a new "bf_cache" view when restoring from the BFCache', () => {
1050+
const { getViewCreateCount, getViewEndCount, getViewUpdate, getViewUpdateCount } = viewTest
1051+
1052+
expect(getViewCreateCount()).toBe(1)
1053+
expect(getViewEndCount()).toBe(0)
1054+
1055+
const pageshowEvent = new Event('pageshow') as PageTransitionEvent
1056+
Object.defineProperty(pageshowEvent, 'persisted', { value: true })
1057+
1058+
window.dispatchEvent(pageshowEvent)
1059+
1060+
expect(getViewEndCount()).toBe(1)
1061+
expect(getViewCreateCount()).toBe(2)
1062+
expect(getViewUpdate(getViewUpdateCount() - 1).loadingType).toBe(ViewLoadingType.BF_CACHE)
1063+
})
1064+
})

packages/rum-core/src/domain/view/trackViews.ts

+30
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import { trackInitialViewMetrics } from './viewMetrics/trackInitialViewMetrics'
3838
import type { InitialViewMetrics } from './viewMetrics/trackInitialViewMetrics'
3939
import type { CommonViewMetrics } from './viewMetrics/trackCommonViewMetrics'
4040
import { trackCommonViewMetrics } from './viewMetrics/trackCommonViewMetrics'
41+
import { onBFCacheRestore } from './bfCacheSupport'
42+
import { measureRestoredFCP, measureRestoredLCP, measureRestoredFID } from './viewMetrics/cwvPolyfill'
4143

4244
export interface ViewEvent {
4345
id: string
@@ -117,6 +119,28 @@ export function trackViews(
117119
locationChangeSubscription = renewViewOnLocationChange(locationChangeObservable)
118120
}
119121

122+
if (configuration.trackBfcacheViews) {
123+
onBFCacheRestore((pageshowEvent) => {
124+
currentView.end()
125+
currentView = startNewView(ViewLoadingType.BF_CACHE)
126+
127+
measureRestoredFCP(pageshowEvent, (fcp) => {
128+
currentView.initialViewMetrics.firstContentfulPaint = fcp
129+
measureRestoredLCP(pageshowEvent, (lcp) => {
130+
currentView.initialViewMetrics.largestContentfulPaint = { value: lcp as RelativeTime }
131+
measureRestoredFID(pageshowEvent, (fid) => {
132+
currentView.initialViewMetrics.firstInput = {
133+
delay: fid.delay,
134+
time: fid.time as RelativeTime,
135+
targetSelector: undefined,
136+
}
137+
currentView.scheduleViewUpdate()
138+
})
139+
})
140+
})
141+
})
142+
}
143+
120144
function startNewView(loadingType: ViewLoadingType, startClocks?: ClocksState, viewOptions?: ViewOptions) {
121145
const newlyCreatedView = newView(
122146
lifeCycle,
@@ -358,6 +382,12 @@ function newView(
358382
name = updatedName
359383
triggerViewUpdate()
360384
},
385+
scheduleViewUpdate,
386+
/**
387+
* we need InitialViewMetrics object so that bfCache logic can update it
388+
* with the restored cwv from the polyfill.
389+
*/
390+
initialViewMetrics,
361391
}
362392
}
363393

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { Duration } from '@datadog/browser-core'
2+
import { measureRestoredFCP, measureRestoredLCP, measureRestoredFID } from './cwvPolyfill'
3+
4+
describe('cwvPolyfill', () => {
5+
let originalRAF: typeof requestAnimationFrame
6+
let originalPerformanceNow: () => number
7+
8+
beforeEach(() => {
9+
originalRAF = window.requestAnimationFrame
10+
originalPerformanceNow = performance.now.bind(performance)
11+
12+
window.requestAnimationFrame = (cb: FrameRequestCallback): number => {
13+
cb(performance.now())
14+
return 0
15+
}
16+
})
17+
18+
afterEach(() => {
19+
window.requestAnimationFrame = originalRAF
20+
performance.now = originalPerformanceNow
21+
})
22+
23+
it('should measure restored FCP correctly', (done) => {
24+
const fakePageshowEvent = { timeStamp: 100 } as PageTransitionEvent
25+
performance.now = () => 150
26+
measureRestoredFCP(fakePageshowEvent, (fcp: Duration) => {
27+
expect(fcp).toEqual(50 as Duration)
28+
done()
29+
})
30+
})
31+
32+
it('should measure restored LCP correctly', (done) => {
33+
const fakePageshowEvent = { timeStamp: 100 } as PageTransitionEvent
34+
performance.now = () => 150
35+
measureRestoredLCP(fakePageshowEvent, (lcp: Duration) => {
36+
expect(lcp).toEqual(50 as Duration)
37+
done()
38+
})
39+
})
40+
41+
it('should measure restored FID correctly', (done) => {
42+
const fakePageshowEvent = { timeStamp: 100 } as PageTransitionEvent
43+
performance.now = () => 150
44+
measureRestoredFID(fakePageshowEvent, (fid) => {
45+
expect(fid.delay).toEqual(0 as Duration)
46+
expect(fid.time).toEqual(50 as Duration)
47+
done()
48+
})
49+
})
50+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { Duration } from '@datadog/browser-core'
2+
3+
// Note : measureRestoreNavigationTiming does not exist in this case, because when the page is restored from bfcache,
4+
// the navigation timing is not pertinent anymore.
5+
6+
export function measureRestoredFCP(pageshowEvent: PageTransitionEvent, callback: (fcp: Duration) => void): void {
7+
requestAnimationFrame(() => {
8+
requestAnimationFrame(() => {
9+
const fcp = performance.now() - pageshowEvent.timeStamp
10+
callback(fcp as Duration)
11+
})
12+
})
13+
}
14+
15+
export function measureRestoredLCP(pageshowEvent: PageTransitionEvent, callback: (lcp: Duration) => void): void {
16+
requestAnimationFrame(() => {
17+
requestAnimationFrame(() => {
18+
const lcp = performance.now() - pageshowEvent.timeStamp
19+
callback(lcp as Duration)
20+
})
21+
})
22+
}
23+
export function measureRestoredFID(
24+
pageshowEvent: PageTransitionEvent,
25+
callback: (fid: { delay: Duration; time: Duration }) => void
26+
): void {
27+
requestAnimationFrame(() => {
28+
requestAnimationFrame(() => {
29+
const fidDelay = 0 as Duration
30+
const fidTime = performance.now() - pageshowEvent.timeStamp
31+
callback({ delay: fidDelay, time: fidTime as Duration })
32+
})
33+
})
34+
}

packages/rum-core/src/rawRumEvent.types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export type PageStateServerEntry = { state: PageState; start: ServerDuration }
188188
export const enum ViewLoadingType {
189189
INITIAL_LOAD = 'initial_load',
190190
ROUTE_CHANGE = 'route_change',
191+
BF_CACHE = 'bf_cache',
191192
}
192193

193194
export interface ViewCustomTimings {

0 commit comments

Comments
 (0)