diff --git a/Libraries/Animated/NativeFabricMeasurerTurboModule.js b/Libraries/Animated/NativeFabricMeasurerTurboModule.js index 1cca2aed05fd1d..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, callback: MeasureOnSuccessCallback) => 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 87ae22c52f0c00..9d69d46157dd82 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; @@ -24,26 +25,37 @@ public NativeFabricMeasurerModule(ReactApplicationContext reactContext) { } @Override - public void measureNatively(double viewTag, Callback callback) { + public void measureNatively(double viewTag, Callback onSuccess, Callback onFail) { 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]); + onSuccess.invoke(0, 0, width, height, x, y); + } + catch(IllegalViewOperationException e) { + // 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(() -> { - 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]); + onSuccess.invoke(x, y, width, height); + } + catch (IllegalViewOperationException e) { + onFail.invoke(onSuccess); + } }); } diff --git a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 8608ab24ac5c69..5dc068d2c7de38 100644 --- a/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -140,6 +140,63 @@ 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}}); +} + +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( jsi::Runtime &runtime, jsi::PropNameID const &name) { @@ -576,49 +633,62 @@ 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") - .asObject(runtime).asFunction(runtime).call(runtime, "NativeFabricMeasurerTurboModule"); - - 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)); - turboModuleCalled = true; - } - - if (turboModuleCalled) { - return jsi::Value::undefined(); - } - - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ true}); + auto nativeMeasurerValue = + runtime.global() + .getProperty(runtime, "__turboModuleProxy") + .asObject(runtime) + .asFunction(runtime) + .call(runtime, "NativeFabricMeasurerTurboModule"); auto onSuccessFunction = arguments[1].getObject(runtime).getFunction(runtime); - if (layoutMetrics == EmptyLayoutMetrics) { - onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); + 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(), + // 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) { + // 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( + uiManager, shadowNode, onSuccessFunction, rt); + return jsi::Value::undefined(); + })); return jsi::Value::undefined(); } - auto newestCloneOfShadowNode = - uiManager->getNewestCloneOfShadowNode(*shadowNode); - auto layoutableShadowNode = traitCast( - newestCloneOfShadowNode.get()); - Point originRelativeToParent = layoutableShadowNode != nullptr - ? layoutableShadowNode->getLayoutMetrics().frame.origin - : Point(); + measureFromShadowTree( + uiManager, shadowNode, onSuccessFunction, runtime); - 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}}); return jsi::Value::undefined(); }); } @@ -634,43 +704,58 @@ 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") - .asObject(runtime).asFunction(runtime).call(runtime, "NativeFabricMeasurerTurboModule"); - - 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; - } - - if (turboModuleCalled) { - return jsi::Value::undefined(); - } - - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNode, - nullptr, - {/* .includeTransform = */ true, - /* .includeViewportOffset = */ true}); - + auto nativeMeasurerValue = + runtime.global() + .getProperty(runtime, "__turboModuleProxy") + .asObject(runtime) + .asFunction(runtime) + .call(runtime, "NativeFabricMeasurerTurboModule"); auto onSuccessFunction = arguments[1].getObject(runtime).getFunction(runtime); - if (layoutMetrics == EmptyLayoutMetrics) { - onSuccessFunction.call(runtime, {0, 0, 0, 0}); + 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(), + 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) { + // 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( + uiManager, shadowNode, onSuccessFunction, rt); + return jsi::Value::undefined(); + })); 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(); }); }