-
Notifications
You must be signed in to change notification settings - Fork 79
feat(replay): PostHogMaskWidget enables web canvas masking on its own #501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
turnipdabeets
merged 24 commits into
feat/web-canvas-masking
from
feat/web-canvas-mask-widget-autoregister
Jul 30, 2026
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
fc3a116
feat(replay): PostHogMaskWidget enables web canvas masking on its own
turnipdabeets bbcf5ad
Merge branch 'wip/web-canvas-masking-fixes' into wip/mask-widget-autoβ¦
turnipdabeets 4e35667
fix(replay): address review β survive a failed mount-triggered apply,β¦
turnipdabeets b37d4de
Merge feat/web-canvas-masking: rename config path to session_recordinβ¦
turnipdabeets 84f0155
Merge feat/web-canvas-masking: correct the rr_dataURL full-snapshot note
turnipdabeets 93402e5
docs(changeset): note pre-mount full snapshots can embed unmasked canβ¦
turnipdabeets 529ca9a
Merge feat/web-canvas-masking: warn once when posthog-js is too old
turnipdabeets 591d5d0
Merge remote-tracking branch 'origin/feat/web-canvas-masking' into HEAD
turnipdabeets e6850ea
Merge remote-tracking branch 'origin/feat/web-canvas-masking' into HEAD
turnipdabeets 04c4bcd
fix(replay): mark applied only after the recording restart succeeds
turnipdabeets e1900cd
fix(replay): only opt in from a PostHogMaskWidget inside the tracked β¦
turnipdabeets 02740e4
Merge feat/web-canvas-masking: container transform, blockSelector tokβ¦
turnipdabeets d1a01b4
fix(replay): enforce a single retry chain; cover tracked-tree mount gβ¦
turnipdabeets 134bbf0
Merge remote-tracking branch 'origin/feat/web-canvas-masking' into HEAD
turnipdabeets 8be5469
docs(replay): scope the outside-tree opt-in claim to mount time; pin β¦
turnipdabeets 6bbaa6c
Merge feat/web-canvas-masking: multi-view changeset caveat
turnipdabeets ee84798
Merge feat/web-canvas-masking: pin posthog-js minimum to 1.408.0
turnipdabeets 83ae5ae
Merge feat/web-canvas-masking: changelog-style changeset rewrite
turnipdabeets 7815171
docs(changeset): rewrite the mask-widget entry per changelog style
turnipdabeets 9cbf50f
docs(changeset): fold the mask-widget entry into the feature changeset
turnipdabeets 7eec532
fix(replay): revalidate mounted mask widgets on every frame, fail cloβ¦
turnipdabeets d0cd4d1
Merge feat/web-canvas-masking: multi-view fail-closed + changelog-styβ¦
turnipdabeets 9672c69
test(replay): pin the provider's own view in regions tests
turnipdabeets e89ff8e
fix(replay): resolve the tracked-tree root the way the masking walk dβ¦
turnipdabeets File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
posthog_flutter/lib/src/replay/mask/canvas_mask_registration_io.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import 'package:flutter/widgets.dart'; | ||
|
|
||
| /// Canvas masking is a Flutter web concern; on every other platform | ||
| /// `PostHogMaskWidget` is honored by the native screenshot pipeline instead. | ||
| void notifyMaskWidgetMounted(BuildContext context) {} | ||
|
|
||
| void notifyMaskWidgetUnmounted(BuildContext context) {} |
80 changes: 80 additions & 0 deletions
80
posthog_flutter/lib/src/replay/mask/canvas_mask_registration_web.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import 'package:flutter/scheduler.dart'; | ||
| import 'package:flutter/widgets.dart'; | ||
|
|
||
| import '../../util/logging.dart'; | ||
| import '../web/web_canvas_mask_provider.dart'; | ||
| import 'posthog_mask_controller.dart'; | ||
|
|
||
| /// A mounted `PostHogMaskWidget` is an explicit request for masking, so it | ||
| /// opts the app into canvas masking even when `posthog.init` never declared | ||
| /// `maskRegionsFn`. | ||
| /// | ||
| /// Deferred to the end of the frame because `initState` runs during Flutter's | ||
| /// build phase: registering calls straight into posthog-js and restarts an | ||
| /// in-flight recording. | ||
| void notifyMaskWidgetMounted(BuildContext context) { | ||
| WebCanvasMaskProvider.registerMaskWidgetContext(context); | ||
| SchedulerBinding.instance.addPostFrameCallback((_) { | ||
| try { | ||
| if (!_isInTrackedTree(context)) { | ||
| printIfDebug( | ||
| 'PostHog: this PostHogMaskWidget is outside the PostHogWidget tree ' | ||
| 'PostHog tracks, so masking could never cover it β it does not ' | ||
| 'enable web canvas masking.', | ||
| ); | ||
| return; | ||
| } | ||
| WebCanvasMaskProvider.notifyMaskWidgetMounted(); | ||
| } catch (e) { | ||
| printIfDebug('PostHog: error enabling web canvas masking: $e'); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| void notifyMaskWidgetUnmounted(BuildContext context) { | ||
| WebCanvasMaskProvider.unregisterMaskWidgetContext(context); | ||
| } | ||
|
|
||
| /// The masking walk only sees the tracked tree (whose route-dependent root is | ||
| /// [WebCanvasMaskProvider.trackedTreeRoot], the boundary this check walks | ||
| /// against), so a mask widget outside it would opt masking in while its own | ||
| /// rects are never produced β the walk would succeed and ship rects that do | ||
| /// not cover the widget. With | ||
| /// no tracked tree at all the opt-in stays allowed: every walk then fails and | ||
| /// frames are skipped (fail closed), which is the documented behavior for an | ||
| /// app missing PostHogWidget. | ||
| /// | ||
| /// The check runs once, in the mount's post-frame callback: a null tracked | ||
| /// context at that moment is treated as the no-PostHogWidget shape and | ||
| /// allowed. That one-shot allowance is backstopped by | ||
| /// [WebCanvasMaskProvider], which revalidates every mounted mask widget when | ||
| /// regions are computed β if a PostHogWidget later mounts without containing | ||
| /// this widget, frames are skipped (fail closed) rather than recorded | ||
| /// unmasked. | ||
| bool _isInTrackedTree(BuildContext context) { | ||
| final trackedContext = | ||
| PostHogMaskController.instance.containerKey.currentContext; | ||
| if (trackedContext == null) { | ||
|
turnipdabeets marked this conversation as resolved.
|
||
| return true; | ||
| } | ||
| final tracked = WebCanvasMaskProvider.trackedTreeRoot(trackedContext); | ||
| if (tracked == null) { | ||
| // cannot prove the mask widget is outside the tracked tree | ||
| return true; | ||
| } | ||
| if (!context.mounted) { | ||
| return false; | ||
| } | ||
| final renderObject = context.findRenderObject(); | ||
| if (renderObject == null) { | ||
| return false; | ||
| } | ||
| RenderObject? node = renderObject; | ||
| while (node != null) { | ||
| if (identical(node, tracked)) { | ||
| return true; | ||
| } | ||
| node = node.parent; | ||
| } | ||
| return false; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.