Skip to content

fix: clean up popover capture listeners#42732

Merged
pedronfigueiredo merged 1 commit into
mainfrom
pnf/popover-memory-leak
May 21, 2026
Merged

fix: clean up popover capture listeners#42732
pedronfigueiredo merged 1 commit into
mainfrom
pnf/popover-memory-leak

Conversation

@pedronfigueiredo

@pedronfigueiredo pedronfigueiredo commented May 15, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes a shared Popover listener leak that retained old route trees during send/settings route churn.

Popover registered document keydown and click listeners with capture enabled, but removed them without matching capture options. Since removeEventListener matches on event type, callback, and capture flag, cleanup failed and leaked document listeners retained their React closure scope.

This PR keeps the change scoped to ui/components/component-library/popover/popover.tsx by:

  • defining one shared capture listener options object
  • adding document listeners only while the Popover is open and the matching callback exists
  • removing those listeners with the same callback and capture options
  • adding a regression test that verifies unmount cleanup uses the same listener and options captured from addEventListener

Changelog

CHANGELOG entry: null

Manual testing steps

  1. Build the test extension:

    source ~/.nvm/nvm.sh && nvm use
    yarn build:test:webpack
  2. The yarn llm:memory profiler is available on the separate profiler PR. With that tooling available and this branch's dist/chrome build, run:

    yarn llm:memory -- --iterations 50 --flow send-open-back --sample final --probe cdp --wait-after-flow 500 --extension-path dist/chrome
    yarn llm:memory -- --iterations 100 --flow settings-route --sample final --probe cdp --wait-after-flow 500 --extension-path dist/chrome
  3. Expected post-fix profile from the prior investigation with the Snow WeakMap patch still applied:

    Flow Before After
    settings-route 50x +118.91 MiB +2.22 MiB rerun, +37 listeners
    settings-route 100x n/a -0.34 MiB, +34 listeners
    settings-route 50x paired heap snapshots n/a -5.84 MiB
    send-open-back 50x +111.66 MiB +0.05 MiB, +307 listeners

Validation

  • source ~/.nvm/nvm.sh && nvm use && yarn test:unit ui/components/component-library/popover/popover.test.tsx
  • source ~/.nvm/nvm.sh && nvm use && yarn lint:changed:fix
  • source ~/.nvm/nvm.sh && nvm use && yarn webpack:tsc
  • source ~/.nvm/nvm.sh && nvm use && yarn build:test:webpack

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Low Risk
Low risk: scoped to Popover’s document event listener lifecycle and adds a regression test; main risk is behavior change in when listeners are attached (only when open and handlers provided).

Overview
Fixes a Popover document-listener leak by reusing a shared capture options object and ensuring removeEventListener is called with the same callback and capture options used for addEventListener.

Listeners for keydown (Esc) and click (outside) are now only attached when the popover is open and the corresponding handler prop is provided, and a new unit test asserts the add/remove signatures match on unmount to prevent regressions.

Reviewed by Cursor Bugbot for commit 0ccfb58. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added the team-confirmations Push issues to confirmations team label May 15, 2026
@metamaskbotv2

metamaskbotv2 Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

✨ Files requiring CODEOWNER review ✨

🎨 @MetaMask/design-system-engineers (2 files, +81 -17)
  • 📁 ui/
    • 📁 components/
      • 📁 component-library/
        • 📁 popover/
          • 📄 popover.test.tsx +44 -0
          • 📄 popover.tsx +37 -17

@sonarqubecloud

Copy link
Copy Markdown

@metamaskbotv2

metamaskbotv2 Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor
Builds ready [0ccfb58]
Deprecated Browserify fallback builds
⚡ Performance Benchmarks (Total: 🟢 15 pass · 🟡 9 warn · 🔴 0 fail)

Baseline (latest main): 51036da | Date: 5/2/2026 | Pipeline: 25933813653 | Baseline logs

Interaction Benchmarks · Samples: 5
Benchmarkchrome-webpackfirefox-webpack
loadNewAccount
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]
confirmTx
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]
bridgeUserActions
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • loadNewAccount/load_new_account: -77%
  • loadNewAccount/total: -77%
  • bridgeUserActions/bridge_load_page: -37%
  • bridgeUserActions/bridge_load_asset_picker: -54%
  • bridgeUserActions/bridge_search_token: -30%
  • bridgeUserActions/total: -36%
  • loadNewAccount/load_new_account: -53%
  • loadNewAccount/total: -53%
  • bridgeUserActions/bridge_load_asset_picker: -36%
  • bridgeUserActions/bridge_search_token: -34%
  • bridgeUserActions/total: -27%

🌐 Core Web Vitals — 🟢 good · 🟡 needs improvement · 🔴 poor (web.dev thresholds)

  • 🟡 loadNewAccount/FCP: p75 1.9s
  • 🟡 confirmTx/FCP: p75 1.9s
  • 🟡 bridgeUserActions/FCP: p75 1.9s
Startup Benchmarks · Samples: 100
Benchmarkchrome-webpackfirefox-webpack
startupStandardHome
[Sentry log · main/release]
🟢 [CI log]🟢 [CI log]
startupPowerUserHome
[Sentry log · main/release]
🟡 [CI log]

