mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Fix findNodeAtPoint returns incorrect view
Summary: This work is based on Ruslan's https://www.internalfb.com/intern/diff/D56185630/ Changelog: [Internal] `Expectation`: In React DevTools, user should be able to select an element on screen and it will show you what React component rendered it. This doesn't work in RN app that is using JS navigation `Root Cause`: In Fabric, when we try to find `ShadowNode` in the `ShadowTree`, `pointerEvents` props are not considered during the lookup of node using coordinate. Hence, in React DevTools when we inspect element, it was hightlighting the overlay `View` with `pointerEvents` props `box-none` was getting highlighted instead of its children view in the hierarchy. Reviewed By: javache Differential Revision: D56334314 fbshipit-source-id: ebfe58c5a1516add347c2c21ab5d075f804df8a9
This commit is contained in:
committed by
Facebook GitHub Bot
parent
7f5bff48dd
commit
a9a7382d95
+14
@@ -84,6 +84,20 @@ class ConcreteViewShadowNode : public ConcreteShadowNode<
|
||||
return BaseShadowNode::getConcreteProps().resolveTransform(layoutMetrics);
|
||||
}
|
||||
|
||||
bool canBeTouchTarget() const override {
|
||||
auto pointerEvents =
|
||||
BaseShadowNode::getConcreteProps().ViewProps::pointerEvents;
|
||||
return pointerEvents == PointerEventsMode::Auto ||
|
||||
pointerEvents == PointerEventsMode::BoxOnly;
|
||||
}
|
||||
|
||||
bool canChildrenBeTouchTarget() const override {
|
||||
auto pointerEvents =
|
||||
BaseShadowNode::getConcreteProps().ViewProps::pointerEvents;
|
||||
return pointerEvents == PointerEventsMode::Auto ||
|
||||
pointerEvents == PointerEventsMode::BoxNone;
|
||||
}
|
||||
|
||||
private:
|
||||
void initialize() noexcept {
|
||||
auto& props = BaseShadowNode::getConcreteProps();
|
||||
|
||||
@@ -262,6 +262,14 @@ Point LayoutableShadowNode::getContentOriginOffset() const {
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
bool LayoutableShadowNode::canBeTouchTarget() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LayoutableShadowNode::canChildrenBeTouchTarget() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
LayoutableShadowNode::UnsharedList
|
||||
LayoutableShadowNode::getLayoutableChildNodes() const {
|
||||
LayoutableShadowNode::UnsharedList layoutableChildren;
|
||||
@@ -314,12 +322,20 @@ ShadowNode::Shared LayoutableShadowNode::findNodeAtPoint(
|
||||
if (layoutableShadowNode == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!layoutableShadowNode->canBeTouchTarget() &&
|
||||
!layoutableShadowNode->canChildrenBeTouchTarget()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto frame = layoutableShadowNode->getLayoutMetrics().frame;
|
||||
auto transformedFrame = frame * layoutableShadowNode->getTransform();
|
||||
auto isPointInside = transformedFrame.containsPoint(point);
|
||||
|
||||
if (!isPointInside) {
|
||||
return nullptr;
|
||||
} else if (!layoutableShadowNode->canChildrenBeTouchTarget()) {
|
||||
return node;
|
||||
}
|
||||
|
||||
auto newPoint = point - transformedFrame.origin -
|
||||
@@ -340,7 +356,7 @@ ShadowNode::Shared LayoutableShadowNode::findNodeAtPoint(
|
||||
return hitView;
|
||||
}
|
||||
}
|
||||
return isPointInside ? node : nullptr;
|
||||
return layoutableShadowNode->canBeTouchTarget() ? node : nullptr;
|
||||
}
|
||||
|
||||
#if RN_DEBUG_STRING_CONVERTIBLE
|
||||
|
||||
@@ -153,6 +153,9 @@ class LayoutableShadowNode : public ShadowNode {
|
||||
virtual Float firstBaseline(Size size) const;
|
||||
virtual Float lastBaseline(Size size) const;
|
||||
|
||||
virtual bool canBeTouchTarget() const;
|
||||
virtual bool canChildrenBeTouchTarget() const;
|
||||
|
||||
/*
|
||||
* Returns layoutable children to iterate on.
|
||||
*/
|
||||
|
||||
@@ -226,3 +226,143 @@ TEST(FindNodeAtPointTest, overlappingViewsWithZIndex) {
|
||||
EXPECT_EQ(
|
||||
LayoutableShadowNode::findNodeAtPoint(parentShadowNode, {50, 50})->getTag(), 2);
|
||||
}
|
||||
|
||||
TEST(FindNodeAtPointTest, overlappingViewsWithParentPointerEventsBoxOnly) {
|
||||
auto builder = simpleComponentBuilder();
|
||||
|
||||
// clang-format off
|
||||
auto element =
|
||||
Element<ViewShadowNode>()
|
||||
.tag(1)
|
||||
.props([] {
|
||||
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
|
||||
sharedProps->pointerEvents = PointerEventsMode::BoxOnly;
|
||||
return sharedProps;
|
||||
})
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
auto layoutMetrics = EmptyLayoutMetrics;
|
||||
layoutMetrics.frame.size = {100, 100};
|
||||
shadowNode.setLayoutMetrics(layoutMetrics);
|
||||
})
|
||||
.children({
|
||||
Element<ViewShadowNode>()
|
||||
.tag(2)
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
auto layoutMetrics = EmptyLayoutMetrics;
|
||||
layoutMetrics.frame.origin = {50, 50};
|
||||
layoutMetrics.frame.size = {50, 50};
|
||||
shadowNode.setLayoutMetrics(layoutMetrics);
|
||||
}),
|
||||
Element<ViewShadowNode>()
|
||||
.tag(3)
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
auto layoutMetrics = EmptyLayoutMetrics;
|
||||
layoutMetrics.frame.origin = {50, 50};
|
||||
layoutMetrics.frame.size = {50, 50};
|
||||
shadowNode.setLayoutMetrics(layoutMetrics);
|
||||
})
|
||||
});
|
||||
|
||||
auto parentShadowNode = builder.build(element);
|
||||
|
||||
EXPECT_EQ(
|
||||
LayoutableShadowNode::findNodeAtPoint(parentShadowNode, {60, 60})->getTag(), 1);
|
||||
}
|
||||
|
||||
TEST(FindNodeAtPointTest, overlappingViewsWithParentPointerEventsBoxNone) {
|
||||
auto builder = simpleComponentBuilder();
|
||||
|
||||
// clang-format off
|
||||
auto element =
|
||||
Element<ViewShadowNode>()
|
||||
.tag(1)
|
||||
.props([] {
|
||||
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
|
||||
sharedProps->pointerEvents = PointerEventsMode::BoxNone;
|
||||
return sharedProps;
|
||||
})
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
auto layoutMetrics = EmptyLayoutMetrics;
|
||||
layoutMetrics.frame.size = {100, 100};
|
||||
shadowNode.setLayoutMetrics(layoutMetrics);
|
||||
})
|
||||
.children({
|
||||
Element<ViewShadowNode>()
|
||||
.tag(2)
|
||||
.props([] {
|
||||
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
|
||||
sharedProps->zIndex = 1;
|
||||
auto &yogaStyle = sharedProps->yogaStyle;
|
||||
yogaStyle.setPositionType(yoga::PositionType::Absolute);
|
||||
return sharedProps;
|
||||
})
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
auto layoutMetrics = EmptyLayoutMetrics;
|
||||
layoutMetrics.frame.origin = {25, 25};
|
||||
layoutMetrics.frame.size = {50, 50};
|
||||
shadowNode.setLayoutMetrics(layoutMetrics);
|
||||
}),
|
||||
Element<ViewShadowNode>()
|
||||
.tag(3)
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
auto layoutMetrics = EmptyLayoutMetrics;
|
||||
layoutMetrics.frame.origin = {50, 50};
|
||||
layoutMetrics.frame.size = {50, 50};
|
||||
shadowNode.setLayoutMetrics(layoutMetrics);
|
||||
})
|
||||
});
|
||||
|
||||
auto parentShadowNode = builder.build(element);
|
||||
|
||||
EXPECT_EQ(
|
||||
LayoutableShadowNode::findNodeAtPoint(parentShadowNode, {50, 50})->getTag(), 2);
|
||||
}
|
||||
|
||||
TEST(FindNodeAtPointTest, overlappingViewsWithParentPointerEventsNone) {
|
||||
auto builder = simpleComponentBuilder();
|
||||
|
||||
// clang-format off
|
||||
auto element =
|
||||
Element<ViewShadowNode>()
|
||||
.tag(1)
|
||||
.props([] {
|
||||
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
|
||||
sharedProps->pointerEvents = PointerEventsMode::None;
|
||||
return sharedProps;
|
||||
})
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
auto layoutMetrics = EmptyLayoutMetrics;
|
||||
layoutMetrics.frame.size = {100, 100};
|
||||
shadowNode.setLayoutMetrics(layoutMetrics);
|
||||
})
|
||||
.children({
|
||||
Element<ViewShadowNode>()
|
||||
.tag(2)
|
||||
.props([] {
|
||||
auto sharedProps = std::make_shared<ViewShadowNodeProps>();
|
||||
sharedProps->zIndex = 1;
|
||||
auto &yogaStyle = sharedProps->yogaStyle;
|
||||
yogaStyle.setPositionType(yoga::PositionType::Absolute);
|
||||
return sharedProps;
|
||||
})
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
auto layoutMetrics = EmptyLayoutMetrics;
|
||||
layoutMetrics.frame.origin = {25, 25};
|
||||
layoutMetrics.frame.size = {50, 50};
|
||||
shadowNode.setLayoutMetrics(layoutMetrics);
|
||||
}),
|
||||
Element<ViewShadowNode>()
|
||||
.tag(3)
|
||||
.finalize([](ViewShadowNode &shadowNode){
|
||||
auto layoutMetrics = EmptyLayoutMetrics;
|
||||
layoutMetrics.frame.origin = {50, 50};
|
||||
layoutMetrics.frame.size = {50, 50};
|
||||
shadowNode.setLayoutMetrics(layoutMetrics);
|
||||
})
|
||||
});
|
||||
|
||||
auto parentShadowNode = builder.build(element);
|
||||
|
||||
EXPECT_EQ(
|
||||
LayoutableShadowNode::findNodeAtPoint(parentShadowNode, {50, 50}), nullptr);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user