Skip to content

Commit 4f5bd2c

Browse files
committed
Compare Fiber tree position with DOM position
1 parent e3f23c9 commit 4f5bd2c

File tree

5 files changed

+587
-213
lines changed

5 files changed

+587
-213
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 140 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
3535
import hasOwnProperty from 'shared/hasOwnProperty';
3636
import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
3737
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
38+
import {
39+
isFiberContainedBy,
40+
isFiberFollowing,
41+
isFiberPreceding,
42+
} from 'react-reconciler/src/ReactFiberTreeReflection';
3843

3944
export {
4045
setCurrentUpdatePriority,
@@ -58,8 +63,9 @@ import {
5863
} from './ReactDOMComponentTree';
5964
import {
6065
traverseFragmentInstance,
61-
getFragmentParentHostInstance,
62-
getNextSiblingHostInstance,
66+
getFragmentParentHostFiber,
67+
getNextSiblingHostFiber,
68+
getInstanceFromHostFiber,
6369
} from 'react-reconciler/src/ReactFiberTreeReflection';
6470

6571
export {detachDeletedInstance};
@@ -2448,7 +2454,6 @@ FragmentInstance.prototype.addEventListener = function (
24482454
listeners.push({type, listener, optionsOrUseCapture});
24492455
traverseFragmentInstance(
24502456
this._fragmentFiber,
2451-
false,
24522457
addEventListenerToChild,
24532458
type,
24542459
listener,
@@ -2458,12 +2463,13 @@ FragmentInstance.prototype.addEventListener = function (
24582463
this._eventListeners = listeners;
24592464
};
24602465
function addEventListenerToChild(
2461-
child: Instance,
2466+
child: Fiber,
24622467
type: string,
24632468
listener: EventListener,
24642469
optionsOrUseCapture?: EventListenerOptionsOrUseCapture,
24652470
): boolean {
2466-
child.addEventListener(type, listener, optionsOrUseCapture);
2471+
const instance = getInstanceFromHostFiber(child);
2472+
instance.addEventListener(type, listener, optionsOrUseCapture);
24672473
return false;
24682474
}
24692475
// $FlowFixMe[prop-missing]
@@ -2480,7 +2486,6 @@ FragmentInstance.prototype.removeEventListener = function (
24802486
if (typeof listeners !== 'undefined' && listeners.length > 0) {
24812487
traverseFragmentInstance(
24822488
this._fragmentFiber,
2483-
false,
24842489
removeEventListenerFromChild,
24852490
type,
24862491
listener,
@@ -2498,12 +2503,13 @@ FragmentInstance.prototype.removeEventListener = function (
24982503
}
24992504
};
25002505
function removeEventListenerFromChild(
2501-
child: Instance,
2506+
child: Fiber,
25022507
type: string,
25032508
listener: EventListener,
25042509
optionsOrUseCapture?: EventListenerOptionsOrUseCapture,
25052510
): boolean {
2506-
child.removeEventListener(type, listener, optionsOrUseCapture);
2511+
const instance = getInstanceFromHostFiber(child);
2512+
instance.removeEventListener(type, listener, optionsOrUseCapture);
25072513
return false;
25082514
}
25092515
// $FlowFixMe[prop-missing]
@@ -2513,34 +2519,32 @@ FragmentInstance.prototype.focus = function (
25132519
): void {
25142520
traverseFragmentInstance(
25152521
this._fragmentFiber,
2516-
false,
2517-
setFocusIfFocusable,
2522+
setFocusOnFiberIfFocusable,
25182523
focusOptions,
25192524
);
25202525
};
2526+
function setFocusOnFiberIfFocusable(
2527+
fiber: Fiber,
2528+
focusOptions?: FocusOptions,
2529+
): boolean {
2530+
const instance = getInstanceFromHostFiber(fiber);
2531+
return setFocusIfFocusable(instance, focusOptions);
2532+
}
25212533
// $FlowFixMe[prop-missing]
25222534
FragmentInstance.prototype.focusLast = function (
25232535
this: FragmentInstanceType,
25242536
focusOptions?: FocusOptions,
25252537
): void {
2526-
const children: Array<Instance> = [];
2527-
traverseFragmentInstance(
2528-
this._fragmentFiber,
2529-
false,
2530-
collectChildren,
2531-
children,
2532-
);
2538+
const children: Array<Fiber> = [];
2539+
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
25332540
for (let i = children.length - 1; i >= 0; i--) {
25342541
const child = children[i];
2535-
if (setFocusIfFocusable(child, focusOptions)) {
2542+
if (setFocusOnFiberIfFocusable(child, focusOptions)) {
25362543
break;
25372544
}
25382545
}
25392546
};
2540-
function collectChildren(
2541-
child: Instance,
2542-
collection: Array<Instance>,
2543-
): boolean {
2547+
function collectChildren(child: Fiber, collection: Array<Fiber>): boolean {
25442548
collection.push(child);
25452549
return false;
25462550
}
@@ -2550,16 +2554,16 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
25502554
// does not contain document.activeElement
25512555
traverseFragmentInstance(
25522556
this._fragmentFiber,
2553-
false,
25542557
blurActiveElementWithinFragment,
25552558
);
25562559
};
2557-
function blurActiveElementWithinFragment(child: Instance): boolean {
2560+
function blurActiveElementWithinFragment(child: Fiber): boolean {
25582561
// TODO: We can get the activeElement from the parent outside of the loop when we have a reference.
2559-
const ownerDocument = child.ownerDocument;
2560-
if (child === ownerDocument.activeElement) {
2562+
const instance = getInstanceFromHostFiber(child);
2563+
const ownerDocument = instance.ownerDocument;
2564+
if (instance === ownerDocument.activeElement) {
25612565
// $FlowFixMe[prop-missing]
2562-
child.blur();
2566+
instance.blur();
25632567
return true;
25642568
}
25652569
return false;
@@ -2573,13 +2577,14 @@ FragmentInstance.prototype.observeUsing = function (
25732577
this._observers = new Set();
25742578
}
25752579
this._observers.add(observer);
2576-
traverseFragmentInstance(this._fragmentFiber, false, observeChild, observer);
2580+
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
25772581
};
25782582
function observeChild(
2579-
child: Instance,
2583+
child: Fiber,
25802584
observer: IntersectionObserver | ResizeObserver,
25812585
) {
2582-
observer.observe(child);
2586+
const instance = getInstanceFromHostFiber(child);
2587+
observer.observe(instance);
25832588
return false;
25842589
}
25852590
// $FlowFixMe[prop-missing]
@@ -2596,48 +2601,41 @@ FragmentInstance.prototype.unobserveUsing = function (
25962601
}
25972602
} else {
25982603
this._observers.delete(observer);
2599-
traverseFragmentInstance(
2600-
this._fragmentFiber,
2601-
false,
2602-
unobserveChild,
2603-
observer,
2604-
);
2604+
traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer);
26052605
}
26062606
};
26072607
function unobserveChild(
2608-
child: Instance,
2608+
child: Fiber,
26092609
observer: IntersectionObserver | ResizeObserver,
26102610
) {
2611-
observer.unobserve(child);
2611+
const instance = getInstanceFromHostFiber(child);
2612+
observer.unobserve(instance);
26122613
return false;
26132614
}
26142615
// $FlowFixMe[prop-missing]
26152616
FragmentInstance.prototype.getClientRects = function (
26162617
this: FragmentInstanceType,
26172618
): Array<DOMRect> {
26182619
const rects: Array<DOMRect> = [];
2619-
traverseFragmentInstance(
2620-
this._fragmentFiber,
2621-
true,
2622-
collectClientRects,
2623-
rects,
2624-
);
2620+
traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects);
26252621
return rects;
26262622
};
2627-
function collectClientRects(child: Instance, rects: Array<DOMRect>): boolean {
2623+
function collectClientRects(child: Fiber, rects: Array<DOMRect>): boolean {
2624+
const instance = getInstanceFromHostFiber(child);
26282625
// $FlowFixMe[method-unbinding]
2629-
rects.push.apply(rects, child.getClientRects());
2626+
rects.push.apply(rects, instance.getClientRects());
26302627
return false;
26312628
}
26322629
// $FlowFixMe[prop-missing]
26332630
FragmentInstance.prototype.getRootNode = function (
26342631
this: FragmentInstanceType,
26352632
getRootNodeOptions?: {composed: boolean},
26362633
): Document | ShadowRoot | FragmentInstanceType {
2637-
const parentHostInstance = getFragmentParentHostInstance(this._fragmentFiber);
2638-
if (parentHostInstance === null) {
2634+
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
2635+
if (parentHostFiber === null) {
26392636
return this;
26402637
}
2638+
const parentHostInstance = getInstanceFromHostFiber(parentHostFiber);
26412639
const rootNode =
26422640
// $FlowFixMe[incompatible-cast] Flow expects Node
26432641
(parentHostInstance.getRootNode(getRootNodeOptions): Document | ShadowRoot);
@@ -2648,56 +2646,54 @@ FragmentInstance.prototype.compareDocumentPosition = function (
26482646
this: FragmentInstanceType,
26492647
otherNode: Instance,
26502648
): number {
2651-
const parentHostInstance = getFragmentParentHostInstance(this._fragmentFiber);
2652-
if (parentHostInstance === null) {
2649+
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
2650+
if (parentHostFiber === null) {
26532651
return Node.DOCUMENT_POSITION_DISCONNECTED;
26542652
}
2653+
const children: Array<Fiber> = [];
2654+
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
26552655

2656-
const children: Array<Instance> = [];
2657-
traverseFragmentInstance(
2658-
this._fragmentFiber,
2659-
true,
2660-
collectChildren,
2661-
children,
2662-
);
2663-
2656+
let result = Node.DOCUMENT_POSITION_DISCONNECTED;
26642657
if (children.length === 0) {
26652658
// If the fragment has no children, we can use the parent and
26662659
// siblings to determine a position.
2667-
if (parentHostInstance === otherNode) {
2668-
return Node.DOCUMENT_POSITION_CONTAINS;
2669-
}
2660+
const parentHostInstance = getInstanceFromHostFiber(parentHostFiber);
26702661
const parentResult = parentHostInstance.compareDocumentPosition(otherNode);
2671-
if (parentResult & Node.DOCUMENT_POSITION_CONTAINED_BY) {
2672-
// otherNode is one of the fragment's siblings. Use the next
2673-
// sibling to determine if its preceding or following.
2674-
const nextSiblingInstance = getNextSiblingHostInstance(
2675-
this._fragmentFiber,
2676-
);
2677-
if (nextSiblingInstance === null) {
2678-
return Node.DOCUMENT_POSITION_PRECEDING;
2679-
}
2680-
if (
2681-
nextSiblingInstance === otherNode ||
2682-
nextSiblingInstance.compareDocumentPosition(otherNode) &
2683-
Node.DOCUMENT_POSITION_FOLLOWING
2684-
) {
2685-
return Node.DOCUMENT_POSITION_FOLLOWING;
2686-
} else {
2687-
return Node.DOCUMENT_POSITION_PRECEDING;
2662+
result = parentResult;
2663+
if (parentHostInstance === otherNode) {
2664+
result = Node.DOCUMENT_POSITION_CONTAINS;
2665+
} else {
2666+
if (parentResult & Node.DOCUMENT_POSITION_CONTAINED_BY) {
2667+
// otherNode is one of the fragment's siblings. Use the next
2668+
// sibling to determine if its preceding or following.
2669+
const nextSiblingFiber = getNextSiblingHostFiber(this._fragmentFiber);
2670+
if (nextSiblingFiber === null) {
2671+
result = Node.DOCUMENT_POSITION_PRECEDING;
2672+
} else {
2673+
const nextSiblingInstance =
2674+
getInstanceFromHostFiber(nextSiblingFiber);
2675+
const nextSiblingResult =
2676+
nextSiblingInstance.compareDocumentPosition(otherNode);
2677+
if (
2678+
nextSiblingResult === 0 ||
2679+
nextSiblingResult & Node.DOCUMENT_POSITION_FOLLOWING
2680+
) {
2681+
result = Node.DOCUMENT_POSITION_FOLLOWING;
2682+
} else {
2683+
result = Node.DOCUMENT_POSITION_PRECEDING;
2684+
}
2685+
}
26882686
}
26892687
}
2690-
return parentResult;
2688+
2689+
result |= Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
2690+
return result;
26912691
}
26922692

2693-
const firstElement = children[0];
2694-
const lastElement = children[children.length - 1];
2693+
const firstElement = getInstanceFromHostFiber(children[0]);
2694+
const lastElement = getInstanceFromHostFiber(children[children.length - 1]);
26952695
const firstResult = firstElement.compareDocumentPosition(otherNode);
26962696
const lastResult = lastElement.compareDocumentPosition(otherNode);
2697-
let result;
2698-
2699-
// If otherNode is a child of the Fragment, it should only be
2700-
// Node.DOCUMENT_POSITION_CONTAINED_BY
27012697
if (
27022698
(firstResult & Node.DOCUMENT_POSITION_FOLLOWING &&
27032699
lastResult & Node.DOCUMENT_POSITION_PRECEDING) ||
@@ -2709,9 +2705,67 @@ FragmentInstance.prototype.compareDocumentPosition = function (
27092705
result = firstResult;
27102706
}
27112707

2712-
return result;
2708+
if (
2709+
result & Node.DOCUMENT_POSITION_DISCONNECTED ||
2710+
result & Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
2711+
) {
2712+
return result;
2713+
}
2714+
2715+
// Now that we have the result from the DOM API, we double check it matches
2716+
// the state of the React tree. If it doesn't, we have a case of portaled or
2717+
// otherwise injected elements and we return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC.
2718+
const documentPositionMatchesFiberPosition =
2719+
validateDocumentPositionWithFiberTree(
2720+
result,
2721+
this._fragmentFiber,
2722+
children[0],
2723+
children[children.length - 1],
2724+
otherNode,
2725+
);
2726+
if (documentPositionMatchesFiberPosition) {
2727+
return result;
2728+
}
2729+
return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
27132730
};
27142731

2732+
function validateDocumentPositionWithFiberTree(
2733+
documentPosition: number,
2734+
fragmentFiber: Fiber,
2735+
precedingBoundaryFiber: Fiber,
2736+
followingBoundaryFiber: Fiber,
2737+
otherNode: Instance,
2738+
): boolean {
2739+
const otherFiber = getClosestInstanceFromNode(otherNode);
2740+
if (documentPosition & Node.DOCUMENT_POSITION_CONTAINED_BY) {
2741+
return !!otherFiber && isFiberContainedBy(fragmentFiber, otherFiber);
2742+
}
2743+
if (documentPosition & Node.DOCUMENT_POSITION_CONTAINS) {
2744+
if (otherFiber === null) {
2745+
// otherFiber could be null if its the document or body element
2746+
const ownerDocument = otherNode.ownerDocument;
2747+
return otherNode === ownerDocument || otherNode === ownerDocument.body;
2748+
}
2749+
return isFiberContainedBy(otherFiber, fragmentFiber);
2750+
}
2751+
if (documentPosition & Node.DOCUMENT_POSITION_PRECEDING) {
2752+
return (
2753+
!!otherFiber &&
2754+
(otherFiber === precedingBoundaryFiber ||
2755+
isFiberPreceding(precedingBoundaryFiber, otherFiber))
2756+
);
2757+
}
2758+
if (documentPosition & Node.DOCUMENT_POSITION_FOLLOWING) {
2759+
return (
2760+
!!otherFiber &&
2761+
(otherFiber === followingBoundaryFiber ||
2762+
isFiberFollowing(followingBoundaryFiber, otherFiber))
2763+
);
2764+
}
2765+
2766+
return false;
2767+
}
2768+
27152769
function normalizeListenerOptions(
27162770
opts: ?EventListenerOptionsOrUseCapture,
27172771
): string {

0 commit comments

Comments
 (0)