From 4e7fe95a7c98a445efbd6c1c9ebad3173494a8c6 Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Fri, 8 Dec 2023 11:50:52 -0800 Subject: [PATCH 01/11] WIP fallback to shadow tree measurement if node tag not found --- .../animated/NativeFabricMeasurerModule.java | 35 ++++++---- .../renderer/uimanager/UIManagerBinding.cpp | 68 +++++++++++-------- 2 files changed, 62 insertions(+), 41 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java index 87ae22c52f0c00..f39f47554bc2e8 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java @@ -10,6 +10,7 @@ import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.uimanager.IllegalViewOperationException; import com.facebook.react.uimanager.NativeViewMeasurer; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.UIManagerHelper; @@ -26,24 +27,34 @@ public NativeFabricMeasurerModule(ReactApplicationContext reactContext) { @Override public void measureNatively(double viewTag, Callback callback) { getReactApplicationContext().runOnUiQueueThread(() -> { - int[] output = measurer.measure((int) viewTag); - float x = PixelUtil.toDIPFromPixel(output[0]); - float y = PixelUtil.toDIPFromPixel(output[1]); - float width = PixelUtil.toDIPFromPixel(output[2]); - float height = PixelUtil.toDIPFromPixel(output[3]); - callback.invoke(0, 0, width, height, x, y); + try { + int[] output = measurer.measure((int) viewTag); + float x = PixelUtil.toDIPFromPixel(output[0]); + float y = PixelUtil.toDIPFromPixel(output[1]); + float width = PixelUtil.toDIPFromPixel(output[2]); + float height = PixelUtil.toDIPFromPixel(output[3]); + callback.invoke(0, 0, width, height, x, y); + } + catch(IllegalViewOperationException e) { + callback.invoke(0, 0, 0, 0, 0, 0); + } }); } @Override public void measureInWindowNatively(double viewTag, Callback callback) { getReactApplicationContext().runOnUiQueueThread(() -> { - int[] output = measurer.measureInWindow((int) viewTag); - float x = PixelUtil.toDIPFromPixel(output[0]); - float y = PixelUtil.toDIPFromPixel(output[1]); - float width = PixelUtil.toDIPFromPixel(output[2]); - float height = PixelUtil.toDIPFromPixel(output[3]); - callback.invoke(x, y, width, height); + try { + int[] output = measurer.measureInWindow((int) viewTag); + float x = PixelUtil.toDIPFromPixel(output[0]); + float y = PixelUtil.toDIPFromPixel(output[1]); + float width = PixelUtil.toDIPFromPixel(output[2]); + float height = PixelUtil.toDIPFromPixel(output[3]); + callback.invoke(x, y, width, height); + } + catch (IllegalViewOperationException e) { + callback.invoke(0,0,0,0); + } }); } diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 8608ab24ac5c69..051e252ee03ceb 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include "bindingUtils.h" @@ -578,47 +579,56 @@ jsi::Value UIManagerBinding::get( auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); bool turboModuleCalled = false; auto nativeMeasurerValue = runtime.global().getProperty(runtime, "__turboModuleProxy") - .asObject(runtime).asFunction(runtime).call(runtime, "NativeFabricMeasurerTurboModule"); + .asObject(runtime).asFunction(runtime).call(runtime, "NativeFabricMeasurerTurboModule"); + auto onSuccessFunction = arguments[1].getObject(runtime).getFunction(runtime); + + void measureFromShadowTree() { + auto layoutMetrics = uiManager->getRelativeLayoutMetrics( + *shadowNode, nullptr, {/* .includeTransform = */ true}); + + if (layoutMetrics == EmptyLayoutMetrics) { + onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); + return jsi::Value::undefined(); + } + auto newestCloneOfShadowNode = + uiManager->getNewestCloneOfShadowNode(*shadowNode); + + auto layoutableShadowNode = traitCast( + newestCloneOfShadowNode.get()); + Point originRelativeToParent = layoutableShadowNode != nullptr + ? layoutableShadowNode->getLayoutMetrics().frame.origin + : Point(); + + auto frame = layoutMetrics.frame; + onSuccessFunction.call( + runtime, + {jsi::Value{runtime, (double)originRelativeToParent.x}, + jsi::Value{runtime, (double)originRelativeToParent.y}, + jsi::Value{runtime, (double)frame.size.width}, + jsi::Value{runtime, (double)frame.size.height}, + jsi::Value{runtime, (double)frame.origin.x}, + jsi::Value{runtime, (double)frame.origin.y}}); + } if (nativeMeasurerValue.isObject()) { // This calls measureNatively if the NativeFabricMeasurerTurboModule is found. // The return value doesn't matter here because the measure values will be passed through the callback. jsi::Value returnValue = nativeMeasurerValue.asObject(runtime).getPropertyAsFunction(runtime, "measureNatively") - .call(runtime, shadowNode.get()->getTag(), arguments[1].getObject(runtime).getFunction(runtime)); + .call(runtime, shadowNode.get()->getTag(), [](double arr[6]) { + if (std::all_of(arr, arr+6, [](int x) {return x==0;})) { + measureFromShadowTree(); + } + onSuccessFunction(arr); + }); turboModuleCalled = true; } if (turboModuleCalled) { return jsi::Value::undefined(); } - - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ true}); - auto onSuccessFunction = - arguments[1].getObject(runtime).getFunction(runtime); - - if (layoutMetrics == EmptyLayoutMetrics) { - onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); - return jsi::Value::undefined(); - } - auto newestCloneOfShadowNode = - uiManager->getNewestCloneOfShadowNode(*shadowNode); - - auto layoutableShadowNode = traitCast( - newestCloneOfShadowNode.get()); - Point originRelativeToParent = layoutableShadowNode != nullptr - ? layoutableShadowNode->getLayoutMetrics().frame.origin - : Point(); - auto frame = layoutMetrics.frame; - onSuccessFunction.call( - runtime, - {jsi::Value{runtime, (double)originRelativeToParent.x}, - jsi::Value{runtime, (double)originRelativeToParent.y}, - jsi::Value{runtime, (double)frame.size.width}, - jsi::Value{runtime, (double)frame.size.height}, - jsi::Value{runtime, (double)frame.origin.x}, - jsi::Value{runtime, (double)frame.origin.y}}); + measureFromShadowTree(); + return jsi::Value::undefined(); }); } From 60fda1426f54ac67d5fce40eeba2154478cc3685 Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Mon, 11 Dec 2023 16:42:23 -0800 Subject: [PATCH 02/11] fix type issues, c++ paradigms --- .../renderer/uimanager/UIManagerBinding.cpp | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 051e252ee03ceb..80ccb7713cfe04 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -141,6 +141,34 @@ void UIManagerBinding::invalidate() const { uiManager_->setDelegate(nullptr); } +void measureFromShadowTree(UIManager* uiManager, ShadowNode::Shared shadowNode, jsi::Function &onSuccessFunction, jsi::Runtime &runtime) { + auto layoutMetrics = uiManager->getRelativeLayoutMetrics( + *shadowNode, nullptr, {/* .includeTransform = */ true}); + + if (layoutMetrics == EmptyLayoutMetrics) { + onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); + return; + } + auto newestCloneOfShadowNode = + uiManager->getNewestCloneOfShadowNode(*shadowNode); + + auto layoutableShadowNode = traitCast( + newestCloneOfShadowNode.get()); + Point originRelativeToParent = layoutableShadowNode != nullptr + ? layoutableShadowNode->getLayoutMetrics().frame.origin + : Point(); + + auto frame = layoutMetrics.frame; + onSuccessFunction.call( + runtime, + {jsi::Value{runtime, (double)originRelativeToParent.x}, + jsi::Value{runtime, (double)originRelativeToParent.y}, + jsi::Value{runtime, (double)frame.size.width}, + jsi::Value{runtime, (double)frame.size.height}, + jsi::Value{runtime, (double)frame.origin.x}, + jsi::Value{runtime, (double)frame.origin.y}}); +} + jsi::Value UIManagerBinding::get( jsi::Runtime &runtime, jsi::PropNameID const &name) { @@ -582,43 +610,15 @@ jsi::Value UIManagerBinding::get( .asObject(runtime).asFunction(runtime).call(runtime, "NativeFabricMeasurerTurboModule"); auto onSuccessFunction = arguments[1].getObject(runtime).getFunction(runtime); - void measureFromShadowTree() { - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ true}); - - if (layoutMetrics == EmptyLayoutMetrics) { - onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); - return jsi::Value::undefined(); - } - auto newestCloneOfShadowNode = - uiManager->getNewestCloneOfShadowNode(*shadowNode); - - auto layoutableShadowNode = traitCast( - newestCloneOfShadowNode.get()); - Point originRelativeToParent = layoutableShadowNode != nullptr - ? layoutableShadowNode->getLayoutMetrics().frame.origin - : Point(); - - auto frame = layoutMetrics.frame; - onSuccessFunction.call( - runtime, - {jsi::Value{runtime, (double)originRelativeToParent.x}, - jsi::Value{runtime, (double)originRelativeToParent.y}, - jsi::Value{runtime, (double)frame.size.width}, - jsi::Value{runtime, (double)frame.size.height}, - jsi::Value{runtime, (double)frame.origin.x}, - jsi::Value{runtime, (double)frame.origin.y}}); - } - if (nativeMeasurerValue.isObject()) { // This calls measureNatively if the NativeFabricMeasurerTurboModule is found. // The return value doesn't matter here because the measure values will be passed through the callback. jsi::Value returnValue = nativeMeasurerValue.asObject(runtime).getPropertyAsFunction(runtime, "measureNatively") - .call(runtime, shadowNode.get()->getTag(), [](double arr[6]) { + .call(runtime, shadowNode.get()->getTag(), [uiManager, &onSuccessFunction, shadowNode, &runtime](double arr[6]) { if (std::all_of(arr, arr+6, [](int x) {return x==0;})) { - measureFromShadowTree(); + measureFromShadowTree(uiManager,shadowNode, onSuccessFunction, runtime); } - onSuccessFunction(arr); + onSuccessFunction.call(runtime, arr); }); turboModuleCalled = true; } @@ -627,7 +627,7 @@ jsi::Value UIManagerBinding::get( return jsi::Value::undefined(); } - measureFromShadowTree(); + measureFromShadowTree(uiManager, shadowNode, onSuccessFunction, runtime); return jsi::Value::undefined(); }); From d1b44dcf111573d98f8ce4789543b297e6e0c5f0 Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Tue, 12 Dec 2023 13:28:15 -0800 Subject: [PATCH 03/11] fix type error with dereference --- ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 80ccb7713cfe04..e8418f37a68257 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -618,7 +618,7 @@ jsi::Value UIManagerBinding::get( if (std::all_of(arr, arr+6, [](int x) {return x==0;})) { measureFromShadowTree(uiManager,shadowNode, onSuccessFunction, runtime); } - onSuccessFunction.call(runtime, arr); + onSuccessFunction.call(runtime, *arr); }); turboModuleCalled = true; } From 7513657b55cd646b99e275a5b6cf06ed4beaee39 Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Wed, 13 Dec 2023 00:36:30 -0800 Subject: [PATCH 04/11] no type issues, untested --- .../renderer/uimanager/UIManagerBinding.cpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index e8418f37a68257..1e92bc88b58fca 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -614,12 +614,20 @@ jsi::Value UIManagerBinding::get( // This calls measureNatively if the NativeFabricMeasurerTurboModule is found. // The return value doesn't matter here because the measure values will be passed through the callback. jsi::Value returnValue = nativeMeasurerValue.asObject(runtime).getPropertyAsFunction(runtime, "measureNatively") - .call(runtime, shadowNode.get()->getTag(), [uiManager, &onSuccessFunction, shadowNode, &runtime](double arr[6]) { - if (std::all_of(arr, arr+6, [](int x) {return x==0;})) { - measureFromShadowTree(uiManager,shadowNode, onSuccessFunction, runtime); - } - onSuccessFunction.call(runtime, *arr); - }); + .call(runtime, shadowNode.get()->getTag(), jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forAscii(runtime, "pleaseRenameThisFunction"), 2,[uiManager, shadowNode, &onSuccessFunction](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) { + auto isAllZeros = true; + for (int i = 0; i < 6; ++i) { + if (args[0].asObject(rt).asArray(rt).getValueAtIndex(rt, i).asNumber() != 0) { + isAllZeros = false; + } + } + if (isAllZeros) { + measureFromShadowTree(uiManager,shadowNode, onSuccessFunction, rt); + return jsi::Value::undefined(); + } + onSuccessFunction.call(rt, args[0].asObject(rt).asArray(rt)); + return jsi::Value::undefined(); + })); turboModuleCalled = true; } From 7fe78d061ab4284dc4d1f55d81cdcd6a9204dce1 Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Wed, 13 Dec 2023 17:52:46 -0800 Subject: [PATCH 05/11] app still crashes when you tap anything but it starts --- .../renderer/uimanager/UIManagerBinding.cpp | 53 ++++++++++++------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 1e92bc88b58fca..ac71d34f77ea67 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -20,6 +20,7 @@ #include #include "bindingUtils.h" +#include namespace facebook::react { @@ -611,24 +612,40 @@ jsi::Value UIManagerBinding::get( auto onSuccessFunction = arguments[1].getObject(runtime).getFunction(runtime); if (nativeMeasurerValue.isObject()) { - // This calls measureNatively if the NativeFabricMeasurerTurboModule is found. - // The return value doesn't matter here because the measure values will be passed through the callback. - jsi::Value returnValue = nativeMeasurerValue.asObject(runtime).getPropertyAsFunction(runtime, "measureNatively") - .call(runtime, shadowNode.get()->getTag(), jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forAscii(runtime, "pleaseRenameThisFunction"), 2,[uiManager, shadowNode, &onSuccessFunction](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) { - auto isAllZeros = true; - for (int i = 0; i < 6; ++i) { - if (args[0].asObject(rt).asArray(rt).getValueAtIndex(rt, i).asNumber() != 0) { - isAllZeros = false; - } - } - if (isAllZeros) { - measureFromShadowTree(uiManager,shadowNode, onSuccessFunction, rt); - return jsi::Value::undefined(); - } - onSuccessFunction.call(rt, args[0].asObject(rt).asArray(rt)); - return jsi::Value::undefined(); - })); - turboModuleCalled = true; + // This calls measureNatively if the NativeFabricMeasurerTurboModule is found. + // The return value doesn't matter here because the measure values will be passed through the callback. + jsi::Value returnValue = nativeMeasurerValue + .asObject(runtime) + .getPropertyAsFunction(runtime, "measureNatively") + .call( + runtime, + shadowNode.get()->getTag(), + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "pleaseRenameThisFunction"), + 6, + [uiManager, shadowNode, &onSuccessFunction]( + jsi::Runtime& rt, + const jsi::Value& thisVal, + const jsi::Value* args, + size_t count) { + auto isAllZeros = true; + for (int i = 0; i < 6; ++i) { + if (args[i].asNumber() != 0) { + isAllZeros = false; + break; + } + } + if (isAllZeros) { + measureFromShadowTree(uiManager,shadowNode, onSuccessFunction, rt); + return jsi::Value::undefined(); + } + onSuccessFunction.call(rt, args, (size_t)6); + return jsi::Value::undefined(); + } + ) + ); + turboModuleCalled = true; } if (turboModuleCalled) { From de307054d6c79c8273fb904af823ba70a67d8d66 Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Fri, 22 Dec 2023 12:34:46 -0800 Subject: [PATCH 06/11] two separate callbacks, pass successfunction back --- .../NativeFabricMeasurerTurboModule.js | 2 +- .../animated/NativeFabricMeasurerModule.java | 6 +++--- .../renderer/uimanager/UIManagerBinding.cpp | 20 ++++++------------- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Libraries/Animated/NativeFabricMeasurerTurboModule.js b/Libraries/Animated/NativeFabricMeasurerTurboModule.js index 1cca2aed05fd1d..e44c798d2ae256 100644 --- a/Libraries/Animated/NativeFabricMeasurerTurboModule.js +++ b/Libraries/Animated/NativeFabricMeasurerTurboModule.js @@ -18,7 +18,7 @@ type MeasureInWindowOnSuccessCallback = ( ) => void; export interface Spec extends TurboModule { - +measureNatively: (viewTag: number, callback: MeasureOnSuccessCallback) => void, + +measureNatively: (viewTag: number, successCallback: MeasureOnSuccessCallback, failCallback: (successCallback: MeasureInWindowOnSuccessCallback) => void) => void, +measureInWindowNatively: ( viewTag: number, callback: MeasureInWindowOnSuccessCallback, diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java index f39f47554bc2e8..cc5d827bf1afc3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java @@ -25,7 +25,7 @@ public NativeFabricMeasurerModule(ReactApplicationContext reactContext) { } @Override - public void measureNatively(double viewTag, Callback callback) { + public void measureNatively(double viewTag, Callback successCallback, Callback failCallback) { getReactApplicationContext().runOnUiQueueThread(() -> { try { int[] output = measurer.measure((int) viewTag); @@ -33,10 +33,10 @@ public void measureNatively(double viewTag, Callback callback) { float y = PixelUtil.toDIPFromPixel(output[1]); float width = PixelUtil.toDIPFromPixel(output[2]); float height = PixelUtil.toDIPFromPixel(output[3]); - callback.invoke(0, 0, width, height, x, y); + successCallback.invoke(0, 0, width, height, x, y); } catch(IllegalViewOperationException e) { - callback.invoke(0, 0, 0, 0, 0, 0); + failCallback.invoke(successCallback); } }); } diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index ac71d34f77ea67..c720f6270d8177 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -160,6 +160,7 @@ void measureFromShadowTree(UIManager* uiManager, ShadowNode::Shared shadowNode, : Point(); auto frame = layoutMetrics.frame; + react_native_log_info("garf measureFromShadowTree about to onSuccessFunction"); onSuccessFunction.call( runtime, {jsi::Value{runtime, (double)originRelativeToParent.x}, @@ -620,27 +621,18 @@ jsi::Value UIManagerBinding::get( .call( runtime, shadowNode.get()->getTag(), + onSuccessFunction, jsi::Function::createFromHostFunction( runtime, jsi::PropNameID::forAscii(runtime, "pleaseRenameThisFunction"), - 6, - [uiManager, shadowNode, &onSuccessFunction]( + 1, + [uiManager, shadowNode]( jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) { - auto isAllZeros = true; - for (int i = 0; i < 6; ++i) { - if (args[i].asNumber() != 0) { - isAllZeros = false; - break; - } - } - if (isAllZeros) { - measureFromShadowTree(uiManager,shadowNode, onSuccessFunction, rt); - return jsi::Value::undefined(); - } - onSuccessFunction.call(rt, args, (size_t)6); + auto onSuccessFunction = args[0].getObject(rt).getFunction(rt); + measureFromShadowTree(uiManager,shadowNode, onSuccessFunction, rt); return jsi::Value::undefined(); } ) From cbd01c0c743f9513c58e193bf6ad9800f73cca75 Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Fri, 22 Dec 2023 14:20:27 -0800 Subject: [PATCH 07/11] formatting and readability --- .../NativeFabricMeasurerTurboModule.js | 5 +- .../animated/NativeFabricMeasurerModule.java | 13 +- .../renderer/uimanager/UIManagerBinding.cpp | 257 +++++++++++------- 3 files changed, 167 insertions(+), 108 deletions(-) diff --git a/Libraries/Animated/NativeFabricMeasurerTurboModule.js b/Libraries/Animated/NativeFabricMeasurerTurboModule.js index e44c798d2ae256..b6e17227176e43 100644 --- a/Libraries/Animated/NativeFabricMeasurerTurboModule.js +++ b/Libraries/Animated/NativeFabricMeasurerTurboModule.js @@ -18,10 +18,11 @@ type MeasureInWindowOnSuccessCallback = ( ) => void; export interface Spec extends TurboModule { - +measureNatively: (viewTag: number, successCallback: MeasureOnSuccessCallback, failCallback: (successCallback: MeasureInWindowOnSuccessCallback) => void) => void, + +measureNatively: (viewTag: number, successCallback: MeasureOnSuccessCallback, failCallback: (successCallback: MeasureOnSuccessCallback) => void) => void, +measureInWindowNatively: ( viewTag: number, - callback: MeasureInWindowOnSuccessCallback, + successCallback: MeasureInWindowOnSuccessCallback, + failCallback: (successCallback: MeasureInWindowOnSuccessCallback) => void ) => void, } diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java index cc5d827bf1afc3..9d69d46157dd82 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeFabricMeasurerModule.java @@ -25,7 +25,7 @@ public NativeFabricMeasurerModule(ReactApplicationContext reactContext) { } @Override - public void measureNatively(double viewTag, Callback successCallback, Callback failCallback) { + public void measureNatively(double viewTag, Callback onSuccess, Callback onFail) { getReactApplicationContext().runOnUiQueueThread(() -> { try { int[] output = measurer.measure((int) viewTag); @@ -33,16 +33,17 @@ public void measureNatively(double viewTag, Callback successCallback, Callback f float y = PixelUtil.toDIPFromPixel(output[1]); float width = PixelUtil.toDIPFromPixel(output[2]); float height = PixelUtil.toDIPFromPixel(output[3]); - successCallback.invoke(0, 0, width, height, x, y); + onSuccess.invoke(0, 0, width, height, x, y); } catch(IllegalViewOperationException e) { - failCallback.invoke(successCallback); + // To avoid a scoping bug in UIManagerBinding.cpp, we need to pass the successCallback back here. + onFail.invoke(onSuccess); } }); } @Override - public void measureInWindowNatively(double viewTag, Callback callback) { + public void measureInWindowNatively(double viewTag, Callback onSuccess, Callback onFail) { getReactApplicationContext().runOnUiQueueThread(() -> { try { int[] output = measurer.measureInWindow((int) viewTag); @@ -50,10 +51,10 @@ public void measureInWindowNatively(double viewTag, Callback callback) { float y = PixelUtil.toDIPFromPixel(output[1]); float width = PixelUtil.toDIPFromPixel(output[2]); float height = PixelUtil.toDIPFromPixel(output[3]); - callback.invoke(x, y, width, height); + onSuccess.invoke(x, y, width, height); } catch (IllegalViewOperationException e) { - callback.invoke(0,0,0,0); + onFail.invoke(onSuccess); } }); } diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index c720f6270d8177..e62a0ff2509d1d 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -19,8 +19,8 @@ #include #include -#include "bindingUtils.h" #include +#include "bindingUtils.h" namespace facebook::react { @@ -105,22 +105,23 @@ void UIManagerBinding::dispatchEvent( return; } - auto instanceHandle = eventTarget != nullptr - ? [&]() { - auto instanceHandle = eventTarget->getInstanceHandle(runtime); - if (instanceHandle.isUndefined()) { - return jsi::Value::null(); - } + auto instanceHandle = eventTarget != nullptr ? [&]() { + auto instanceHandle = eventTarget->getInstanceHandle(runtime); + if (instanceHandle.isUndefined()) { + return jsi::Value::null(); + } - // Mixing `target` into `payload`. - if (!payload.isObject()) { - LOG(ERROR) << "payload for dispatchEvent is not an object: " << eventTarget->getTag(); - } - react_native_assert(payload.isObject()); - payload.asObject(runtime).setProperty(runtime, "target", eventTarget->getTag()); - return instanceHandle; - }() - : jsi::Value::null(); + // Mixing `target` into `payload`. + if (!payload.isObject()) { + LOG(ERROR) << "payload for dispatchEvent is not an object: " + << eventTarget->getTag(); + } + react_native_assert(payload.isObject()); + payload.asObject(runtime).setProperty( + runtime, "target", eventTarget->getTag()); + return instanceHandle; + }() + : jsi::Value::null(); if (instanceHandle.isNull()) { LOG(WARNING) << "instanceHandle is null, event will be dropped"; @@ -142,33 +143,61 @@ void UIManagerBinding::invalidate() const { uiManager_->setDelegate(nullptr); } -void measureFromShadowTree(UIManager* uiManager, ShadowNode::Shared shadowNode, jsi::Function &onSuccessFunction, jsi::Runtime &runtime) { - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ true}); +void measureFromShadowTree( + UIManager *uiManager, + ShadowNode::Shared shadowNode, + jsi::Function &onSuccessFunction, + jsi::Runtime &runtime) { + auto layoutMetrics = uiManager->getRelativeLayoutMetrics( + *shadowNode, nullptr, {/* .includeTransform = */ true}); - if (layoutMetrics == EmptyLayoutMetrics) { - onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); - return; - } - auto newestCloneOfShadowNode = - uiManager->getNewestCloneOfShadowNode(*shadowNode); - - auto layoutableShadowNode = traitCast( - newestCloneOfShadowNode.get()); - Point originRelativeToParent = layoutableShadowNode != nullptr - ? layoutableShadowNode->getLayoutMetrics().frame.origin - : Point(); - - auto frame = layoutMetrics.frame; - react_native_log_info("garf measureFromShadowTree about to onSuccessFunction"); - onSuccessFunction.call( - runtime, - {jsi::Value{runtime, (double)originRelativeToParent.x}, - jsi::Value{runtime, (double)originRelativeToParent.y}, - jsi::Value{runtime, (double)frame.size.width}, - jsi::Value{runtime, (double)frame.size.height}, - jsi::Value{runtime, (double)frame.origin.x}, - jsi::Value{runtime, (double)frame.origin.y}}); + if (layoutMetrics == EmptyLayoutMetrics) { + onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); + return; + } + auto newestCloneOfShadowNode = + uiManager->getNewestCloneOfShadowNode(*shadowNode); + + auto layoutableShadowNode = + traitCast(newestCloneOfShadowNode.get()); + Point originRelativeToParent = layoutableShadowNode != nullptr + ? layoutableShadowNode->getLayoutMetrics().frame.origin + : Point(); + + auto frame = layoutMetrics.frame; + onSuccessFunction.call( + runtime, + {jsi::Value{runtime, (double)originRelativeToParent.x}, + jsi::Value{runtime, (double)originRelativeToParent.y}, + jsi::Value{runtime, (double)frame.size.width}, + jsi::Value{runtime, (double)frame.size.height}, + jsi::Value{runtime, (double)frame.origin.x}, + jsi::Value{runtime, (double)frame.origin.y}}); +} + +void measureInWindowFromShadowTree( + UIManager *uiManager, + ShadowNode::Shared shadowNode, + jsi::Function &onSuccessFunction, + jsi::Runtime &runtime) { + auto layoutMetrics = uiManager->getRelativeLayoutMetrics( + *shadowNode, + nullptr, + {/* .includeTransform = */ true, + /* .includeViewportOffset = */ true}); + + if (layoutMetrics == EmptyLayoutMetrics) { + onSuccessFunction.call(runtime, {0, 0, 0, 0}); + return; + } + + auto frame = layoutMetrics.frame; + onSuccessFunction.call( + runtime, + {jsi::Value{runtime, (double)frame.origin.x}, + jsi::Value{runtime, (double)frame.origin.y}, + jsi::Value{runtime, (double)frame.size.width}, + jsi::Value{runtime, (double)frame.size.height}}); } jsi::Value UIManagerBinding::get( @@ -608,44 +637,59 @@ jsi::Value UIManagerBinding::get( size_t /*count*/) noexcept -> jsi::Value { auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); bool turboModuleCalled = false; - auto nativeMeasurerValue = runtime.global().getProperty(runtime, "__turboModuleProxy") - .asObject(runtime).asFunction(runtime).call(runtime, "NativeFabricMeasurerTurboModule"); - auto onSuccessFunction = arguments[1].getObject(runtime).getFunction(runtime); + auto nativeMeasurerValue = + runtime.global() + .getProperty(runtime, "__turboModuleProxy") + .asObject(runtime) + .asFunction(runtime) + .call(runtime, "NativeFabricMeasurerTurboModule"); + auto onSuccessFunction = + arguments[1].getObject(runtime).getFunction(runtime); if (nativeMeasurerValue.isObject()) { - // This calls measureNatively if the NativeFabricMeasurerTurboModule is found. - // The return value doesn't matter here because the measure values will be passed through the callback. - jsi::Value returnValue = nativeMeasurerValue - .asObject(runtime) - .getPropertyAsFunction(runtime, "measureNatively") - .call( - runtime, - shadowNode.get()->getTag(), - onSuccessFunction, - jsi::Function::createFromHostFunction( - runtime, - jsi::PropNameID::forAscii(runtime, "pleaseRenameThisFunction"), - 1, - [uiManager, shadowNode]( - jsi::Runtime& rt, - const jsi::Value& thisVal, - const jsi::Value* args, - size_t count) { - auto onSuccessFunction = args[0].getObject(rt).getFunction(rt); - measureFromShadowTree(uiManager,shadowNode, onSuccessFunction, rt); - return jsi::Value::undefined(); - } - ) - ); + // This calls measureNatively if the NativeFabricMeasurerTurboModule + // is found. The return value doesn't matter here because the + // measure values will be passed through the callback. + jsi::Value returnValue = + nativeMeasurerValue.asObject(runtime) + .getPropertyAsFunction(runtime, "measureNatively") + .call( + runtime, + shadowNode.get()->getTag(), + // measureNatively takes two callbacks, one for if the + // layout is found and one for if an error occurs so we + // can gracefully fallback to measuring out of the + // shadow tree. + onSuccessFunction, + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii( + runtime, "onNativeMeasureFail"), + 1, + [uiManager, shadowNode]( + jsi::Runtime &rt, + const jsi::Value &thisVal, + const jsi::Value *args, + size_t count) { + // We return onSuccessFunction out of the onFail + // instead of using the one defined above to avoid + // a scoping bug. + auto onSuccessFunction = + args[0].getObject(rt).getFunction(rt); + measureFromShadowTree( + uiManager, shadowNode, onSuccessFunction, rt); + return jsi::Value::undefined(); + })); turboModuleCalled = true; } if (turboModuleCalled) { - return jsi::Value::undefined(); + return jsi::Value::undefined(); } - measureFromShadowTree(uiManager, shadowNode, onSuccessFunction, runtime); - + measureFromShadowTree( + uiManager, shadowNode, onSuccessFunction, runtime); + return jsi::Value::undefined(); }); } @@ -662,42 +706,55 @@ jsi::Value UIManagerBinding::get( size_t /*count*/) noexcept -> jsi::Value { auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); bool turboModuleCalled = false; - auto nativeMeasurerValue = runtime.global().getProperty(runtime, "__turboModuleProxy") - .asObject(runtime).asFunction(runtime).call(runtime, "NativeFabricMeasurerTurboModule"); + auto nativeMeasurerValue = + runtime.global() + .getProperty(runtime, "__turboModuleProxy") + .asObject(runtime) + .asFunction(runtime) + .call(runtime, "NativeFabricMeasurerTurboModule"); + auto onSuccessFunction = + arguments[1].getObject(runtime).getFunction(runtime); if (nativeMeasurerValue.isObject()) { - // This calls measureNatively if the NativeFabricMeasurerTurboModule is found. - // The return value doesn't matter here because the measure values will be passed through the callback. - jsi::Value returnValue = nativeMeasurerValue.asObject(runtime).getPropertyAsFunction(runtime, "measureInWindowNatively") - .call(runtime, shadowNode.get()->getTag(), arguments[1].getObject(runtime).getFunction(runtime)); - turboModuleCalled = true; + // This calls measureNatively if the NativeFabricMeasurerTurboModule + // is found. The return value doesn't matter here because the + // measure values will be passed through the callback. + jsi::Value returnValue = + nativeMeasurerValue.asObject(runtime) + .getPropertyAsFunction(runtime, "measureInWindowNatively") + .call( + runtime, + shadowNode.get()->getTag(), + onSuccessFunction, + jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii( + runtime, "onNativeMeasureFail"), + 1, + [uiManager, shadowNode]( + jsi::Runtime &rt, + const jsi::Value &thisVal, + const jsi::Value *args, + size_t count) { + // We return onSuccessFunction out of the onFail + // instead of using the one defined above to avoid + // a scoping bug. + auto onSuccessFunction = + args[0].getObject(rt).getFunction(rt); + measureInWindowFromShadowTree( + uiManager, shadowNode, onSuccessFunction, rt); + return jsi::Value::undefined(); + })); + turboModuleCalled = true; } if (turboModuleCalled) { - return jsi::Value::undefined(); - } - - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNode, - nullptr, - {/* .includeTransform = */ true, - /* .includeViewportOffset = */ true}); - - auto onSuccessFunction = - arguments[1].getObject(runtime).getFunction(runtime); - - if (layoutMetrics == EmptyLayoutMetrics) { - onSuccessFunction.call(runtime, {0, 0, 0, 0}); return jsi::Value::undefined(); } - auto frame = layoutMetrics.frame; - onSuccessFunction.call( - runtime, - {jsi::Value{runtime, (double)frame.origin.x}, - jsi::Value{runtime, (double)frame.origin.y}, - jsi::Value{runtime, (double)frame.size.width}, - jsi::Value{runtime, (double)frame.size.height}}); + measureInWindowFromShadowTree( + uiManager, shadowNode, onSuccessFunction, runtime); + return jsi::Value::undefined(); }); } From 3bbd06d50ac12f29a6f6815ba538dd47474b39ae Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Fri, 22 Dec 2023 14:21:30 -0800 Subject: [PATCH 08/11] unneeded #includes --- ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index e62a0ff2509d1d..fefb9e50387c7a 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -16,10 +16,8 @@ #include #include -#include #include -#include #include "bindingUtils.h" namespace facebook::react { From 02755157cd57e100bb65693c47ce928798c627c4 Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Fri, 22 Dec 2023 14:24:06 -0800 Subject: [PATCH 09/11] revert overzealous formatting --- .../renderer/uimanager/UIManagerBinding.cpp | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index fefb9e50387c7a..ee5c7a9220d041 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -103,23 +103,22 @@ void UIManagerBinding::dispatchEvent( return; } - auto instanceHandle = eventTarget != nullptr ? [&]() { - auto instanceHandle = eventTarget->getInstanceHandle(runtime); - if (instanceHandle.isUndefined()) { - return jsi::Value::null(); - } - - // Mixing `target` into `payload`. - if (!payload.isObject()) { - LOG(ERROR) << "payload for dispatchEvent is not an object: " - << eventTarget->getTag(); - } - react_native_assert(payload.isObject()); - payload.asObject(runtime).setProperty( - runtime, "target", eventTarget->getTag()); - return instanceHandle; - }() - : jsi::Value::null(); + auto instanceHandle = eventTarget != nullptr + ? [&]() { + auto instanceHandle = eventTarget->getInstanceHandle(runtime); + if (instanceHandle.isUndefined()) { + return jsi::Value::null(); + } + + // Mixing `target` into `payload`. + if (!payload.isObject()) { + LOG(ERROR) << "payload for dispatchEvent is not an object: " << eventTarget->getTag(); + } + react_native_assert(payload.isObject()); + payload.asObject(runtime).setProperty(runtime, "target", eventTarget->getTag()); + return instanceHandle; + }() + : jsi::Value::null(); if (instanceHandle.isNull()) { LOG(WARNING) << "instanceHandle is null, event will be dropped"; From 6a7d884127821e3e502d28fac626be47025c968e Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Mon, 8 Jan 2024 16:11:42 -0800 Subject: [PATCH 10/11] no need for checking boolean to return, just return --- .../react/renderer/uimanager/UIManagerBinding.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index ee5c7a9220d041..47609b30432049 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -633,7 +633,6 @@ jsi::Value UIManagerBinding::get( jsi::Value const *arguments, size_t /*count*/) noexcept -> jsi::Value { auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); - bool turboModuleCalled = false; auto nativeMeasurerValue = runtime.global() .getProperty(runtime, "__turboModuleProxy") @@ -677,10 +676,6 @@ jsi::Value UIManagerBinding::get( uiManager, shadowNode, onSuccessFunction, rt); return jsi::Value::undefined(); })); - turboModuleCalled = true; - } - - if (turboModuleCalled) { return jsi::Value::undefined(); } @@ -702,7 +697,6 @@ jsi::Value UIManagerBinding::get( jsi::Value const *arguments, size_t /*count*/) noexcept -> jsi::Value { auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); - bool turboModuleCalled = false; auto nativeMeasurerValue = runtime.global() .getProperty(runtime, "__turboModuleProxy") @@ -742,10 +736,6 @@ jsi::Value UIManagerBinding::get( uiManager, shadowNode, onSuccessFunction, rt); return jsi::Value::undefined(); })); - turboModuleCalled = true; - } - - if (turboModuleCalled) { return jsi::Value::undefined(); } From 97595727a378b2211c955a4165b09a4bb173481e Mon Sep 17 00:00:00 2001 From: Isaac Garfinkle Date: Mon, 8 Jan 2024 16:21:55 -0800 Subject: [PATCH 11/11] more verbose comment --- .../renderer/uimanager/UIManagerBinding.cpp | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 47609b30432049..5dc068d2c7de38 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -667,9 +667,16 @@ jsi::Value UIManagerBinding::get( const jsi::Value &thisVal, const jsi::Value *args, size_t count) { - // We return onSuccessFunction out of the onFail - // instead of using the one defined above to avoid - // a scoping bug. + // There's a bug that occurs if, below, we try to + // call the onSuccessFunction we extracted + // earlier. It seems that JSIFunctions can't be + // passed into capture clauses for c++ anonymous + // functions, because they lack the appropriate + // copy constructor. Since we do definitely need + // access to the onSuccessFunction, we obtain that + // access by passing it into the .call() above and + // then passing it BACK OUT in native land, so we + // can extract it from args and use it right here. auto onSuccessFunction = args[0].getObject(rt).getFunction(rt); measureFromShadowTree( @@ -727,9 +734,16 @@ jsi::Value UIManagerBinding::get( const jsi::Value &thisVal, const jsi::Value *args, size_t count) { - // We return onSuccessFunction out of the onFail - // instead of using the one defined above to avoid - // a scoping bug. + // There's a bug that occurs if, below, we try to + // call the onSuccessFunction we extracted + // earlier. It seems that JSIFunctions can't be + // passed into capture clauses for c++ anonymous + // functions, because they lack the appropriate + // copy constructor. Since we do definitely need + // access to the onSuccessFunction, we obtain that + // access by passing it into the .call() above and + // then passing it BACK OUT in native land, so we + // can extract it from args and use it right here. auto onSuccessFunction = args[0].getObject(rt).getFunction(rt); measureInWindowFromShadowTree(