Skip to content

Commit f166a90

Browse files
Lms24claude
andcommitted
feat(browser): Check for clock drift on page lifecycle events
Also check on `freeze`/`resume` (Chromium only) and `pagehide`/`pageshow` (back/forward cache). This makes the time origin correction happen at the pause, not at the next regular timestamp call. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 4a8b3bc commit f166a90

2 files changed

Lines changed: 23 additions & 0 deletions

File tree

‎packages/browser/src/client.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,17 @@ export class BrowserClient extends Client<BrowserClientOptions> {
162162
});
163163
}
164164
});
165+
166+
// `freeze` and `resume` are only fired by Chromium browsers. Other browsers never fire them, so these listeners
167+
// are no-ops there.
168+
WINDOW.document.addEventListener('freeze', () => timestampInSeconds());
169+
WINDOW.document.addEventListener('resume', () => timestampInSeconds());
165170
}
166171

172+
// Pages restored from the back/forward cache were paused while they were cached.
173+
WINDOW.addEventListener?.('pagehide', () => timestampInSeconds());
174+
WINDOW.addEventListener?.('pageshow', () => timestampInSeconds());
175+
167176
if (userInfo) {
168177
this.on('beforeSendSession', addAutoIpAddressToSession);
169178
}

‎packages/browser/test/client.test.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ describe('BrowserClient', () => {
6363
expect(SentryCore.timestampInSeconds).toHaveBeenCalled();
6464
});
6565

66+
it.each([
67+
['freeze', () => WINDOW.document],
68+
['resume', () => WINDOW.document],
69+
['pagehide', () => WINDOW],
70+
['pageshow', () => WINDOW],
71+
])('checks the clocks for drift on %s', (eventName, getTarget) => {
72+
client = new BrowserClient(getDefaultBrowserClientOptions());
73+
vi.mocked(SentryCore.timestampInSeconds).mockClear();
74+
75+
getTarget().dispatchEvent(new Event(eventName));
76+
77+
expect(SentryCore.timestampInSeconds).toHaveBeenCalled();
78+
});
79+
6680
it('does not flush outcomes when sendClientReports is disabled but still flushes the client', async () => {
6781
client = new BrowserClient(getDefaultBrowserClientOptions({ sendClientReports: false }));
6882
const flushSpy = vi.spyOn(client, 'flush').mockReturnValue(Promise.resolve(true) as any);

0 commit comments

Comments
 (0)