Skip to content

release(runway): cherry-pick fix: Fix Firefox detatched-window memory leak by avoiding DocumentPictureInPicture instantiation in Snow hook cp-13.39.2 - #44401

Merged
Gudahtt merged 1 commit into
release/13.39.2from
runway-cherry-pick-13.39.2-1783948796
Jul 13, 2026
Merged

Gudahtt merged 1 commit into
release/13.39.2from
runway-cherry-pick-13.39.2-1783948796

Conversation

@runway-github

@runway-github runway-github Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Motivation

On Firefox, every closed MetaMask window (popup, notification, home) is
retained in memory
(#42891
and recent internal reports).

  • Rate: ~70 detached window entries (~105MB, fresh test wallet) per
    popup open/close, linear with use. 30 cycles leave 3.56GB (13.37.0)
    / 3.31GB (13.38.1) of detached documents, surviving forced GC.
    State-heavy wallets multiply every retained document.
  • Mechanism (heap-graph traced, intervention-tested): Snow's
    picture-in-picture hook reads
    win?.documentPictureInPicture?.requestWindow in every window. The read
    lazily instantiates a per-window DocumentPictureInPicture, and
    Firefox's cycle collector fails to break the instance's
    preserved-wrapper cycle after window close, pinning the entire realm.
  • Why now: Firefox shipped the API in 151 (May 2026). The April-2023
    hook was dormant until users auto-updated. The collection failure itself
    is a Gecko defect (upstream filing in progress; prior art: Bugzilla
    1902438
    ), but
    this fix does not depend on Mozilla.

Description

