Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

45 changes: 0 additions & 45 deletions patches/@shopify/react-native-skia/details.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,48 +23,3 @@
- Upstream PR/issue:
- E/App issue: https://github.com/Expensify/App/issues/90135
- PR introducing patch: https://github.com/Expensify/App/pull/93295

### [@shopify+react-native-skia+2.4.14+002+fallback-to-software-surface.patch](@shopify+react-native-skia+2.4.14+002+fallback-to-software-surface.patch)

- Reason:

```
Fixes an uncatchable crash on web (Sentry APP-7MV: "failed to create webgl
context: err 0") when the browser cannot create a WebGL2 context - hardware
acceleration disabled, GPU blocklisted, or the per-page live context limit
exhausted.

WebGLRenderer.onResize() calls CanvasKit.MakeWebGLCanvasSurface(canvas), which
throws instead of returning null when no context can be created. onResize runs
from a ResizeObserver callback, so the throw escapes as an unhandled error that
no try/catch or React error boundary can reach, and the chart area is left blank.
Guarding at the call site cannot prevent this: a capability check runs when the
chart mounts, but the context is created much later, after the CanvasKit WASM
module loads.

Fix: catch the failure and fall back to CanvasKit.MakeSWCanvasSurface, so the
chart still renders (on the CPU) instead of crashing the page. If that also
fails, leave this.surface null - the constructor already initialises it to null
and both draw() and makeImageSnapshot() null-check it. This mirrors the sibling
renderPictureToSurface path, which already treats a failed WebGL surface as
recoverable rather than fatal. Charts on capable clients are unaffected and
still render through WebGL.

The software fallback is only used when the canvas can actually provide a 2D
context. MakeSWCanvasSurface just stores the canvas and calls getContext("2d")
later, when the surface is flushed, so a canvas that already holds a WebGL
context (one whose chart rendered before WebGL became unavailable) would return
null there and turn into an uncatchable "Cannot read properties of null (reading
'putImageData')". Checking the 2D context up front keeps that case stable.

When no surface can be created at all, the renderer dispatches a bubbling
"skia-surface-unavailable" CustomEvent on its canvas (a no-op without a listener).
A capability check cannot cover this case - it runs at chart mount while the
context is created only after the CanvasKit WASM module loads - so the event is
the only reliable signal, and SkiaWebChart uses it to swap in its "unable to
display chart" empty state instead of leaving a blank canvas.
```

- Upstream PR/issue: https://github.com/Shopify/react-native-skia/pull/3996 — applies the same defensive handling (and the `skia-surface-unavailable` event) to upstream `main`, where the throws now live in the renderer constructor and `onResize`. Once it ships in a release we consume, this patch can be dropped.
- E/App issue: https://github.com/Expensify/App/issues/97104
- PR introducing patch: https://github.com/Expensify/App/pull/97219
26 changes: 8 additions & 18 deletions src/components/Charts/SkiaWebChart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ import useThemeStyles from '@hooks/useThemeStyles';

import variables from '@styles/variables';

import viewRef from '@src/types/utils/viewRef';

import type {ComponentType} from 'react';

import {WithSkiaWeb} from '@shopify/react-native-skia/lib/module/web';
import React, {useRef, useState} from 'react';
import React, {useState} from 'react';
import {View} from 'react-native';

import isSkiaWebSupported from './isSkiaWebSupported';
import useIsSkiaSurfaceUnavailable from './useIsSkiaSurfaceUnavailable';

type SkiaWebChartProps<TProps> = {
/** Lazily imports the Skia-backed chart component to render. */
Expand Down Expand Up @@ -57,18 +54,13 @@ function ChartUnavailable() {
// eslint-disable-next-line @typescript-eslint/no-restricted-types
function SkiaWebChart<TProps extends object>({getComponent, componentProps}: SkiaWebChartProps<TProps>) {
const styles = useThemeStyles();
const containerRef = useRef<HTMLElement | null>(null);

// Probe once per mount (not per render) so re-rendering doesn't repeatedly create WebGL contexts,
// while a fresh chart still re-checks capability instead of trusting a stale session-wide result.
const [isSupported] = useState(() => isSkiaWebSupported());

// The probe can pass while the renderer still ends up without a drawing surface, so also listen for the
// renderer reporting that and degrade to the empty state.
const isSurfaceUnavailable = useIsSkiaSurfaceUnavailable(containerRef);

// If unsupported, the device can't give CanvasKit a usable WebGL surface.
if (!isSupported || isSurfaceUnavailable) {
if (!isSupported) {
return <ChartUnavailable />;
}

Expand All @@ -79,14 +71,12 @@ function SkiaWebChart<TProps extends object>({getComponent, componentProps}: Ski
);

return (
<View ref={viewRef(containerRef)}>
<WithSkiaWeb
opts={{locateFile: (file: string) => `/${file}`}}
getComponent={getComponent}
componentProps={componentProps}
fallback={fallback}
/>
</View>
<WithSkiaWeb
opts={{locateFile: (file: string) => `/${file}`}}
getComponent={getComponent}
componentProps={componentProps}
fallback={fallback}
/>
);
}

Expand Down

This file was deleted.

This file was deleted.

Loading