Skip to content

Commit b57b847

Browse files
committed
Compare Fiber tree position with DOM position
1 parent ec67519 commit b57b847

File tree

4 files changed

+586
-213
lines changed

4 files changed

+586
-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
@@ -37,6 +37,11 @@ import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
3737
import hasOwnProperty from 'shared/hasOwnProperty';
3838
import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
3939
import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
40+
import {
41+
isFiberContainedBy,
42+
isFiberFollowing,
43+
isFiberPreceding,
44+
} from 'react-reconciler/src/ReactFiberTreeReflection';
4045

4146
export {
4247
setCurrentUpdatePriority,
@@ -60,8 +65,9 @@ import {
6065
} from './ReactDOMComponentTree';
6166
import {
6267
traverseFragmentInstance,
63-
getFragmentParentHostInstance,
64-
getNextSiblingHostInstance,
68+
getFragmentParentHostFiber,
69+
getNextSiblingHostFiber,
70+
getInstanceFromHostFiber,
6571
} from 'react-reconciler/src/ReactFiberTreeReflection';
6672

6773
export {detachDeletedInstance};
@@ -2629,7 +2635,6 @@ FragmentInstance.prototype.addEventListener = function (
26292635
listeners.push({type, listener, optionsOrUseCapture});
26302636
traverseFragmentInstance(
26312637
this._fragmentFiber,
2632-
false,
26332638
addEventListenerToChild,
26342639
type,
26352640
listener,
@@ -2639,12 +2644,13 @@ FragmentInstance.prototype.addEventListener = function (
26392644
this._eventListeners = listeners;
26402645
};
26412646
function addEventListenerToChild(
2642-
child: Instance,
2647+
child: Fiber,
26432648
type: string,
26442649
listener: EventListener,
26452650
optionsOrUseCapture?: EventListenerOptionsOrUseCapture,
26462651
): boolean {
2647-
child.addEventListener(type, listener, optionsOrUseCapture);
2652+
const instance = getInstanceFromHostFiber(child);
2653+
instance.addEventListener(type, listener, optionsOrUseCapture);
26482654
return false;
26492655
}
26502656
// $FlowFixMe[prop-missing]
@@ -2661,7 +2667,6 @@ FragmentInstance.prototype.removeEventListener = function (
26612667
if (typeof listeners !== 'undefined' && listeners.length > 0) {
26622668
traverseFragmentInstance(
26632669
this._fragmentFiber,
2664-
false,
26652670
removeEventListenerFromChild,
26662671
type,
26672672
listener,
@@ -2679,12 +2684,13 @@ FragmentInstance.prototype.removeEventListener = function (
26792684
}
26802685
};
26812686
function removeEventListenerFromChild(
2682-
child: Instance,
2687+
child: Fiber,
26832688
type: string,
26842689
listener: EventListener,
26852690
optionsOrUseCapture?: EventListenerOptionsOrUseCapture,
26862691
): boolean {
2687-
child.removeEventListener(type, listener, optionsOrUseCapture);
2692+
const instance = getInstanceFromHostFiber(child);
2693+
instance.removeEventListener(type, listener, optionsOrUseCapture);
26882694
return false;
26892695
}
26902696
// $FlowFixMe[prop-missing]
@@ -2694,34 +2700,32 @@ FragmentInstance.prototype.focus = function (
26942700
): void {
26952701
traverseFragmentInstance(
26962702
this._fragmentFiber,
2697-
false,
2698-
setFocusIfFocusable,
2703+
setFocusOnFiberIfFocusable,
26992704
focusOptions,
27002705
);
27012706
};
2707+
function setFocusOnFiberIfFocusable(
2708+
fiber: Fiber,
2709+
focusOptions?: FocusOptions,
2710+
): boolean {
2711+
const instance = getInstanceFromHostFiber(fiber);
2712+
return setFocusIfFocusable(instance, focusOptions);
2713+
}
27022714
// $FlowFixMe[prop-missing]
27032715
FragmentInstance.prototype.focusLast = function (
27042716
this: FragmentInstanceType,
27052717
focusOptions?: FocusOptions,
27062718
): void {
2707-
const children: Array<Instance> = [];
2708-
traverseFragmentInstance(
2709-
this._fragmentFiber,
2710-
false,
2711-
collectChildren,
2712-
children,
2713-
);
2719+
const children: Array<Fiber> = [];
2720+
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
27142721
for (let i = children.length - 1; i >= 0; i--) {
27152722
const child = children[i];
2716-
if (setFocusIfFocusable(child, focusOptions)) {
2723+
if (setFocusOnFiberIfFocusable(child, focusOptions)) {
27172724
break;
27182725
}
27192726
}
27202727
};
2721-
function collectChildren(
2722-
child: Instance,
2723-
collection: Array<Instance>,
2724-
): boolean {
2728+
function collectChildren(child: Fiber, collection: Array<Fiber>): boolean {
27252729
collection.push(child);
27262730
return false;
27272731
}
@@ -2731,16 +2735,16 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void {
27312735
// does not contain document.activeElement
27322736
traverseFragmentInstance(
27332737
this._fragmentFiber,
2734-
false,
27352738
blurActiveElementWithinFragment,
27362739
);
27372740
};
2738-
function blurActiveElementWithinFragment(child: Instance): boolean {
2741+
function blurActiveElementWithinFragment(child: Fiber): boolean {
27392742
// TODO: We can get the activeElement from the parent outside of the loop when we have a reference.
2740-
const ownerDocument = child.ownerDocument;
2741-
if (child === ownerDocument.activeElement) {
2743+
const instance = getInstanceFromHostFiber(child);
2744+
const ownerDocument = instance.ownerDocument;
2745+
if (instance === ownerDocument.activeElement) {
27422746
// $FlowFixMe[prop-missing]
2743-
child.blur();
2747+
instance.blur();
27442748
return true;
27452749
}
27462750
return false;
@@ -2754,13 +2758,14 @@ FragmentInstance.prototype.observeUsing = function (
27542758
this._observers = new Set();
27552759
}
27562760
this._observers.add(observer);
2757-
traverseFragmentInstance(this._fragmentFiber, false, observeChild, observer);
2761+
traverseFragmentInstance(this._fragmentFiber, observeChild, observer);
27582762
};
27592763
function observeChild(
2760-
child: Instance,
2764+
child: Fiber,
27612765
observer: IntersectionObserver | ResizeObserver,
27622766
) {
2763-
observer.observe(child);
2767+
const instance = getInstanceFromHostFiber(child);
2768+
observer.observe(instance);
27642769
return false;
27652770
}
27662771
// $FlowFixMe[prop-missing]
@@ -2777,48 +2782,41 @@ FragmentInstance.prototype.unobserveUsing = function (
27772782
}
27782783
} else {
27792784
this._observers.delete(observer);
2780-
traverseFragmentInstance(
2781-
this._fragmentFiber,
2782-
false,
2783-
unobserveChild,
2784-
observer,
2785-
);
2785+
traverseFragmentInstance(this._fragmentFiber, unobserveChild, observer);
27862786
}
27872787
};
27882788
function unobserveChild(
2789-
child: Instance,
2789+
child: Fiber,
27902790
observer: IntersectionObserver | ResizeObserver,
27912791
) {
2792-
observer.unobserve(child);
2792+
const instance = getInstanceFromHostFiber(child);
2793+
observer.unobserve(instance);
27932794
return false;
27942795
}
27952796
// $FlowFixMe[prop-missing]
27962797
FragmentInstance.prototype.getClientRects = function (
27972798
this: FragmentInstanceType,
27982799
): Array<DOMRect> {
27992800
const rects: Array<DOMRect> = [];
2800-
traverseFragmentInstance(
2801-
this._fragmentFiber,
2802-
true,
2803-
collectClientRects,
2804-
rects,
2805-
);
2801+
traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects);
28062802
return rects;
28072803
};
2808-
function collectClientRects(child: Instance, rects: Array<DOMRect>): boolean {
2804+
function collectClientRects(child: Fiber, rects: Array<DOMRect>): boolean {
2805+
const instance = getInstanceFromHostFiber(child);
28092806
// $FlowFixMe[method-unbinding]
2810-
rects.push.apply(rects, child.getClientRects());
2807+
rects.push.apply(rects, instance.getClientRects());
28112808
return false;
28122809
}
28132810
// $FlowFixMe[prop-missing]
28142811
FragmentInstance.prototype.getRootNode = function (
28152812
this: FragmentInstanceType,
28162813
getRootNodeOptions?: {composed: boolean},
28172814
): Document | ShadowRoot | FragmentInstanceType {
2818-
const parentHostInstance = getFragmentParentHostInstance(this._fragmentFiber);
2819-
if (parentHostInstance === null) {
2815+
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
2816+
if (parentHostFiber === null) {
28202817
return this;
28212818
}
2819+
const parentHostInstance = getInstanceFromHostFiber(parentHostFiber);
28222820
const rootNode =
28232821
// $FlowFixMe[incompatible-cast] Flow expects Node
28242822
(parentHostInstance.getRootNode(getRootNodeOptions): Document | ShadowRoot);
@@ -2829,56 +2827,54 @@ FragmentInstance.prototype.compareDocumentPosition = function (
28292827
this: FragmentInstanceType,
28302828
otherNode: Instance,
28312829
): number {
2832-
const parentHostInstance = getFragmentParentHostInstance(this._fragmentFiber);
2833-
if (parentHostInstance === null) {
2830+
const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber);
2831+
if (parentHostFiber === null) {
28342832
return Node.DOCUMENT_POSITION_DISCONNECTED;
28352833
}
2834+
const children: Array<Fiber> = [];
2835+
traverseFragmentInstance(this._fragmentFiber, collectChildren, children);
28362836

2837-
const children: Array<Instance> = [];
2838-
traverseFragmentInstance(
2839-
this._fragmentFiber,
2840-
true,
2841-
collectChildren,
2842-
children,
2843-
);
2844-
2837+
let result = Node.DOCUMENT_POSITION_DISCONNECTED;
28452838
if (children.length === 0) {
28462839
// If the fragment has no children, we can use the parent and
28472840
// siblings to determine a position.
2848-
if (parentHostInstance === otherNode) {
2849-
return Node.DOCUMENT_POSITION_CONTAINS;
2850-
}
2841+
const parentHostInstance = getInstanceFromHostFiber(parentHostFiber);
28512842
const parentResult = parentHostInstance.compareDocumentPosition(otherNode);
2852-
if (parentResult & Node.DOCUMENT_POSITION_CONTAINED_BY) {
2853-
// otherNode is one of the fragment's siblings. Use the next
2854-
// sibling to determine if its preceding or following.
2855-
const nextSiblingInstance = getNextSiblingHostInstance(
2856-
this._fragmentFiber,
2857-
);
2858-
if (nextSiblingInstance === null) {
2859-
return Node.DOCUMENT_POSITION_PRECEDING;
2860-
}
2861-
if (
2862-
nextSiblingInstance === otherNode ||
2863-
nextSiblingInstance.compareDocumentPosition(otherNode) &
2864-
Node.DOCUMENT_POSITION_FOLLOWING
2865-
) {
2866-
return Node.DOCUMENT_POSITION_FOLLOWING;
2867-
} else {
2868-
return Node.DOCUMENT_POSITION_PRECEDING;
2843+
result = parentResult;
2844+
if (parentHostInstance === otherNode) {
2845+
result = Node.DOCUMENT_POSITION_CONTAINS;
2846+
} else {
2847+
if (parentResult & Node.DOCUMENT_POSITION_CONTAINED_BY) {
2848+
// otherNode is one of the fragment's siblings. Use the next
2849+
// sibling to determine if its preceding or following.
2850+
const nextSiblingFiber = getNextSiblingHostFiber(this._fragmentFiber);
2851+
if (nextSiblingFiber === null) {
2852+
result = Node.DOCUMENT_POSITION_PRECEDING;
2853+
} else {
2854+
const nextSiblingInstance =
2855+
getInstanceFromHostFiber(nextSiblingFiber);
2856+
const nextSiblingResult =
2857+
nextSiblingInstance.compareDocumentPosition(otherNode);
2858+
if (
2859+
nextSiblingResult === 0 ||
2860+
nextSiblingResult & Node.DOCUMENT_POSITION_FOLLOWING
2861+
) {
2862+
result = Node.DOCUMENT_POSITION_FOLLOWING;
2863+
} else {
2864+
result = Node.DOCUMENT_POSITION_PRECEDING;
2865+
}
2866+
}
28692867
}
28702868
}
2871-
return parentResult;
2869+
2870+
result |= Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
2871+
return result;
28722872
}
28732873

2874-
const firstElement = children[0];
2875-
const lastElement = children[children.length - 1];
2874+
const firstElement = getInstanceFromHostFiber(children[0]);
2875+
const lastElement = getInstanceFromHostFiber(children[children.length - 1]);
28762876
const firstResult = firstElement.compareDocumentPosition(otherNode);
28772877
const lastResult = lastElement.compareDocumentPosition(otherNode);
2878-
let result;
2879-
2880-
// If otherNode is a child of the Fragment, it should only be
2881-
// Node.DOCUMENT_POSITION_CONTAINED_BY
28822878
if (
28832879
(firstResult & Node.DOCUMENT_POSITION_FOLLOWING &&
28842880
lastResult & Node.DOCUMENT_POSITION_PRECEDING) ||
@@ -2890,9 +2886,67 @@ FragmentInstance.prototype.compareDocumentPosition = function (
28902886
result = firstResult;
28912887
}
28922888

2893-
return result;
2889+
if (
2890+
result & Node.DOCUMENT_POSITION_DISCONNECTED ||
2891+
result & Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
2892+
) {
2893+
return result;
2894+
}
2895+
2896+
// Now that we have the result from the DOM API, we double check it matches
2897+
// the state of the React tree. If it doesn't, we have a case of portaled or
2898+
// otherwise injected elements and we return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC.
2899+
const documentPositionMatchesFiberPosition =
2900+
validateDocumentPositionWithFiberTree(
2901+
result,
2902+
this._fragmentFiber,
2903+
children[0],
2904+
children[children.length - 1],
2905+
otherNode,
2906+
);
2907+
if (documentPositionMatchesFiberPosition) {
2908+
return result;
2909+
}
2910+
return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
28942911
};
28952912

2913+
function validateDocumentPositionWithFiberTree(
2914+
documentPosition: number,
2915+
fragmentFiber: Fiber,
2916+
precedingBoundaryFiber: Fiber,
2917+
followingBoundaryFiber: Fiber,
2918+
otherNode: Instance,
2919+
): boolean {
2920+
const otherFiber = getClosestInstanceFromNode(otherNode);
2921+
if (documentPosition & Node.DOCUMENT_POSITION_CONTAINED_BY) {
2922+
return !!otherFiber && isFiberContainedBy(fragmentFiber, otherFiber);
2923+
}
2924+
if (documentPosition & Node.DOCUMENT_POSITION_CONTAINS) {
2925+
if (otherFiber === null) {
2926+
// otherFiber could be null if its the document or body element
2927+
const ownerDocument = otherNode.ownerDocument;
2928+
return otherNode === ownerDocument || otherNode === ownerDocument.body;
2929+
}
2930+
return isFiberContainedBy(otherFiber, fragmentFiber);
2931+
}
2932+
if (documentPosition & Node.DOCUMENT_POSITION_PRECEDING) {
2933+
return (
2934+
!!otherFiber &&
2935+
(otherFiber === precedingBoundaryFiber ||
2936+
isFiberPreceding(precedingBoundaryFiber, otherFiber))
2937+
);
2938+
}
2939+
if (documentPosition & Node.DOCUMENT_POSITION_FOLLOWING) {
2940+
return (
2941+
!!otherFiber &&
2942+
(otherFiber === followingBoundaryFiber ||
2943+
isFiberFollowing(followingBoundaryFiber, otherFiber))
2944+
);
2945+
}
2946+
2947+
return false;
2948+
}
2949+
28962950
function normalizeListenerOptions(
28972951
opts: ?EventListenerOptionsOrUseCapture,
28982952
): string {

0 commit comments

Comments
 (0)