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. |
| Keep replay autofill off through attribute mutations |
#4861 (rrweb/src/replay/index.ts:2064) |
novel — closes a hole in merged #1771 |
Upstream #1771 (merged, shipped in 2.1.3) forces autocomplete="off" at rebuild time only. A recorded attribute mutation on that field then sets it back to the page's real value, or removes it outright, and the viewer's browser offers autofill again part-way through the replay — the exact leak #1771 exists to prevent. Ours also skips autocomplete mutations on <input>/<textarea> when applying them, in both the live and fast-forward paths. Small, self-contained, and strictly completes work upstream already accepted, so it should be an easy sell. Covered by rrweb/test/replayer.test.ts + test/events/input-autocomplete-mutation.ts, which fail without the guard (expected [ 'email', null ] to deeply equal [ 'off', 'off' ]) — the two failure modes at once. Best next PR on this list. |
| Narrow custom-event re-cast on seek |
shouldCastInSyncMode (rrweb/src/replay/index.ts:835) |
novel — upstream took the blunt version |
Upstream #1769 (merged, shipped in 2.1.2; the same author's #1771 then landed in 2.1.3) simply deletes the case EventType.Custom skip, so every custom event re-fires on every seek. Ours re-casts only the event that carries DOM state (rrweb/fullscreen) and keeps side-effect-free signals skipped. Offer it as a refinement on top of #1769 — or drop ours and take upstream's. Decide first. |
| Page-lifetime keepalive for the untainted-prototype iframe |
#4128 (utils/src/index.ts keepIframeAttached) |
mostly overtaken by OPEN #1934 — do not open a PR |
We kept the shared iframe attached for the page lifetime because upstream removed it on recorder teardown, which detaches it while live per-document observers still need the cached prototype, and breaks stop/restart cycles. Upstream has since converged on the same conclusion from the other side: #1934 keeps the WebKit keepalive but narrows it to MutationObserver only, which is the single key that needs a live ScriptExecutionContext. Ours is now the over-broad one — we retain an iframe for all four BasePrototypeCache keys. The part still worth saying upstream is small: the page-lifetime vs teardown argument for MutationObserver. Say it as a comment on #1934, not as a PR. Our ph-no-capture tag stays fork-only (our blockClass differs). |
attributeFilter shadow-root plumbing + empty-array guard |
#4129 (record/index.ts, record/observer.ts) |
novel — fixes OPEN #1873 |
Reviewing #1873 for adoption found two gaps: upstream never threads attributeFilter through shadow roots (bypassOptions), and an empty array silently disables all attribute recording instead of being treated as unset. Both are small comments/commits on the open PR — cheapest contribute-back on the list. |
| Hoist the non-user-initiated source list out of the emit path |
#4131 (record/index.ts nonUserInitiatedSources) |
novel — refines OPEN #1697 |
Upstream allocates the source list per emitted event; ours hoists it into a module-level Set. One-line comment on the open PR. |
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. |
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.1.2+ 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.rrweb/src/replay/index.ts:2064)2.1.3) forcesautocomplete="off"at rebuild time only. A recorded attribute mutation on that field then sets it back to the page's real value, or removes it outright, and the viewer's browser offers autofill again part-way through the replay — the exact leak #1771 exists to prevent. Ours also skipsautocompletemutations on<input>/<textarea>when applying them, in both the live and fast-forward paths. Small, self-contained, and strictly completes work upstream already accepted, so it should be an easy sell. Covered byrrweb/test/replayer.test.ts+test/events/input-autocomplete-mutation.ts, which fail without the guard (expected [ 'email', null ] to deeply equal [ 'off', 'off' ]) — the two failure modes at once. Best next PR on this list.shouldCastInSyncMode(rrweb/src/replay/index.ts:835)2.1.2; the same author's #1771 then landed in2.1.3) simply deletes thecase EventType.Customskip, so every custom event re-fires on every seek. Ours re-casts only the event that carries DOM state (rrweb/fullscreen) and keeps side-effect-free signals skipped. Offer it as a refinement on top of #1769 — or drop ours and take upstream's. Decide first.utils/src/index.tskeepIframeAttached)MutationObserveronly, which is the single key that needs a liveScriptExecutionContext. Ours is now the over-broad one — we retain an iframe for all fourBasePrototypeCachekeys. The part still worth saying upstream is small: the page-lifetime vs teardown argument forMutationObserver. Say it as a comment on #1934, not as a PR. Ourph-no-capturetag stays fork-only (our blockClass differs).attributeFiltershadow-root plumbing + empty-array guardrecord/index.ts,record/observer.ts)attributeFilterthrough shadow roots (bypassOptions), and an empty array silently disables all attribute recording instead of being treated as unset. Both are small comments/commits on the open PR — cheapest contribute-back on the list.record/index.tsnonUserInitiatedSources)Set. One-line comment on the open PR.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.2.1.2)console.log-> suppressible warnrrweb/src/replay/index.ts:298).link.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 + 14 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. The iframe-keepalive row dropped off the novel list — upstream fix: dont mangle some surveys properties #1934 got there independently, and with a tighter scope than ours.featureFlags.overridein favor offeatureFlags.overrideFeatureFlags, a new function that supports overriding flags and flag payloads #1697, and one on fix: dont mangle some surveys properties #1934 (whyMutationObserverretention should last the page lifetime, not just until recorder teardown). All four are comments on open PRs, and all four come from work we already did.isDeepEqualmanually #1686, fix: check url matching on URL change for widget-type surveys #1755, CORS Error #1814) — nudging these to land is still the highest-leverage move. refactor: SimplifyPosthog.init()signature #1712 merged 2026-09-04; fix: handle non Error objects being passed to captureException #1806 / chore: allow push to main in GH actions #1825 / feat: remote image masking #1826 were closed unmerged on 2026-06-17.ph_*out of fix(replay): fix absolute position bug in ph-no-capture #3678.Assessed against upstream
rrweb-io/rrwebon 2026-06-08; PR states and new divergences re-verified on 2026-09-08 against the2.1.2, then the2.1.3/2.1.4releases; the autofill-mutation divergence added 2026-09-10 when we adopted #1771. Re-verify a row's status before acting on it.