/* * 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 "UIManagerBinding.h" #include #include #include #include #include #include #include #include #include #include #include "bindingUtils.h" namespace facebook::react { void UIManagerBinding::createAndInstallIfNeeded( jsi::Runtime& runtime, const std::shared_ptr& uiManager) { auto uiManagerModuleName = "nativeFabricUIManager"; auto uiManagerValue = runtime.global().getProperty(runtime, uiManagerModuleName); if (uiManagerValue.isUndefined()) { // The global namespace does not have an instance of the binding; // we need to create, install and return it. auto uiManagerBinding = std::make_shared(uiManager); auto object = jsi::Object::createFromHostObject(runtime, uiManagerBinding); runtime.global().setProperty( runtime, uiManagerModuleName, std::move(object)); } } std::shared_ptr UIManagerBinding::getBinding( jsi::Runtime& runtime) { auto uiManagerModuleName = "nativeFabricUIManager"; auto uiManagerValue = runtime.global().getProperty(runtime, uiManagerModuleName); if (uiManagerValue.isUndefined()) { return nullptr; } auto uiManagerObject = uiManagerValue.asObject(runtime); return uiManagerObject.getHostObject(runtime); } UIManagerBinding::UIManagerBinding(std::shared_ptr uiManager) : uiManager_(std::move(uiManager)) {} UIManagerBinding::~UIManagerBinding() { LOG(WARNING) << "UIManagerBinding::~UIManagerBinding() was called (address: " << this << ")."; } jsi::Value UIManagerBinding::getInspectorDataForInstance( jsi::Runtime& runtime, const EventEmitter& eventEmitter) const { auto eventTarget = eventEmitter.eventTarget_; EventEmitter::DispatchMutex().lock(); if (!runtime.global().hasProperty(runtime, "__fbBatchedBridge") || !eventTarget) { return jsi::Value::undefined(); } eventTarget->retain(runtime); auto instanceHandle = eventTarget->getInstanceHandle(runtime); eventTarget->release(runtime); EventEmitter::DispatchMutex().unlock(); if (instanceHandle.isUndefined()) { return jsi::Value::undefined(); } return callMethodOfModule( runtime, "ReactFabric", "getInspectorDataForInstance", {std::move(instanceHandle)}); } void UIManagerBinding::dispatchEvent( jsi::Runtime& runtime, const EventTarget* eventTarget, const std::string& type, ReactEventPriority priority, const EventPayload& eventPayload) const { SystraceSection s("UIManagerBinding::dispatchEvent", "type", type); if (eventPayload.getType() == EventPayloadType::PointerEvent) { auto pointerEvent = static_cast(eventPayload); auto dispatchCallback = [this, &runtime]( const ShadowNode& targetNode, const std::string& type, ReactEventPriority priority, const EventPayload& eventPayload) { auto eventTarget = targetNode.getEventEmitter()->getEventTarget(); if (eventTarget != nullptr) { eventTarget->retain(runtime); this->dispatchEventToJS( runtime, eventTarget.get(), type, priority, eventPayload); eventTarget->release(runtime); } }; auto targetNode = PointerEventsProcessor::getShadowNodeFromEventTarget( runtime, eventTarget); if (targetNode != nullptr) { pointerEventsProcessor_.interceptPointerEvent( targetNode, type, priority, pointerEvent, dispatchCallback, *uiManager_); } } else { dispatchEventToJS(runtime, eventTarget, type, priority, eventPayload); } } void UIManagerBinding::dispatchEventToJS( jsi::Runtime& runtime, const EventTarget* eventTarget, const std::string& type, ReactEventPriority priority, const EventPayload& eventPayload) const { auto payload = eventPayload.asJSIValue(runtime); // If a payload is null, the factory has decided to cancel the event if (payload.isNull()) { return; } auto instanceHandle = eventTarget != nullptr ? [&]() { auto instanceHandle = eventTarget->getInstanceHandle(runtime); if (instanceHandle.isUndefined()) { return jsi::Value::null(); } // Mixing `target` into `payload`. if (!payload.isObject()) { LOG(ERROR) << "payload for dispatchEvent is not an object: " << eventTarget->getTag(); } react_native_assert(payload.isObject()); payload.asObject(runtime).setProperty( runtime, "target", eventTarget->getTag()); return instanceHandle; }() : jsi::Value::null(); if (instanceHandle.isNull()) { // Do not log all missing instanceHandles to avoid log spam LOG_EVERY_N(INFO, 10) << "instanceHandle is null, event of type " << type << " will be dropped"; } currentEventPriority_ = priority; if (eventHandler_) { eventHandler_->call( runtime, std::move(instanceHandle), jsi::String::createFromUtf8(runtime, type), std::move(payload)); } currentEventPriority_ = ReactEventPriority::Default; } void UIManagerBinding::invalidate() const { uiManager_->setDelegate(nullptr); } static void validateArgumentCount( jsi::Runtime& runtime, const std::string& methodName, size_t expected, size_t actual) { if (actual < expected) { throw jsi::JSError( runtime, methodName + " requires " + std::to_string(expected) + " arguments, but only " + std::to_string(actual) + " were passed"); } } jsi::Value UIManagerBinding::get( jsi::Runtime& runtime, const jsi::PropNameID& name) { auto methodName = name.utf8(runtime); // Convert shared_ptr to a raw ptr // Why? Because: // 1) UIManagerBinding strongly retains UIManager. The JS VM // strongly retains UIManagerBinding (through the JSI). // These functions are JSI functions and are only called via // the JS VM; if the JS VM is torn down, those functions can't // execute and these lambdas won't execute. // 2) The UIManager is only deallocated when all references to it // are deallocated, including the UIManagerBinding. That only // happens when the JS VM is deallocated. So, the raw pointer // is safe. // // Even if it's safe, why not just use shared_ptr anyway as // extra insurance? // 1) Using shared_ptr or weak_ptr when they're not needed is // a pessimisation. It's more instructions executed without // any additional value in this case. // 2) How and when exactly these lambdas is deallocated is // complex. Adding shared_ptr to them which causes the UIManager // to potentially live longer is unnecessary, complicated cognitive // overhead. // 3) There is a strong suspicion that retaining UIManager from // these C++ lambdas, which are retained by an object that is held onto // by the JSI, caused some crashes upon deallocation of the // Scheduler and JS VM. This could happen if, for instance, C++ // semantics cause these lambda to not be deallocated until // a CPU tick (or more) after the JS VM is deallocated. UIManager* uiManager = uiManager_.get(); // Semantic: Creates a new node with given pieces. if (methodName == "createNode") { auto paramCount = 5; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { try { validateArgumentCount(runtime, methodName, paramCount, count); auto instanceHandle = instanceHandleFromValue(runtime, arguments[4], arguments[0]); if (!instanceHandle) { react_native_assert(false); return jsi::Value::undefined(); } return valueFromShadowNode( runtime, uiManager->createNode( tagFromValue(arguments[0]), stringFromValue(runtime, arguments[1]), surfaceIdFromValue(runtime, arguments[2]), RawProps(runtime, arguments[3]), std::move(instanceHandle)), true); } catch (const std::logic_error& ex) { LOG(FATAL) << "logic_error in createNode: " << ex.what(); } }); } if (methodName == "setIsJSResponder") { auto paramCount = 3; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); uiManager->setIsJSResponder( shadowNodeFromValue(runtime, arguments[0]), arguments[1].getBool(), arguments[2].getBool()); return jsi::Value::undefined(); }); } if (methodName == "findNodeAtPoint") { auto paramCount = 4; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) { validateArgumentCount(runtime, methodName, paramCount, count); auto node = shadowNodeFromValue(runtime, arguments[0]); auto locationX = (Float)arguments[1].getNumber(); auto locationY = (Float)arguments[2].getNumber(); auto onSuccessFunction = arguments[3].getObject(runtime).getFunction(runtime); auto targetNode = uiManager->findNodeAtPoint(node, Point{locationX, locationY}); if (!targetNode) { onSuccessFunction.call(runtime, jsi::Value::null()); return jsi::Value::undefined(); } auto& eventTarget = targetNode->getEventEmitter()->eventTarget_; EventEmitter::DispatchMutex().lock(); eventTarget->retain(runtime); auto instanceHandle = eventTarget->getInstanceHandle(runtime); eventTarget->release(runtime); EventEmitter::DispatchMutex().unlock(); onSuccessFunction.call(runtime, std::move(instanceHandle)); return jsi::Value::undefined(); }); } // Semantic: Clones the node with *same* props and *given* children. if (methodName == "cloneNodeWithNewChildren") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { // TODO: re-enable when passChildrenWhenCloningPersistedNodes is // rolled out // validateArgumentCount(runtime, methodName, paramCount, count); return valueFromShadowNode( runtime, uiManager->cloneNode( *shadowNodeFromValue(runtime, arguments[0]), count > 1 ? shadowNodeListFromValue(runtime, arguments[1]) : ShadowNode::emptySharedShadowNodeSharedList(), RawProps()), true); }); } // Semantic: Clones the node with *given* props and *same* children. if (methodName == "cloneNodeWithNewProps") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); return valueFromShadowNode( runtime, uiManager->cloneNode( *shadowNodeFromValue(runtime, arguments[0]), nullptr, RawProps(runtime, arguments[1])), true); }); } // Semantic: Clones the node with *given* props and *given* children. if (methodName == "cloneNodeWithNewChildrenAndProps") { auto paramCount = 3; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { // TODO: re-enable when passChildrenWhenCloningPersistedNodes is // rolled out // validateArgumentCount(runtime, methodName, paramCount, count); bool hasChildrenArg = count == 3; return valueFromShadowNode( runtime, uiManager->cloneNode( *shadowNodeFromValue(runtime, arguments[0]), hasChildrenArg ? shadowNodeListFromValue(runtime, arguments[1]) : ShadowNode::emptySharedShadowNodeSharedList(), RawProps(runtime, arguments[hasChildrenArg ? 2 : 1])), true); }); } if (methodName == "appendChild") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); uiManager->appendChild( shadowNodeFromValue(runtime, arguments[0]), shadowNodeFromValue(runtime, arguments[1])); return jsi::Value::undefined(); }); } // TODO: remove when passChildrenWhenCloningPersistedNodes is rolled out if (methodName == "createChildSet") { return jsi::Function::createFromHostFunction( runtime, name, 0, [](jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* /*arguments*/, size_t /*count*/) -> jsi::Value { auto shadowNodeList = std::make_shared( ShadowNode::ListOfShared({})); return valueFromShadowNodeList(runtime, shadowNodeList); }); } // TODO: remove when passChildrenWhenCloningPersistedNodes is rolled out if (methodName == "appendChildToSet") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); auto shadowNodeList = shadowNodeListFromValue(runtime, arguments[0]); auto shadowNode = shadowNodeFromValue(runtime, arguments[1]); shadowNodeList->push_back(shadowNode); return jsi::Value::undefined(); }); } if (methodName == "completeRoot") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); auto runtimeSchedulerBinding = RuntimeSchedulerBinding::getBinding(runtime); auto surfaceId = surfaceIdFromValue(runtime, arguments[0]); auto shadowNodeList = shadowNodeListFromValue(runtime, arguments[1]); uiManager->completeSurface( surfaceId, shadowNodeList, {.enableStateReconciliation = true, .mountSynchronously = false}); return jsi::Value::undefined(); }); } if (methodName == "registerEventHandler") { auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [this, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); auto eventHandler = arguments[0].getObject(runtime).getFunction(runtime); eventHandler_ = std::make_unique(std::move(eventHandler)); return jsi::Value::undefined(); }); } if (methodName == "getRelativeLayoutMetrics") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); auto layoutMetrics = uiManager->getRelativeLayoutMetrics( *shadowNodeFromValue(runtime, arguments[0]), shadowNodeFromValue(runtime, arguments[1]).get(), {/* .includeTransform = */ false}); auto frame = layoutMetrics.frame; auto result = jsi::Object(runtime); result.setProperty(runtime, "left", frame.origin.x); result.setProperty(runtime, "top", frame.origin.y); result.setProperty(runtime, "width", frame.size.width); result.setProperty(runtime, "height", frame.size.height); return result; }); } if (methodName == "dispatchCommand") { auto paramCount = 3; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); if (shadowNode) { uiManager->dispatchCommand( shadowNode, stringFromValue(runtime, arguments[1]), commandArgsFromValue(runtime, arguments[2])); } return jsi::Value::undefined(); }); } if (methodName == "setNativeProps") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); uiManager->setNativeProps_DEPRECATED( shadowNodeFromValue(runtime, arguments[0]), RawProps(runtime, arguments[1])); return jsi::Value::undefined(); }); } // Legacy API if (methodName == "measureLayout") { auto paramCount = 4; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) { validateArgumentCount(runtime, methodName, paramCount, count); auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto relativeToShadowNode = shadowNodeFromValue(runtime, arguments[1]); auto onFailFunction = arguments[2].getObject(runtime).getFunction(runtime); auto onSuccessFunction = arguments[3].getObject(runtime).getFunction(runtime); auto currentRevision = uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( shadowNode->getSurfaceId()); if (currentRevision == nullptr) { onFailFunction.call(runtime); return jsi::Value::undefined(); } auto maybeRect = dom::measureLayout( currentRevision, *shadowNode, *relativeToShadowNode); if (!maybeRect) { onFailFunction.call(runtime); return jsi::Value::undefined(); } auto rect = maybeRect.value(); onSuccessFunction.call( runtime, {jsi::Value{runtime, rect.x}, jsi::Value{runtime, rect.y}, jsi::Value{runtime, rect.width}, jsi::Value{runtime, rect.height}}); return jsi::Value::undefined(); }); } if (methodName == "measure") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) { validateArgumentCount(runtime, methodName, paramCount, count); auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto callbackFunction = arguments[1].getObject(runtime).getFunction(runtime); auto currentRevision = uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( shadowNode->getSurfaceId()); if (currentRevision == nullptr) { callbackFunction.call(runtime, {0, 0, 0, 0, 0, 0}); return jsi::Value::undefined(); } auto measureRect = dom::measure(currentRevision, *shadowNode); callbackFunction.call( runtime, {jsi::Value{runtime, measureRect.x}, jsi::Value{runtime, measureRect.y}, jsi::Value{runtime, measureRect.width}, jsi::Value{runtime, measureRect.height}, jsi::Value{runtime, measureRect.pageX}, jsi::Value{runtime, measureRect.pageY}}); return jsi::Value::undefined(); }); } if (methodName == "measureInWindow") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) { validateArgumentCount(runtime, methodName, paramCount, count); auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto callbackFunction = arguments[1].getObject(runtime).getFunction(runtime); auto currentRevision = uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( shadowNode->getSurfaceId()); if (currentRevision == nullptr) { callbackFunction.call(runtime, {0, 0, 0, 0}); return jsi::Value::undefined(); } auto rect = dom::measureInWindow(currentRevision, *shadowNode); callbackFunction.call( runtime, {jsi::Value{runtime, rect.x}, jsi::Value{runtime, rect.y}, jsi::Value{runtime, rect.width}, jsi::Value{runtime, rect.height}}); return jsi::Value::undefined(); }); } if (methodName == "sendAccessibilityEvent") { auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); uiManager->sendAccessibilityEvent( shadowNodeFromValue(runtime, arguments[0]), stringFromValue(runtime, arguments[1])); return jsi::Value::undefined(); }); } if (methodName == "configureNextLayoutAnimation") { auto paramCount = 3; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); uiManager->configureNextLayoutAnimation( runtime, // TODO: pass in JSI value instead of folly::dynamic to RawValue RawValue(commandArgsFromValue(runtime, arguments[0])), arguments[1], arguments[2]); return jsi::Value::undefined(); }); } if (methodName == "unstable_getCurrentEventPriority") { return jsi::Function::createFromHostFunction( runtime, name, 0, [this]( jsi::Runtime& /*runtime*/, const jsi::Value& /*thisValue*/, const jsi::Value* /*arguments*/, size_t /*count*/) -> jsi::Value { return {serialize(currentEventPriority_)}; }); } if (methodName == "unstable_DefaultEventPriority") { return {serialize(ReactEventPriority::Default)}; } if (methodName == "unstable_DiscreteEventPriority") { return {serialize(ReactEventPriority::Discrete)}; } if (methodName == "findShadowNodeByTag_DEPRECATED") { auto paramCount = 1; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value&, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); auto shadowNode = uiManager->findShadowNodeByTag_DEPRECATED( tagFromValue(arguments[0])); if (!shadowNode) { return jsi::Value::null(); } return valueFromShadowNode(runtime, shadowNode); }); } if (methodName == "getBoundingClientRect") { // This has been moved to `NativeDOM` but we need to keep it here because // there are still some callsites using this method in apps that don't have // the DOM APIs enabled yet. auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); bool includeTransform = arguments[1].getBool(); auto currentRevision = uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( shadowNode->getSurfaceId()); if (currentRevision == nullptr) { return jsi::Value::undefined(); } auto domRect = dom::getBoundingClientRect( currentRevision, *shadowNode, includeTransform); return jsi::Array::createWithElements( runtime, jsi::Value{runtime, domRect.x}, jsi::Value{runtime, domRect.y}, jsi::Value{runtime, domRect.width}, jsi::Value{runtime, domRect.height}); }); } if (methodName == "compareDocumentPosition") { // This has been moved to `NativeDOM` but we need to keep it here because // there are still some callsites using this method in apps that don't have // the DOM APIs enabled yet. auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, name, paramCount, [uiManager, methodName, paramCount]( jsi::Runtime& runtime, const jsi::Value& /*thisValue*/, const jsi::Value* arguments, size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto otherShadowNode = shadowNodeFromValue(runtime, arguments[1]); auto currentRevision = uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( shadowNode->getSurfaceId()); double documentPosition = 0; if (currentRevision != nullptr) { documentPosition = (double)dom::compareDocumentPosition( currentRevision, *shadowNode, *otherShadowNode); } return {documentPosition}; }); } return jsi::Value::undefined(); } UIManager& UIManagerBinding::getUIManager() { return *uiManager_; } PointerEventsProcessor& UIManagerBinding::getPointerEventsProcessor() { return pointerEventsProcessor_; } } // namespace facebook::react