161 lines
5.4 KiB
C++
161 lines
5.4 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 "NativeMutationObserver.h"
|
||
|
#include <cxxreact/SystraceSection.h>
|
||
|
#include <react/featureflags/ReactNativeFeatureFlags.h>
|
||
|
#include <react/renderer/core/ShadowNode.h>
|
||
|
#include <react/renderer/uimanager/UIManagerBinding.h>
|
||
|
#include <react/renderer/uimanager/primitives.h>
|
||
|
|
||
|
#include "Plugins.h"
|
||
|
|
||
|
std::shared_ptr<facebook::react::TurboModule>
|
||
|
NativeMutationObserverModuleProvider(
|
||
|
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
|
||
|
return std::make_shared<facebook::react::NativeMutationObserver>(
|
||
|
std::move(jsInvoker));
|
||
|
}
|
||
|
|
||
|
namespace facebook::react {
|
||
|
|
||
|
static UIManager& getUIManagerFromRuntime(jsi::Runtime& runtime) {
|
||
|
return UIManagerBinding::getBinding(runtime)->getUIManager();
|
||
|
}
|
||
|
|
||
|
NativeMutationObserver::NativeMutationObserver(
|
||
|
std::shared_ptr<CallInvoker> jsInvoker)
|
||
|
: NativeMutationObserverCxxSpec(std::move(jsInvoker)) {}
|
||
|
|
||
|
void NativeMutationObserver::observe(
|
||
|
jsi::Runtime& runtime,
|
||
|
NativeMutationObserverObserveOptions options) {
|
||
|
auto mutationObserverId = options.mutationObserverId;
|
||
|
auto subtree = options.subtree;
|
||
|
auto shadowNode =
|
||
|
shadowNodeFromValue(runtime, std::move(options).targetShadowNode);
|
||
|
auto& uiManager = getUIManagerFromRuntime(runtime);
|
||
|
|
||
|
mutationObserverManager_.observe(
|
||
|
mutationObserverId, shadowNode, subtree, uiManager);
|
||
|
}
|
||
|
|
||
|
void NativeMutationObserver::unobserve(
|
||
|
jsi::Runtime& runtime,
|
||
|
MutationObserverId mutationObserverId,
|
||
|
jsi::Object targetShadowNode) {
|
||
|
auto shadowNode = shadowNodeFromValue(runtime, std::move(targetShadowNode));
|
||
|
mutationObserverManager_.unobserve(mutationObserverId, *shadowNode);
|
||
|
}
|
||
|
|
||
|
void NativeMutationObserver::connect(
|
||
|
jsi::Runtime& runtime,
|
||
|
jsi::Function notifyMutationObservers,
|
||
|
SyncCallback<jsi::Value(jsi::Value)> getPublicInstanceFromInstanceHandle) {
|
||
|
auto& uiManager = getUIManagerFromRuntime(runtime);
|
||
|
|
||
|
runtime_ = &runtime;
|
||
|
notifyMutationObservers_.emplace(std::move(notifyMutationObservers));
|
||
|
getPublicInstanceFromInstanceHandle_.emplace(
|
||
|
std::move(getPublicInstanceFromInstanceHandle));
|
||
|
|
||
|
auto onMutationsCallback = [&](std::vector<MutationRecord>& records) {
|
||
|
return onMutations(records);
|
||
|
};
|
||
|
|
||
|
mutationObserverManager_.connect(uiManager, std::move(onMutationsCallback));
|
||
|
}
|
||
|
|
||
|
void NativeMutationObserver::disconnect(jsi::Runtime& runtime) {
|
||
|
auto& uiManager = getUIManagerFromRuntime(runtime);
|
||
|
mutationObserverManager_.disconnect(uiManager);
|
||
|
runtime_ = nullptr;
|
||
|
notifyMutationObservers_.reset();
|
||
|
getPublicInstanceFromInstanceHandle_.reset();
|
||
|
}
|
||
|
|
||
|
std::vector<NativeMutationRecord> NativeMutationObserver::takeRecords(
|
||
|
jsi::Runtime& /*runtime*/) {
|
||
|
notifiedMutationObservers_ = false;
|
||
|
|
||
|
std::vector<NativeMutationRecord> records;
|
||
|
pendingRecords_.swap(records);
|
||
|
return records;
|
||
|
}
|
||
|
|
||
|
jsi::Value NativeMutationObserver::getPublicInstanceFromShadowNode(
|
||
|
const ShadowNode& shadowNode) const {
|
||
|
auto instanceHandle = shadowNode.getInstanceHandle(*runtime_);
|
||
|
if (!instanceHandle.isObject()) {
|
||
|
return jsi::Value::null();
|
||
|
}
|
||
|
return getPublicInstanceFromInstanceHandle_.value().call(
|
||
|
std::move(instanceHandle));
|
||
|
}
|
||
|
|
||
|
std::vector<jsi::Value>
|
||
|
NativeMutationObserver::getPublicInstancesFromShadowNodes(
|
||
|
const std::vector<ShadowNode::Shared>& shadowNodes) const {
|
||
|
std::vector<jsi::Value> publicInstances;
|
||
|
publicInstances.reserve(shadowNodes.size());
|
||
|
|
||
|
for (const auto& shadowNode : shadowNodes) {
|
||
|
publicInstances.push_back(getPublicInstanceFromShadowNode(*shadowNode));
|
||
|
}
|
||
|
|
||
|
return publicInstances;
|
||
|
}
|
||
|
|
||
|
void NativeMutationObserver::onMutations(std::vector<MutationRecord>& records) {
|
||
|
SystraceSection s("NativeMutationObserver::onMutations");
|
||
|
|
||
|
for (const auto& record : records) {
|
||
|
pendingRecords_.emplace_back(NativeMutationRecord{
|
||
|
record.mutationObserverId,
|
||
|
// FIXME(T157129303) Instead of assuming we can call into JS from here,
|
||
|
// we should use an API that explicitly indicates it.
|
||
|
getPublicInstanceFromShadowNode(*record.targetShadowNode),
|
||
|
getPublicInstancesFromShadowNodes(record.addedShadowNodes),
|
||
|
getPublicInstancesFromShadowNodes(record.removedShadowNodes)});
|
||
|
}
|
||
|
|
||
|
notifyMutationObserversIfNecessary();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method allows us to avoid scheduling multiple calls to notify observers
|
||
|
* in the JS thread. We schedule one and skip subsequent ones (we just append
|
||
|
* the records to the pending list and wait for the scheduled task to consume
|
||
|
* all of them).
|
||
|
*/
|
||
|
void NativeMutationObserver::notifyMutationObserversIfNecessary() {
|
||
|
bool dispatchNotification = false;
|
||
|
|
||
|
if (!pendingRecords_.empty() && !notifiedMutationObservers_) {
|
||
|
notifiedMutationObservers_ = true;
|
||
|
dispatchNotification = true;
|
||
|
}
|
||
|
|
||
|
if (dispatchNotification) {
|
||
|
SystraceSection s("NativeMutationObserver::notifyObservers");
|
||
|
if (ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
|
||
|
!ReactNativeFeatureFlags::disableEventLoopOnBridgeless()) {
|
||
|
runtime_->queueMicrotask(notifyMutationObservers_.value());
|
||
|
} else {
|
||
|
jsInvoker_->invokeAsync([&](jsi::Runtime& runtime) {
|
||
|
// It's possible that the last observer was disconnected before we could
|
||
|
// dispatch this notification.
|
||
|
if (notifyMutationObservers_) {
|
||
|
notifyMutationObservers_.value().call(runtime);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} // namespace facebook::react
|