Track posthog-js rrweb divergences to contribute back upstream
We maintain a divergent fork of rrweb-io/rrweb under packages/rrweb/*. Where a divergence is a general improvement (not a PostHog-product decision), we want to contribute it back so our fork stays thin and upstream benefits.
This replaces the old needs-to-be-contributed-back label workflow from the retired PostHog/posthog-rrweb repo. The label has been recreated here — apply needs-to-be-contributed-back to any future posthog-js PR that adds an rrweb divergence, and add a row below.
Each candidate is assessed against current upstream (2.0.1 + open PRs) as one of: novel (worth opening a PR), in-flight (we already have an open upstream PR — just needs landing), adopted (upstream already fixed it — drop), or product-specific (keep in fork). Companion pull-in tracker: #3765.
Strongest novel candidates — open these upstream
These are general, low-coupling, and PostHog-agnostic.
| Divergence |
Source |
Upstream status |
Notes |
CSS var() shorthand <style> textContent preservation |
#3542 (rrweb-snapshot/src/utils.ts hasEmptyShorthandLonghand) |
novel — fixes OPEN bug |
Upstream issue #1667 is open and unfixed; PR #1322 adds only a failing test. Our fix + #1322's test = a ready-made PR. Top priority. |
| preload-as-style infinite polling/listener leak |
#3667 (snapshot.ts stylesheetLoadTracked + resetStylesheetLoadTracking) |
novel |
Upstream onceStylesheetLoaded has only a local fired flag + timeout — no dedupe Map, no AbortController, no reset. Real leak on SPAs re-snapshotting <link rel=preload as=style>. |
| Replay scroll re-apply after fast-forward/seek |
#3736 (replay/index.ts lastScrollMap, re-applied on Flush) |
novel |
Upstream replayer does a single scrollTo with no retry; scrolls to not-yet-scrollable targets clamp to 0. Clean standalone replayer fix. |
<link> missing-href guard |
old #18 (snapshot.ts hrefFrom()) |
novel |
Upstream still does unguarded (n as HTMLLinkElement).href. Tiny defensive fix. |
maxDepth cap on DOM serialization |
old #130 (snapshot.ts) |
novel |
Upstream serializeNodeWithId has no depth cap; general crash/payload fix (default 50 + one-time warn). No upstream PR/issue exists. |
| empty-CDATA rebuild guard |
old #70 (rebuild.ts) |
novel (overlaps OPEN #1740) |
Upstream createCDATASection has no empty guard; OPEN PR #1740 fixes the same area differently (text-node swap). Coordinate / pick one approach. |
Angular Zone __symbol__ unpatched-original detection |
old #24 (utils/src/index.ts angularZoneUnpatchedAlternative) |
novel |
Upstream still uses the older isAngularZonePresent(). Could fold into our open #1633. |
Replayer destroy() cleanup + idempotency |
old #92 (+#122) (replay/index.ts emitterHandlers) |
novel |
Upstream destroy() only pauses + resets mirrors/media; doesn't track handlers, isn't idempotent, doesn't clear imageMap/canvasEventMap/timeouts. |
Plumb dataURLOptions (type/quality) into canvas toDataURL |
old #95 (record/observers/canvas/serialize-args.ts) |
novel (plumbing only) |
Upstream calls toDataURL() with no args. Contribute the plumbing; leave PostHog's webp/q0.4 defaults out. |
Coordinate with in-flight / open PRs (don't open parallel PRs)
| Divergence |
Source |
Related upstream |
Action |
sheet.href for SPA stylesheet-href stability |
#3635 (snapshot.ts transformAttribute/serializeElementNode) |
OPEN #1825 (d0ugal, different code path — CSS content, not the href attribute); merged #1705; closed #1700 |
Frame ours as the companion to #1825 (href attribute vs CSS content). Coordinate with d0ugal. |
| WebGL context-lost canvas skip |
#3527 (canvas-manager.ts isContextLost() pre-flight + getCanvas try/catch) |
OPEN #1469 (daibhin, catches createImageBitmap errors); merged #1777 (unrelated WebGL-exists guard) |
Ours is the better mechanism (skip lost contexts before they poison the fingerprint-dedup map). Land jointly with / on top of #1469. |
| iframe mutation-buffer cleanup race |
#3558 (observer.ts findAndRemoveIframeBuffer(knownDocs) + attachedDocuments) |
OPEN #1755 (megboehlert) |
Tightly coupled with #3570 below. |
| same-origin iframe state leak on removal |
#3570 (record/index.ts iframeObserverCleanups; IframeManager.destroy()) |
seeds OPEN #1755; prior art closed #1746/#1747 |
Contribute #3558+#3570 as one combined iframe-lifecycle PR that extends #1755. |
| iframe attach/re-attach + lifecycle cleanup cluster |
old #108, #121, #123, #142, #91, #94, #100 (iframe-manager.ts, record/index.ts) |
addresses upstream issue #1720; overlaps external OPEN #1791 "Address Iframe memory leaks" |
Same surface as #1755/#1791. Bundle into the combined iframe-lifecycle PR rather than many small ones. |
Our PRs already open / merged / closed upstream
PostHog-authored (or PostHog-seeded) PRs on rrweb-io/rrweb. Verified individually by PR number (the multi-author search index is unreliable cross-repo).
| PR |
State |
Corresponds to |
Next action |
| #1597 |
merged |
Angular wrapped MutationObserver detection (pre-fork) |
Done — in our fork base. |
| #1705 |
merged |
node.baseURI for stylesheet hrefs (related to #3635) |
Done. Our #3635 layers on top. |
| #1633 |
open |
untainted prototype access in Angular |
Nudge to land. |
| #1635 |
open |
iframe + custom web components (Chrome) |
Nudge to land. |
| #1641 |
open |
preserve adopted styles (virtual dom) |
Nudge to land. |
| #1686 |
open |
stringifyRule error swallowing |
Reflected in our utils.ts (cites #1686). Nudge to land. |
| #1712 |
open (APPROVED) |
console.log -> suppressible warn |
Nudge for merge. |
| #1755 |
open |
iframe pagehide buffer/mirror cleanup -> seeds #3558+#3570 |
Extend into the combined iframe-lifecycle PR. |
| #1806 |
open |
virtual-dom backward-skip playback |
Nudge to land (also a pull-in item — not yet vendored). |
| #1814 |
open |
untainted add/remove listeners |
Nudge to land. |
| #1825 |
open |
link.sheet for CSS capture on WebKit (companion to #3635) |
Coordinate with #3635. |
| #1826 |
open |
Zone.js unpatched MutationObserver on Safari |
Nudge to land. |
| #1685 |
closed |
stringifyRule (superseded by #1686) |
None. |
| #1700 |
closed |
document.baseURI sheetHref (superseded by #1705) |
None. |
| #1746 / #1747 |
closed |
iframe mem-leak (prior art for #3570) |
Cite as prior art in the combined PR. |
| #1818 |
closed |
swallow CSS insertRule parse errors |
Decide whether to re-propose. |
Possibly-ours (high-trust contributors — confirm affiliation before claiming): #1642 (kevinatown, blockElementFn), #1469 (daibhin, bitmap errors), #1769 / #1771 (heathdutton, seek custom events / replay autocomplete).
Adopted upstream / product-specific — drop or carve out
| Item |
Source |
Verdict |
applyStyleDeclaration null-safety |
old #86 |
Adopted upstream via merged #1775. Drop. |
splitCssText/normalizeCssString caching |
old #17 |
Gone from our fork (lost in an upstream re-sync; upstream replaced it with 0px-normalization). Moot. |
| Don't bundle css parsing / postcss into recorder |
old #96, #101 |
Packaging-specific (@posthog/rrweb-snapshot/record subpath exports + our vite config). Keep fork-only. (Upstream's own version is OPEN #1784.) |
| base64 image recompression + striped-placeholder |
new (maxBase64ImageLength, recompressBase64Image, STRIPED_PLACEHOLDER_SVG) + old #99 |
Product/storage-cost policy (lossy webp q0.4 + fixed placeholder). Keep in fork; at most offer upstream as opt-in config. |
ph-no-capture blocked-element layout fix |
#3678 |
Carve out: contribute the general rr_position/rr_transform/rr_display flow-preservation; drop the ph_rr_could_not_detect_modal diagnostic attribute (PostHog-specific). |
| CanvasManager reference-counting teardown |
#3726 |
Novel and correct, but higher coupling — changes the manager lifecycle contract (every per-root teardown must acquire()/reset() in pairs). Get a maintainer design nod before opening a PR; don't lead with it. |
Summary
Assessed against upstream rrweb-io/rrweb on 2026-06-08; re-verify a row's status before acting on it.
Track posthog-js rrweb divergences to contribute back upstream
We maintain a divergent fork of
rrweb-io/rrwebunderpackages/rrweb/*. Where a divergence is a general improvement (not a PostHog-product decision), we want to contribute it back so our fork stays thin and upstream benefits.This replaces the old
needs-to-be-contributed-backlabel workflow from the retiredPostHog/posthog-rrwebrepo. The label has been recreated here — applyneeds-to-be-contributed-backto any future posthog-js PR that adds an rrweb divergence, and add a row below.Each candidate is assessed against current upstream (
2.0.1+ open PRs) as one of: novel (worth opening a PR), in-flight (we already have an open upstream PR — just needs landing), adopted (upstream already fixed it — drop), or product-specific (keep in fork). Companion pull-in tracker: #3765.Strongest novel candidates — open these upstream
These are general, low-coupling, and PostHog-agnostic.
var()shorthand<style>textContent preservationrrweb-snapshot/src/utils.tshasEmptyShorthandLonghand)snapshot.tsstylesheetLoadTracked+resetStylesheetLoadTracking)onceStylesheetLoadedhas only a localfiredflag + timeout — no dedupe Map, no AbortController, no reset. Real leak on SPAs re-snapshotting<link rel=preload as=style>.replay/index.tslastScrollMap, re-applied on Flush)scrollTowith no retry; scrolls to not-yet-scrollable targets clamp to 0. Clean standalone replayer fix.<link>missing-hrefguardsnapshot.tshrefFrom())(n as HTMLLinkElement).href. Tiny defensive fix.maxDepthcap on DOM serializationsnapshot.ts)serializeNodeWithIdhas no depth cap; general crash/payload fix (default 50 + one-time warn). No upstream PR/issue exists.rebuild.ts)createCDATASectionhas no empty guard; OPEN PR #1740 fixes the same area differently (text-node swap). Coordinate / pick one approach.__symbol__unpatched-original detectionutils/src/index.tsangularZoneUnpatchedAlternative)isAngularZonePresent(). Could fold into our open #1633.destroy()cleanup + idempotencyreplay/index.tsemitterHandlers)destroy()only pauses + resets mirrors/media; doesn't track handlers, isn't idempotent, doesn't clear imageMap/canvasEventMap/timeouts.dataURLOptions(type/quality) into canvastoDataURLrecord/observers/canvas/serialize-args.ts)toDataURL()with no args. Contribute the plumbing; leave PostHog's webp/q0.4 defaults out.Coordinate with in-flight / open PRs (don't open parallel PRs)
sheet.hreffor SPA stylesheet-href stabilitysnapshot.tstransformAttribute/serializeElementNode)canvas-manager.tsisContextLost()pre-flight +getCanvastry/catch)createImageBitmaperrors); merged #1777 (unrelated WebGL-exists guard)observer.tsfindAndRemoveIframeBuffer(knownDocs)+attachedDocuments)record/index.tsiframeObserverCleanups;IframeManager.destroy())iframe-manager.ts,record/index.ts)Our PRs already open / merged / closed upstream
PostHog-authored (or PostHog-seeded) PRs on
rrweb-io/rrweb. Verified individually by PR number (the multi-author search index is unreliable cross-repo).node.baseURIfor stylesheet hrefs (related to #3635)stringifyRuleerror swallowingutils.ts(cites #1686). Nudge to land.console.log-> suppressible warnlink.sheetfor CSS capture on WebKit (companion to #3635)stringifyRule(superseded by #1686)document.baseURIsheetHref (superseded by #1705)insertRuleparse errorsAdopted upstream / product-specific — drop or carve out
applyStyleDeclarationnull-safetysplitCssText/normalizeCssStringcaching0px-normalization). Moot.@posthog/rrweb-snapshot/recordsubpath exports + our vite config). Keep fork-only. (Upstream's own version is OPEN #1784.)maxBase64ImageLength,recompressBase64Image,STRIPED_PLACEHOLDER_SVG) + old #99ph-no-captureblocked-element layout fixrr_position/rr_transform/rr_displayflow-preservation; drop theph_rr_could_not_detect_modaldiagnostic attribute (PostHog-specific).acquire()/reset()in pairs). Get a maintainer design nod before opening a PR; don't lead with it.Summary
needs-to-be-contributed-backPRs + 10 post-move divergences assessed against current upstream.var(), fix(replay): stop polling preload-as-style <link> elements forever #3667 preload leak, fix(replay): re-apply scroll after fast-forward catch-up #3736 scroll re-apply).Posthog.init()signature #1712 is already APPROVED).ph_*out of fix(replay): fix absolute position bug in ph-no-capture #3678.Assessed against upstream
rrweb-io/rrwebon 2026-06-08; re-verify a row's status before acting on it.