Implement mount hooks in UIManager (#37460)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37460 ## Context This implements the concept of mount hooks in UIManager which, similarly to commit hooks, receive a notification when a root shadow tree has been mounted in the host platform. This is meant to be used internally in React Native, not by user-land libraries or products. This will be used to implement `IntersectionObserver` in a following diff. Changelog: [Internal] Reviewed By: javache, sammy-SC Differential Revision: D45866244 fbshipit-source-id: 4df48bf237a5cc89e37709faaeaa0ce582c0d0cc
This commit is contained in:
Родитель
f30716323a
Коммит
32bd60f863
|
@ -67,6 +67,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)animationTick;
|
||||
|
||||
- (void)reportMount:(facebook::react::SurfaceId)surfaceId;
|
||||
|
||||
- (void)addEventListener:(std::shared_ptr<facebook::react::EventListener> const &)listener;
|
||||
|
||||
- (void)removeEventListener:(std::shared_ptr<facebook::react::EventListener> const &)listener;
|
||||
|
|
|
@ -132,6 +132,11 @@ class LayoutAnimationDelegateProxy : public LayoutAnimationStatusDelegate, publi
|
|||
_scheduler->animationTick();
|
||||
}
|
||||
|
||||
- (void)reportMount:(facebook::react::SurfaceId)surfaceId
|
||||
{
|
||||
_scheduler->reportMount(surfaceId);
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_animationDriver) {
|
||||
|
|
|
@ -280,6 +280,10 @@ static BackgroundExecutor RCTGetBackgroundExecutor()
|
|||
CoreFeatures::enableGranularScrollViewStateUpdatesIOS = true;
|
||||
}
|
||||
|
||||
if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:enable_mount_hooks_ios")) {
|
||||
CoreFeatures::enableMountHooks = true;
|
||||
}
|
||||
|
||||
auto componentRegistryFactory =
|
||||
[factory = wrapManagedObject(_mountingManager.componentViewRegistry.componentViewFactory)](
|
||||
EventDispatcher::Weak const &eventDispatcher, ContextContainer::Shared const &contextContainer) {
|
||||
|
@ -442,6 +446,15 @@ static BackgroundExecutor RCTGetBackgroundExecutor()
|
|||
[observer didMountComponentsWithRootTag:rootTag];
|
||||
}
|
||||
}
|
||||
|
||||
RCTScheduler *scheduler = [self scheduler];
|
||||
if (scheduler) {
|
||||
// Notify mount when the effects are visible and prevent mount hooks to
|
||||
// delay paint.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[scheduler reportMount:rootTag];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<id<RCTSurfacePresenterObserver>> *)_getObservers
|
||||
|
|
|
@ -156,4 +156,7 @@ public class ReactFeatureFlags {
|
|||
* HostObject pattern
|
||||
*/
|
||||
public static boolean useNativeState = false;
|
||||
|
||||
/** Report mount operations from the host platform to notify mount hooks. */
|
||||
public static boolean enableMountHooks = false;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,8 @@ public interface Binding {
|
|||
|
||||
public void driveCxxAnimations();
|
||||
|
||||
public void reportMount(int surfaceId);
|
||||
|
||||
public ReadableNativeMap getInspectorDataForInstance(EventEmitterWrapper eventEmitterWrapper);
|
||||
|
||||
public void register(
|
||||
|
|
|
@ -86,6 +86,8 @@ public class BindingImpl implements Binding {
|
|||
|
||||
public native void driveCxxAnimations();
|
||||
|
||||
public native void reportMount(int surfaceId);
|
||||
|
||||
public native ReadableNativeMap getInspectorDataForInstance(
|
||||
EventEmitterWrapper eventEmitterWrapper);
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;
|
|||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
@ -81,10 +83,13 @@ import com.facebook.react.uimanager.events.EventDispatcherImpl;
|
|||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.facebook.react.views.text.TextLayoutManager;
|
||||
import com.facebook.react.views.text.TextLayoutManagerMapBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* We instruct ProGuard not to strip out any fields or methods, because many of these methods are
|
||||
|
@ -166,6 +171,9 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
|||
@NonNull
|
||||
private final CopyOnWriteArrayList<UIManagerListener> mListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
@NonNull private final AtomicBoolean mMountNotificationScheduled = new AtomicBoolean(false);
|
||||
@NonNull private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@ThreadConfined(UI)
|
||||
@NonNull
|
||||
private final DispatchUIFrameCallback mDispatchUIFrameCallback;
|
||||
|
@ -1179,17 +1187,50 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
|
|||
|
||||
private class MountItemDispatchListener implements MountItemDispatcher.ItemDispatchListener {
|
||||
@Override
|
||||
public void willMountItems() {
|
||||
public void willMountItems(@Nullable List<MountItem> mountItems) {
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.willMountItems(FabricUIManager.this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didMountItems() {
|
||||
public void didMountItems(@Nullable List<MountItem> mountItems) {
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.didMountItems(FabricUIManager.this);
|
||||
}
|
||||
|
||||
if (!ReactFeatureFlags.enableMountHooks) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean mountNotificationScheduled = mMountNotificationScheduled.getAndSet(true);
|
||||
if (!mountNotificationScheduled) {
|
||||
// Notify mount when the effects are visible and prevent mount hooks to
|
||||
// delay paint.
|
||||
mMainThreadHandler.postAtFrontOfQueue(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mMountNotificationScheduled.set(false);
|
||||
|
||||
if (mountItems == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect surface IDs for all the mount items
|
||||
List<Integer> surfaceIds = new ArrayList();
|
||||
for (MountItem mountItem : mountItems) {
|
||||
if (!surfaceIds.contains(mountItem.getSurfaceId())) {
|
||||
surfaceIds.add(mountItem.getSurfaceId());
|
||||
}
|
||||
}
|
||||
|
||||
for (int surfaceId : surfaceIds) {
|
||||
mBinding.reportMount(surfaceId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -192,7 +192,7 @@ public class MountItemDispatcher {
|
|||
return false;
|
||||
}
|
||||
|
||||
mItemDispatchListener.willMountItems();
|
||||
mItemDispatchListener.willMountItems(mountItemsToDispatch);
|
||||
|
||||
// As an optimization, execute all ViewCommands first
|
||||
// This should be:
|
||||
|
@ -301,7 +301,7 @@ public class MountItemDispatcher {
|
|||
mBatchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime;
|
||||
}
|
||||
|
||||
mItemDispatchListener.didMountItems();
|
||||
mItemDispatchListener.didMountItems(mountItemsToDispatch);
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
|
||||
|
@ -419,9 +419,9 @@ public class MountItemDispatcher {
|
|||
}
|
||||
|
||||
public interface ItemDispatchListener {
|
||||
void willMountItems();
|
||||
void willMountItems(List<MountItem> mountItems);
|
||||
|
||||
void didMountItems();
|
||||
void didMountItems(List<MountItem> mountItems);
|
||||
|
||||
void didDispatchMountItems();
|
||||
}
|
||||
|
|
|
@ -97,6 +97,15 @@ void Binding::driveCxxAnimations() {
|
|||
scheduler_->animationTick();
|
||||
}
|
||||
|
||||
void Binding::reportMount(SurfaceId surfaceId) {
|
||||
const auto &scheduler = getScheduler();
|
||||
if (!scheduler) {
|
||||
LOG(ERROR) << "Binding::reportMount: scheduler disappeared";
|
||||
return;
|
||||
}
|
||||
scheduler->reportMount(surfaceId);
|
||||
}
|
||||
|
||||
#pragma mark - Surface management
|
||||
|
||||
void Binding::startSurface(
|
||||
|
@ -570,6 +579,7 @@ void Binding::registerNatives() {
|
|||
makeNativeMethod("setConstraints", Binding::setConstraints),
|
||||
makeNativeMethod("setPixelDensity", Binding::setPixelDensity),
|
||||
makeNativeMethod("driveCxxAnimations", Binding::driveCxxAnimations),
|
||||
makeNativeMethod("reportMount", Binding::reportMount),
|
||||
makeNativeMethod(
|
||||
"uninstallFabricUIManager", Binding::uninstallFabricUIManager),
|
||||
makeNativeMethod("registerSurface", Binding::registerSurface),
|
||||
|
|
|
@ -119,6 +119,7 @@ class Binding : public jni::HybridClass<Binding>,
|
|||
void setPixelDensity(float pointScaleFactor);
|
||||
|
||||
void driveCxxAnimations();
|
||||
void reportMount(SurfaceId surfaceId);
|
||||
|
||||
void uninstallFabricUIManager();
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ Pod::Spec.new do |s|
|
|||
s.dependency "React-debug"
|
||||
s.dependency "React-utils"
|
||||
s.dependency "React-runtimescheduler"
|
||||
s.dependency "React-cxxreact"
|
||||
|
||||
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
|
||||
s.dependency "hermes-engine"
|
||||
|
|
|
@ -17,5 +17,6 @@ bool CoreFeatures::cacheLastTextMeasurement = false;
|
|||
bool CoreFeatures::cancelImageDownloadsOnRecycle = false;
|
||||
bool CoreFeatures::disableTransactionCommit = false;
|
||||
bool CoreFeatures::enableGranularScrollViewStateUpdatesIOS = false;
|
||||
bool CoreFeatures::enableMountHooks = false;
|
||||
|
||||
} // namespace facebook::react
|
||||
|
|
|
@ -52,6 +52,9 @@ class CoreFeatures {
|
|||
// When enabled, RCTScrollViewComponentView will trigger ShadowTree state
|
||||
// updates for all changes in scroll position.
|
||||
static bool enableGranularScrollViewStateUpdatesIOS;
|
||||
|
||||
// Report mount operations from the host platform to notify mount hooks.
|
||||
static bool enableMountHooks;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
|
|
|
@ -179,6 +179,10 @@ TelemetryController const &MountingCoordinator::getTelemetryController() const {
|
|||
return telemetryController_;
|
||||
}
|
||||
|
||||
ShadowTreeRevision const &MountingCoordinator::getBaseRevision() const {
|
||||
return baseRevision_;
|
||||
}
|
||||
|
||||
void MountingCoordinator::setMountingOverrideDelegate(
|
||||
std::weak_ptr<MountingOverrideDelegate const> delegate) const {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
|
|
@ -71,6 +71,8 @@ class MountingCoordinator final {
|
|||
|
||||
TelemetryController const &getTelemetryController() const;
|
||||
|
||||
ShadowTreeRevision const &getBaseRevision() const;
|
||||
|
||||
/*
|
||||
* Methods from this section are meant to be used by
|
||||
* `MountingOverrideDelegate` only.
|
||||
|
|
|
@ -377,6 +377,10 @@ void Scheduler::uiManagerDidSetIsJSResponder(
|
|||
}
|
||||
}
|
||||
|
||||
void Scheduler::reportMount(SurfaceId surfaceId) const {
|
||||
uiManager_->reportMount(surfaceId);
|
||||
}
|
||||
|
||||
ContextContainer::Shared Scheduler::getContextContainer() const {
|
||||
return contextContainer_;
|
||||
}
|
||||
|
|
|
@ -108,6 +108,8 @@ class Scheduler final : public UIManagerDelegate {
|
|||
#pragma mark - UIManager
|
||||
std::shared_ptr<UIManager> getUIManager() const;
|
||||
|
||||
void reportMount(SurfaceId surfaceId) const;
|
||||
|
||||
#pragma mark - Event listeners
|
||||
void addEventListener(const std::shared_ptr<EventListener const> &listener);
|
||||
void removeEventListener(
|
||||
|
|
|
@ -33,6 +33,7 @@ target_link_libraries(react_render_uimanager
|
|||
react_render_runtimescheduler
|
||||
react_render_mounting
|
||||
react_config
|
||||
reactnative
|
||||
rrc_root
|
||||
rrc_view
|
||||
runtimeexecutor
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "UIManager.h"
|
||||
|
||||
#include <cxxreact/JSExecutor.h>
|
||||
#include <react/debug/react_native_assert.h>
|
||||
#include <react/renderer/core/DynamicPropsUtilities.h>
|
||||
#include <react/renderer/core/PropsParserContext.h>
|
||||
|
@ -16,6 +17,7 @@
|
|||
#include <react/renderer/uimanager/SurfaceRegistryBinding.h>
|
||||
#include <react/renderer/uimanager/UIManagerBinding.h>
|
||||
#include <react/renderer/uimanager/UIManagerCommitHook.h>
|
||||
#include <react/renderer/uimanager/UIManagerMountHook.h>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
|
@ -612,6 +614,21 @@ void UIManager::unregisterCommitHook(
|
|||
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(
|
||||
|
@ -640,6 +657,28 @@ void UIManager::shadowTreeDidFinishTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
void UIManager::reportMount(SurfaceId surfaceId) const {
|
||||
auto time = JSExecutor::performanceNow();
|
||||
|
||||
auto rootShadowNode = RootShadowNode::Shared{};
|
||||
shadowTreeRegistry_.visit(surfaceId, [&](ShadowTree const &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) {
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace facebook::react {
|
|||
|
||||
class UIManagerBinding;
|
||||
class UIManagerCommitHook;
|
||||
class UIManagerMountHook;
|
||||
|
||||
class UIManager final : public ShadowTreeDelegate {
|
||||
public:
|
||||
|
@ -82,6 +83,12 @@ class UIManager final : public ShadowTreeDelegate {
|
|||
void registerCommitHook(UIManagerCommitHook const &commitHook) const;
|
||||
void unregisterCommitHook(UIManagerCommitHook const &commitHook) const;
|
||||
|
||||
/*
|
||||
* Registers and unregisters a mount hook.
|
||||
*/
|
||||
void registerMountHook(UIManagerMountHook &mountHook);
|
||||
void unregisterMountHook(UIManagerMountHook &mountHook);
|
||||
|
||||
ShadowNode::Shared getNewestCloneOfShadowNode(
|
||||
ShadowNode const &shadowNode) const;
|
||||
|
||||
|
@ -191,6 +198,8 @@ class UIManager final : public ShadowTreeDelegate {
|
|||
|
||||
ShadowTreeRegistry const &getShadowTreeRegistry() const;
|
||||
|
||||
void reportMount(SurfaceId surfaceId) const;
|
||||
|
||||
private:
|
||||
friend class UIManagerBinding;
|
||||
friend class Scheduler;
|
||||
|
@ -217,6 +226,9 @@ class UIManager final : public ShadowTreeDelegate {
|
|||
mutable std::shared_mutex commitHookMutex_;
|
||||
mutable std::vector<UIManagerCommitHook const *> commitHooks_;
|
||||
|
||||
mutable std::shared_mutex mountHookMutex_;
|
||||
mutable std::vector<UIManagerMountHook *> mountHooks_;
|
||||
|
||||
std::unique_ptr<LeakChecker> leakChecker_;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <react/renderer/components/root/RootShadowNode.h>
|
||||
#include "UIManager.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class ShadowTree;
|
||||
class UIManager;
|
||||
|
||||
/*
|
||||
* Implementing a mount hook allows to observe Shadow Trees being mounted in
|
||||
* the host platform.
|
||||
*/
|
||||
class UIManagerMountHook {
|
||||
public:
|
||||
/*
|
||||
* Called right after a `ShadowTree` is mounted in the host platform.
|
||||
*/
|
||||
virtual void shadowTreeDidMount(
|
||||
RootShadowNode::Shared const &rootShadowNode,
|
||||
double mountTime) noexcept = 0;
|
||||
|
||||
virtual ~UIManagerMountHook() noexcept = default;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
Загрузка…
Ссылка в новой задаче