Skip to content

Track posthog-js rrweb divergences to contribute back upstream #3766

@pauldambra

Description

@pauldambra

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions