-
Notifications
You must be signed in to change notification settings - Fork 192
⚗️ Add Canvas image capture [3/n] #4980
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
base: main
Are you sure you want to change the base?
Changes from 9 commits
7cb6936
32e95d5
702d981
af5efab
a209092
3d772c4
e88d1dd
04324e8
fc53067
1eed2ca
08b9d72
d830fc9
6e9b197
b3b3f5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,47 @@ | ||
| export interface CanvasManager { | ||
| clearDirtyCanvases: () => void | ||
| getCapturableCanvases: () => HTMLCanvasElement[] | ||
| getDirtyCanvases: () => HTMLCanvasElement[] | ||
| getPreviousHash: (canvas: HTMLCanvasElement) => string | undefined | ||
| isCanvasCaptureInFlight: (canvas: HTMLCanvasElement, captureId: number) => boolean | ||
| isCanvasDirty: (canvas: HTMLCanvasElement) => boolean | ||
| markCanvasCaptureFinished: (canvas: HTMLCanvasElement, captureId: number) => void | ||
| markCanvasCaptureStarted: (canvas: HTMLCanvasElement) => number | undefined | ||
| markCanvasClean: (canvas: HTMLCanvasElement) => void | ||
| markCanvasCleanIfUnchanged: (canvas: HTMLCanvasElement, captureId: number) => void | ||
| markCanvasDirty: (canvas: HTMLCanvasElement) => void | ||
| markCanvasTainted: (canvas: HTMLCanvasElement) => void | ||
| reset: () => void | ||
| setPreviousHash: (canvas: HTMLCanvasElement, hash: string) => void | ||
| } | ||
|
|
||
| export function createCanvasManager(): CanvasManager { | ||
| const dirtyCanvases = new Set<HTMLCanvasElement>() | ||
| const taintedCanvases = new WeakSet<HTMLCanvasElement>() | ||
| let dirtyVersions = new WeakMap<HTMLCanvasElement, number>() | ||
| let previousHashes = new WeakMap<HTMLCanvasElement, string>() | ||
| let inFlightCaptures = new WeakMap<HTMLCanvasElement, { id: number; dirtyVersion: number }>() | ||
| let nextCaptureId = 0 | ||
|
|
||
| function getDirtyVersion(canvas: HTMLCanvasElement) { | ||
| return dirtyVersions.get(canvas) ?? 0 | ||
| } | ||
|
|
||
| return { | ||
| clearDirtyCanvases: () => dirtyCanvases.clear(), | ||
| getCapturableCanvases: () => { | ||
| const capturableCanvases: HTMLCanvasElement[] = [] | ||
|
|
||
| dirtyCanvases.forEach((canvas) => { | ||
| if (!canvas.isConnected) { | ||
| dirtyCanvases.delete(canvas) | ||
| } else if (!taintedCanvases.has(canvas) && !inFlightCaptures.has(canvas)) { | ||
| capturableCanvases.push(canvas) | ||
| } | ||
| }) | ||
|
|
||
| return capturableCanvases | ||
| }, | ||
| getDirtyCanvases: () => { | ||
| const connectedCanvases: HTMLCanvasElement[] = [] | ||
|
|
||
|
|
@@ -24,12 +55,46 @@ export function createCanvasManager(): CanvasManager { | |
|
|
||
| return connectedCanvases | ||
| }, | ||
| getPreviousHash: (canvas) => previousHashes.get(canvas), | ||
| isCanvasCaptureInFlight: (canvas, captureId) => inFlightCaptures.get(canvas)?.id === captureId, | ||
| isCanvasDirty: (canvas) => dirtyCanvases.has(canvas), | ||
| markCanvasCaptureFinished: (canvas, captureId) => { | ||
| if (inFlightCaptures.get(canvas)?.id === captureId) { | ||
| inFlightCaptures.delete(canvas) | ||
| } | ||
| }, | ||
| markCanvasCaptureStarted: (canvas) => { | ||
| if (taintedCanvases.has(canvas) || inFlightCaptures.has(canvas)) { | ||
| return undefined | ||
| } | ||
|
|
||
| const captureId = nextCaptureId++ | ||
| inFlightCaptures.set(canvas, { id: captureId, dirtyVersion: getDirtyVersion(canvas) }) | ||
| return captureId | ||
| }, | ||
| markCanvasClean: (canvas) => dirtyCanvases.delete(canvas), | ||
| markCanvasCleanIfUnchanged: (canvas, captureId) => { | ||
| const capture = inFlightCaptures.get(canvas) | ||
| if (capture?.id === captureId && capture.dirtyVersion === getDirtyVersion(canvas)) { | ||
| dirtyCanvases.delete(canvas) | ||
| } | ||
| }, | ||
| markCanvasDirty: (canvas) => { | ||
| if (canvas.isConnected) { | ||
| if (canvas.isConnected && !taintedCanvases.has(canvas)) { | ||
| dirtyCanvases.add(canvas) | ||
| dirtyVersions.set(canvas, getDirtyVersion(canvas) + 1) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
issue: When an origin-unclean canvas is restored by assigning Useful? React with 👍 / 👎. |
||
| } | ||
| }, | ||
| markCanvasTainted: (canvas) => { | ||
| taintedCanvases.add(canvas) | ||
| dirtyCanvases.delete(canvas) | ||
| }, | ||
| reset: () => { | ||
| dirtyCanvases.clear() | ||
| dirtyVersions = new WeakMap() | ||
| previousHashes = new WeakMap() | ||
| inFlightCaptures = new WeakMap() | ||
| }, | ||
| setPreviousHash: (canvas, hash) => previousHashes.set(canvas, hash), | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ import { | |
| trackViewportResize, | ||
| trackVisualViewportResize, | ||
| trackCanvasContent, | ||
| trackCanvasCapture, | ||
| } from './trackers' | ||
| import { createElementsScrollPositions } from './elementsScrollPositions' | ||
| import type { ShadowRootsController } from './shadowRootsController' | ||
|
|
@@ -83,6 +84,7 @@ export function record(options: RecordOptions): RecordAPI { | |
| trackVisualViewportResize(processRecord), | ||
| trackViewEnd(lifeCycle, processRecord, flushMutations), | ||
| trackCanvasContent(scope), | ||
| trackCanvasCapture(scope), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
issue: When the experimental canvas option is enabled, this production call omits Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not an issue. |
||
| ] | ||
|
|
||
| return { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: These schema entries reject
hashingMaxDimension > 100andmaxImageDimension > 1000, but the newly exposed configuration fields only describe them as maximum dimensions and do not state either permitted range. A customer can therefore reasonably configure values such asmaxImageDimension: 1200and causevalidateAndBuildRumConfiguration()to reject the entire RUM initialization rather than merely getting a larger replay image; document the 1–100 and 1–1000 ranges alongside the options, as is already done formaxFramesPerSecond.Useful? React with 👍 / 👎.