Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface Spec {
+appendChild: (parentNode: Node, child: Node) => Node;
+appendChildToSet: (childSet: NodeSet, child: Node) => void;
+completeRoot: (rootTag: RootTag, childSet: NodeSet) => void;
+mountPortalChildren: (targetTag: number, childSet: NodeSet) => void;
+measure: (
node: Node | NativeElementReference,
callback: MeasureOnSuccessCallback,
Expand Down Expand Up @@ -114,6 +115,7 @@ const CACHED_PROPERTIES = [
'appendChild',
'appendChildToSet',
'completeRoot',
'mountPortalChildren',
'measure',
'measureInWindow',
'measureLayout',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ export const getPublicInstanceFromInternalInstanceHandle: ReactFabricType['getPu
export const getPublicInstanceFromRootTag: ReactFabricType['getPublicInstanceFromRootTag'] =
getFabricMethod('getPublicInstanceFromRootTag');

export const createPortal: ReactFabricType['createPortal'] =
getFabricMethod('createPortal');

export function isProfilingRenderer(): boolean {
return Boolean(__DEV__);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9091,10 +9091,11 @@ __DEV__ &&
pushHostContext(workInProgress);
break;
case 4:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo
);
var _containerInfo = workInProgress.stateNode.containerInfo;
if (_containerInfo._isPortal) {
_containerInfo.surfaceId = requiredContext(rootInstanceStackCursor.current).containerTag;
}
pushHostContainer(workInProgress, _containerInfo);
break;
case 10:
pushProvider(
Expand Down Expand Up @@ -9426,11 +9427,15 @@ __DEV__ &&
return null;
case 13:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case 4:
case 4: {
var _containerInfo2 = workInProgress.stateNode.containerInfo;
if (_containerInfo2._isPortal) {
_containerInfo2.surfaceId = requiredContext(rootInstanceStackCursor.current).containerTag;
}
return (
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo
_containerInfo2
),
(returnFiber = workInProgress.pendingProps),
null === current
Expand All @@ -9448,6 +9453,7 @@ __DEV__ &&
),
workInProgress.child
);
}
case 11:
return updateForwardRef(
current,
Expand Down Expand Up @@ -10056,7 +10062,7 @@ __DEV__ &&
node: createNode(
renderLanes,
_type2.uiViewClassName,
current.containerTag,
current.surfaceId || current.containerTag,
keepChildren,
workInProgress
),
Expand Down Expand Up @@ -15902,7 +15908,7 @@ __DEV__ &&
node: createNode(
hostContext,
"RCTRawText",
rootContainerInstance.containerTag,
rootContainerInstance.surfaceId || rootContainerInstance.containerTag,
{ text: text },
internalInstanceHandle
)
Expand Down Expand Up @@ -15961,7 +15967,14 @@ __DEV__ &&
};
}
function replaceContainerChildren(container, newChildren) {
completeRoot(container.containerTag, newChildren);
if (container._isPortal) {
mountPortalChildren(container.containerTag, newChildren);
if (newChildren.length === 0) {
portalContainerCache.delete(container.containerTag);
}
} else {
completeRoot(container.containerTag, newChildren);
}
}
function nativeOnUncaughtError(error, errorInfo) {
!1 !==
Expand Down Expand Up @@ -18701,6 +18714,7 @@ __DEV__ &&
appendChildNode = _nativeFabricUIManage.appendChild,
appendChildNodeToSet = _nativeFabricUIManage.appendChildToSet,
completeRoot = _nativeFabricUIManage.completeRoot,
mountPortalChildren = _nativeFabricUIManage.mountPortalChildren,
registerEventHandler = _nativeFabricUIManage.registerEventHandler,
FabricDiscretePriority =
_nativeFabricUIManage.unstable_DiscreteEventPriority,
Expand Down Expand Up @@ -18924,10 +18938,20 @@ __DEV__ &&
internals.getCurrentFiber = getCurrentFiberForDevTools;
return injectInternals(internals);
})();
var portalContainerCache = new Map();
exports.createPortal = function (children, containerTag) {
var portalContainer = portalContainerCache.get(containerTag);
if (!portalContainer) {
portalContainer = {
containerTag: containerTag,
publicInstance: null,
_isPortal: true
};
portalContainerCache.set(containerTag, portalContainer);
}
return createPortal$1(
children,
containerTag,
portalContainer,
null,
2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : null
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ export type ReactFabricType = {
internalInstanceHandle: InternalInstanceHandle,
): PublicInstance | PublicTextInstance | null,
getPublicInstanceFromRootTag(rootTag: number): PublicRootInstance | null,
createPortal(
children: React.Node,
containerTag: number,
key?: ?string,
): React.MixedElement,
...
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <glog/logging.h>

#include <unordered_set>
#include <utility>

namespace {
Expand Down Expand Up @@ -182,6 +183,29 @@ void UIManager::appendChild(
componentDescriptor.appendChild(parentShadowNode, childShadowNode);
}

static std::shared_ptr<const ShadowNode> findShadowNodeByTagRecursively(
std::shared_ptr<const ShadowNode> parentShadowNode,
Tag tag);

void UIManager::mountPortalChildren(
Tag targetTag,
const ShadowNode::UnsharedListOfShared& portalChildren) {
if (portalChildren->empty()) {
auto it = portalChildren_.find(targetTag);
if (it != portalChildren_.end()) {
std::unordered_set<Tag> tags;
for (const auto& child : *(it->second)) {
tags.insert(child->getTag());
}
pendingPortalRemovals_[targetTag] = std::move(tags);
portalChildren_.erase(it);
}
} else {
portalChildren_[targetTag] = portalChildren;
pendingPortalRemovals_.erase(targetTag);
}
}

void UIManager::completeSurface(
SurfaceId surfaceId,
const ShadowNode::UnsharedListOfShared& rootChildren,
Expand All @@ -191,12 +215,82 @@ void UIManager::completeSurface(
shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) {
auto result = shadowTree.commit(
[&](const RootShadowNode& oldRootShadowNode) {
return std::make_shared<RootShadowNode>(
auto newRoot = std::make_shared<RootShadowNode>(
oldRootShadowNode,
ShadowNodeFragment{
.props = ShadowNodeFragment::propsPlaceholder(),
.children = rootChildren,
});

// Append portal children to the target node
for (const auto& [targetTag, portalChildren] : portalChildren_) {
const auto& localPortalChildren = portalChildren;

auto targetNode = findShadowNodeByTagRecursively(
std::static_pointer_cast<const ShadowNode>(newRoot),
targetTag);

if (targetNode) {
auto clonedRoot = newRoot->cloneTree(
targetNode->getFamily(),
[&](const ShadowNode& oldNode) {
auto existingChildren = oldNode.getChildren();

std::unordered_set<Tag> portalTags;
for (const auto& child : *localPortalChildren) {
portalTags.insert(child->getTag());
}

auto mergedChildren = std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>();
mergedChildren->reserve(existingChildren.size() + localPortalChildren->size());
for (const auto& child : existingChildren) {
if (portalTags.find(child->getTag()) == portalTags.end()) {
mergedChildren->push_back(child);
}
}

// Append portal children
for (const auto& child : *localPortalChildren) {
mergedChildren->push_back(child);
}

return oldNode.clone({.children = mergedChildren});
});
if (clonedRoot) {
newRoot = std::static_pointer_cast<RootShadowNode>(clonedRoot);
}
}
}

// Remove unmounted portal children
for (const auto& [targetTag, tagsToRemove] : pendingPortalRemovals_) {
const auto& localTagsToRemove = tagsToRemove;

auto targetNode = findShadowNodeByTagRecursively(
std::static_pointer_cast<const ShadowNode>(newRoot),
targetTag);

if (targetNode) {
auto clonedRoot = newRoot->cloneTree(
targetNode->getFamily(),
[&](const ShadowNode& oldNode) {
auto existingChildren = oldNode.getChildren();
auto newChildren = std::make_shared<std::vector<std::shared_ptr<const ShadowNode>>>();
for (const auto& child : existingChildren) {
if (localTagsToRemove.find(child->getTag()) == localTagsToRemove.end()) {
newChildren->push_back(child);
}
}
return oldNode.clone({.children = newChildren});
});
if (clonedRoot) {
newRoot = std::static_pointer_cast<RootShadowNode>(clonedRoot);
}
}
}
pendingPortalRemovals_.clear();

return newRoot;
},
commitOptions);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ class UIManager final : public ShadowTreeDelegate {
const ShadowNode::UnsharedListOfShared &rootChildren,
ShadowTree::CommitOptions commitOptions);

void mountPortalChildren(
Tag targetTag,
const ShadowNode::UnsharedListOfShared &portalChildren);

void setIsJSResponder(
const std::shared_ptr<const ShadowNode> &shadowNode,
bool isJSResponder,
Expand Down Expand Up @@ -249,6 +253,12 @@ class UIManager final : public ShadowTreeDelegate {
std::unique_ptr<LazyShadowTreeRevisionConsistencyManager> lazyShadowTreeRevisionConsistencyManager_;

std::shared_ptr<UIManagerAnimationBackend> animationBackend_;

// Portal children that are active and need to be committed
std::unordered_map<Tag, ShadowNode::UnsharedListOfShared> portalChildren_;

// Portal children that are pending removal and need to be removed on the next commit
std::unordered_map<Tag, std::unordered_set<Tag>> pendingPortalRemovals_;
};

} // namespace facebook::react
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,28 @@ jsi::Value UIManagerBinding::get(
});
}

if (methodName == "mountPortalChildren") {
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 targetTag = static_cast<Tag>(arguments[0].asNumber());
auto shadowNodeList =
shadowNodeListFromValue(runtime, arguments[1]);
uiManager->mountPortalChildren(targetTag, shadowNodeList);

return jsi::Value::undefined();
});
}

if (methodName == "registerEventHandler") {
auto paramCount = 1;
return jsi::Function::createFromHostFunction(
Expand Down
Loading
Loading