/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr int DOCUMENT_POSITION_DISCONNECTED = 1; constexpr int DOCUMENT_POSITION_PRECEDING = 2; constexpr int DOCUMENT_POSITION_FOLLOWING = 4; constexpr int DOCUMENT_POSITION_CONTAINS = 8; constexpr int DOCUMENT_POSITION_CONTAINED_BY = 16; } // namespace 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 constructLeakCheckerIfNeeded( const RuntimeExecutor& runtimeExecutor) { #ifdef REACT_NATIVE_DEBUG return std::make_unique(runtimeExecutor); #else return {}; #endif } UIManager::UIManager( const RuntimeExecutor& 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 << ")."; } ShadowNode::Shared UIManager::createNode( Tag tag, const std::string& name, SurfaceId surfaceId, const RawProps& rawProps, const InstanceHandle::Shared& instanceHandle) const { SystraceSection s("UIManager::createNode", "componentName", name); auto& componentDescriptor = componentDescriptorRegistry_->at(name); auto fallbackDescriptor = componentDescriptorRegistry_->getFallbackComponentDescriptor(); PropsParserContext propsParserContext{surfaceId, *contextContainer_.get()}; const auto fragment = ShadowNodeFamilyFragment{tag, surfaceId, instanceHandle}; auto family = componentDescriptor.createFamily(fragment); const auto props = componentDescriptor.cloneProps(propsParserContext, nullptr, rawProps); const auto state = componentDescriptor.createInitialState(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_ != nullptr) { delegate_->uiManagerDidCreateShadowNode(*shadowNode); } if (leakChecker_) { leakChecker_->uiManagerDidCreateShadowNodeFamily(family); } return shadowNode; } ShadowNode::Shared UIManager::cloneNode( const ShadowNode& shadowNode, const ShadowNode::SharedListOfShared& children, const RawProps* rawProps) const { SystraceSection s( "UIManager::cloneNode", "componentName", shadowNode.getComponentName()); PropsParserContext propsParserContext{ shadowNode.getFamily().getSurfaceId(), *contextContainer_.get()}; auto& componentDescriptor = shadowNode.getComponentDescriptor(); auto& family = shadowNode.getFamily(); auto props = ShadowNodeFragment::propsPlaceholder(); if (rawProps != nullptr) { if (family.nativeProps_DEPRECATED != nullptr) { // Values in `rawProps` patch (take precedence over) // `nativeProps_DEPRECATED`. For example, if both `nativeProps_DEPRECATED` // and `rawProps` contain key 'A'. Value from `rawProps` overrides what // was previously in `nativeProps_DEPRECATED`. family.nativeProps_DEPRECATED = std::make_unique(mergeDynamicProps( *family.nativeProps_DEPRECATED, (folly::dynamic)*rawProps)); props = componentDescriptor.cloneProps( propsParserContext, shadowNode.getProps(), RawProps(*family.nativeProps_DEPRECATED)); } else { props = componentDescriptor.cloneProps( propsParserContext, shadowNode.getProps(), *rawProps); } } auto clonedShadowNode = componentDescriptor.cloneShadowNode( shadowNode, { /* .props = */ props, /* .children = */ children, }); 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, const ShadowNode::UnsharedListOfShared& rootChildren, ShadowTree::CommitOptions commitOptions) const { SystraceSection s("UIManager::completeSurface", "surfaceId", surfaceId); shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) { shadowTree.commit( [&](RootShadowNode const& oldRootShadowNode) { return std::make_shared( oldRootShadowNode, ShadowNodeFragment{ /* .props = */ ShadowNodeFragment::propsPlaceholder(), /* .children = */ rootChildren, }); }, commitOptions); }); } void UIManager::setIsJSResponder( const ShadowNode::Shared& shadowNode, bool isJSResponder, bool blockNativeResponder) const { if (delegate_ != nullptr) { delegate_->uiManagerDidSetIsJSResponder( shadowNode, isJSResponder, blockNativeResponder); } } void UIManager::startSurface( ShadowTree::Unique&& shadowTree, const std::string& moduleName, const folly::dynamic& 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, const std::string& moduleName, const folly::dynamic& 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); if (shadowTree) { // 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( const ShadowNode& shadowNode) const { auto ancestorShadowNode = ShadowNode::Shared{}; shadowTreeRegistry_.visit( shadowNode.getSurfaceId(), [&](const ShadowTree& 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::getNewestParentOfShadowNode( const ShadowNode& shadowNode) const { auto ancestorShadowNode = ShadowNode::Shared{}; shadowTreeRegistry_.visit( shadowNode.getSurfaceId(), [&](const ShadowTree& shadowTree) { ancestorShadowNode = shadowTree.getCurrentRevision().rootShadowNode; }); if (!ancestorShadowNode) { return nullptr; } auto ancestors = shadowNode.getFamily().getAncestors(*ancestorShadowNode); if (ancestors.empty()) { return nullptr; } if (ancestors.size() == 1) { // The parent is the shadow root return ancestorShadowNode; } auto parentOfParentPair = ancestors[ancestors.size() - 2]; return parentOfParentPair.first.get().getChildren().at( parentOfParentPair.second); } std::string UIManager::getTextContentInNewestCloneOfShadowNode( const ShadowNode& shadowNode) const { auto newestCloneOfShadowNode = getNewestCloneOfShadowNode(shadowNode); std::string result; getTextContentInShadowNode(*newestCloneOfShadowNode, result); return result; } int UIManager::compareDocumentPosition( const ShadowNode& shadowNode, const ShadowNode& otherShadowNode) const { // Quick check for node vs. itself if (&shadowNode == &otherShadowNode) { return 0; } if (shadowNode.getSurfaceId() != otherShadowNode.getSurfaceId()) { return DOCUMENT_POSITION_DISCONNECTED; } auto ancestorShadowNode = ShadowNode::Shared{}; shadowTreeRegistry_.visit( shadowNode.getSurfaceId(), [&](const ShadowTree& shadowTree) { ancestorShadowNode = shadowTree.getCurrentRevision().rootShadowNode; }); if (!ancestorShadowNode) { return DOCUMENT_POSITION_DISCONNECTED; } auto ancestors = shadowNode.getFamily().getAncestors(*ancestorShadowNode); if (ancestors.empty()) { return DOCUMENT_POSITION_DISCONNECTED; } auto otherAncestors = otherShadowNode.getFamily().getAncestors(*ancestorShadowNode); if (ancestors.empty()) { return DOCUMENT_POSITION_DISCONNECTED; } // Consume all common ancestors size_t i = 0; while (i < ancestors.size() && i < otherAncestors.size() && ancestors[i].second == otherAncestors[i].second) { i++; } if (i == ancestors.size()) { return (DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING); } if (i == otherAncestors.size()) { return (DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING); } if (ancestors[i].second > otherAncestors[i].second) { return DOCUMENT_POSITION_PRECEDING; } return DOCUMENT_POSITION_FOLLOWING; } ShadowNode::Shared UIManager::findNodeAtPoint( const ShadowNode::Shared& node, Point point) const { return LayoutableShadowNode::findNodeAtPoint( getNewestCloneOfShadowNode(*node), point); } LayoutMetrics UIManager::getRelativeLayoutMetrics( const ShadowNode& shadowNode, const ShadowNode* 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 == nullptr) { shadowTreeRegistry_.visit( shadowNode.getSurfaceId(), [&](const ShadowTree& 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(ancestorShadowNode); if (layoutableAncestorShadowNode == nullptr) { return EmptyLayoutMetrics; } return LayoutableShadowNode::computeRelativeLayoutMetrics( shadowNode.getFamily(), *layoutableAncestorShadowNode, policy); } void UIManager::updateState(const StateUpdate& stateUpdate) const { SystraceSection s( "UIManager::updateState", "componentName", stateUpdate.family->getComponentName()); auto& callback = stateUpdate.callback; auto& family = stateUpdate.family; auto& componentDescriptor = family->getComponentDescriptor(); shadowTreeRegistry_.visit( family->getSurfaceId(), [&](const ShadowTree& 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(rootNode) : nullptr; }, {/* default commit options */}); }); } void UIManager::dispatchCommand( const ShadowNode::Shared& shadowNode, const std::string& commandName, const folly::dynamic& args) const { if (delegate_ != nullptr) { delegate_->uiManagerDidDispatchCommand(shadowNode, commandName, args); } } void UIManager::setNativeProps_DEPRECATED( const ShadowNode::Shared& shadowNode, const RawProps& rawProps) const { auto& family = shadowNode->getFamily(); if (family.nativeProps_DEPRECATED) { // Values in `rawProps` patch (take precedence over) // `nativeProps_DEPRECATED`. For example, if both `nativeProps_DEPRECATED` // and `rawProps` contain key 'A'. Value from `rawProps` overrides what was // previously in `nativeProps_DEPRECATED`. family.nativeProps_DEPRECATED = std::make_unique(mergeDynamicProps( *family.nativeProps_DEPRECATED, (folly::dynamic)rawProps)); } else { family.nativeProps_DEPRECATED = std::make_unique((folly::dynamic)rawProps); } shadowTreeRegistry_.visit( family.getSurfaceId(), [&](const ShadowTree& shadowTree) { shadowTree.commit( [&](RootShadowNode const& oldRootShadowNode) { auto rootNode = oldRootShadowNode.cloneTree( family, [&](ShadowNode const& oldShadowNode) { auto& componentDescriptor = componentDescriptorRegistry_->at( shadowNode->getComponentHandle()); PropsParserContext propsParserContext{ family.getSurfaceId(), *contextContainer_.get()}; auto props = componentDescriptor.cloneProps( propsParserContext, getNewestCloneOfShadowNode(*shadowNode)->getProps(), rawProps); return oldShadowNode.clone({/* .props = */ props}); }); return std::static_pointer_cast(rootNode); }, {/* default commit options */}); }); } void UIManager::sendAccessibilityEvent( const ShadowNode::Shared& shadowNode, const std::string& eventType) { if (delegate_ != nullptr) { delegate_->uiManagerDidSendAccessibilityEvent(shadowNode, eventType); } } void UIManager::configureNextLayoutAnimation( jsi::Runtime& runtime, const RawValue& config, const jsi::Value& successCallback, const jsi::Value& failureCallback) const { if (animationDelegate_ != nullptr) { animationDelegate_->uiManagerDidConfigureNextLayoutAnimation( runtime, config, std::move(successCallback), std::move(failureCallback)); } } static ShadowNode::Shared findShadowNodeByTagRecursively( ShadowNode::Shared parentShadowNode, Tag tag) { if (parentShadowNode->getTag() == tag) { return parentShadowNode; } for (const ShadowNode::Shared& shadowNode : parentShadowNode->getChildren()) { auto result = findShadowNodeByTagRecursively(shadowNode, tag); if (result) { return result; } } return nullptr; } ShadowNode::Shared UIManager::findShadowNodeByTag_DEPRECATED(Tag tag) const { auto shadowNode = ShadowNode::Shared{}; shadowTreeRegistry_.enumerate([&](const ShadowTree& shadowTree, bool& stop) { RootShadowNode const* rootShadowNode; // The public interface of `ShadowTree` discourages accessing a stored // pointer to a root node because of the possible data race. // To work around this, we ask for a commit and immediately cancel it // returning `nullptr` instead of a new shadow tree. // We don't want to add a way to access a stored pointer to a root node // because this `findShadowNodeByTag` is deprecated. It is only added // to make migration to the new architecture easier. shadowTree.tryCommit( [&](RootShadowNode const& oldRootShadowNode) { rootShadowNode = &oldRootShadowNode; return nullptr; }, {/* default commit options */}); if (rootShadowNode != nullptr) { auto const& children = rootShadowNode->getChildren(); if (!children.empty()) { auto const& child = children.front(); shadowNode = findShadowNodeByTagRecursively(child, tag); if (shadowNode) { stop = true; } } } }); return shadowNode; } void UIManager::setComponentDescriptorRegistry( const SharedComponentDescriptorRegistry& componentDescriptorRegistry) { componentDescriptorRegistry_ = componentDescriptorRegistry; } void UIManager::setDelegate(UIManagerDelegate* delegate) { delegate_ = delegate; } UIManagerDelegate* UIManager::getDelegate() { return delegate_; } void UIManager::visitBinding( const std::function& callback, jsi::Runtime& runtime) const { auto uiManagerBinding = UIManagerBinding::getBinding(runtime); if (uiManagerBinding) { callback(*uiManagerBinding); } } const ShadowTreeRegistry& UIManager::getShadowTreeRegistry() const { return shadowTreeRegistry_; } void UIManager::registerCommitHook(UIManagerCommitHook& commitHook) { std::unique_lock 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& commitHook) { std::unique_lock lock(commitHookMutex_); auto iterator = std::find(commitHooks_.begin(), commitHooks_.end(), &commitHook); react_native_assert(iterator != commitHooks_.end()); commitHooks_.erase(iterator); commitHook.commitHookWasUnregistered(*this); } void UIManager::registerMountHook(UIManagerMountHook& mountHook) { std::unique_lock lock(mountHookMutex_); react_native_assert( std::find(mountHooks_.begin(), mountHooks_.end(), &mountHook) == mountHooks_.end()); mountHooks_.push_back(&mountHook); } void UIManager::unregisterMountHook(UIManagerMountHook& mountHook) { std::unique_lock lock(mountHookMutex_); auto iterator = std::find(mountHooks_.begin(), mountHooks_.end(), &mountHook); react_native_assert(iterator != mountHooks_.end()); mountHooks_.erase(iterator); } #pragma mark - ShadowTreeDelegate RootShadowNode::Unshared UIManager::shadowTreeWillCommit( const ShadowTree& shadowTree, const RootShadowNode::Shared& oldRootShadowNode, const RootShadowNode::Unshared& newRootShadowNode) const { SystraceSection s("UIManager::shadowTreeWillCommit"); std::shared_lock lock(commitHookMutex_); auto resultRootShadowNode = newRootShadowNode; for (auto* commitHook : commitHooks_) { resultRootShadowNode = commitHook->shadowTreeWillCommit( shadowTree, oldRootShadowNode, resultRootShadowNode); } return resultRootShadowNode; } void UIManager::shadowTreeDidFinishTransaction( MountingCoordinator::Shared mountingCoordinator, bool mountSynchronously) const { SystraceSection s("UIManager::shadowTreeDidFinishTransaction"); if (delegate_ != nullptr) { delegate_->uiManagerDidFinishTransaction( std::move(mountingCoordinator), mountSynchronously); } } void UIManager::reportMount(SurfaceId surfaceId) const { SystraceSection s("UIManager::reportMount"); auto time = JSExecutor::performanceNow(); auto rootShadowNode = RootShadowNode::Shared{}; shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) { rootShadowNode = shadowTree.getMountingCoordinator()->getBaseRevision().rootShadowNode; }); if (!rootShadowNode) { return; } { std::shared_lock lock(mountHookMutex_); for (auto* mountHook : mountHooks_) { mountHook->shadowTreeDidMount(rootShadowNode, time); } } } #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([](const ShadowTree& shadowTree, bool&) { shadowTree.notifyDelegatesOfUpdates(); }); } } } // namespace facebook::react