-
Notifications
You must be signed in to change notification settings - Fork 79
feat(replay): mask canvas session replay recordings on Flutter web #499
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
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
01e3715
feat(replay): mask canvas session replay recordings on Flutter web
turnipdabeets 6f44d83
fix(replay): keep retrying web canvas mask registration until posthogβ¦
turnipdabeets 62e77eb
refactor(replay): follow posthog-js rename to session_recording.canvaβ¦
turnipdabeets c2a3904
docs(changeset): correct the rr_dataURL full-snapshot note β CanvasKiβ¦
turnipdabeets db7004d
fix(replay): warn once when posthog-js is too old to mask canvas frames
turnipdabeets 207f4c1
fix(replay): cancel the predecessor provider's retry chain on register
turnipdabeets bdcbbe6
fix(replay): retry registration when the first apply throws mid-way
turnipdabeets e7d982c
fix(replay): honor maskAllTexts=false for Text widgets in the shared β¦
turnipdabeets 7b8480d
fix(replay): address review β container transform, exact blockSelectoβ¦
turnipdabeets 551ff8c
docs(changeset): trim the canvas-masking changeset to the essentials
turnipdabeets 017c8a1
fix(replay): back off retries when the apply keeps throwing
turnipdabeets 75b59e4
docs(changeset): note multi-view foreign Flutter canvases are skipped
turnipdabeets 06c43d2
chore(replay): pin the posthog-js minimum to 1.408.0 (ships maskRegioβ¦
turnipdabeets 6e41deb
docs(changeset): rewrite entries per changelog style β one-line, userβ¦
turnipdabeets 863317e
fix(replay): fail closed when multiple Flutter views share one host eβ¦
turnipdabeets 6258c55
feat(replay): PostHogMaskWidget enables web canvas masking on its ownβ¦
turnipdabeets 46364cc
fix(replay): harden canvas masking edge paths from review
turnipdabeets 68ea063
Merge branch 'main' into feat/web-canvas-masking
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "posthog_flutter": minor | ||
| --- | ||
|
|
||
| Add session replay canvas masking on Flutter web: `maskAllTexts`, `maskAllImages`, `PostHogMaskWidget`, and obscured text fields now apply to the CanvasKit canvas β enable by declaring `session_recording.canvasCapture.maskRegionsFn` in `posthog.init`, or just by mounting a `PostHogMaskWidget` (requires posthog-js 1.408.0+) | ||
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,5 @@ | ||
| --- | ||
| "posthog_flutter": patch | ||
| --- | ||
|
|
||
| Fix `maskAllTexts: false` still masking `Text` widgets when `maskAllImages` is enabled |
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
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
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) { | ||
| 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
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
22 changes: 22 additions & 0 deletions
22
posthog_flutter/lib/src/replay/web/web_canvas_mask_geometry.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,22 @@ | ||
| import 'package:flutter/rendering.dart'; | ||
|
|
||
| import '../element_parsers/element_data.dart'; | ||
|
|
||
| /// Converts parsed widget elements to axis-aligned mask rects in the | ||
| /// PostHogWidget container's coordinate space. | ||
| List<Rect> containerMaskRects(List<ElementData> elements) { | ||
| final rects = <Rect>[]; | ||
| for (final element in elements) { | ||
| final transform = element.transform; | ||
| final rect = transform != null | ||
| ? MatrixUtils.transformRect(transform, element.rect) | ||
| : element.rect; | ||
| if (!rect.isFinite || rect.isEmpty) { | ||
| continue; | ||
| } | ||
|
turnipdabeets marked this conversation as resolved.
|
||
| // outset so capture-resolution rounding can't leave a sub-pixel glyph | ||
| // edge visible at the mask border | ||
| rects.add(rect.inflate(1.0)); | ||
| } | ||
| return rects; | ||
| } | ||
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.