From cc1985fa02b943b133c22680d1b319dbb79d221b Mon Sep 17 00:00:00 2001 From: Peter Abbondanzo Date: Thu, 10 Oct 2024 15:27:34 -0700 Subject: [PATCH] Swallow exceptions on draw, forward to onError (#46964) Summary: Graceful degradation is better than outright crashing. When rendering images that exceed Android's memory limits, React Native applications will fatally crash. This change intervenes by swallowing the exception that Android raises and forwards it to the `onError` handler. As a result, no image will be rendered instead of fatally crashing. Fresco already intervenes by default. It will raise a `PoolSizeViolationException` if the bitmap size exceeds memory limits set by the OS and applies a 2048 pixel maximum dimension to JPEG images, but it's still possible for these configuration limits to be removed and for images to fall just short of Fresco's memory limit. The exception is raised when we attempt to draw the bitmap, not when the bitmap is allocated in memory, so we must handle the exception here and not in Fresco. ## Changelog [Android][Fixed] - Apps will no longer fatally crash when trying to draw large images Reviewed By: tdn120 Differential Revision: D64144596 --- .../react/views/image/ReactImageView.kt | 11 ++++++++- .../js/examples/Image/ImageExample.js | 23 ++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index 5ed3c15d781e29..6a2a6c922ed0aa 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -338,7 +338,16 @@ public class ReactImageView( public override fun onDraw(canvas: Canvas) { BackgroundStyleApplicator.clipToPaddingBox(this, canvas) - super.onDraw(canvas) + try { + super.onDraw(canvas) + } catch (e: RuntimeException) { + // Only provide updates if downloadListener is set (shouldNotify is true) + if (downloadListener != null) { + val eventDispatcher = + UIManagerHelper.getEventDispatcherForReactTag(context as ReactContext, id) + eventDispatcher?.dispatchEvent(createErrorEvent(UIManagerHelper.getSurfaceId(this), id, e)) + } + } } public fun maybeUpdateView() { diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 05bb37e214c5e2..20bb44c754cfc3 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -10,6 +10,7 @@ 'use strict'; +import type {ImageProps} from 'react-native/Libraries/Image/ImageProps'; import type {LayoutEvent} from 'react-native/Libraries/Types/CoreEventTypes'; import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags'; @@ -228,12 +229,8 @@ type NetworkImageExampleState = {| progress: $ReadOnlyArray, |}; -type NetworkImageExampleProps = $ReadOnly<{| - source: ImageSource, -|}>; - class NetworkImageExample extends React.Component< - NetworkImageExampleProps, + ImageProps, NetworkImageExampleState, > { state: NetworkImageExampleState = { @@ -248,7 +245,7 @@ class NetworkImageExample extends React.Component< ) : ( <> this.setState({loading: true})} onError={e => @@ -970,6 +967,20 @@ exports.examples = [ ); }, }, + { + title: 'Error Handler for Large Images', + render: function (): React.Node { + return ( + 100MB bitmap + uri: 'https://upload.wikimedia.org/wikipedia/commons/2/2c/Mars_topography_%28MOLA_dataset%29_with_poles_HiRes.jpg', + }} + /> + ); + }, + }, { title: 'Image Download Progress', render: function (): React.Node {