Files
react-native/ReactCommon/react/renderer/core/LayoutableShadowNode.h
T
Samuel SuslaandFacebook GitHub Bot 43b2c6dd85 Fix conditions to fire onLayout event
Summary:
Changelog: [Internal]

# Problem

## Step 1
JS clones a node that has size {100, 100} and changes props that cause the node to increase size to {200, 200}. JS holds pointer to this node.

Now, the size (stored in LayoutableShadowNode.layoutMetrics_) changes after Yoga layout is triggered.

However, the node gets cloned inside State Reconciliation before Yoga layout phase. The JS pointer points to a node with size {100, 100}, not to a node with size {200, 200}.

## Step 2

Again, JS clones node (with old reference, therefore gets old layoutMetrics_ with size {100, 100}) and it changes props that cause the node to decrease its size back to {100, 100}.
We go all the way to Yoga layout and looking for nodes that have been affected by the node. The node, affected by the layout because it went from {200, 200} to {100, 100}, will be evaluated as not affected. This causes onLayout event to not be fired.

# Fix
We can safely remove the frame equality check (please see below). This can be done because we already check for equality before dispatching onLayout. It happens here:

https://www.internalfb.com/intern/diffusion/FBS/browsefile/master/xplat/js/react-native-github/ReactCommon/react/renderer/components/view/ViewEventEmitter.cpp?commit=881853eb0c42625fd0812bd2652bf36fcbd614ee&lines=43

As far as I know, `affectedNodes` isn't used for anything else besides dispatching onLayout.

# Discussion

This problem manifests itself only when a node has two different sizes that it flips between. To better understand this, please watch the video in Test plan labelled "before". Notice how the text has 2 different values that it flips between.

Here is a code that was affected by it https://fburl.com/diffusion/3hwo0iy5
If you inspect it closely, you will notice that it depends on `onLayout` to return correct value to calculate offset from left.

Reviewed By: JoshuaGross

Differential Revision: D22999891

fbshipit-source-id: e2d0f5771c1bf3cd788e5e9da0155c92e33fb84e
2020-08-10 05:11:29 -07:00

