Skip to content

Commit e9eef76

Browse files
seer-by-sentry[bot]getsantry[bot]billyvg
authored
fix(replays): Handle non-canvas elements and toDataURL errors in canvas replayer (#91725)
👋 Hi there! This PR was automatically generated by Autofix 🤖 Fixes [JAVASCRIPT-2SKA](https://sentry.io/organizations/sentry/issues/5213327583/). The issue was that: Canvas replay fails due to `replayer.getMirror().getNode` returning a non-canvas element, bypassed by a type assertion, causing `toDataURL` to error. - Added a check to ensure that the node being cloned is a canvas element. - Added a try-catch block around the `toDataURL` call to handle potential errors. - Added a check to ensure that the target is a canvas element before calling `toDataURL`. - Changed `Sentry.captureException` to `Sentry.captureMessage` with level warning for canvas node errors. If you have any questions or feedback for the Sentry team about this fix, please email [autofix@sentry.io](mailto:autofix@sentry.io) with the Run ID: 34843. --------- Co-authored-by: sentry-autofix[bot] <157164994+sentry-autofix[bot]@users.noreply.github.com> Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com> Co-authored-by: Billy Vong <billyvg@users.noreply.github.com>
1 parent da440cf commit e9eef76

File tree

1 file changed

+27
-9
lines changed

1 file changed

+27
-9
lines changed

static/app/components/replays/canvasReplayerPlugin.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ type CanvasEventWithTime = eventWithTime & {
1919
type: EventType.IncrementalSnapshot;
2020
};
2121

22+
function isCanvasElement(node: Node): node is HTMLCanvasElement {
23+
return node instanceof HTMLCanvasElement;
24+
}
25+
2226
function isCanvasMutationEvent(e: eventWithTime): e is CanvasEventWithTime {
2327
return (
2428
e.type === EventType.IncrementalSnapshot &&
@@ -173,8 +177,17 @@ export function CanvasReplayerPlugin(events: eventWithTime[]): ReplayPlugin {
173177
* The image element is saved to `containers` map, which will later get
174178
* written to when replay is being played.
175179
*/
176-
function cloneCanvas(id: number, node: HTMLCanvasElement) {
177-
const cloneNode = node.cloneNode() as HTMLCanvasElement;
180+
function cloneCanvas(id: number, node: Node) {
181+
if (!isCanvasElement(node)) {
182+
return null;
183+
}
184+
const cloneNode = node.cloneNode();
185+
186+
if (!isCanvasElement(cloneNode)) {
187+
Sentry.logger.warn('Replay: cloned node is not a canvas element');
188+
return null;
189+
}
190+
178191
canvases.set(id, cloneNode);
179192
document.adoptNode(cloneNode);
180193
return cloneNode;
@@ -263,9 +276,7 @@ export function CanvasReplayerPlugin(events: eventWithTime[]): ReplayPlugin {
263276
preload(e);
264277

265278
const source = replayer.getMirror().getNode(e.data.id);
266-
const target =
267-
canvases.get(e.data.id) ||
268-
(source && cloneCanvas(e.data.id, source as HTMLCanvasElement));
279+
const target = canvases.get(e.data.id) || (source && cloneCanvas(e.data.id, source));
269280

270281
if (!target) {
271282
throw new InvalidCanvasNodeError('No canvas found for id');
@@ -285,10 +296,17 @@ export function CanvasReplayerPlugin(events: eventWithTime[]): ReplayPlugin {
285296
});
286297

287298
const img = containers.get(e.data.id);
288-
if (img) {
289-
img.src = target.toDataURL();
290-
img.style.maxWidth = '100%';
291-
img.style.maxHeight = '100%';
299+
if (img && isCanvasElement(target)) {
300+
try {
301+
img.src = target.toDataURL();
302+
img.style.maxWidth = '100%';
303+
img.style.maxHeight = '100%';
304+
} catch (err) {
305+
Sentry.logger.warn('Replay: Failed to copy canvas to image', {
306+
error: err,
307+
element: target.tagName,
308+
});
309+
}
292310
}
293311

294312
prune(e);

0 commit comments

Comments
 (0)