@@ -187,6 +187,89 @@ test('initialization - should still use L2 timings when responseStart is 0', asy
187187 resetAgent ( testAgent )
188188} )
189189
190+ test ( 'initialization - should still store in relative timestamps when called with L1/absolute timings' , async ( ) => {
191+ const testAgent = setupAgent ( )
192+
193+ // Save original values
194+ const originalPerformanceNavigationTiming = global . PerformanceNavigationTiming
195+ const originalPerformanceTiming = performance . timing
196+
197+ // Simulate legacy browser without PerformanceNavigationTiming
198+ delete global . PerformanceNavigationTiming
199+
200+ // Mock performance.timing with absolute timestamps (legacy API)
201+ const mockAbsoluteTiming = {
202+ navigationStart : 1234567890000 ,
203+ domContentLoadedEventEnd : 1234567890250 ,
204+ loadEventEnd : 1234567890300
205+ }
206+ Object . defineProperty ( performance , 'timing' , {
207+ value : mockAbsoluteTiming ,
208+ writable : true ,
209+ configurable : true
210+ } )
211+
212+ const sessionTraceInstrument = new SessionTrace ( testAgent )
213+ await new Promise ( process . nextTick )
214+ const testAggregate = sessionTraceInstrument . featAggregate
215+
216+ // Mock timeKeeper to have deterministic timestamp conversion
217+ testAggregate . agentRef . runtime . timeKeeper = {
218+ ready : true ,
219+ correctAbsoluteTimestamp : ( val ) => val , // No correction
220+ convertAbsoluteTimestamp : ( val ) => val - 1234567890000 // Convert to relative by subtracting navigationStart
221+ }
222+
223+ // Mock drain to prevent buffer clearing
224+ const drainSpy = jest . spyOn ( testAggregate , 'drain' ) . mockImplementation ( ( ) => { } )
225+ const storeTimingSpy = jest . spyOn ( testAggregate . traceStorage , 'storeTiming' )
226+
227+ // Act - triggers initialization, should use legacy path with absolute timestamps
228+ testAggregate . ee . emit ( 'rumresp' , [ { st : 1 , sts : MODE . FULL } ] )
229+ await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) )
230+
231+ // Assertions
232+ // - Should be called with performance.timing and isAbsoluteTimestamp=true
233+ expect ( storeTimingSpy ) . toHaveBeenCalledWith ( mockAbsoluteTiming , true )
234+
235+ // - Verify that timing nodes were stored with timestamps converted from absolute to relative
236+ const bufferedEvents = testAggregate . events . get ( )
237+
238+ expect ( bufferedEvents ) . toContainEqual ( expect . objectContaining ( {
239+ n : 'navigationStart' ,
240+ s : 0 , // 1234567890000 - 1234567890000 = 0
241+ e : 0 ,
242+ o : 'document' ,
243+ t : 'timing'
244+ } ) )
245+
246+ expect ( bufferedEvents ) . toContainEqual ( expect . objectContaining ( {
247+ n : 'domContentLoadedEventEnd' ,
248+ s : 250 , // 1234567890250 - 1234567890000 = 250
249+ e : 250 ,
250+ o : 'document' ,
251+ t : 'timing'
252+ } ) )
253+
254+ expect ( bufferedEvents ) . toContainEqual ( expect . objectContaining ( {
255+ n : 'loadEventEnd' ,
256+ s : 300 , // 1234567890300 - 1234567890000 = 300
257+ e : 300 ,
258+ o : 'document' ,
259+ t : 'timing'
260+ } ) )
261+
262+ // Cleanup
263+ drainSpy . mockRestore ( )
264+ Object . defineProperty ( performance , 'timing' , {
265+ value : originalPerformanceTiming ,
266+ writable : true ,
267+ configurable : true
268+ } )
269+ global . PerformanceNavigationTiming = originalPerformanceNavigationTiming
270+ resetAgent ( testAgent )
271+ } )
272+
190273test ( 'tracks previously stored events and processes them once per occurrence' , done => {
191274 document . addEventListener ( 'visibilitychange' , ( ) => 1 )
192275 document . addEventListener ( 'visibilitychange' , ( ) => 2 )
0 commit comments