mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
58a2eb7f37
Summary: This PR fixes RTTI (run-time type information) for ShadowNodeWrapper and ShadowNodeListWrapper classes, i.e., calls to dynamic_cast and dynamic_pointer_cast that are called via JSI's getHostObject calls. The fix is simply to add a so-called "key function" in a form of virtual destructor. Key functions needs to be a virtual non-pure and non-inlined functions that points the compiler as to which library contains the vtable/type information for a given class (see https://itanium-cxx-abi.github.io/cxx-abi/abi.html#vague-vtable and https://developer.android.com/ndk/guides/common-problems#rttiexceptions_not_working_across_library_boundaries) Without the "key function", calls to dynamic_cast for ShadowNodeWrapper instances won't work across library boundaries because the class will have separate definitions in each separate library, therefore objects created in one of those libraries won't be recognized as the same type by the other library. This has been a problem in reanimated and gesture-handler libraries where we call `object.getHostObject<ShadowNodeWrapper>(rt)` (this is a method from JSI) in order to access ShadowNode instance from a handle we have in JS. I think, this issue is going to be relevant to more libraries that cope with view instances. In this scenario, we have a separate library, say "libreanimated.so" that calls to `getHostObject` which is an inline function that calls `dynamic_cast` for the `ShadowNodeWrapper` class. On the other hand, the instances of `ShadowNodeWrapper` are created by the code from `libreact_render_uimanager.so`. Because of that `dynamic_cast` fails even though it is called on instance of `ShadowNodeWrapper` because the class has separate vtable/type info: one in `libreanimated.so` and one in `libreact_render_uimanager.so` (by "fails" I mean that it actually returns `nullptr`). This problem has been documented here: https://developer.android.com/ndk/guides/common-problems#rttiexceptions_not_working_across_library_boundaries where the solution is for the class to have a so-called "key function". The key function makes it so that compiler sees that one of the implementation for a given class is missing and therefore can safely assume that a vtable/type info for a given class is embedded into some library we link to. This change adds a virtual destructor that is declared in the header file but defined in file that gets compiled as a part of `libreact_render_uimanager`. As a result, the compiler only creates one vtable/type info and calls to dynamic_cast works as expected in all libraries for `ShadowNodeWrapper` and `ShadowNodeListWrapper` classes. This issue would only surface on Android, because on iOS all libraries by default are bundled together via Pods, whereas on Android each library is loaded separately using dynamic loading. ## Changelog [Fabric][Android specific] - Fix dynamic_cast (RTTI) for ShadowNodeWrapper and similar classes when accessed by third-party libraries. Pull Request resolved: https://github.com/facebook/react-native/pull/33500 Test Plan: 1. In order to test this you need to add a library that'd include `<react/renderer/uimanager/primitives.h>` (i.e. use this branch of reanimated library: https://github.com/software-mansion/react-native-reanimated/tree/fabric) 2. After compiling the app inspect libreact_render_uimanager.so and libreanimated.so artifacts with `nm` tool 3. Notice that symbols like `vtable for facebook::react::ShadowNodeWrapper` and `typeinfo for facebook::react::ShadowNodeWrapper` are only present in the former and not in the latter library (before this change you'd see them both) Reviewed By: ShikaSD Differential Revision: D35143600 Pulled By: javache fbshipit-source-id: 5fb25a02365b99a515edc81e5485a77017c56eb8
454 lines
14 KiB
C++
454 lines
14 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include "UIManager.h"
|
|
|
|
#include <react/debug/react_native_assert.h>
|
|
#include <react/renderer/core/PropsParserContext.h>
|
|
#include <react/renderer/core/ShadowNodeFragment.h>
|
|
#include <react/renderer/debug/SystraceSection.h>
|
|
#include <react/renderer/graphics/Geometry.h>
|
|
#include <react/renderer/uimanager/SurfaceRegistryBinding.h>
|
|
#include <react/renderer/uimanager/UIManagerBinding.h>
|
|
#include <react/renderer/uimanager/UIManagerCommitHook.h>
|
|
|
|
#include <glog/logging.h>
|
|
|
|
#include <utility>
|
|
|
|
namespace facebook::react {
|
|
|
|
// Explicitly define destructors here, as they to exist in order to act as a
|
|
// "key function" for the ShadowNodeWrapper class -- this allow for RTTI to work
|
|
// properly across dynamic library boundaries (i.e. dynamic_cast that is used by
|
|
// isHostObject method)
|
|
ShadowNodeWrapper::~ShadowNodeWrapper() = default;
|
|
ShadowNodeListWrapper::~ShadowNodeListWrapper() = default;
|
|
|
|
static std::unique_ptr<LeakChecker> constructLeakCheckerIfNeeded(
|
|
RuntimeExecutor const &runtimeExecutor) {
|
|
#ifdef REACT_NATIVE_DEBUG
|
|
return std::make_unique<LeakChecker>(runtimeExecutor);
|
|
#else
|
|
return {};
|
|
#endif
|
|
}
|
|
|
|
UIManager::UIManager(
|
|
RuntimeExecutor const &runtimeExecutor,
|
|
BackgroundExecutor backgroundExecutor,
|
|
ContextContainer::Shared contextContainer)
|
|
: runtimeExecutor_(runtimeExecutor),
|
|
backgroundExecutor_(std::move(backgroundExecutor)),
|
|
contextContainer_(std::move(contextContainer)),
|
|
leakChecker_(constructLeakCheckerIfNeeded(runtimeExecutor)) {}
|
|
|
|
UIManager::~UIManager() {
|
|
LOG(WARNING) << "UIManager::~UIManager() was called (address: " << this
|
|
<< ").";
|
|
}
|
|
|
|
SharedShadowNode UIManager::createNode(
|
|
Tag tag,
|
|
std::string const &name,
|
|
SurfaceId surfaceId,
|
|
const RawProps &rawProps,
|
|
SharedEventTarget eventTarget) const {
|
|
SystraceSection s("UIManager::createNode");
|
|
|
|
auto &componentDescriptor = componentDescriptorRegistry_->at(name);
|
|
auto fallbackDescriptor =
|
|
componentDescriptorRegistry_->getFallbackComponentDescriptor();
|
|
|
|
PropsParserContext propsParserContext{surfaceId, *contextContainer_.get()};
|
|
|
|
auto const fragment = ShadowNodeFamilyFragment{tag, surfaceId, nullptr};
|
|
auto family =
|
|
componentDescriptor.createFamily(fragment, std::move(eventTarget));
|
|
auto const props =
|
|
componentDescriptor.cloneProps(propsParserContext, nullptr, rawProps);
|
|
auto const state =
|
|
componentDescriptor.createInitialState(ShadowNodeFragment{props}, family);
|
|
|
|
auto shadowNode = componentDescriptor.createShadowNode(
|
|
ShadowNodeFragment{
|
|
/* .props = */
|
|
fallbackDescriptor != nullptr &&
|
|
fallbackDescriptor->getComponentHandle() ==
|
|
componentDescriptor.getComponentHandle()
|
|
? componentDescriptor.cloneProps(
|
|
propsParserContext,
|
|
props,
|
|
RawProps(folly::dynamic::object("name", name)))
|
|
: props,
|
|
/* .children = */ ShadowNodeFragment::childrenPlaceholder(),
|
|
/* .state = */ state,
|
|
},
|
|
family);
|
|
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidCreateShadowNode(*shadowNode);
|
|
}
|
|
if (leakChecker_) {
|
|
leakChecker_->uiManagerDidCreateShadowNodeFamily(family);
|
|
}
|
|
|
|
return shadowNode;
|
|
}
|
|
|
|
SharedShadowNode UIManager::cloneNode(
|
|
ShadowNode const &shadowNode,
|
|
SharedShadowNodeSharedList const &children,
|
|
RawProps const *rawProps) const {
|
|
SystraceSection s("UIManager::cloneNode");
|
|
|
|
PropsParserContext propsParserContext{
|
|
shadowNode.getFamily().getSurfaceId(), *contextContainer_.get()};
|
|
|
|
auto &componentDescriptor = shadowNode.getComponentDescriptor();
|
|
auto clonedShadowNode = componentDescriptor.cloneShadowNode(
|
|
shadowNode,
|
|
{
|
|
/* .props = */
|
|
rawProps ? componentDescriptor.cloneProps(
|
|
propsParserContext, shadowNode.getProps(), *rawProps)
|
|
: ShadowNodeFragment::propsPlaceholder(),
|
|
/* .children = */ children,
|
|
});
|
|
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidCloneShadowNode(shadowNode, *clonedShadowNode);
|
|
}
|
|
|
|
return clonedShadowNode;
|
|
}
|
|
|
|
void UIManager::appendChild(
|
|
const ShadowNode::Shared &parentShadowNode,
|
|
const ShadowNode::Shared &childShadowNode) const {
|
|
SystraceSection s("UIManager::appendChild");
|
|
|
|
auto &componentDescriptor = parentShadowNode->getComponentDescriptor();
|
|
componentDescriptor.appendChild(parentShadowNode, childShadowNode);
|
|
}
|
|
|
|
void UIManager::completeSurface(
|
|
SurfaceId surfaceId,
|
|
SharedShadowNodeUnsharedList const &rootChildren,
|
|
ShadowTree::CommitOptions commitOptions) const {
|
|
SystraceSection s("UIManager::completeSurface");
|
|
|
|
shadowTreeRegistry_.visit(surfaceId, [&](ShadowTree const &shadowTree) {
|
|
shadowTree.commit(
|
|
[&](RootShadowNode const &oldRootShadowNode) {
|
|
return std::make_shared<RootShadowNode>(
|
|
oldRootShadowNode,
|
|
ShadowNodeFragment{
|
|
/* .props = */ ShadowNodeFragment::propsPlaceholder(),
|
|
/* .children = */ rootChildren,
|
|
});
|
|
},
|
|
commitOptions);
|
|
});
|
|
}
|
|
|
|
void UIManager::setIsJSResponder(
|
|
ShadowNode::Shared const &shadowNode,
|
|
bool isJSResponder,
|
|
bool blockNativeResponder) const {
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidSetIsJSResponder(
|
|
shadowNode, isJSResponder, blockNativeResponder);
|
|
}
|
|
}
|
|
|
|
void UIManager::startSurface(
|
|
ShadowTree::Unique &&shadowTree,
|
|
std::string const &moduleName,
|
|
folly::dynamic const &props,
|
|
DisplayMode displayMode) const {
|
|
SystraceSection s("UIManager::startSurface");
|
|
|
|
auto surfaceId = shadowTree->getSurfaceId();
|
|
shadowTreeRegistry_.add(std::move(shadowTree));
|
|
|
|
runtimeExecutor_([=](jsi::Runtime &runtime) {
|
|
SystraceSection s("UIManager::startSurface::onRuntime");
|
|
SurfaceRegistryBinding::startSurface(
|
|
runtime, surfaceId, moduleName, props, displayMode);
|
|
});
|
|
}
|
|
|
|
void UIManager::setSurfaceProps(
|
|
SurfaceId surfaceId,
|
|
std::string const &moduleName,
|
|
folly::dynamic const &props,
|
|
DisplayMode displayMode) const {
|
|
SystraceSection s("UIManager::setSurfaceProps");
|
|
|
|
runtimeExecutor_([=](jsi::Runtime &runtime) {
|
|
SurfaceRegistryBinding::setSurfaceProps(
|
|
runtime, surfaceId, moduleName, props, displayMode);
|
|
});
|
|
}
|
|
|
|
ShadowTree::Unique UIManager::stopSurface(SurfaceId surfaceId) const {
|
|
SystraceSection s("UIManager::stopSurface");
|
|
|
|
// Stop any ongoing animations.
|
|
stopSurfaceForAnimationDelegate(surfaceId);
|
|
|
|
// Waiting for all concurrent commits to be finished and unregistering the
|
|
// `ShadowTree`.
|
|
auto shadowTree = getShadowTreeRegistry().remove(surfaceId);
|
|
|
|
// We execute JavaScript/React part of the process at the very end to minimize
|
|
// any visible side-effects of stopping the Surface. Any possible commits from
|
|
// the JavaScript side will not be able to reference a `ShadowTree` and will
|
|
// fail silently.
|
|
runtimeExecutor_([=](jsi::Runtime &runtime) {
|
|
SurfaceRegistryBinding::stopSurface(runtime, surfaceId);
|
|
});
|
|
|
|
if (leakChecker_) {
|
|
leakChecker_->stopSurface(surfaceId);
|
|
}
|
|
|
|
return shadowTree;
|
|
}
|
|
|
|
ShadowNode::Shared UIManager::getNewestCloneOfShadowNode(
|
|
ShadowNode const &shadowNode) const {
|
|
auto ancestorShadowNode = ShadowNode::Shared{};
|
|
shadowTreeRegistry_.visit(
|
|
shadowNode.getSurfaceId(), [&](ShadowTree const &shadowTree) {
|
|
ancestorShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
|
|
});
|
|
|
|
if (!ancestorShadowNode) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto ancestors = shadowNode.getFamily().getAncestors(*ancestorShadowNode);
|
|
|
|
if (ancestors.empty()) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto pair = ancestors.rbegin();
|
|
return pair->first.get().getChildren().at(pair->second);
|
|
}
|
|
|
|
ShadowNode::Shared UIManager::findNodeAtPoint(
|
|
ShadowNode::Shared const &node,
|
|
Point point) const {
|
|
return LayoutableShadowNode::findNodeAtPoint(
|
|
getNewestCloneOfShadowNode(*node), point);
|
|
}
|
|
|
|
LayoutMetrics UIManager::getRelativeLayoutMetrics(
|
|
ShadowNode const &shadowNode,
|
|
ShadowNode const *ancestorShadowNode,
|
|
LayoutableShadowNode::LayoutInspectingPolicy policy) const {
|
|
SystraceSection s("UIManager::getRelativeLayoutMetrics");
|
|
|
|
// We might store here an owning pointer to `ancestorShadowNode` to ensure
|
|
// that the node is not deallocated during method execution lifetime.
|
|
auto owningAncestorShadowNode = ShadowNode::Shared{};
|
|
|
|
if (!ancestorShadowNode) {
|
|
shadowTreeRegistry_.visit(
|
|
shadowNode.getSurfaceId(), [&](ShadowTree const &shadowTree) {
|
|
owningAncestorShadowNode =
|
|
shadowTree.getCurrentRevision().rootShadowNode;
|
|
ancestorShadowNode = owningAncestorShadowNode.get();
|
|
});
|
|
} else {
|
|
// It is possible for JavaScript (or other callers) to have a reference
|
|
// to a previous version of ShadowNodes, but we enforce that
|
|
// metrics are only calculated on most recently committed versions.
|
|
owningAncestorShadowNode = getNewestCloneOfShadowNode(*ancestorShadowNode);
|
|
ancestorShadowNode = owningAncestorShadowNode.get();
|
|
}
|
|
|
|
auto layoutableAncestorShadowNode =
|
|
traitCast<LayoutableShadowNode const *>(ancestorShadowNode);
|
|
|
|
if (!layoutableAncestorShadowNode) {
|
|
return EmptyLayoutMetrics;
|
|
}
|
|
|
|
return LayoutableShadowNode::computeRelativeLayoutMetrics(
|
|
shadowNode.getFamily(), *layoutableAncestorShadowNode, policy);
|
|
}
|
|
|
|
void UIManager::updateState(StateUpdate const &stateUpdate) const {
|
|
auto &callback = stateUpdate.callback;
|
|
auto &family = stateUpdate.family;
|
|
auto &componentDescriptor = family->getComponentDescriptor();
|
|
|
|
shadowTreeRegistry_.visit(
|
|
family->getSurfaceId(), [&](ShadowTree const &shadowTree) {
|
|
shadowTree.commit([&](RootShadowNode const &oldRootShadowNode) {
|
|
auto isValid = true;
|
|
|
|
auto rootNode = oldRootShadowNode.cloneTree(
|
|
*family, [&](ShadowNode const &oldShadowNode) {
|
|
auto newData =
|
|
callback(oldShadowNode.getState()->getDataPointer());
|
|
|
|
if (!newData) {
|
|
isValid = false;
|
|
// Just return something, we will discard it anyway.
|
|
return oldShadowNode.clone({});
|
|
}
|
|
|
|
auto newState =
|
|
componentDescriptor.createState(*family, newData);
|
|
|
|
return oldShadowNode.clone({
|
|
/* .props = */ ShadowNodeFragment::propsPlaceholder(),
|
|
/* .children = */
|
|
ShadowNodeFragment::childrenPlaceholder(),
|
|
/* .state = */ newState,
|
|
});
|
|
});
|
|
|
|
return isValid ? std::static_pointer_cast<RootShadowNode>(rootNode)
|
|
: nullptr;
|
|
});
|
|
});
|
|
}
|
|
|
|
void UIManager::dispatchCommand(
|
|
const ShadowNode::Shared &shadowNode,
|
|
std::string const &commandName,
|
|
folly::dynamic const &args) const {
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidDispatchCommand(shadowNode, commandName, args);
|
|
}
|
|
}
|
|
|
|
void UIManager::sendAccessibilityEvent(
|
|
const ShadowNode::Shared &shadowNode,
|
|
std::string const &eventType) {
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidSendAccessibilityEvent(shadowNode, eventType);
|
|
}
|
|
}
|
|
|
|
void UIManager::configureNextLayoutAnimation(
|
|
jsi::Runtime &runtime,
|
|
RawValue const &config,
|
|
jsi::Value const &successCallback,
|
|
jsi::Value const &failureCallback) const {
|
|
if (animationDelegate_) {
|
|
animationDelegate_->uiManagerDidConfigureNextLayoutAnimation(
|
|
runtime,
|
|
config,
|
|
std::move(successCallback),
|
|
std::move(failureCallback));
|
|
}
|
|
}
|
|
|
|
void UIManager::setComponentDescriptorRegistry(
|
|
const SharedComponentDescriptorRegistry &componentDescriptorRegistry) {
|
|
componentDescriptorRegistry_ = componentDescriptorRegistry;
|
|
}
|
|
|
|
void UIManager::setDelegate(UIManagerDelegate *delegate) {
|
|
delegate_ = delegate;
|
|
}
|
|
|
|
UIManagerDelegate *UIManager::getDelegate() {
|
|
return delegate_;
|
|
}
|
|
|
|
void UIManager::visitBinding(
|
|
std::function<void(UIManagerBinding const &uiManagerBinding)> const
|
|
&callback,
|
|
jsi::Runtime &runtime) const {
|
|
auto uiManagerBinding = UIManagerBinding::getBinding(runtime);
|
|
if (uiManagerBinding) {
|
|
callback(*uiManagerBinding);
|
|
}
|
|
}
|
|
|
|
ShadowTreeRegistry const &UIManager::getShadowTreeRegistry() const {
|
|
return shadowTreeRegistry_;
|
|
}
|
|
|
|
void UIManager::registerCommitHook(
|
|
UIManagerCommitHook const &commitHook) const {
|
|
std::unique_lock<butter::shared_mutex> lock(commitHookMutex_);
|
|
react_native_assert(
|
|
std::find(commitHooks_.begin(), commitHooks_.end(), &commitHook) ==
|
|
commitHooks_.end());
|
|
commitHook.commitHookWasRegistered(*this);
|
|
commitHooks_.push_back(&commitHook);
|
|
}
|
|
|
|
void UIManager::unregisterCommitHook(
|
|
UIManagerCommitHook const &commitHook) const {
|
|
std::unique_lock<butter::shared_mutex> lock(commitHookMutex_);
|
|
auto iterator =
|
|
std::find(commitHooks_.begin(), commitHooks_.end(), &commitHook);
|
|
react_native_assert(iterator != commitHooks_.end());
|
|
commitHooks_.erase(iterator);
|
|
commitHook.commitHookWasUnregistered(*this);
|
|
}
|
|
|
|
#pragma mark - ShadowTreeDelegate
|
|
|
|
RootShadowNode::Unshared UIManager::shadowTreeWillCommit(
|
|
ShadowTree const &shadowTree,
|
|
RootShadowNode::Shared const &oldRootShadowNode,
|
|
RootShadowNode::Unshared const &newRootShadowNode) const {
|
|
std::shared_lock<butter::shared_mutex> lock(commitHookMutex_);
|
|
|
|
auto resultRootShadowNode = newRootShadowNode;
|
|
for (auto const *commitHook : commitHooks_) {
|
|
resultRootShadowNode = commitHook->shadowTreeWillCommit(
|
|
shadowTree, oldRootShadowNode, resultRootShadowNode);
|
|
}
|
|
|
|
return resultRootShadowNode;
|
|
}
|
|
|
|
void UIManager::shadowTreeDidFinishTransaction(
|
|
ShadowTree const &shadowTree,
|
|
MountingCoordinator::Shared const &mountingCoordinator) const {
|
|
SystraceSection s("UIManager::shadowTreeDidFinishTransaction");
|
|
|
|
if (delegate_) {
|
|
delegate_->uiManagerDidFinishTransaction(mountingCoordinator);
|
|
}
|
|
}
|
|
|
|
#pragma mark - UIManagerAnimationDelegate
|
|
|
|
void UIManager::setAnimationDelegate(UIManagerAnimationDelegate *delegate) {
|
|
animationDelegate_ = delegate;
|
|
}
|
|
|
|
void UIManager::stopSurfaceForAnimationDelegate(SurfaceId surfaceId) const {
|
|
if (animationDelegate_ != nullptr) {
|
|
animationDelegate_->stopSurface(surfaceId);
|
|
}
|
|
}
|
|
|
|
void UIManager::animationTick() const {
|
|
if (animationDelegate_ != nullptr &&
|
|
animationDelegate_->shouldAnimateFrame()) {
|
|
shadowTreeRegistry_.enumerate([](ShadowTree const &shadowTree) {
|
|
shadowTree.notifyDelegatesOfUpdates();
|
|
});
|
|
}
|
|
}
|
|
|
|
} // namespace facebook::react
|