Skip to content

Commit

Permalink
Dispatch notifications from MutationObserver as microtasks if availab…
Browse files Browse the repository at this point in the history
…le (facebook#43664)

Summary:
Pull Request resolved: facebook#43664

Changelog: [internal]

Now that we have the event loop, we can modify the implementation of `MutationObserver` (which is still not enabled by default) to dispatch the notifications as microtasks, making the API more spec-compliant.

Reviewed By: javache

Differential Revision: D55380178
  • Loading branch information
rubennorte authored and facebook-github-bot committed Mar 28, 2024
1 parent 8fca6df commit b02fe10
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#include "NativeMutationObserver.h"
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/renderer/core/ShadowNode.h>
#include <react/renderer/debug/SystraceSection.h>
#include <react/renderer/uimanager/UIManagerBinding.h>
Expand Down Expand Up @@ -53,8 +54,8 @@ void NativeMutationObserver::unobserve(

void NativeMutationObserver::connect(
jsi::Runtime& runtime,
AsyncCallback<> notifyMutationObservers,
jsi::Function getPublicInstanceFromInstanceHandle) {
jsi::Function notifyMutationObservers,
SyncCallback<jsi::Value(jsi::Value)> getPublicInstanceFromInstanceHandle) {
auto& uiManager = getUIManagerFromRuntime(runtime);

// MutationObserver is not compatible with background executor.
Expand All @@ -68,25 +69,10 @@ void NativeMutationObserver::connect(
"MutationObserver: could not start observation because MutationObserver is incompatible with UIManager using background executor.");
}

getPublicInstanceFromInstanceHandle_ =
jsi::Value(runtime, getPublicInstanceFromInstanceHandle);

// This should always be called from the JS thread, as it's unsafe to call
// into JS otherwise (via `getPublicInstanceFromInstanceHandle`).
getPublicInstanceFromShadowNode_ = [&](const ShadowNode& shadowNode) {
auto instanceHandle = shadowNode.getInstanceHandle(runtime);
if (!instanceHandle.isObject() ||
!getPublicInstanceFromInstanceHandle_.isObject() ||
!getPublicInstanceFromInstanceHandle_.asObject(runtime).isFunction(
runtime)) {
return jsi::Value::null();
}
return getPublicInstanceFromInstanceHandle_.asObject(runtime)
.asFunction(runtime)
.call(runtime, instanceHandle);
};

notifyMutationObservers_ = std::move(notifyMutationObservers);
runtime_ = &runtime;
notifyMutationObservers_.emplace(std::move(notifyMutationObservers));
getPublicInstanceFromInstanceHandle_.emplace(
std::move(getPublicInstanceFromInstanceHandle));

auto onMutationsCallback = [&](std::vector<MutationRecord>& records) {
return onMutations(records);
Expand All @@ -98,9 +84,9 @@ void NativeMutationObserver::connect(
void NativeMutationObserver::disconnect(jsi::Runtime& runtime) {
auto& uiManager = getUIManagerFromRuntime(runtime);
mutationObserverManager_.disconnect(uiManager);
getPublicInstanceFromInstanceHandle_ = jsi::Value::undefined();
getPublicInstanceFromShadowNode_ = nullptr;
notifyMutationObservers_ = nullptr;
runtime_ = nullptr;
notifyMutationObservers_.reset();
getPublicInstanceFromInstanceHandle_.reset();
}

std::vector<NativeMutationRecord> NativeMutationObserver::takeRecords(
Expand All @@ -112,14 +98,24 @@ std::vector<NativeMutationRecord> NativeMutationObserver::takeRecords(
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));
publicInstances.push_back(getPublicInstanceFromShadowNode(*shadowNode));
}

return publicInstances;
Expand All @@ -133,7 +129,7 @@ void NativeMutationObserver::onMutations(std::vector<MutationRecord>& records) {
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),
getPublicInstanceFromShadowNode(*record.targetShadowNode),
getPublicInstancesFromShadowNodes(record.addedShadowNodes),
getPublicInstancesFromShadowNodes(record.removedShadowNodes)});
}
Expand All @@ -157,7 +153,17 @@ void NativeMutationObserver::notifyMutationObserversIfNecessary() {

if (dispatchNotification) {
SystraceSection s("NativeMutationObserver::notifyObservers");
notifyMutationObservers_();
if (ReactNativeFeatureFlags::enableMicrotasks()) {
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);
}
});
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
#include <react/renderer/observers/mutation/MutationObserverManager.h>
#include <react/renderer/uimanager/UIManager.h>
#include <functional>
#include <vector>

namespace facebook::react {
Expand Down Expand Up @@ -60,8 +59,8 @@ class NativeMutationObserver

void connect(
jsi::Runtime& runtime,
AsyncCallback<> notifyMutationObservers,
jsi::Function getPublicInstanceFromInstanceHandle);
jsi::Function notifyMutationObservers,
SyncCallback<jsi::Value(jsi::Value)> getPublicInstanceFromInstanceHandle);

void disconnect(jsi::Runtime& runtime);

Expand All @@ -72,17 +71,22 @@ class NativeMutationObserver

std::vector<NativeMutationRecord> pendingRecords_;

// This is passed to `connect` so we can retain references to public instances
// when mutation occur, before React cleans up unmounted instances.
jsi::Value getPublicInstanceFromInstanceHandle_ = jsi::Value::undefined();
std::function<jsi::Value(const ShadowNode&)> getPublicInstanceFromShadowNode_;
// We need to keep a reference to the JS runtime so we can schedule the
// notifications as microtasks when mutations occur. This is safe because
// mutations will only happen when executing JavaScript and because this
// native module will never survive the runtime.
jsi::Runtime* runtime_{};

bool notifiedMutationObservers_{};
std::function<void()> notifyMutationObservers_;
std::optional<jsi::Function> notifyMutationObservers_;
std::optional<SyncCallback<jsi::Value(jsi::Value)>>
getPublicInstanceFromInstanceHandle_;

void onMutations(std::vector<MutationRecord>& records);
void notifyMutationObserversIfNecessary();

jsi::Value getPublicInstanceFromShadowNode(
const ShadowNode& shadowNode) const;
std::vector<jsi::Value> getPublicInstancesFromShadowNodes(
const std::vector<ShadowNode::Shared>& shadowNodes) const;
};
Expand Down

0 comments on commit b02fe10

Please sign in to comment.