… 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
DocumentPictureInPictureinstantiation in Snow hook cp-13.39.2 (fix: Fix Firefox detatched-window memory leak by avoidingDocumentPictureInPictureinstantiation in Snow hook cp-13.39.2 #44352)Motivation
On Firefox, every closed MetaMask window (popup, notification, home) is
retained in memory
(#42891
and recent internal reports).
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.
picture-in-picture hook reads
win?.documentPictureInPicture?.requestWindowin every window. The readlazily instantiates a per-window
DocumentPictureInPicture, andFirefox's cycle collector fails to break the instance's
preserved-wrapper cycle after window close, pinning the entire realm.
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 interfaceobject, which does not invoke the instance-creating getter.
prototype,
thisstays the instance, same wrapped native. Snow's PiPrealm monitoring is preserved.
snow.prod.jsandsnow.jsin addition tosrc/request.js: the extension consumes Snow's prebuilt bundle as aLavaMoat static shim (
LavamoatPlugin), and webpack never processes@lavamoat/snow/**.@lavamoat/snowis bumped to arelease containing the upstream fix. The bump is the removal trigger.
Cherry-pick candidacy
Firefox base via auto-update). Reproduces from a fresh wallet in five
popup opens. No workaround short of a browser restart.
considering.
is part of this review.
Verification
d4dd55f), probed directly|
Related issues
Fixes: #42891
Manual testing steps
profile via
about:debugging→ Load Temporary Add-on. Onboard with atest SRP.
DocumentPictureInPicture.prototype.requestWindow.name→"open"(stock:
"requestWindow").a few opens.
about:memory→ Minimize memory usage → Measure → filterdetached:no
moz-extensionUI-document cohorts (stock: ~70 entries / ~105MB percycle).
Screenshots/Recordings
Heap-graph traces, memory reports, and retaining-path analysis: see the
verification bundle above.
Pre-merge author checklist
Docs and MetaMask
Extension Coding
Standards.
if applicable
guidelines).
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 942441b