Files
react-native/React/Fabric/Mounting/UIView+ComponentViewProtocol.mm
T
Valentin Shergin 6694ce00bb Fabric: Fixed incorrect early-return in UIView+ComponentViewProtocol::updateLayoutMetrics
Summary:
Before the change, an incorrect (NaN or Inf) values in LayoutMetrics might force an early return in the `updateLayoutMetrics:oldMetrics:` method implementation. This was not correct because the rest of the method also didn't run in this case, so it might force some value to stale.
E.g., imagine we have an instruction that contains NaN size and `display: none`. Previously, the function might just return right before applying sizes and progress the stored "already applied" value of LayoutMetrics which will cause the view being visible even if it should not.

Changelog: [Internal] Fabric-specific internal change.

Reviewed By: JoshuaGross

Differential Revision: D21110644

fbshipit-source-id: 501319d7b1dcd5c18f27e0ceca3c8d207485c49b
2020-04-19 22:32:51 -07:00

118 lines
3.9 KiB
Plaintext

/*
* 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.
*/
#import "UIView+ComponentViewProtocol.h"
#import <React/RCTAssert.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
#import "RCTConversions.h"
using namespace facebook::react;
@implementation UIView (ComponentViewProtocol)
+ (ComponentDescriptorProvider)componentDescriptorProvider
{
RCTAssert(NO, @"`-[RCTComponentViewProtocol componentDescriptorProvider]` must be implemented in a concrete class.");
return {};
}
+ (std::vector<facebook::react::ComponentDescriptorProvider>)supplementalComponentDescriptorProviders
{
return {};
}
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
[self insertSubview:childComponentView atIndex:index];
}
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
RCTAssert(childComponentView.superview == self, @"Attempt to unmount improperly mounted component view.");
[childComponentView removeFromSuperview];
}
- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
// Default implementation does nothing.
}
- (void)updateEventEmitter:(EventEmitter::Shared const &)eventEmitter
{
// Default implementation does nothing.
}
- (void)updateState:(facebook::react::State::Shared const &)state
oldState:(facebook::react::State::Shared const &)oldState
{
// Default implementation does nothing.
}
- (void)handleCommand:(NSString *)commandName args:(NSArray *)args
{
// Default implementation does nothing.
}
- (void)updateLayoutMetrics:(LayoutMetrics const &)layoutMetrics
oldLayoutMetrics:(LayoutMetrics const &)oldLayoutMetrics
{
bool forceUpdate = oldLayoutMetrics == EmptyLayoutMetrics;
if (forceUpdate || (layoutMetrics.frame != oldLayoutMetrics.frame)) {
CGRect frame = RCTCGRectFromRect(layoutMetrics.frame);
if (!std::isfinite(frame.origin.x) || !std::isfinite(frame.origin.y) || !std::isfinite(frame.size.width) ||
!std::isfinite(frame.size.height)) {
// CALayer will crash if we pass NaN or Inf values.
// It's unclear how to detect this case on cross-platform manner holistically, so we have to do it on the mounting
// layer as well. NaN/Inf is a kinda valid result of some math operations. Even if we can (and should) detect (and
// report early) incorrect (NaN and Inf) values which come from JavaScript side, we sometimes cannot backtrace the
// sources of a calculation that produced an incorrect/useless result.
RCTLogWarn(
@"-[UIView(ComponentViewProtocol) updateLayoutMetrics:oldLayoutMetrics:]: Received invalid layout metrics (%@) for a view (%@).",
NSStringFromCGRect(frame),
self);
} else {
// Note: Changing `frame` when `layer.transform` is not the `identity transform` is undefined behavior.
// Therefore, we must use `center` and `bounds`.
self.center = CGPoint{CGRectGetMidX(frame), CGRectGetMidY(frame)};
self.bounds = CGRect{CGPointZero, frame.size};
}
}
if (forceUpdate || (layoutMetrics.layoutDirection != oldLayoutMetrics.layoutDirection)) {
self.semanticContentAttribute = layoutMetrics.layoutDirection == LayoutDirection::RightToLeft
? UISemanticContentAttributeForceRightToLeft
: UISemanticContentAttributeForceLeftToRight;
}
if (forceUpdate || (layoutMetrics.displayType != oldLayoutMetrics.displayType)) {
self.hidden = layoutMetrics.displayType == DisplayType::None;
}
}
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask
{
// Default implementation does nothing.
}
- (void)prepareForRecycle
{
// Default implementation does nothing.
}
- (facebook::react::SharedProps)props
{
RCTAssert(NO, @"props access should be implemented by RCTViewComponentView.");
return nullptr;
}
@end