214 lines
6.3 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <array>
#include <cmath>
#include <memory>
#include <vector>
#include <better/small_vector.h>
#include <react/renderer/core/LayoutMetrics.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/core/ShadowNodeFragment.h>
#include <react/renderer/debug/DebugStringConvertible.h>
#include <react/renderer/graphics/Geometry.h>
#include <react/renderer/graphics/Transform.h>
namespace facebook {
namespace react {
struct LayoutConstraints;
struct LayoutContext;
/*
* Describes all sufficient layout API (in approach-agnostic way)
* which makes a concurrent layout possible.
*/
class LayoutableShadowNode : public ShadowNode {
public:
LayoutableShadowNode(
ShadowNodeFragment const &fragment,
ShadowNodeFamily::Shared const &family,
ShadowNodeTraits traits);
LayoutableShadowNode(
ShadowNode const &sourceShadowNode,
ShadowNodeFragment const &fragment);
static ShadowNodeTraits BaseTraits();
class LayoutInspectingPolicy final {
public:
bool includeTransform{true};
};
using UnsharedList = better::
small_vector<LayoutableShadowNode *, kShadowNodeChildrenSmallVectorSize>;
/*
* Returns layout metrics of a node represented as `descendantNodeFamily`
* computed relatively to given `ancestorNode`. Returns `EmptyLayoutMetrics`
* if the nodes don't form an ancestor-descender relationship in the same
* tree.
*/
static LayoutMetrics computeRelativeLayoutMetrics(
ShadowNodeFamily const &descendantNodeFamily,
LayoutableShadowNode const &ancestorNode,
LayoutInspectingPolicy policy);
/*
* Performs layout of the tree starting from this node. Usually is being
* called on the root node.
* Default implementation does nothing.
*/
virtual void layoutTree(
LayoutContext layoutContext,
LayoutConstraints layoutConstraints);
/*
* Measures the node (and node content, probably recursively) with
* given constrains and relying on possible layout.
* Default implementation returns zero size.
*/
virtual Size measureContent(
LayoutContext const &layoutContext,
LayoutConstraints const &layoutConstraints) const;
/*
* Measures the node with given `layoutContext` and `layoutConstraints`.
* The size of nested content and the padding should be included, the margin
* should *not* be included. Default implementation returns zero size.
*/
virtual Size measure(
LayoutContext const &layoutContext,
LayoutConstraints const &layoutConstraints) const;
/*
* Computes layout recursively.
* Additional environmental constraints might be provided via `layoutContext`
* argument.
*
* The typical concrete-layout-specific implementation of this method should:
* - Measure children with `LayoutConstraints` calculated from its size using
* a particular layout approach;
* - Calculate and assign `LayoutMetrics` for the children;
* - Call itself recursively on every child if needed.
*/
virtual void layout(LayoutContext layoutContext);
/*
* Returns layout metrics computed during previous layout pass.
*/
LayoutMetrics getLayoutMetrics() const;
/*
* Returns a transform object that represents transformations that will/should
* be applied on top of regular layout metrics by mounting layer.
* The `transform` value modifies a coordinate space of a layout system.
* Default implementation returns `Identity` transform.
*/
virtual Transform getTransform() const;
/*
* Returns offset which is applied to children's origin in
* `LayoutableShadowNode::getRelativeLayoutMetrics` and
* `LayoutableShadowNode::findNodeAtPoint`.
*/
virtual Point getContentOriginOffset() const;
/*
* Returns layout metrics relatively to the given ancestor node.
* Uses `computeRelativeLayoutMetrics()` under the hood.
*/
LayoutMetrics getRelativeLayoutMetrics(
ShadowNodeFamily const &descendantNodeFamily,
LayoutInspectingPolicy policy) const;
/*
* Returns layout metrics relatively to the given ancestor node.
*/
LayoutMetrics getRelativeLayoutMetrics(
LayoutableShadowNode const &ancestorLayoutableShadowNode,
LayoutInspectingPolicy policy) const;
/*
* Sets layout metrics for the shadow node.
*/
void setLayoutMetrics(LayoutMetrics layoutMetrics);
/*
* Returns the ShadowNode that is rendered at the Point received as a
* parameter.
*/
static ShadowNode::Shared findNodeAtPoint(
ShadowNode::Shared node,
Point point);
/*
* Clean or Dirty layout state:
* Indicates whether all nodes (and possibly their subtrees) along the path
* to the root node should be re-laid out.
*/
virtual void cleanLayout() = 0;
virtual void dirtyLayout() = 0;
virtual bool getIsLayoutClean() const = 0;
/*
* Unifed methods to access text layout metrics.
*/
virtual Float firstBaseline(Size size) const;
virtual Float lastBaseline(Size size) const;
/*
* Returns layoutable children to interate on.
*/
LayoutableShadowNode::UnsharedList getLayoutableChildNodes() const;
#pragma mark - DebugStringConvertible
#if RN_DEBUG_STRING_CONVERTIBLE
SharedDebugStringConvertibleList getDebugProps() const;
#endif
LayoutMetrics layoutMetrics_;
};
template <>
inline LayoutableShadowNode const &traitCast<LayoutableShadowNode const &>(
ShadowNode const &shadowNode) {
bool castable =
shadowNode.getTraits().check(ShadowNodeTraits::Trait::LayoutableKind);
assert(
castable ==
(dynamic_cast<LayoutableShadowNode const *>(&shadowNode) != nullptr));
assert(castable);
(void)castable;
return static_cast<LayoutableShadowNode const &>(shadowNode);
}
template <>
inline LayoutableShadowNode const *traitCast<LayoutableShadowNode const *>(
ShadowNode const *shadowNode) {
if (!shadowNode) {
return nullptr;
}
bool castable =
shadowNode->getTraits().check(ShadowNodeTraits::Trait::LayoutableKind);
assert(
castable ==
(dynamic_cast<LayoutableShadowNode const *>(shadowNode) != nullptr));
if (!castable) {
return nullptr;
}
return static_cast<LayoutableShadowNode const *>(shadowNode);
}
} // namespace react
} // namespace facebook