📈 Results compared to the previous 5 runs on main

  • startupStandardHome/uiStartup: -23%
  • startupStandardHome/load: -18%
  • startupStandardHome/domContentLoaded: -18%
  • startupStandardHome/firstPaint: -42%
  • startupStandardHome/backgroundConnect: -43%
  • startupStandardHome/firstReactRender: -22%
  • startupStandardHome/loadScripts: -18%
  • startupStandardHome/numNetworkReqs: -50%
  • startupStandardHome/domInteractive: -55%
  • startupStandardHome/backgroundConnect: +12%
  • startupStandardHome/initialActions: +20%
  • startupStandardHome/setupStore: -47%
  • startupStandardHome/numNetworkReqs: -45%
  • startupPowerUserHome/uiStartup: -43%
  • startupPowerUserHome/domInteractive: -79%
  • startupPowerUserHome/backgroundConnect: -72%
  • startupPowerUserHome/setupStore: -87%
  • startupPowerUserHome/numNetworkReqs: -78%

🌐 Core Web Vitals — 🟢 good · 🟡 needs improvement · 🔴 poor (web.dev thresholds)

  • 🟡 startupPowerUserHome/LCP: p75 3.0s
User Journey Benchmarks · Samples: 5 · mock API
Benchmarkchrome-webpackfirefox-webpack
onboardingImportWallet
[Sentry log · main/release]
🟢 [CI log]🟢 [CI log]
onboardingNewWallet
[Sentry log · main/release]
🟢 [CI log]🟢 [CI log]
assetDetails
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]
solanaAssetDetails
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]
importSrpHome
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]
sendTransactions
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]
swap
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • onboardingImportWallet/srpButtonToSrpForm: -82%
  • onboardingImportWallet/pwFormToMetricsScreen: +582%
  • onboardingImportWallet/metricsToWalletReadyScreen: -32%
  • onboardingImportWallet/doneButtonToHomeScreen: -80%
  • onboardingImportWallet/openAccountMenuToAccountListLoaded: +26%
  • onboardingImportWallet/total: -45%
  • onboardingNewWallet/srpButtonToPwForm: -75%
  • onboardingNewWallet/createPwToRecoveryScreen: +1141%
  • onboardingNewWallet/skipBackupToMetricsScreen: -66%
  • onboardingNewWallet/agreeButtonToOnboardingSuccess: -18%
  • onboardingNewWallet/doneButtonToAssetList: -32%
  • onboardingNewWallet/total: -28%
  • assetDetails/assetClickToPriceChart: -66%
  • assetDetails/total: -66%
  • solanaAssetDetails/assetClickToPriceChart: -87%
  • solanaAssetDetails/total: -87%
  • importSrpHome/loginToHomeScreen: -37%
  • importSrpHome/openAccountMenuAfterLogin: -80%
  • importSrpHome/homeAfterImportWithNewWallet: -68%
  • importSrpHome/total: -64%
  • swap/openSwapPageFromHome: -97%
  • swap/fetchAndDisplaySwapQuotes: +36%
  • swap/total: +12%

🌐 Core Web Vitals — 🟢 good · 🟡 needs improvement · 🔴 poor (web.dev thresholds)

  • 🟡 assetDetails/INP: p75 208ms
  • 🟡 assetDetails/FCP: p75 1.8s
  • 🟡 solanaAssetDetails/FCP: p75 1.9s
  • 🟡 importSrpHome/FCP: p75 2.1s
  • 🟡 sendTransactions/FCP: p75 1.9s
  • 🟡 swap/FCP: p75 1.9s
Dapp Page Load Benchmarks · Samples: 100
Benchmarkchrome-webpack
dappPageLoad
[Sentry log · main/release]
🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • dappPageLoad/pageLoadTime: +26%
Bundle size diffs
  • background: 58 Bytes (0%)
  • ui: 0 Bytes (0%)
  • common: 20 Bytes (0%)

@pedronfigueiredo pedronfigueiredo marked this pull request as ready for review May 20, 2026 12:37
@pedronfigueiredo pedronfigueiredo requested a review from a team as a code owner May 20, 2026 12:37

@kirillzyusko kirillzyusko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks good, but I have 2 questions:

  1. Do we need to port these changes to DSR repo? (thought popover component may be not there yet)
  2. Do we need to apply the same fix for ModalContent? The code there is very similar to what we had in Popover component so decided to ask

@pedronfigueiredo

Copy link
Copy Markdown
Contributor Author

@kirillzyusko thanks for reviewing. Popover is not on DSR yet, only PopoverHeader, so there's no listener cleanup issue there. ModalContent is similar but it does not have the leak fixed here. It already adds/removes keydown and mousedown without capture options in both calls, so listener identity matches.

@kirillzyusko kirillzyusko self-requested a review May 21, 2026 14:56

@kirillzyusko kirillzyusko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! 🚀

@pedronfigueiredo pedronfigueiredo added this pull request to the merge queue May 21, 2026
Merged via the queue into main with commit 11c224d May 21, 2026
223 of 224 checks passed
@pedronfigueiredo pedronfigueiredo deleted the pnf/popover-memory-leak branch May 21, 2026 18:05
@github-actions github-actions Bot locked and limited conversation to collaborators May 21, 2026
@metamaskbot metamaskbot added the release-13.33.0 Issue or pull request that will be included in release 13.33.0 label May 21, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-13.33.0 Issue or pull request that will be included in release 13.33.0 size-S team-confirmations Push issues to confirmations team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants