feat: optimize page leave events for bfcache#2458
Conversation
📝 WalkthroughWalkthroughAdds an isIE11 detector to analytics-js-common, uses it to conditionally register beforeunload only on IE11, updates tests to simulate IE11 and switch a test to pagehide, removes the local isIE11 export from the capabilities manager detection module, and increments a size-limit value. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant App
participant PageUtils as analytics-js-common/utilities/page
participant Detect as analytics-js-common/utilities/detect
Note over App,PageUtils: onPageLeave(callback) initialization
App->>PageUtils: onPageLeave(callback)
PageUtils->>Detect: isIE11()
alt IE11
Note right of PageUtils: register beforeunload listener
PageUtils->>PageUtils: addEventListener('beforeunload', ...)
else Non-IE11
Note right of PageUtils: skip beforeunload
end
Note over PageUtils: register blur, focus, pagehide, visibilitychange listeners
rect rgba(230,245,255,0.6)
Note over App,PageUtils: Page exit signals
App-->>PageUtils: pagehide / visibilitychange / blur
PageUtils-->>App: invoke callback (true/false depending on path)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/analytics-js-common/src/utilities/page.ts (1)
51-55: Also listen for pagehide on window to match the specpagehide is a Window event in specs; some engines may not deliver it to document. Safe to register on window in addition to document.
- document.addEventListener('pagehide', () => { + // Listen on both Window and Document for broader compatibility + (globalThis as typeof window).addEventListener('pagehide', () => { + isAccessible = document.visibilityState === 'hidden'; + handleOnLeave(); + }); + document.addEventListener('pagehide', () => { isAccessible = document.visibilityState === 'hidden'; handleOnLeave(); });
🧹 Nitpick comments (4)
packages/analytics-js/__tests__/app/RudderAnalytics.test.ts (1)
804-806: Good switch to pagehide; consider exercising bfcache via PageTransitionEventDispatching pagehide on document aligns with the listener site. Optionally, emit a PageTransitionEvent (when available) to validate bfcache scenarios (persisted true/false).
- const event = new Event('pagehide'); - document.dispatchEvent(event); + const event = + typeof (globalThis as any).PageTransitionEvent === 'function' + ? new (globalThis as any).PageTransitionEvent('pagehide', { persisted: false }) + : new Event('pagehide'); + document.dispatchEvent(event);packages/analytics-js-common/src/utilities/page.ts (1)
39-45: Blur-based fallback can overfire on simple tab switchesBlur fires on tab switches too; consider narrowing to iOS Safari only to avoid false positives.
- (globalThis as typeof window).addEventListener('blur', () => { + const isIosSafari = + /iP(hone|ad|od)/.test(globalThis.navigator?.platform || '') && + /Safari/.test(globalThis.navigator?.userAgent || '') && + !/Chrome|CriOS|FxiOS/.test(globalThis.navigator?.userAgent || ''); + (globalThis as typeof window).addEventListener('blur', () => { isAccessible = true; handleOnLeave(); - }); + }, { passive: true });packages/analytics-js/src/components/capabilitiesManager/detection/index.ts (1)
2-2: isIE11 re-export removed — verify no remaining imports rely on this pathSearch for imports from './detection' pulling isIE11 and update them to @rudderstack/analytics-js-common/utilities/detect to avoid breakages.
Optionally, keep a transitional re-export to ease migration:
export { isBrowser, isNode, hasCrypto, hasUAClientHints, hasBeacon } from './browser'; +export { isIE11 } from '@rudderstack/analytics-js-common/utilities/detect';packages/analytics-js-common/__tests__/utilities/page.test.ts (1)
49-51: Prefer spying on navigator.userAgent over direct assignmentuserAgent may be non-writable; spying is safer and avoids global mutation. Consider a small helper to DRY the pattern.
Example helper:
const withUserAgent = (ua: string, fn: () => void) => { const spy = jest.spyOnProperty(globalThis.navigator, 'userAgent', 'get').mockReturnValue(ua as any); try { fn(); } finally { spy.mockRestore(); } };Usage:
withUserAgent('Mozilla/5.0 (Windows NT 10.0; Trident/7.0; rv:11.0) like Gecko', () => { const evCallback = jest.fn(); onPageLeave(evCallback); dispatchWindowEvent('beforeunload'); expect(evCallback).toHaveBeenCalledTimes(1); expect(evCallback).toHaveBeenCalledWith(false); });Also applies to: 106-109, 118-121, 181-184, 201-204
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
packages/analytics-js-common/__tests__/utilities/detect.test.ts(2 hunks)packages/analytics-js-common/__tests__/utilities/page.test.ts(6 hunks)packages/analytics-js-common/src/utilities/detect.ts(1 hunks)packages/analytics-js-common/src/utilities/page.ts(2 hunks)packages/analytics-js/.size-limit.mjs(1 hunks)packages/analytics-js/__tests__/app/RudderAnalytics.test.ts(1 hunks)packages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.ts(1 hunks)packages/analytics-js/src/components/capabilitiesManager/detection/browser.ts(1 hunks)packages/analytics-js/src/components/capabilitiesManager/detection/index.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/002-javascript-typescript.mdc)
Code must work in browsers AND service workers (no Node.js APIs)
Files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js-common/src/utilities/detect.tspackages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js-common/__tests__/utilities/detect.test.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.tspackages/analytics-js-common/src/utilities/page.tspackages/analytics-js-common/__tests__/utilities/page.test.ts
packages/*/__tests__/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place unit tests under packages/[package-name]/tests/
Files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js-common/__tests__/utilities/detect.test.tspackages/analytics-js-common/__tests__/utilities/page.test.ts
packages/*/__tests__/**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Jest for tests and MSW for HTTP mocking
Files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js-common/__tests__/utilities/detect.test.tspackages/analytics-js-common/__tests__/utilities/page.test.ts
**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Adhere to the repository’s ESLint configuration for JavaScript/TypeScript code
Files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js-common/src/utilities/detect.tspackages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js-common/__tests__/utilities/detect.test.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.tspackages/analytics-js-common/src/utilities/page.tspackages/analytics-js-common/__tests__/utilities/page.test.ts
packages/*/src/**
📄 CodeRabbit inference engine (CLAUDE.md)
Place all package source code under packages/[package-name]/src/
Files:
packages/analytics-js-common/src/utilities/detect.tspackages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.tspackages/analytics-js-common/src/utilities/page.ts
packages/analytics-js/src/**/*.{js,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use @preact/signals-core for reactive state in the core SDK (analytics-js)
Files:
packages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.ts
packages/!(analytics-js-common)/**/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Prefer shared TypeScript types from analytics-js-common across packages
Files:
packages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.ts
🧠 Learnings (16)
📓 Common learnings
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1754
File: packages/analytics-js-service-worker/README.md:0-0
Timestamp: 2024-10-08T15:52:59.819Z
Learning: saikumarrs prefers simplified language in documentation, avoiding redundant phrases.
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1754
File: packages/analytics-js-service-worker/README.md:0-0
Timestamp: 2024-06-14T09:50:33.511Z
Learning: saikumarrs prefers simplified language in documentation, avoiding redundant phrases.
📚 Learning: 2024-10-09T06:41:05.073Z
Learnt from: MoumitaM
PR: rudderlabs/rudder-sdk-js#1876
File: packages/analytics-js/src/app/RudderAnalytics.ts:0-0
Timestamp: 2024-10-09T06:41:05.073Z
Learning: The `trackPageLifecycleEvents` method in `packages/analytics-js/src/app/RudderAnalytics.ts` does not require refactoring as the current implementation is preferred.
Applied to files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.tspackages/analytics-js-common/src/utilities/page.ts
📚 Learning: 2024-07-27T07:02:57.329Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1708
File: packages/analytics-js/__tests__/nativeSdkLoader.js:31-33
Timestamp: 2024-07-27T07:02:57.329Z
Learning: The loading snippet in `packages/analytics-js/__tests__/nativeSdkLoader.js` is a standard part of the SDK, and no changes are desired on it.
Applied to files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.ts
📚 Learning: 2024-10-08T15:52:59.819Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1708
File: packages/analytics-js-plugins/__tests__/deviceModeTransformation/index.test.ts:10-11
Timestamp: 2024-10-08T15:52:59.819Z
Learning: The misuse of `IHttpClient` in type assertions within the file `packages/analytics-js-plugins/__tests__/deviceModeTransformation/index.test.ts` has been corrected by the user.
Applied to files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js-common/__tests__/utilities/detect.test.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.tspackages/analytics-js-common/__tests__/utilities/page.test.ts
📚 Learning: 2024-10-08T15:52:59.819Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1754
File: packages/analytics-js-common/src/utilities/page.ts:1-34
Timestamp: 2024-10-08T15:52:59.819Z
Learning: The compatibility issue with `globalThis` in `packages/analytics-js-common/src/utilities/page.ts` is handled elsewhere in the codebase as per user `saikumarrs`.
Applied to files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.tspackages/analytics-js-common/src/utilities/page.tspackages/analytics-js-common/__tests__/utilities/page.test.ts
📚 Learning: 2024-07-11T08:44:37.825Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1782
File: packages/analytics-js-common/src/utilities/eventMethodOverloads.ts:0-0
Timestamp: 2024-07-11T08:44:37.825Z
Learning: In `packages/analytics-js-common/src/utilities/eventMethodOverloads.ts`, the `delete` operator has been replaced with setting the value to `undefined` for better performance.
Applied to files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.tspackages/analytics-js-common/src/utilities/page.ts
📚 Learning: 2024-11-08T13:17:51.356Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1823
File: packages/analytics-js-common/src/utilities/retryQueue/utilities.ts:0-0
Timestamp: 2024-11-08T13:17:51.356Z
Learning: The issue regarding missing test coverage for the `findOtherQueues` function in `packages/analytics-js-common/src/utilities/retryQueue/utilities.ts` is no longer applicable.
Applied to files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.ts
📚 Learning: 2024-10-28T08:03:12.163Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1902
File: packages/analytics-js-plugins/src/utilities/eventsDelivery.ts:0-0
Timestamp: 2024-10-28T08:03:12.163Z
Learning: In `packages/analytics-js-plugins/src/utilities/eventsDelivery.ts`, the issue regarding inconsistent error handling approaches in `getDMTDeliveryPayload` is no longer valid.
Applied to files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.ts
📚 Learning: 2024-11-09T06:40:30.520Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1823
File: packages/analytics-js-common/src/types/LoadOptions.ts:0-0
Timestamp: 2024-11-09T06:40:30.520Z
Learning: In the `packages/analytics-js-common/src/types/LoadOptions.ts` file, the `dataplanes` property within the `SourceConfigResponse` type has been removed as it is no longer necessary.
Applied to files:
packages/analytics-js/__tests__/app/RudderAnalytics.test.tspackages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.tspackages/analytics-js/src/components/capabilitiesManager/detection/index.tspackages/analytics-js/src/components/capabilitiesManager/detection/browser.ts
📚 Learning: 2024-07-27T07:02:57.329Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1740
File: packages/analytics-js-common/src/utilities/url.ts:10-10
Timestamp: 2024-07-27T07:02:57.329Z
Learning: The `isValidURL` function in `packages/analytics-js-common/src/utilities/url.ts` uses `string | undefined` instead of `any` for the `url` parameter to enhance type safety.
Applied to files:
packages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.ts
📚 Learning: 2024-07-27T07:02:57.329Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1730
File: packages/analytics-js/src/constants/logMessages.ts:0-0
Timestamp: 2024-07-27T07:02:57.329Z
Learning: The `INVALID_POLYFILL_URL_WARNING` function in `packages/analytics-js/src/constants/logMessages.ts` should use `string` instead of `any` for the `customPolyfillUrl` parameter to enhance type safety.
Applied to files:
packages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.ts
📚 Learning: 2024-11-09T06:16:23.609Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1823
File: packages/analytics-js-common/src/utilities/time.ts:19-29
Timestamp: 2024-11-09T06:16:23.609Z
Learning: In the file `packages/analytics-js-common/src/utilities/time.ts`, prefer using the regex-based implementation for timezone detection in the `getTimezone()` function instead of `Intl.DateTimeFormat().resolvedOptions().timeZone`.
Applied to files:
packages/analytics-js-common/__tests__/utilities/detect.test.ts
📚 Learning: 2024-10-08T15:52:59.819Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1730
File: packages/analytics-js/src/components/utilities/url.ts:10-10
Timestamp: 2024-10-08T15:52:59.819Z
Learning: The `removeTrailingSlashes` function in `packages/analytics-js/src/components/utilities/url.ts` now uses optional chaining for better safety and readability.
Applied to files:
packages/analytics-js/src/components/capabilitiesManager/detection/browser.ts
📚 Learning: 2024-11-06T16:32:46.257Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1823
File: packages/analytics-js-plugins/src/xhrQueue/index.ts:34-34
Timestamp: 2024-11-06T16:32:46.257Z
Learning: The deprecated plugins have been removed from the `PluginName` type, so the explicit `PluginName` type annotation is no longer necessary in `packages/analytics-js-plugins/src/xhrQueue/index.ts`.
Applied to files:
packages/analytics-js/src/components/capabilitiesManager/detection/browser.ts
📚 Learning: 2024-10-07T05:43:26.038Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1823
File: packages/analytics-js-integrations/__tests__/integrations/Amplitude/browser.test.js:193-194
Timestamp: 2024-10-07T05:43:26.038Z
Learning: In the Amplitude integration tests (`browser.test.js`), the tests no longer rely on `appVersion`, so including `appVersion` in the mocked navigator object is unnecessary.
Applied to files:
packages/analytics-js-common/__tests__/utilities/page.test.ts
📚 Learning: 2024-11-08T12:31:40.009Z
Learnt from: saikumarrs
PR: rudderlabs/rudder-sdk-js#1823
File: packages/analytics-js-integrations/src/integrations/Sprig/browser.js:10-22
Timestamp: 2024-11-08T12:31:40.009Z
Learning: Tests for the constructor in `packages/analytics-js-integrations/src/integrations/Sprig/browser.js` are not required.
Applied to files:
packages/analytics-js-common/__tests__/utilities/page.test.ts
🧬 Code graph analysis (3)
packages/analytics-js/__tests__/app/RudderAnalytics.test.ts (1)
packages/analytics-v1.1/src/utils/pageProperties.js (1)
document(53-53)
packages/analytics-js-common/__tests__/utilities/detect.test.ts (1)
packages/analytics-js-common/src/utilities/detect.ts (1)
isIE11(9-9)
packages/analytics-js-common/src/utilities/page.ts (1)
packages/analytics-js-common/src/utilities/detect.ts (1)
isIE11(9-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Code quality checks
- GitHub Check: Unit Tests and Lint
🔇 Additional comments (7)
packages/analytics-js/.size-limit.mjs (1)
116-116: Size limit bumped — confirm CODEOWNERS approvalThe file header says “DO NOT EDIT … contact CODEOWNERS.” Please confirm approval and capture rationale in the PR description or commit message.
packages/analytics-js/src/components/capabilitiesManager/detection/browser.ts (1)
23-23: Stale imports cleared: verified no remaining imports of./detection/browser; allisIE11references correctly use the shared detector from@rudderstack/analytics-js-common.packages/analytics-js-common/src/utilities/page.ts (1)
30-37: Gate beforeunload to IE11 — nice bfcache-friendly changeScoped beforeunload to IE11 to avoid bfcache busting on modern browsers. Looks correct.
packages/analytics-js-common/__tests__/utilities/detect.test.ts (2)
1-1: Import changes look correctisIE11 should come from analytics-js-common now. LGTM.
8-8: No-op changeNothing to note here.
packages/analytics-js-common/__tests__/utilities/page.test.ts (2)
19-20: Good: snapshot original UACapturing original UA prevents test bleed. LGTM.
1-220: Confirm listener cleanup to avoid accumulation across testsIf onPageLeave registers listeners globally, ensure it de-dupes or returns a disposer that tests can call. Otherwise callbacks may accumulate.
size-limit report 📦
|
|
There was a problem hiding this comment.
Pull Request Overview
This PR optimizes browser back/forward cache (bfcache) performance by restricting the beforeunload event handler to only IE11, as this event registration negatively impacts bfcache optimization in modern browsers.
- Moved IE11 detection from analytics-js package to shared analytics-js-common utilities
- Conditionally registered
beforeunloadevent only for IE11 browsers - Updated tests to reflect IE11-specific behavior and added comprehensive browser detection tests
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/analytics-js/src/components/capabilitiesManager/detection/index.ts | Removed isIE11 export from local detection module |
| packages/analytics-js/src/components/capabilitiesManager/detection/browser.ts | Removed isIE11 function implementation |
| packages/analytics-js/src/components/capabilitiesManager/CapabilitiesManager.ts | Updated import to use isIE11 from shared utilities |
| packages/analytics-js/tests/app/RudderAnalytics.test.ts | Changed test to use pagehide event instead of beforeunload |
| packages/analytics-js/.size-limit.mjs | Increased bundle size limit to accommodate changes |
| packages/analytics-js-common/src/utilities/page.ts | Added conditional beforeunload registration for IE11 only |
| packages/analytics-js-common/src/utilities/detect.ts | Added isIE11 detection function to shared utilities |
| packages/analytics-js-common/tests/utilities/page.test.ts | Updated tests to mock IE11 user agent for beforeunload tests |
| packages/analytics-js-common/tests/utilities/detect.test.ts | Added comprehensive tests for isIE11 detection |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.



PR Description
As
beforeunloadevent registration impacts bfcache optimisation on modern browsers, so restricting it to only IE 11.Linear task (optional)
https://linear.app/rudderstack/issue/SDK-3739/spike-investigate-if-we-can-get-away-with-beforeunload-event-handler
Cross Browser Tests
Please confirm you have tested for the following browsers:
Sanity Suite
Security
Summary by CodeRabbit
Bug Fixes
Refactor
Tests
Chores