From 2af1da42ff517232f1309efed7565fe9ddbbac77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Wed, 27 Mar 2024 13:04:34 -0700 Subject: [PATCH] Add legacy layout methods from Fabric to DOM native module (#43659) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/43659 Changelog: [internal] This adds an implementation for the legacy layout measurement methods in React Native (`measure`, `measureInWindow` and `measureLayout`) in the DOM native module, so we can clean up the API from the `nativeFabricUIManager` binding. Reviewed By: javache Differential Revision: D55368141 fbshipit-source-id: 196d4d29be3b78ffc22fdc136be6e0cf5ab9dd26 --- .../react/nativemodule/dom/NativeDOM.cpp | 100 ++++++++++++++++-- .../react/nativemodule/dom/NativeDOM.h | 17 +++ .../webapis/dom/nodes/specs/NativeDOM.js | 83 +++++++++++++++ .../nodes/specs/__mocks__/NativeDOMMock.js | 37 +++++++ 4 files changed, 229 insertions(+), 8 deletions(-) diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp index 9305b112876..301932f97fc 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp @@ -21,10 +21,11 @@ std::shared_ptr NativeDOMModuleProvider( return std::make_shared(std::move(jsInvoker)); } -namespace { -using namespace facebook::react; +namespace facebook::react { -RootShadowNode::Shared getCurrentShadowTreeRevision( +#pragma mark - Private helpers + +static RootShadowNode::Shared getCurrentShadowTreeRevision( facebook::jsi::Runtime& runtime, SurfaceId surfaceId) { auto& uiManager = @@ -33,13 +34,14 @@ RootShadowNode::Shared getCurrentShadowTreeRevision( return shadowTreeRevisionProvider->getCurrentRevision(surfaceId); } -facebook::react::PointerEventsProcessor& getPointerEventsProcessorFromRuntime( - facebook::jsi::Runtime& runtime) { +static facebook::react::PointerEventsProcessor& +getPointerEventsProcessorFromRuntime(facebook::jsi::Runtime& runtime) { return facebook::react::UIManagerBinding::getBinding(runtime) ->getPointerEventsProcessor(); } -std::vector getArrayOfInstanceHandlesFromShadowNodes( +static std::vector +getArrayOfInstanceHandlesFromShadowNodes( const ShadowNode::ListOfShared& nodes, facebook::jsi::Runtime& runtime) { // JSI doesn't support adding elements to an array after creation, @@ -56,9 +58,8 @@ std::vector getArrayOfInstanceHandlesFromShadowNodes( return nonNullInstanceHandles; } -} // namespace -namespace facebook::react { +#pragma mark - NativeDOM NativeDOM::NativeDOM(std::shared_ptr jsInvoker) : NativeDOMCxxSpec(std::move(jsInvoker)) {} @@ -244,6 +245,8 @@ std::string NativeDOM::getTagName( return dom::getTagName(*shadowNode); } +#pragma mark - Pointer events + bool NativeDOM::hasPointerCapture( jsi::Runtime& rt, jsi::Value shadowNodeValue, @@ -269,4 +272,85 @@ void NativeDOM::releasePointerCapture( pointerId, shadowNodeFromValue(rt, shadowNodeValue).get()); } +#pragma mark - Legacy RN layout APIs + +void NativeDOM::measure( + jsi::Runtime& rt, + jsi::Value shadowNodeValue, + jsi::Function callback) { + auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { + callback.call(rt, {0, 0, 0, 0, 0, 0}); + return; + } + + auto measureRect = dom::measure(currentRevision, *shadowNode); + + callback.call( + rt, + {jsi::Value{rt, measureRect.x}, + jsi::Value{rt, measureRect.y}, + jsi::Value{rt, measureRect.width}, + jsi::Value{rt, measureRect.height}, + jsi::Value{rt, measureRect.pageX}, + jsi::Value{rt, measureRect.pageY}}); +} + +void NativeDOM::measureInWindow( + jsi::Runtime& rt, + jsi::Value shadowNodeValue, + jsi::Function callback) { + auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { + callback.call(rt, {0, 0, 0, 0}); + return; + } + + auto rect = dom::measureInWindow(currentRevision, *shadowNode); + callback.call( + rt, + {jsi::Value{rt, rect.x}, + jsi::Value{rt, rect.y}, + jsi::Value{rt, rect.width}, + jsi::Value{rt, rect.height}}); +} + +void NativeDOM::measureLayout( + jsi::Runtime& rt, + jsi::Value shadowNodeValue, + jsi::Value relativeToShadowNodeValue, + jsi::Function onFail, + jsi::Function onSuccess) { + auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); + auto relativeToShadowNode = + shadowNodeFromValue(rt, relativeToShadowNodeValue); + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { + onFail.call(rt); + return; + } + + auto maybeRect = + dom::measureLayout(currentRevision, *shadowNode, *relativeToShadowNode); + + if (!maybeRect) { + onFail.call(rt); + return; + } + + auto rect = maybeRect.value(); + + onSuccess.call( + rt, + {jsi::Value{rt, rect.x}, + jsi::Value{rt, rect.y}, + jsi::Value{rt, rect.width}, + jsi::Value{rt, rect.height}}); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h index 07c30d2e1a2..4e16f03b5a6 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.h @@ -88,6 +88,23 @@ class NativeDOM : public NativeDOMCxxSpec { jsi::Runtime& rt, jsi::Value shadowNodeValue, double pointerId); + + // Legacy layout APIs + + void + measure(jsi::Runtime& rt, jsi::Value shadowNodeValue, jsi::Function callback); + + void measureInWindow( + jsi::Runtime& rt, + jsi::Value shadowNodeValue, + jsi::Function callback); + + void measureLayout( + jsi::Runtime& rt, + jsi::Value shadowNodeValue, + jsi::Value relativeToShadowNodeValue, + jsi::Function onFail, + jsi::Function onSuccess); }; } // namespace facebook::react 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 c1ca2aeef84..2124a103939 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 @@ -17,6 +17,29 @@ import type {TurboModule} from '../../../../../../Libraries/TurboModule/RCTExpor import * as TurboModuleRegistry from '../../../../../../Libraries/TurboModule/TurboModuleRegistry'; import nullthrows from 'nullthrows'; +export type MeasureInWindowOnSuccessCallback = ( + x: number, + y: number, + width: number, + height: number, +) => void; + +export type MeasureOnSuccessCallback = ( + x: number, + y: number, + width: number, + height: number, + pageX: number, + pageY: number, +) => void; + +export type MeasureLayoutOnSuccessCallback = ( + left: number, + top: number, + width: number, + height: number, +) => void; + export interface Spec extends TurboModule { +getParentNode: ( shadowNode: mixed /* ShadowNode */, @@ -76,6 +99,24 @@ export interface Spec extends TurboModule { shadowNode: mixed /* ShadowNode */, pointerId: number, ) => void; + + /** + * Legacy layout APIs + */ + + +measure: (shadowNode: mixed, callback: MeasureOnSuccessCallback) => void; + + +measureInWindow: ( + shadowNode: mixed, + callback: MeasureInWindowOnSuccessCallback, + ) => void; + + +measureLayout: ( + shadowNode: mixed, + relativeNode: mixed, + onFail: () => void, + onSuccess: MeasureLayoutOnSuccessCallback, + ) => void; } const RawNativeDOM = (TurboModuleRegistry.get('NativeDOMCxx'): ?Spec); @@ -271,6 +312,27 @@ export interface RefinedSpec { +setPointerCapture: (shadowNode: ShadowNode, pointerId: number) => void; +releasePointerCapture: (shadowNode: ShadowNode, pointerId: number) => void; + + /** + * Legacy layout APIs + */ + + +measure: ( + shadowNode: ShadowNode, + callback: MeasureOnSuccessCallback, + ) => void; + + +measureInWindow: ( + shadowNode: ShadowNode, + callback: MeasureInWindowOnSuccessCallback, + ) => void; + + +measureLayout: ( + shadowNode: ShadowNode, + relativeNode: ShadowNode, + onFail: () => void, + onSuccess: MeasureLayoutOnSuccessCallback, + ) => void; } const NativeDOM: RefinedSpec = { @@ -380,6 +442,27 @@ const NativeDOM: RefinedSpec = { pointerId, ); }, + + /** + * Legacy layout APIs + */ + + measure(shadowNode, callback) { + return nullthrows(RawNativeDOM).measure(shadowNode, callback); + }, + + measureInWindow(shadowNode, callback) { + return nullthrows(RawNativeDOM).measureInWindow(shadowNode, callback); + }, + + measureLayout(shadowNode, relativeNode, onFail, onSuccess) { + return nullthrows(RawNativeDOM).measureLayout( + shadowNode, + relativeNode, + onFail, + onSuccess, + ); + }, }; export default NativeDOM; diff --git a/packages/react-native/src/private/webapis/dom/nodes/specs/__mocks__/NativeDOMMock.js b/packages/react-native/src/private/webapis/dom/nodes/specs/__mocks__/NativeDOMMock.js index 106d4d26ac5..ab8eddc021f 100644 --- a/packages/react-native/src/private/webapis/dom/nodes/specs/__mocks__/NativeDOMMock.js +++ b/packages/react-native/src/private/webapis/dom/nodes/specs/__mocks__/NativeDOMMock.js @@ -13,6 +13,11 @@ import type { InternalInstanceHandle, Node, } from '../../../../../../../Libraries/Renderer/shims/ReactNativeTypes'; +import type { + MeasureInWindowOnSuccessCallback, + MeasureLayoutOnSuccessCallback, + MeasureOnSuccessCallback, +} from '../NativeDOM'; import typeof NativeDOM from '../NativeDOM'; import { @@ -371,6 +376,38 @@ const NativeDOMMock: NativeDOM = { ensureHostNode(node); return 'RN:' + fromNode(node).viewName; }), + + /** + * Legacy layout APIs + */ + + measure: jest.fn((node: Node, callback: MeasureOnSuccessCallback): void => { + ensureHostNode(node); + + callback(10, 10, 100, 100, 0, 0); + }), + + measureInWindow: jest.fn( + (node: Node, callback: MeasureInWindowOnSuccessCallback): void => { + ensureHostNode(node); + + callback(10, 10, 100, 100); + }, + ), + + measureLayout: jest.fn( + ( + node: Node, + relativeNode: Node, + onFail: () => void, + onSuccess: MeasureLayoutOnSuccessCallback, + ): void => { + ensureHostNode(node); + ensureHostNode(relativeNode); + + onSuccess(1, 1, 100, 100); + }, + ), }; export default NativeDOMMock;