Applies the upstream Snow fix
(LavaMoat/snow#171) as a
Yarn patch: the hook moves from the per-window instance to
DocumentPictureInPicture.prototype, reached through the interface
object, which does not invoke the instance-creating getter.

  • Call semantics unchanged: instance lookup falls through to the patched
    prototype, this stays the instance, same wrapped native. Snow's PiP
    realm monitoring is preserved.
  • The patch covers snow.prod.js and snow.js in addition to
    src/request.js: the extension consumes Snow's prebuilt bundle as a
    LavaMoat static shim (LavamoatPlugin), and webpack never processes
    @lavamoat/snow/**.
  • Interim: remove this patch when @lavamoat/snow is bumped to a
    release containing the upstream fix. The bump is the removal trigger.

Cherry-pick candidacy

  • Affected population: every Firefox user on 151+ (effectively the whole
    Firefox base via auto-update). Reproduces from a fresh wallet in five
    popup opens. No workaround short of a browser restart.
  • Proposed: next RC, with a hotfix on the current production line worth
    considering.
  • Snow is security-team-owned. Their sign-off on shipping a patched Snow
    is part of this review.

Verification

Check Result
Intervention: official 13.37.0 artifact, only this hook changed, 30
cycles +2,100 docs (3.35GB) → -13 (1.6MB total). Hook is the sole
delta, so this is also the causal proof
This PR's CI artifact (d4dd55f), probed directly Patched hook
present; 5 cycles: +7 KB-scale entries (1.1MB) vs +345 docs (+534MB)
stock
Field Original reporter confirms the leak is gone on this PR's CI
build
Independent repro Manual steps + automated probe:
https://github.com/MajorLift/snow/tree/verification/pip-realm-leak/verification

|

Related issues

Fixes: #42891

Manual testing steps

  1. Install this PR's Firefox build (metamaskbot comment) in a fresh
    profile via about:debugging → Load Temporary Add-on. Onboard with a
    test SRP.
  2. Confirm the fix is live: extension console →
    DocumentPictureInPicture.prototype.requestWindow.name → "open"
    (stock: "requestWindow").
  3. Open/close the popup five times, visiting activity or a swap view on
    a few opens.
  4. about:memory → Minimize memory usage → Measure → filter detached:
    no moz-extension UI-document cohorts (stock: ~70 entries / ~105MB per
    cycle).

Screenshots/Recordings

Heap-graph traces, memory reports, and retaining-path analysis: see the
verification bundle above.

Pre-merge author checklist

CHANGELOG entry: Fixed a memory leak on Firefox where closed MetaMask
windows were retained in memory, growing extension memory use with every
popup open 942441b

… leak by avoiding `DocumentPictureInPicture` instantiation in Snow hook cp-13.39.2 (#44352)

## **Motivation**

On Firefox, every closed MetaMask window (popup, notification, home) is
retained in memory
([#42891](#42891)
and recent internal reports).

- **Rate:** ~70 detached window entries (~105MB, fresh test wallet) per
popup open/close, linear with use. 30 cycles leave **3.56GB** (13.37.0)
/ **3.31GB** (13.38.1) of detached documents, surviving forced GC.
State-heavy wallets multiply every retained document.
- **Mechanism** (heap-graph traced, intervention-tested): Snow's
picture-in-picture hook reads
`win?.documentPictureInPicture?.requestWindow` in every window. The read
lazily instantiates a per-window `DocumentPictureInPicture`, and
Firefox's cycle collector fails to break the instance's
preserved-wrapper cycle after window close, pinning the entire realm.
- **Why now:** Firefox shipped the API in 151 (May 2026). The April-2023
hook was dormant until users auto-updated. The collection failure itself
is a Gecko defect (upstream filing in progress; prior art: [Bugzilla
1902438](https://bugzilla.mozilla.org/show_bug.cgi?id=1902438)), but
this fix does not depend on Mozilla.

## **Description**

Applies the upstream Snow fix
([LavaMoat/snow#171](LavaMoat/snow#171)) as a
Yarn patch: the hook moves from the per-window instance to
`DocumentPictureInPicture.prototype`, reached through the interface
object, which does not invoke the instance-creating getter.

- Call semantics unchanged: instance lookup falls through to the patched
prototype, `this` stays the instance, same wrapped native. Snow's PiP
realm monitoring is preserved.
- The patch covers `snow.prod.js` and `snow.js` in addition to
`src/request.js`: the extension consumes Snow's prebuilt bundle as a
LavaMoat static shim (`LavamoatPlugin`), and webpack never processes
`@lavamoat/snow/**`.
- **Interim:** remove this patch when `@lavamoat/snow` is bumped to a
release containing the upstream fix. The bump is the removal trigger.

## **Cherry-pick candidacy**

- Affected population: every Firefox user on 151+ (effectively the whole
Firefox base via auto-update). Reproduces from a fresh wallet in five
popup opens. No workaround short of a browser restart.
- Proposed: next RC, with a hotfix on the current production line worth
considering.
- Snow is security-team-owned. Their sign-off on shipping a patched Snow
is part of this review.

## **Verification**

| Check | Result |
|---|---|
| Intervention: official 13.37.0 artifact, only this hook changed, 30
cycles | **+2,100 docs (3.35GB) → -13 (1.6MB total)**. Hook is the sole
delta, so this is also the causal proof |
| This PR's CI artifact (`d4dd55f`), probed directly | Patched hook
present; 5 cycles: +7 KB-scale entries (1.1MB) vs +345 docs (+534MB)
stock |
| Field | Original reporter confirms the leak is gone on this PR's CI
build |
| Independent repro | Manual steps + automated probe:
https://github.com/MajorLift/snow/tree/verification/pip-realm-leak/verification
|

## **Related issues**

Fixes: #42891

## **Manual testing steps**

1. Install this PR's Firefox build (metamaskbot comment) in a fresh
profile via `about:debugging` → Load Temporary Add-on. Onboard with a
test SRP.
2. Confirm the fix is live: extension console →
`DocumentPictureInPicture.prototype.requestWindow.name` → `"open"`
(stock: `"requestWindow"`).
3. Open/close the popup five times, visiting activity or a swap view on
a few opens.
4. `about:memory` → Minimize memory usage → Measure → filter `detached`:
no `moz-extension` UI-document cohorts (stock: ~70 entries / ~105MB per
cycle).

## **Screenshots/Recordings**

Heap-graph traces, memory reports, and retaining-path analysis: see the
verification bundle above.

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

CHANGELOG entry: Fixed a memory leak on Firefox where closed MetaMask
windows were retained in memory, growing extension memory use with every
popup open
@runway-github
runway-github Bot requested a review from a team as a code owner July 13, 2026 13:20
@metamask-ci metamask-ci Bot added the team-bots Bot team (for MetaMask Bot, Runway Bot, etc.) label Jul 13, 2026
@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.

@Gudahtt
Gudahtt enabled auto-merge (squash) July 13, 2026 13:23
@metamask-ci

metamask-ci Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor
Builds ready [45be892]
Deprecated Browserify fallback builds
⚡ Performance Benchmarks (Total: 🟢 12 pass · 🟡 11 warn · 🔴 1 fail)

Baseline (latest main): 1716549 | Date: 6/29/2026 | Pipeline: 29253388239 | Baseline logs

Metricschrome-webpackfirefox-webpack
onboardingNewWallet
[Sentry log · main/release]
🟢 [CI log]🔴 [CI log]

Regressions (🔴 1 failure)

Interaction Benchmarks · Samples: 5
Benchmarkchrome-webpackfirefox-webpack
loadNewAccount
[Sentry log · main/release]
🟢 [CI log]🟡 [CI log]
🔴 load_new_account
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: -22%
  • ↓ loadNewAccount/total: -22%
  • ↓ loadNewAccount/inp: -18%
  • ↑ confirmTx/inp: +84%
  • ↑ confirmTx/lcp: +12%
  • ↓ bridgeUserActions/bridge_load_page: -21%
  • ↓ bridgeUserActions/bridge_load_asset_picker: -15%
  • ↓ bridgeUserActions/longTaskCount: -17%
  • ↓ bridgeUserActions/longTaskTotalDuration: -19%
  • ↓ bridgeUserActions/tbt: -23%
  • ↓ bridgeUserActions/inp: -14%
  • ↑ loadNewAccount/load_new_account: +526%
  • ↑ loadNewAccount/total: +526%
  • ↓ loadNewAccount/inp: -26%
  • ↑ loadNewAccount/lcp: +1058%
  • ↑ confirmTx/confirm_tx: +10%
  • ↓ confirmTx/longTaskCount: -100%
  • ↓ confirmTx/longTaskTotalDuration: -100%
  • ↓ confirmTx/longTaskMaxDuration: -100%
  • ↓ confirmTx/tbt: -100%
  • ↑ confirmTx/total: +10%
  • ↑ confirmTx/lcp: +1143%
  • ↑ bridgeUserActions/bridge_load_page: +81%
  • ↑ bridgeUserActions/bridge_load_asset_picker: +48%
  • ↓ bridgeUserActions/longTaskCount: -100%
  • ↓ bridgeUserActions/longTaskTotalDuration: -100%
  • ↓ bridgeUserActions/longTaskMaxDuration: -100%
  • ↓ bridgeUserActions/tbt: -100%
  • ↑ bridgeUserActions/total: +11%
  • ↓ bridgeUserActions/inp: -14%
  • ↓ bridgeUserActions/fcp: -49%
  • ↑ bridgeUserActions/lcp: +1164%

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

  • 🟡 loadNewAccount/FCP: p75 1.8s
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: -18%
  • ↓ startupStandardHome/load: -18%
  • ↓ startupStandardHome/domContentLoaded: -19%
  • ↓ startupStandardHome/domInteractive: -21%
  • ↓ startupStandardHome/backgroundConnect: -19%
  • ↓ startupStandardHome/firstReactRender: -20%
  • ↓ startupStandardHome/initialActions: -33%
  • ↓ startupStandardHome/loadScripts: -18%
  • ↓ startupStandardHome/longTaskCount: -33%
  • ↓ startupStandardHome/longTaskTotalDuration: -27%
  • ↓ startupStandardHome/longTaskMaxDuration: -21%
  • ↓ startupStandardHome/tbt: -25%
  • ↓ startupStandardHome/inp: -13%
  • ↓ startupStandardHome/fcp: -18%
  • ↓ startupStandardHome/lcp: -13%
  • ↑ startupStandardHome/setupStore: +45%
  • ↓ startupPowerUserHome/domInteractive: -35%
  • ↓ startupPowerUserHome/backgroundConnect: -25%
  • ↓ startupPowerUserHome/fcp: -29%

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

  • 🟡 startupPowerUserHome/INP: p75 216ms
  • 🟡 startupPowerUserHome/LCP: p75 3.7s
User Journey Benchmarks · Samples: 5 · mock API 🔴 1
Benchmarkchrome-webpackfirefox-webpack
onboardingImportWallet
[Sentry log · main/release]
🟢 [CI log]🟢 [CI log]
onboardingNewWallet
[Sentry log · main/release]
🟢 [CI log]🔴 [CI log]
🔴 total
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/confirmSrpToPwForm: +12%
  • ↓ onboardingImportWallet/metricsToWalletReadyScreen: -16%
  • ↓ onboardingImportWallet/doneButtonToHomeScreen: -86%
  • ↓ onboardingImportWallet/openAccountMenuToAccountListLoaded: -97%
  • ↓ onboardingImportWallet/longTaskCount: -80%
  • ↓ onboardingImportWallet/longTaskTotalDuration: -92%
  • ↓ onboardingImportWallet/longTaskMaxDuration: -89%
  • ↓ onboardingImportWallet/tbt: -97%
  • ↓ onboardingImportWallet/total: -85%
  • ↑ onboardingNewWallet/skipBackupToMetricsScreen: +12%
  • ↑ onboardingNewWallet/agreeButtonToOnboardingSuccess: +13%
  • ↓ onboardingNewWallet/longTaskTotalDuration: -36%
  • ↓ onboardingNewWallet/longTaskMaxDuration: -42%
  • ↓ onboardingNewWallet/tbt: -86%
  • ↓ solanaAssetDetails/assetClickToPriceChart: -69%
  • ↓ solanaAssetDetails/total: -69%
  • ↑ solanaAssetDetails/inp: +11%
  • ↓ solanaAssetDetails/lcp: -14%
  • ↓ importSrpHome/longTaskCount: -27%
  • ↓ importSrpHome/longTaskTotalDuration: -35%
  • ↓ importSrpHome/longTaskMaxDuration: -13%
  • ↓ importSrpHome/tbt: -31%
  • ↓ importSrpHome/total: -11%
  • ↓ importSrpHome/inp: -33%
  • ↓ importSrpHome/lcp: -58%
  • ↓ importSrpHome/cls: -47%
  • ↓ sendTransactions/openSendPageFromHome: -30%
  • ↓ sendTransactions/selectTokenToSendFormLoaded: -51%
  • ↑ sendTransactions/reviewTransactionToConfirmationPage: +42%
  • ↓ sendTransactions/longTaskCount: -20%
  • ↓ sendTransactions/longTaskTotalDuration: -23%
  • ↓ sendTransactions/tbt: -40%
  • ↑ sendTransactions/total: +41%
  • ↓ sendTransactions/inp: -11%
  • ↓ sendTransactions/cls: -40%
  • ↓ swap/openSwapPageFromHome: -35%
  • ↑ swap/fetchAndDisplaySwapQuotes: +144%
  • ↓ swap/tbt: -35%
  • ↑ swap/total: +141%
  • ↓ swap/cls: -63%

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

  • 🟡 assetDetails/FCP: p75 1.9s
  • 🟡 solanaAssetDetails/FCP: p75 1.8s
  • 🟡 sendTransactions/FCP: p75 1.8s
  • 🟡 swap/FCP: p75 1.8s
  • 🟡 assetDetails/FCP: p75 1.9s
  • 🟡 solanaAssetDetails/FCP: p75 1.9s
  • 🟡 importSrpHome/FCP: p75 1.9s
  • 🟡 sendTransactions/FCP: p75 1.9s
  • 🟡 swap/FCP: p75 1.8s
Dapp Page Load Benchmarks · Samples: 100
Benchmarkchrome-webpack
dappPageLoad
[Sentry log · main/release]
🟢 [CI log]
Bundle size diffs [🚨 Warning! Bundle size has increased!]
  • background: 108.78 KiB (0.99%)
  • ui: 51.31 KiB (0.3%)
  • common: 0 Bytes (0%)
  • other: 102 Bytes (0.01%)
  • contentScripts: -27 Bytes (0%)
  • zip: 70.54 KiB (0.26%)

@sonarqubecloud

Copy link
Copy Markdown

@Gudahtt
Gudahtt merged commit c738358 into release/13.39.2 Jul 13, 2026
366 of 384 checks passed
@Gudahtt
Gudahtt deleted the runway-cherry-pick-13.39.2-1783948796 branch July 13, 2026 14:26
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 13, 2026
@metamaskbot metamaskbot added the release-13.39.2 Issue or pull request that will be included in release 13.39.2 label Jul 14, 2026
@gauthierpetetin

Copy link
Copy Markdown
Contributor

No release label on PR. Adding release label release-13.39.2 on PR, as PR was cherry-picked in branch 13.39.2.

This branch was previously deployed

1 inactive deployment
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-13.39.2 Issue or pull request that will be included in release 13.39.2 risk:low team-bots Bot team (for MetaMask Bot, Runway Bot, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants