From 6325319e0ef9c20f5fa4b2f1ff9083a6a282d10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 20 Oct 2025 14:38:37 +0200 Subject: [PATCH 1/9] fix(android): fix pressability issues, introduce measureAsyncOnUI to nativeDOM --- .../Libraries/Pressability/Pressability.js | 2 +- .../react/fabric/FabricUIManager.java | 40 +++++++++++++ .../react/fabric/mounting/MeasureAsyncUtil.kt | 56 +++++++++++++++++++ .../react/fabric/mounting/MountingManager.kt | 27 +++++++++ .../react/fabric/FabricMountingManager.cpp | 13 +++++ .../jni/react/fabric/FabricMountingManager.h | 4 ++ .../react/fabric/FabricUIManagerBinding.cpp | 11 ++++ .../jni/react/fabric/FabricUIManagerBinding.h | 4 ++ .../react/nativemodule/dom/NativeDOM.cpp | 24 ++++++++ .../react/nativemodule/dom/NativeDOM.h | 8 +++ .../react/renderer/scheduler/Scheduler.cpp | 9 +++ .../react/renderer/scheduler/Scheduler.h | 3 + .../renderer/scheduler/SchedulerDelegate.h | 4 ++ .../renderer/uimanager/UIManagerDelegate.h | 8 +++ .../src/private/types/HostInstance.js | 5 ++ .../webapis/dom/nodes/ReactNativeElement.js | 9 +++ .../webapis/dom/nodes/specs/NativeDOM.js | 10 ++++ 17 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt diff --git a/packages/react-native/Libraries/Pressability/Pressability.js b/packages/react-native/Libraries/Pressability/Pressability.js index eabfb8320feb79..fdb5fd3e2b309a 100644 --- a/packages/react-native/Libraries/Pressability/Pressability.js +++ b/packages/react-native/Libraries/Pressability/Pressability.js @@ -805,7 +805,7 @@ export default class Pressability { if (typeof this._responderID === 'number') { UIManager.measure(this._responderID, this._measureCallback); } else { - this._responderID.measure(this._measureCallback); + this._responderID.measureAsyncOnUI(this._measureCallback); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 3f765f814155dd..5f1cf3328be5ab 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -25,6 +25,7 @@ import android.view.View; import android.view.accessibility.AccessibilityEvent; import androidx.annotation.AnyThread; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.core.util.Preconditions; @@ -34,6 +35,7 @@ import com.facebook.infer.annotation.Nullsafe; import com.facebook.infer.annotation.ThreadConfined; import com.facebook.proguard.annotations.DoNotStripAny; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ColorPropConverter; import com.facebook.react.bridge.GuardedRunnable; import com.facebook.react.bridge.LifecycleEventListener; @@ -1291,6 +1293,44 @@ public String toString() { }); } + public void measureAsyncOnUI(int surfaceId, int reactTag, final Callback callback) { + mMountItemDispatcher.addMountItem( + new MountItem() { + @Override + public void execute(@NonNull MountingManager mountingManager) { + int[] mMeasureBuffer = new int[4]; + boolean result = mountingManager.measureAsyncOnUI( + surfaceId, + reactTag, + mMeasureBuffer + ); + + if (!result) { + // TODO: add error handling callback + return; + } + + double x = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]); + double y = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]); + double width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]); + double height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]); + + callback.invoke(0, 0, width, height, x, y); + } + + @Override + public int getSurfaceId() { + return surfaceId; + } + + @NonNull + @Override + public String toString() { + return "MEASURE_VIEW"; + } + }); + } + @Override public void profileNextBatch() { // TODO T31905686: Remove this method and add support for multi-threading performance counters diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt new file mode 100644 index 00000000000000..b891debad4aea7 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt @@ -0,0 +1,56 @@ +package com.facebook.react.fabric.mounting + +import android.graphics.RectF +import android.view.View +import android.view.ViewParent + +public object MeasureAsyncUtil { + private val mBoundingBox = RectF() + + /** + * Output buffer will be {x, y, width, height}. + */ + public fun measure(rootView: View, viewToMeasure: View, outputBuffer: IntArray) { + computeBoundingBox(rootView, outputBuffer) + val rootX = outputBuffer[0] + val rootY = outputBuffer[1] + computeBoundingBox(viewToMeasure, outputBuffer) + outputBuffer[0] -= rootX + outputBuffer[1] -= rootY + } + + private fun computeBoundingBox(view: View, outputBuffer: IntArray) { + mBoundingBox.set(0f, 0f, view.width.toFloat(), view.height.toFloat()) + mapRectFromViewToWindowCoords(view, mBoundingBox) + + outputBuffer[0] = Math.round(mBoundingBox.left) + outputBuffer[1] = Math.round(mBoundingBox.top) + outputBuffer[2] = Math.round(mBoundingBox.right - mBoundingBox.left) + outputBuffer[3] = Math.round(mBoundingBox.bottom - mBoundingBox.top) + } + + private fun mapRectFromViewToWindowCoords(view: View, rect: RectF) { + var matrix = view.getMatrix() + if (!matrix.isIdentity) { + matrix.mapRect(rect) + } + + rect.offset(view.left.toFloat(), view.top.toFloat()) + + var parent: ViewParent? = view.parent + while (parent is View) { + val parentView = parent as View + + rect.offset(-parentView.scrollX.toFloat(), -parentView.scrollY.toFloat()) + + matrix = parentView.getMatrix() + if (!matrix.isIdentity) { + matrix.mapRect(rect) + } + + rect.offset(parentView.left.toFloat(), parentView.top.toFloat()) + + parent = parentView.parent + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt index c40beec2172797..60c65577bebec9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt @@ -22,6 +22,7 @@ import com.facebook.react.bridge.WritableMap import com.facebook.react.fabric.events.EventEmitterWrapper import com.facebook.react.fabric.mounting.mountitems.MountItem import com.facebook.react.touch.JSResponderHandler +import com.facebook.react.uimanager.IllegalViewOperationException import com.facebook.react.uimanager.RootViewManager import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ViewManagerRegistry @@ -333,6 +334,32 @@ internal class MountingManager( attachmentsPositions, ) + @UiThread + @ThreadConfined(ThreadConfined.UI) + fun measureAsyncOnUI( + surfaceId: Int, + reactTag: Int, + outputBuffer: IntArray, + ): Boolean { + val smm = getSurfaceManagerEnforced(surfaceId, "measure") + val view = try { + smm.getView(reactTag); + } catch (ex: IllegalViewOperationException) { + FLog.e(TAG, "Failed to find view for tag: %d. Error: %s", reactTag, ex.message); + return false + } + + val rootView = try { + smm.getView(surfaceId) + } catch (ex: IllegalViewOperationException) { + FLog.e(TAG, "Failed to find root view for surfaceId: %d. Error: %s", surfaceId, ex.message); + return false + } + + MeasureAsyncUtil.measure(rootView, view, outputBuffer) + return true; + } + fun enqueuePendingEvent( surfaceId: Int, reactTag: Int, diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp index e29be7f9cade5c..cd22b9b9467d52 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -1082,4 +1083,16 @@ void FabricMountingManager::synchronouslyUpdateViewOnUIThread( synchronouslyUpdateViewOnUIThreadJNI(javaUIManager_, viewTag, propsMap); } +void FabricMountingManager::measureAsyncOnUI( + const ShadowView& shadowView, + const std::function& callback) { + static auto measureJNI = + JFabricUIManager::javaClassStatic()->getMethod)>( + "measureAsyncOnUI"); + + auto javaCallback = JCxxCallbackImpl::newObjectCxxArgs(callback); + + measureJNI(javaUIManager_, shadowView.surfaceId, shadowView.tag, javaCallback); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h index 61978317ff5eda..8357c2552924a9 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h @@ -64,6 +64,10 @@ class FabricMountingManager final { Tag viewTag, const folly::dynamic& props); + void measureAsyncOnUI( + const ShadowView& shadowView, + const std::function& callback); + private: bool isOnMainThread(); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp index 801c8f1fd6017a..2c80969cfbdc36 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp @@ -728,6 +728,17 @@ void FabricUIManagerBinding::schedulerDidUpdateShadowTree( // no-op } +void FabricUIManagerBinding::schedulerMeasureAsyncOnUI( + const facebook::react::ShadowView& shadowView, + const std::function& callback) { + auto mountingManager = getMountingManager("schedulerMeasureAsyncOnUI"); + if (!mountingManager) { + return; + } + + mountingManager->measureAsyncOnUI(shadowView, callback); +} + void FabricUIManagerBinding::onAnimationStarted() { auto mountingManager = getMountingManager("onAnimationStarted"); if (!mountingManager) { diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h index 39e724038c6534..22a717b7671a85 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h @@ -129,6 +129,10 @@ class FabricUIManagerBinding : public jni::HybridClass, void schedulerDidUpdateShadowTree( const std::unordered_map& tagToProps) override; + void schedulerMeasureAsyncOnUI( + const ShadowView& shadowView, + const std::function& callback) override; + void setPixelDensity(float pointScaleFactor); void driveCxxAnimations(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp index da40ea3b7899ea..ef7a336b125ce3 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp @@ -466,6 +466,30 @@ void NativeDOM::measureLayout( onSuccess(rect.x, rect.y, rect.width, rect.height); } +void NativeDOM::measureAsyncOnUI( + jsi::Runtime& rt, + std::shared_ptr shadowNode, + const MeasureAsyncOnUICallback& callback) { + UIManager& uiManager = getUIManagerFromRuntime(rt); + UIManagerDelegate* uiManagerDelegate = uiManager.getDelegate(); + if (uiManagerDelegate == nullptr) { + return; + } + + std::function jsCallback = [callback](const folly::dynamic& args) { + // TODO: can we make the rest accept an AsyncFunction directly? + callback.call( + args.at(0).getDouble(), + args.at(1).getDouble(), + args.at(2).getDouble(), + args.at(3).getDouble(), + args.at(4).getDouble(), + args.at(5).getDouble()); + }; + + uiManagerDelegate->uiManagerMeasureAsyncOnUI(shadowNode, jsCallback); +} + #pragma mark - Legacy direct manipulation APIs (for `ReactNativeElement`). void NativeDOM::setNativeProps( diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h index 1c6a1c84e7b8cb..23824f4c707631 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h @@ -30,6 +30,9 @@ using MeasureInWindowOnSuccessCallback = using MeasureLayoutOnSuccessCallback = SyncCallback; +using MeasureAsyncOnUICallback = + AsyncCallback; + class NativeDOM : public NativeDOMCxxSpec { public: NativeDOM(std::shared_ptr jsInvoker); @@ -143,6 +146,11 @@ class NativeDOM : public NativeDOMCxxSpec { jsi::Function onFail, const MeasureLayoutOnSuccessCallback& onSuccess); + void measureAsyncOnUI( + jsi::Runtime& rt, + std::shared_ptr shadowNode, + const MeasureAsyncOnUICallback& callback); + #pragma mark - Legacy direct manipulation APIs (for `ReactNativeElement`). void setNativeProps( diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 7d0e07ba9c147a..7992ce933dc072 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -386,6 +386,15 @@ void Scheduler::uiManagerDidStartSurface(const ShadowTree& shadowTree) { } } +void Scheduler::uiManagerMeasureAsyncOnUI( + const std::shared_ptr& shadowNode, + const std::function& callback) { + if (delegate_ != nullptr) { + auto shadowView = ShadowView(*shadowNode); + delegate_->schedulerMeasureAsyncOnUI(shadowView, callback); + } +} + void Scheduler::reportMount(SurfaceId surfaceId) const { uiManager_->reportMount(surfaceId); } diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h index bd7dad014ba10a..6a0bfe3a318c3d 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -104,6 +104,9 @@ class Scheduler final : public UIManagerDelegate { void uiManagerShouldRemoveEventListener( const std::shared_ptr& listener) final; void uiManagerDidStartSurface(const ShadowTree& shadowTree) override; + void uiManagerMeasureAsyncOnUI( + const std::shared_ptr& shadowNode, + const std::function& callback) override; #pragma mark - ContextContainer std::shared_ptr getContextContainer() const; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h index 0a88e2433937a8..fdae7f79f2fafb 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h @@ -71,6 +71,10 @@ class SchedulerDelegate { virtual void schedulerDidUpdateShadowTree( const std::unordered_map& tagToProps) = 0; + virtual void schedulerMeasureAsyncOnUI( + const ShadowView& shadowView, + const std::function& callback) = 0; + virtual ~SchedulerDelegate() noexcept = default; }; diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h index e649a273742e22..2d9424a04f67da 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h @@ -84,6 +84,14 @@ class UIManagerDelegate { virtual void uiManagerShouldRemoveEventListener( const std::shared_ptr& listener) = 0; + /* + * Measures the layout of the shadow node async on the UI thread using the native layout hierarchy. + * As this has to schedule a call on the UI thread its async. + */ + virtual void uiManagerMeasureAsyncOnUI( + const std::shared_ptr& shadowNode, + const std::function& callback) = 0; + /* * Start surface. */ diff --git a/packages/react-native/src/private/types/HostInstance.js b/packages/react-native/src/private/types/HostInstance.js index cb7dff7dcc44ea..ff01e4b28274e3 100644 --- a/packages/react-native/src/private/types/HostInstance.js +++ b/packages/react-native/src/private/types/HostInstance.js @@ -72,6 +72,11 @@ export interface LegacyHostInstanceMethods { * prop](docs/view.html#onlayout) instead. */ measure(callback: MeasureOnSuccessCallback): void; + /** + * Same as `measure()`, but instead of using the shadow nodes layout information, + * it uses the native layout hierarchy to measure the view on the UI thread. + */ + measureAsyncOnUI(callback: MeasureOnSuccessCallback): void; /** * Determines the location of the given view in the window and returns the * values via an async callback. If the React root view is embedded in diff --git a/packages/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js b/packages/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js index 78ab5696f43cc0..3ca56d6bcae4d4 100644 --- a/packages/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js +++ b/packages/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js @@ -164,6 +164,15 @@ class ReactNativeElement extends ReadOnlyElement implements NativeMethods { } } + measureAsyncOnUI(callback: MeasureOnSuccessCallback) { + const node = getNativeElementReference(this); + if (node != null) { + console.log("ReactNativeElement node.measureAsyncOnUI()"); + // TODO: this is only available on native mobile platforms - when using web, do we need to fallback to measure()? + NativeDOM.measureAsyncOnUI(node, callback); + } + } + measureInWindow(callback: MeasureInWindowOnSuccessCallback) { const node = getNativeElementReference(this); if (node != null) { diff --git a/packages/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js b/packages/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js index 565662fd5fc3e1..d9d3c51ef66a1d 100644 --- a/packages/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js +++ b/packages/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js @@ -159,6 +159,11 @@ export interface Spec extends TurboModule { onSuccess: MeasureLayoutOnSuccessCallback, ) => void; + +measureAsyncOnUI: ( + nativeElementReference: mixed, + callback: MeasureInWindowOnSuccessCallback, + ) => void; + /** * Legacy direct manipulation APIs (for `ReactNativeElement`). */ @@ -429,6 +434,11 @@ export interface RefinedSpec { onSuccess: MeasureLayoutOnSuccessCallback, ) => void; + +measureAsyncOnUI: ( + nativeElementReference: NativeElementReference, + callback: MeasureInWindowOnSuccessCallback, + ) => void; + /** * Legacy direct manipulation APIs */ From 32fbcfd42c073ee9ec104600759165b13e8342bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Mon, 20 Oct 2025 14:41:30 +0200 Subject: [PATCH 2/9] add copyright header --- .../com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt index b891debad4aea7..9680cf5e99e31c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MeasureAsyncUtil.kt @@ -1,3 +1,10 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + package com.facebook.react.fabric.mounting import android.graphics.RectF From 6ffd91148d7cda7e5197e734766b3b2993f262ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 21 Oct 2025 10:23:37 +0200 Subject: [PATCH 3/9] implement iOS --- .../Fabric/Mounting/RCTMountingManager.h | 3 +++ .../Fabric/Mounting/RCTMountingManager.mm | 26 +++++++++++++++++++ .../react-native/React/Fabric/RCTScheduler.h | 3 +++ .../react-native/React/Fabric/RCTScheduler.mm | 5 ++++ .../React/Fabric/RCTSurfacePresenter.mm | 13 ++++++++++ 5 files changed, 50 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.h b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.h index 70a56433d57bf3..e1f240eec1fadc 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.h +++ b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.h @@ -9,6 +9,7 @@ #import #import +#import #import #import #import @@ -69,6 +70,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)synchronouslyUpdateViewOnUIThread:(ReactTag)reactTag changedProps:(folly::dynamic)props componentDescriptor:(const facebook::react::ComponentDescriptor &)componentDescriptor; + +- (void)measureAsyncOnUI:(ReactTag)reactTag rootView:(UIView*)rootView callback:(const std::function &)callback; @end NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm index fa65e055d05d95..5add32c3ee39a8 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm +++ b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm @@ -342,4 +342,30 @@ - (void)synchronouslyDispatchAccessbilityEventOnUIThread:(ReactTag)reactTag even } } +- (void)measureAsyncOnUI:(ReactTag)reactTag rootView:(UIView*)rootView callback:(const std::function &)callback { + RCTAssertMainQueue(); + + UIView *view = [self->_componentViewRegistry findComponentViewWithTag:reactTag]; + if (!view) { + // this view was probably collapsed out + RCTLogWarn(@"measure cannot find view with tag #%@", reactTag); + callback({}); + return; + } + + // By convention, all coordinates, whether they be touch coordinates, or + // measurement coordinates are with respect to the root view. + CGRect frame = view.frame; + CGRect globalBounds = [view convertRect:view.bounds toView:rootView]; + + callback( + folly::dynamic::array(frame.origin.x, + frame.origin.y, + globalBounds.size.width, + globalBounds.size.height, + globalBounds.origin.x, + globalBounds.origin.y) + ); +} + @end diff --git a/packages/react-native/React/Fabric/RCTScheduler.h b/packages/react-native/React/Fabric/RCTScheduler.h index ed585390890b1d..8c549b31f4a35c 100644 --- a/packages/react-native/React/Fabric/RCTScheduler.h +++ b/packages/react-native/React/Fabric/RCTScheduler.h @@ -43,6 +43,9 @@ NS_ASSUME_NONNULL_BEGIN forShadowView:(const facebook::react::ShadowView &)shadowView; - (void)schedulerDidSynchronouslyUpdateViewOnUIThread:(facebook::react::Tag)reactTag props:(folly::dynamic)props; + +- (void)schedulerMeasureAsyncOnUI:(const facebook::react::ShadowView &)shadowView + callback:(const std::function &)callback; @end /** diff --git a/packages/react-native/React/Fabric/RCTScheduler.mm b/packages/react-native/React/Fabric/RCTScheduler.mm index 6cc4e5b6feb730..d5139eef5bfa19 100644 --- a/packages/react-native/React/Fabric/RCTScheduler.mm +++ b/packages/react-native/React/Fabric/RCTScheduler.mm @@ -77,6 +77,11 @@ void schedulerDidUpdateShadowTree(const std::unordered_map // Does nothing. // This delegate method is not currently used on iOS. } + + void schedulerMeasureAsyncOnUI(const ShadowView& shadowView, const std::function &callback) override { + RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + [scheduler.delegate schedulerMeasureAsyncOnUI:shadowView callback:callback]; + } private: void *scheduler_; diff --git a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm index 9a5b94696ccf5d..22d7c2d871ba4e 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm @@ -334,6 +334,19 @@ - (void)schedulerDidSetIsJSResponder:(BOOL)isJSResponder [_mountingManager setIsJSResponder:isJSResponder blockNativeResponder:blockNativeResponder forShadowView:shadowView]; } +- (void)schedulerMeasureAsyncOnUI:(const facebook::react::ShadowView &)shadowView callback:(const std::function &)callback { + ReactTag tag = shadowView.tag; + SurfaceId surfaceId = shadowView.surfaceId; + RCTFabricSurface *surface = [self surfaceForRootTag:surfaceId]; + + std::function callbackCopy = callback; + RCTExecuteOnMainQueue(^{ + UIView *rootView = surface.view; + [self->_mountingManager measureAsyncOnUI:tag rootView:rootView callback:callbackCopy]; + }); +} + + - (void)addObserver:(id)observer { std::unique_lock lock(_observerListMutex); From 6d735ba11d071f35d9a55e830d89aaa014e3ba20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 21 Oct 2025 10:28:44 +0200 Subject: [PATCH 4/9] remove logging --- .../src/private/webapis/dom/nodes/ReactNativeElement.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js b/packages/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js index 3ca56d6bcae4d4..442ea0476028da 100644 --- a/packages/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js +++ b/packages/react-native/src/private/webapis/dom/nodes/ReactNativeElement.js @@ -167,7 +167,6 @@ class ReactNativeElement extends ReadOnlyElement implements NativeMethods { measureAsyncOnUI(callback: MeasureOnSuccessCallback) { const node = getNativeElementReference(this); if (node != null) { - console.log("ReactNativeElement node.measureAsyncOnUI()"); // TODO: this is only available on native mobile platforms - when using web, do we need to fallback to measure()? NativeDOM.measureAsyncOnUI(node, callback); } From 272dcfd46e96788dfef819f48f1ddac2b099fe41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 21 Oct 2025 10:33:30 +0200 Subject: [PATCH 5/9] formatting --- .../Fabric/Mounting/RCTMountingManager.h | 6 ++- .../Fabric/Mounting/RCTMountingManager.mm | 44 +++++++++---------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.h b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.h index e1f240eec1fadc..24e8fe989596df 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.h +++ b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.h @@ -7,9 +7,9 @@ #import +#import #import #import -#import #import #import #import @@ -71,7 +71,9 @@ NS_ASSUME_NONNULL_BEGIN changedProps:(folly::dynamic)props componentDescriptor:(const facebook::react::ComponentDescriptor &)componentDescriptor; -- (void)measureAsyncOnUI:(ReactTag)reactTag rootView:(UIView*)rootView callback:(const std::function &)callback; +- (void)measureAsyncOnUI:(ReactTag)reactTag + rootView:(UIView *)rootView + callback:(const std::function &)callback; @end NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm index 5add32c3ee39a8..62593cca9a5f13 100644 --- a/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm +++ b/packages/react-native/React/Fabric/Mounting/RCTMountingManager.mm @@ -344,28 +344,28 @@ - (void)synchronouslyDispatchAccessbilityEventOnUIThread:(ReactTag)reactTag even - (void)measureAsyncOnUI:(ReactTag)reactTag rootView:(UIView*)rootView callback:(const std::function &)callback { RCTAssertMainQueue(); - - UIView *view = [self->_componentViewRegistry findComponentViewWithTag:reactTag]; - if (!view) { - // this view was probably collapsed out - RCTLogWarn(@"measure cannot find view with tag #%@", reactTag); - callback({}); - return; - } - - // By convention, all coordinates, whether they be touch coordinates, or - // measurement coordinates are with respect to the root view. - CGRect frame = view.frame; - CGRect globalBounds = [view convertRect:view.bounds toView:rootView]; - - callback( - folly::dynamic::array(frame.origin.x, - frame.origin.y, - globalBounds.size.width, - globalBounds.size.height, - globalBounds.origin.x, - globalBounds.origin.y) - ); + + UIView *view = [self->_componentViewRegistry findComponentViewWithTag:reactTag]; + if (!view) { + // this view was probably collapsed out + RCTLogWarn(@"measure cannot find view with tag #%@", reactTag); + callback({}); + return; + } + + // By convention, all coordinates, whether they be touch coordinates, or + // measurement coordinates are with respect to the root view. + CGRect frame = view.frame; + CGRect globalBounds = [view convertRect:view.bounds toView:rootView]; + + callback( + folly::dynamic::array(frame.origin.x, + frame.origin.y, + globalBounds.size.width, + globalBounds.size.height, + globalBounds.origin.x, + globalBounds.origin.y) + ); } @end From 0cddab89bc9d5c8c6914c05f472f11aea480e7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 21 Oct 2025 10:40:56 +0200 Subject: [PATCH 6/9] yarn run build-types --- packages/react-native/ReactNativeApi.d.ts | 150 +++++++++++----------- 1 file changed, 76 insertions(+), 74 deletions(-) diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 2ac82083cbfec3..e335b8ed6a3265 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<4e0d1f9ebc86fb237989b7099bf6f9df>> + * @generated SignedSource<> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -2985,6 +2985,7 @@ declare interface LegacyHostInstanceMethods { blur(): void focus(): void measure(callback: MeasureOnSuccessCallback): void + measureAsyncOnUI(callback: MeasureOnSuccessCallback): void measureInWindow(callback: MeasureInWindowOnSuccessCallback): void measureLayout( relativeToNativeNode: HostInstance | number, @@ -3983,6 +3984,7 @@ declare class ReactNativeElement_default ) focus(): void measure(callback: MeasureOnSuccessCallback): void + measureAsyncOnUI(callback: MeasureOnSuccessCallback): void measureInWindow(callback: MeasureInWindowOnSuccessCallback): void measureLayout( relativeToNativeNode: HostInstance | number, @@ -5910,7 +5912,7 @@ declare type WrapperComponentProvider = ( appParameters: Object, ) => React.ComponentType export { - AccessibilityActionEvent, // f6181a2c + AccessibilityActionEvent, // bcdb8372 AccessibilityInfo, // 70604904 AccessibilityProps, // 5a2836fc AccessibilityRole, // f2f2e066 @@ -5919,14 +5921,14 @@ export { ActionSheetIOS, // 88e6bfb0 ActionSheetIOSOptions, // 1756eb5a ActivityIndicator, // 8d041a45 - ActivityIndicatorProps, // 0fa4e79d + ActivityIndicatorProps, // 9613c00a Alert, // 5bf12165 AlertButton, // bf1a3b60 AlertButtonStyle, // ec9fb242 AlertOptions, // a0cdac0f AlertType, // 5ab91217 AndroidKeyboardEvent, // e03becc8 - Animated, // 6b6a0b2e + Animated, // deb7a21d AppConfig, // ebddad4b AppRegistry, // 6cdee1d6 AppState, // f7097b1b @@ -5936,12 +5938,12 @@ export { AutoCapitalize, // c0e857a0 BackHandler, // a9f9bad9 BackPressEventName, // 4620fb76 - BlurEvent, // 870b9bb5 + BlurEvent, // 5fd88ed0 BoxShadowValue, // b679703f Button, // dd130b61 - ButtonProps, // 3c081e75 + ButtonProps, // 236e0ebe Clipboard, // 9b8c878e - CodegenTypes, // 030a94b8 + CodegenTypes, // 289204de ColorSchemeName, // 31a4350e ColorValue, // 98989a8f ComponentProvider, // b5c60ddd @@ -5958,8 +5960,8 @@ export { DisplayMetrics, // 1dc35cef DisplayMetricsAndroid, // 872e62eb DrawerLayoutAndroid, // 14121b61 - DrawerLayoutAndroidProps, // 123d3a9d - DrawerSlideEvent, // cc43db83 + DrawerLayoutAndroidProps, // ac1f6dd1 + DrawerSlideEvent, // 0e49b035 DropShadowValue, // e9df2606 DynamicColorIOS, // 1f9b3410 DynamicColorIOSTuple, // 023ce58e @@ -5972,28 +5974,28 @@ export { EventSubscription, // b8d084aa ExtendedExceptionData, // 5a6ccf5a FilterFunction, // bf24c0e3 - FlatList, // cbb48cbe - FlatListProps, // 451be810 - FocusEvent, // 529b43eb + FlatList, // 124296a3 + FlatListProps, // 53bf0eb3 + FocusEvent, // b6ab43aa FontVariant, // 7c7558bb - GestureResponderEvent, // b466f6d6 - GestureResponderHandlers, // 8356843d + GestureResponderEvent, // caec0108 + GestureResponderHandlers, // 2c19fb27 Handle, // 2d65285d - HostComponent, // 5e13ff5a - HostInstance, // 489cbe7f + HostComponent, // 66ba7e82 + HostInstance, // 6678e078 I18nManager, // f2fa58ce IOSKeyboardEvent, // e67bfe3a IgnorePattern, // ec6f6ece Image, // 04474205 - ImageBackground, // 489b1c17 - ImageBackgroundProps, // 1b209e36 - ImageErrorEvent, // b7b2ae63 - ImageLoadEvent, // 5baae813 - ImageProgressEventIOS, // adb35052 - ImageProps, // 40c727e1 + ImageBackground, // be114932 + ImageBackgroundProps, // 8cf5595f + ImageErrorEvent, // 562f4896 + ImageLoadEvent, // e0b55da2 + ImageProgressEventIOS, // bba5624a + ImageProps, // c6fb6c80 ImagePropsAndroid, // 9fd9bcbb - ImagePropsBase, // 715b84bf - ImagePropsIOS, // 318adce2 + ImagePropsBase, // 16eeea72 + ImagePropsIOS, // 78c585dd ImageRequireSource, // 681d683b ImageResolvedAssetSource, // f3060931 ImageSize, // 1c47cf88 @@ -6007,8 +6009,8 @@ export { Insets, // e7fe432a InteractionManager, // 301bfa63 Keyboard, // 87311c77 - KeyboardAvoidingView, // d88d0d4c - KeyboardAvoidingViewProps, // bc844418 + KeyboardAvoidingView, // cf4cda30 + KeyboardAvoidingViewProps, // 30700314 KeyboardEvent, // c3f895d4 KeyboardEventEasing, // af4091c8 KeyboardEventName, // 59299ad6 @@ -6021,7 +6023,7 @@ export { LayoutAnimationProperty, // 52995f01 LayoutAnimationType, // 2da0a29b LayoutAnimationTypes, // 081b3bde - LayoutChangeEvent, // c674f902 + LayoutChangeEvent, // fbfdf4dd LayoutConformanceProps, // 055f03b8 LayoutRectangle, // 6601b294 Linking, // 292de0a0 @@ -6034,31 +6036,31 @@ export { MeasureLayoutOnSuccessCallback, // 3592502a MeasureOnSuccessCallback, // 82824e59 Modal, // 78e8a79d - ModalBaseProps, // 0c81c9b1 - ModalProps, // 270223fa + ModalBaseProps, // 6a3a63df + ModalProps, // 4e6c1c78 ModalPropsAndroid, // 515fb173 - ModalPropsIOS, // 4fbcedf6 - ModeChangeEvent, // b889a7ce - MouseEvent, // 53ede3db + ModalPropsIOS, // a8b21dfc + ModeChangeEvent, // 5563d49f + MouseEvent, // f79a350b NativeAppEventEmitter, // b4d20c1d NativeColorValue, // d2094c29 - NativeComponentRegistry, // 7fd99ba6 + NativeComponentRegistry, // 79db9e8f NativeDialogManagerAndroid, // 6254873e NativeEventEmitter, // d72906cc NativeEventSubscription, // de3942e7 - NativeMethods, // 03dc51c5 - NativeMethodsMixin, // 4b061b7e + NativeMethods, // a64dcff8 + NativeMethodsMixin, // abc19d6f NativeModules, // 1cf72876 - NativeMouseEvent, // ff25cf35 - NativePointerEvent, // 89c1f3ad + NativeMouseEvent, // 490211ee + NativePointerEvent, // 2b3d66c2 NativeScrollEvent, // caad7f53 - NativeSyntheticEvent, // d2a1fe6a + NativeSyntheticEvent, // 3ea81d05 NativeTouchEvent, // 59b676df NativeUIEvent, // 44ac26ac Networking, // b674447b OpaqueColorValue, // 25f3fa5b PanResponder, // 98a9b6fc - PanResponderCallbacks, // d325aa56 + PanResponderCallbacks, // 5a0faa7a PanResponderGestureState, // 54baf558 PanResponderInstance, // c8b0d00c Permission, // 06473f4f @@ -6070,14 +6072,14 @@ export { PlatformOSType, // 0a17561e PlatformSelectSpec, // 09ed7758 PointValue, // 69db075f - PointerEvent, // ff3129ff + PointerEvent, // 1430fb1e Pressable, // 3c6e4eb9 PressableAndroidRippleConfig, // 42bc9727 - PressableProps, // 96c8132d + PressableProps, // e00cf009 PressableStateCallbackType, // 9af36561 ProcessedColorValue, // 33f74304 ProgressBarAndroid, // 03e66cf5 - ProgressBarAndroidProps, // 29338dc2 + ProgressBarAndroidProps, // 7bbc49e6 PromiseTask, // 5102c862 PublicRootInstance, // 8040afd7 PublicTextInstance, // 7d73f802 @@ -6086,12 +6088,12 @@ export { PushNotificationPermissions, // c2e7ae4f Rationale, // 5df1b1c1 ReactNativeVersion, // abd76827 - RefreshControl, // 036f45cf - RefreshControlProps, // b7de1e77 + RefreshControl, // ac6464bc + RefreshControlProps, // 39749080 RefreshControlPropsAndroid, // 99f64c97 RefreshControlPropsIOS, // 72a36381 Registry, // e1ed403e - ResponderSyntheticEvent, // e0d1564d + ResponderSyntheticEvent, // 498b0945 ReturnKeyTypeOptions, // afd47ba3 Role, // af7b889d RootTag, // 3cd10504 @@ -6101,19 +6103,19 @@ export { Runnables, // d3749ae1 SafeAreaView, // 4364c7bb ScaledSize, // 07e417c7 - ScrollEvent, // 84e5b805 - ScrollResponderType, // d39056e7 + ScrollEvent, // 4bc89766 + ScrollResponderType, // 9db13c81 ScrollToLocationParamsType, // d7ecdad1 ScrollView, // 7fb7c469 - ScrollViewImperativeMethods, // eb20aa46 - ScrollViewProps, // 27986ff5 + ScrollViewImperativeMethods, // 988704e0 + ScrollViewProps, // a14c5089 ScrollViewPropsAndroid, // 84e2134b - ScrollViewPropsIOS, // d83c9733 + ScrollViewPropsIOS, // 09092114 ScrollViewScrollToOptions, // 3313411e SectionBase, // b376bddc - SectionList, // ff1193b2 + SectionList, // 9a19925d SectionListData, // 119baf83 - SectionListProps, // c9ac8e07 + SectionListProps, // b4939665 SectionListRenderItem, // 1fad0435 SectionListRenderItemInfo, // 745e1992 Separators, // 6a45f7e3 @@ -6133,8 +6135,8 @@ export { StyleSheet, // 366689d4 SubmitBehavior, // c4ddf490 Switch, // aebc9941 - SwitchChangeEvent, // 2e5bd2de - SwitchProps, // cb21930d + SwitchChangeEvent, // e6174bf6 + SwitchProps, // 051e924f Systrace, // b5aa21fc TVViewPropsIOS, // 330ce7b5 TargetedEvent, // 16e98910 @@ -6143,28 +6145,28 @@ export { TextContentType, // 239b3ecc TextInput, // 282b394e TextInputAndroidProps, // 3f09ce49 - TextInputChangeEvent, // 380cbe93 - TextInputContentSizeChangeEvent, // 5fba3f54 - TextInputEndEditingEvent, // 8c22fac3 - TextInputFocusEvent, // c36e977c + TextInputChangeEvent, // 968ce4b1 + TextInputContentSizeChangeEvent, // 9880abfd + TextInputEndEditingEvent, // 1f31b715 + TextInputFocusEvent, // fc40c306 TextInputIOSProps, // 0d05a855 - TextInputKeyPressEvent, // 967178c2 - TextInputProps, // 8f3237f1 - TextInputSelectionChangeEvent, // a1a7622f - TextInputSubmitEditingEvent, // 48d903af - TextLayoutEvent, // 45b0a8d7 - TextProps, // 95d8874d + TextInputKeyPressEvent, // 7a505746 + TextInputProps, // cde7188d + TextInputSelectionChangeEvent, // 56ba70fe + TextInputSubmitEditingEvent, // 1c7f4d4c + TextLayoutEvent, // 575744bd + TextProps, // 32c9c1b4 TextStyle, // f3404e2b ToastAndroid, // b4875e35 Touchable, // 93eb6c63 TouchableHighlight, // b4304a98 - TouchableHighlightProps, // c871f353 - TouchableNativeFeedback, // aaa5b42c - TouchableNativeFeedbackProps, // 372d3213 + TouchableHighlightProps, // c2d9116e + TouchableNativeFeedback, // b3ed3b27 + TouchableNativeFeedbackProps, // 9d1a9567 TouchableOpacity, // 7e33acfd - TouchableOpacityProps, // ba6c0ba4 - TouchableWithoutFeedback, // 7363a906 - TouchableWithoutFeedbackProps, // 68e3d87f + TouchableOpacityProps, // f1bebeeb + TouchableWithoutFeedback, // 10c7c8bc + TouchableWithoutFeedbackProps, // 3d90610e TransformsStyle, // 65e70f18 TurboModule, // dfe29706 TurboModuleRegistry, // 4ace6db2 @@ -6172,15 +6174,15 @@ export { UTFSequence, // baacd11b Vibration, // 315e131d View, // 39dd4de4 - ViewProps, // f8aca212 - ViewPropsAndroid, // 21385d96 + ViewProps, // 9e394be2 + ViewPropsAndroid, // f4f81976 ViewPropsIOS, // 58ee19bf ViewStyle, // c2db0e6e VirtualViewMode, // 85a69ef6 VirtualizedList, // 4d513939 - VirtualizedListProps, // a99d36db + VirtualizedListProps, // 75415c6d VirtualizedSectionList, // 446ba0df - VirtualizedSectionListProps, // 6cd4b378 + VirtualizedSectionListProps, // 18a5d8f1 WrapperComponentProvider, // 9cf3844c codegenNativeCommands, // e16d62f7 codegenNativeComponent, // ed4c8103 From 72d098ab24e5f703e48c6e99d263a9e0098da071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 21 Oct 2025 10:49:18 +0200 Subject: [PATCH 7/9] add stub in ReactFabricHostComponent --- .../ReactFabricPublicInstance/ReactFabricHostComponent.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js index df6d96c04f5d79..22b866440ba297 100644 --- a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js +++ b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js @@ -120,6 +120,11 @@ export default class ReactFabricHostComponent implements NativeMethods { } } + measureAsyncOnUI(callback: MeasureOnSuccessCallback): void { + // Note: this is only implemented in ReactNativeElement with NativeDOM + enableAccessToHostTreeInFabric + this.measure(callback); + } + unstable_getBoundingClientRect(): DOMRect { const node = getNodeFromInternalInstanceHandle( this.__internalInstanceHandle, From ee0493847f48f87928264eb75cec451cf67c5f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 21 Oct 2025 10:50:53 +0200 Subject: [PATCH 8/9] use correct callback type --- .../src/private/webapis/dom/nodes/specs/NativeDOM.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js b/packages/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js index d9d3c51ef66a1d..1ad1264139704d 100644 --- a/packages/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js +++ b/packages/react-native/src/private/webapis/dom/nodes/specs/NativeDOM.js @@ -436,7 +436,7 @@ export interface RefinedSpec { +measureAsyncOnUI: ( nativeElementReference: NativeElementReference, - callback: MeasureInWindowOnSuccessCallback, + callback: MeasureOnSuccessCallback, ) => void; /** From 7f2df56d55f967dab1b16a1a7a32085b2121ee0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanno=20J=2E=20G=C3=B6decke?= Date: Tue, 21 Oct 2025 11:15:10 +0200 Subject: [PATCH 9/9] fix fantom test --- .../react/renderer/scheduler/SchedulerDelegateImpl.cpp | 6 ++++++ .../react/renderer/scheduler/SchedulerDelegateImpl.h | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp index ad9ca80551be87..121ea3e9c7a38c 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp +++ b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.cpp @@ -63,4 +63,10 @@ void SchedulerDelegateImpl::schedulerDidUpdateShadowTree( mountingManager_->onUpdateShadowTree(tagToProps); } +void SchedulerDelegateImpl::schedulerMeasureAsyncOnUI( + const ShadowView& shadowView, + const std::function& callback) { + // No-op implementation, can be overridden by subclasses if needed +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h index b9c4296be2d0c0..153e13899daa4a 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h +++ b/packages/react-native/ReactCxxPlatform/react/renderer/scheduler/SchedulerDelegateImpl.h @@ -59,6 +59,10 @@ class SchedulerDelegateImpl : public SchedulerDelegate { void schedulerDidUpdateShadowTree( const std::unordered_map& tagToProps) override; + void schedulerMeasureAsyncOnUI( + const ShadowView& shadowView, + const std::function& callback) override; + std::shared_ptr mountingManager_; };