@samitouri / QOS-React-2 / commits / 663c13d71d

Refactor Enter/Leave listener accumulation (#18405)

* Refactor Enter/Leave listener accumulation

Dominic Gannaway committed Apr 2, 2020 at 11:07 UTC 663c13d71d902aa38bb93b14ef93ffa803d1913c
9 files changed +376 -239
packages/legacy-events/EventPropagators.js deleted
-76
@@ -1,76 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow
8 - */
9 -
10 -import type {Fiber} from 'react-reconciler/src/ReactFiber';
11 -import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
12 -
13 -import getListener from 'legacy-events/getListener';
14 -
15 -import {traverseEnterLeave} from 'react-reconciler/src/ReactTreeTraversal';
16 -import accumulateInto from './accumulateInto';
17 -import forEachAccumulated from './forEachAccumulated';
18 -
19 -/**
20 - * A small set of propagation patterns, each of which will accept a small amount
21 - * of information, and generate a set of "dispatch ready event objects" - which
22 - * are sets of events that have already been annotated with a set of dispatched
23 - * listener functions/ids. The API is designed this way to discourage these
24 - * propagation strategies from actually executing the dispatches, since we
25 - * always want to collect the entire set of dispatches before executing even a
26 - * single one.
27 - */
28 -
29 -/**
30 - * Accumulates without regard to direction, does not look for phased
31 - * registration names. Same as `accumulateDirectDispatchesSingle` but without
32 - * requiring that the `dispatchMarker` be the same as the dispatched ID.
33 - */
34 -function accumulateDispatches(
35 - inst: Fiber,
36 - ignoredDirection: ?boolean,
37 - event: ReactSyntheticEvent,
38 -): void {
39 - if (inst && event && event.dispatchConfig.registrationName) {
40 - const registrationName = event.dispatchConfig.registrationName;
41 - const listener = getListener(inst, registrationName);
42 - if (listener) {
43 - event._dispatchListeners = accumulateInto(
44 - event._dispatchListeners,
45 - listener,
46 - );
47 - event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
48 - }
49 - }
50 -}
51 -
52 -/**
53 - * Accumulates dispatches on an `SyntheticEvent`, but only for the
54 - * `dispatchMarker`.
55 - * @param {SyntheticEvent} event
56 - */
57 -function accumulateDirectDispatchesSingle(event: ReactSyntheticEvent) {
58 - if (event && event.dispatchConfig.registrationName) {
59 - accumulateDispatches(event._targetInst, null, event);
60 - }
61 -}
62 -
63 -export function accumulateEnterLeaveDispatches(
64 - leave: ReactSyntheticEvent,
65 - enter: ReactSyntheticEvent,
66 - from: Fiber,
67 - to: Fiber,
68 -) {
69 - traverseEnterLeave(from, to, accumulateDispatches, leave, enter);
70 -}
71 -
72 -export function accumulateDirectDispatches(
73 - events: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent),
74 -) {
75 - forEachAccumulated(events, accumulateDirectDispatchesSingle);
76 -}
packages/legacy-events/ReactSyntheticEventType.js
+1
@@ -27,6 +27,7 @@ export type CustomDispatchConfig = {|
27 bubbled: null,
28 captured: null,
29 |},
30 + registrationName?: string,
31 customEvent: true,
32 |};
33
packages/legacy-events/ResponderEventPlugin.js
+125 -9
@@ -5,20 +5,12 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import {
9 - getLowestCommonAncestor,
10 - isAncestor,
11 - getParentInstance,
12 - traverseTwoPhase,
13 -} from 'react-reconciler/src/ReactTreeTraversal';
14 -
8 import {
9 executeDirectDispatch,
10 hasDispatches,
11 executeDispatchesInOrderStopAtTrue,
12 getInstanceFromNode,
13 } from './EventPluginUtils';
21 -import {accumulateDirectDispatches} from './EventPropagators';
14 import ResponderSyntheticEvent from './ResponderSyntheticEvent';
15 import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
16 import accumulate from './accumulate';
@@ -36,6 +28,7 @@ import {
28 import getListener from './getListener';
29 import accumulateInto from './accumulateInto';
30 import forEachAccumulated from './forEachAccumulated';
31 +import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
32
33 /**
34 * Instance of element that should respond to touch/move types of interactions,
@@ -158,6 +151,91 @@ const eventTypes = {
151 // Start of inline: the below functions were inlined from
152 // EventPropagator.js, as they deviated from ReactDOM's newer
153 // implementations.
154 +
155 +function getParent(inst) {
156 + do {
157 + inst = inst.return;
158 + // TODO: If this is a HostRoot we might want to bail out.
159 + // That is depending on if we want nested subtrees (layers) to bubble
160 + // events to their parent. We could also go through parentNode on the
161 + // host node but that wouldn't work for React Native and doesn't let us
162 + // do the portal feature.
163 + } while (inst && inst.tag !== HostComponent);
164 + if (inst) {
165 + return inst;
166 + }
167 + return null;
168 +}
169 +
170 +/**
171 + * Return the lowest common ancestor of A and B, or null if they are in
172 + * different trees.
173 + */
174 +export function getLowestCommonAncestor(instA, instB) {
175 + let depthA = 0;
176 + for (let tempA = instA; tempA; tempA = getParent(tempA)) {
177 + depthA++;
178 + }
179 + let depthB = 0;
180 + for (let tempB = instB; tempB; tempB = getParent(tempB)) {
181 + depthB++;
182 + }
183 +
184 + // If A is deeper, crawl up.
185 + while (depthA - depthB > 0) {
186 + instA = getParent(instA);
187 + depthA--;
188 + }
189 +
190 + // If B is deeper, crawl up.
191 + while (depthB - depthA > 0) {
192 + instB = getParent(instB);
193 + depthB--;
194 + }
195 +
196 + // Walk in lockstep until we find a match.
197 + let depth = depthA;
198 + while (depth--) {
199 + if (instA === instB || instA === instB.alternate) {
200 + return instA;
201 + }
202 + instA = getParent(instA);
203 + instB = getParent(instB);
204 + }
205 + return null;
206 +}
207 +
208 +/**
209 + * Return if A is an ancestor of B.
210 + */
211 +export function isAncestor(instA, instB) {
212 + while (instB) {
213 + if (instA === instB || instA === instB.alternate) {
214 + return true;
215 + }
216 + instB = getParent(instB);
217 + }
218 + return false;
219 +}
220 +
221 +/**
222 + * Simulates the traversal of a two-phase, capture/bubble event dispatch.
223 + */
224 +export function traverseTwoPhase(inst, fn, arg) {
225 + const path = [];
226 + while (inst) {
227 + path.push(inst);
228 + inst = getParent(inst);
229 + }
230 + let i;
231 + for (i = path.length; i-- > 0; ) {
232 + fn(path[i], 'captured', arg);
233 + }
234 + for (i = 0; i < path.length; i++) {
235 + fn(path[i], 'bubbled', arg);
236 + }
237 +}
238 +
239 function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
240 const registrationName =
241 event.dispatchConfig.phasedRegistrationNames[propagationPhase];
@@ -180,10 +258,48 @@ function accumulateDirectionalDispatches(inst, phase, event) {
258 }
259 }
260
261 +/**
262 + * Accumulates without regard to direction, does not look for phased
263 + * registration names. Same as `accumulateDirectDispatchesSingle` but without
264 + * requiring that the `dispatchMarker` be the same as the dispatched ID.
265 + */
266 +function accumulateDispatches(
267 + inst: Object,
268 + ignoredDirection: ?boolean,
269 + event: Object,
270 +): void {
271 + if (inst && event && event.dispatchConfig.registrationName) {
272 + const registrationName = event.dispatchConfig.registrationName;
273 + const listener = getListener(inst, registrationName);
274 + if (listener) {
275 + event._dispatchListeners = accumulateInto(
276 + event._dispatchListeners,
277 + listener,
278 + );
279 + event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
280 + }
281 + }
282 +}
283 +
284 +/**
285 + * Accumulates dispatches on an `SyntheticEvent`, but only for the
286 + * `dispatchMarker`.
287 + * @param {SyntheticEvent} event
288 + */
289 +function accumulateDirectDispatchesSingle(event: Object) {
290 + if (event && event.dispatchConfig.registrationName) {
291 + accumulateDispatches(event._targetInst, null, event);
292 + }
293 +}
294 +
295 +function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {
296 + forEachAccumulated(events, accumulateDirectDispatchesSingle);
297 +}
298 +
299 function accumulateTwoPhaseDispatchesSingleSkipTarget(event) {
300 if (event && event.dispatchConfig.phasedRegistrationNames) {
301 const targetInst = event._targetInst;
186 - const parentInst = targetInst ? getParentInstance(targetInst) : null;
302 + const parentInst = targetInst ? getParent(targetInst) : null;
303 traverseTwoPhase(parentInst, accumulateDirectionalDispatches, event);
304 }
305 }
packages/legacy-events/__tests__/ResponderEventPlugin-test.internal.js
+3 -2
@@ -1378,7 +1378,8 @@ describe('ResponderEventPlugin', () => {
1378 // ResponderEventPlugin uses `getLowestCommonAncestor`
1379 const React = require('react');
1380 const ReactTestUtils = require('react-dom/test-utils');
1381 - const ReactTreeTraversal = require('react-reconciler/src/ReactTreeTraversal');
1381 + const getLowestCommonAncestor = require('legacy-events/ResponderEventPlugin')
1382 + .getLowestCommonAncestor;
1383 const ReactDOMComponentTree = require('../../react-dom/src/client/ReactDOMComponentTree');
1384
1385 class ChildComponent extends React.Component {
@@ -1451,7 +1452,7 @@ describe('ResponderEventPlugin', () => {
1452 let i;
1453 for (i = 0; i < ancestors.length; i++) {
1454 const plan = ancestors[i];
1454 - const firstCommon = ReactTreeTraversal.getLowestCommonAncestor(
1455 + const firstCommon = getLowestCommonAncestor(
1456 ReactDOMComponentTree.getInstanceFromNode(plan.one),
1457 ReactDOMComponentTree.getInstanceFromNode(plan.two),
1458 );
packages/react-dom/src/events/EnterLeaveEventPlugin.js
+2 -3
@@ -5,8 +5,6 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import {accumulateEnterLeaveDispatches} from 'legacy-events/EventPropagators';
9 -
8 import {
9 TOP_MOUSE_OUT,
10 TOP_MOUSE_OVER,
@@ -23,6 +21,7 @@ import {
21 import {HostComponent, HostText} from 'react-reconciler/src/ReactWorkTags';
22 import {getNearestMountedFiber} from 'react-reconciler/src/ReactFiberTreeReflection';
23 import {enableModernEventSystem} from 'shared/ReactFeatureFlags';
24 +import accumulateEnterLeaveListeners from './accumulateEnterLeaveListeners';
25
26 const eventTypes = {
27 mouseEnter: {
@@ -172,7 +171,7 @@ const EnterLeaveEventPlugin = {
171 enter.target = toNode;
172 enter.relatedTarget = fromNode;
173
175 - accumulateEnterLeaveDispatches(leave, enter, from, to);
174 + accumulateEnterLeaveListeners(leave, enter, from, to);
175
176 if (!enableModernEventSystem) {
177 // If we are not processing the first ancestor, then we
packages/react-dom/src/events/accumulateEnterLeaveListeners.js new
+139
@@ -0,0 +1,139 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {Fiber} from 'react-reconciler/src/ReactFiber';
11 +import type {ReactSyntheticEvent} from 'legacy-events/ReactSyntheticEventType';
12 +
13 +import getListener from 'legacy-events/getListener';
14 +import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
15 +
16 +function getParent(inst: Fiber | null): Fiber | null {
17 + if (inst === null) {
18 + return null;
19 + }
20 + do {
21 + inst = inst.return;
22 + // TODO: If this is a HostRoot we might want to bail out.
23 + // That is depending on if we want nested subtrees (layers) to bubble
24 + // events to their parent. We could also go through parentNode on the
25 + // host node but that wouldn't work for React Native and doesn't let us
26 + // do the portal feature.
27 + } while (inst && inst.tag !== HostComponent);
28 + if (inst) {
29 + return inst;
30 + }
31 + return null;
32 +}
33 +
34 +/**
35 + * Return the lowest common ancestor of A and B, or null if they are in
36 + * different trees.
37 + */
38 +function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null {
39 + let nodeA = instA;
40 + let nodeB = instB;
41 + let depthA = 0;
42 + for (let tempA = nodeA; tempA; tempA = getParent(tempA)) {
43 + depthA++;
44 + }
45 + let depthB = 0;
46 + for (let tempB = nodeB; tempB; tempB = getParent(tempB)) {
47 + depthB++;
48 + }
49 +
50 + // If A is deeper, crawl up.
51 + while (depthA - depthB > 0) {
52 + nodeA = getParent(nodeA);
53 + depthA--;
54 + }
55 +
56 + // If B is deeper, crawl up.
57 + while (depthB - depthA > 0) {
58 + nodeB = getParent(nodeB);
59 + depthB--;
60 + }
61 +
62 + // Walk in lockstep until we find a match.
63 + let depth = depthA;
64 + while (depth--) {
65 + if (nodeA === nodeB || (nodeB !== null && nodeA === nodeB.alternate)) {
66 + return nodeA;
67 + }
68 + nodeA = getParent(nodeA);
69 + nodeB = getParent(nodeB);
70 + }
71 + return null;
72 +}
73 +
74 +function accumulateEnterLeaveListenersForEvent(
75 + event: ReactSyntheticEvent,
76 + target: Fiber,
77 + common: Fiber | null,
78 + capture: boolean,
79 +): void {
80 + const registrationName = event.dispatchConfig.registrationName;
81 + if (registrationName === undefined) {
82 + return;
83 + }
84 + const dispatchListeners = [];
85 + const dispatchInstances = [];
86 +
87 + let node = target;
88 + while (node !== null) {
89 + if (node === common) {
90 + break;
91 + }
92 + const alternate = node.alternate;
93 + if (alternate !== null && alternate === common) {
94 + break;
95 + }
96 + if (node.tag === HostComponent) {
97 + if (capture) {
98 + const captureListener = getListener(node, registrationName);
99 + if (captureListener != null) {
100 + // Capture listeners/instances should go at the start, so we
101 + // unshift them to the start of the array.
102 + dispatchListeners.unshift(captureListener);
103 + dispatchInstances.unshift(node);
104 + }
105 + } else {
106 + const bubbleListener = getListener(node, registrationName);
107 + if (bubbleListener != null) {
108 + // Bubble listeners/instances should go at the end, so we
109 + // push them to the end of the array.
110 + dispatchListeners.push(bubbleListener);
111 + dispatchInstances.push(node);
112 + }
113 + }
114 + }
115 + node = node.return;
116 + }
117 + // To prevent allocation to the event unless we actually
118 + // have listeners we check the length of one of the arrays.
119 + if (dispatchListeners.length > 0) {
120 + event._dispatchListeners = dispatchListeners;
121 + event._dispatchInstances = dispatchInstances;
122 + }
123 +}
124 +
125 +export default function accumulateEnterLeaveListeners(
126 + leaveEvent: ReactSyntheticEvent,
127 + enterEvent: ReactSyntheticEvent,
128 + from: Fiber | null,
129 + to: Fiber | null,
130 +): void {
131 + const common = from && to ? getLowestCommonAncestor(from, to) : null;
132 +
133 + if (from !== null) {
134 + accumulateEnterLeaveListenersForEvent(leaveEvent, from, common, false);
135 + }
136 + if (to !== null) {
137 + accumulateEnterLeaveListenersForEvent(enterEvent, to, common, true);
138 + }
139 +}
packages/react-dom/src/test-utils/ReactTestUtils.js
+33 -1
@@ -23,7 +23,6 @@ import {PLUGIN_EVENT_SYSTEM} from 'legacy-events/EventSystemFlags';
23 import act from './ReactTestUtilsAct';
24 import forEachAccumulated from 'legacy-events/forEachAccumulated';
25 import accumulateInto from 'legacy-events/accumulateInto';
26 -import {traverseTwoPhase} from 'react-reconciler/src/ReactTreeTraversal';
26
27 const {findDOMNode} = ReactDOM;
28 // Keep in sync with ReactDOMUnstableNativeDependencies.js
@@ -390,6 +389,39 @@ function isInteractive(tag) {
389 );
390 }
391
392 +function getParent(inst) {
393 + do {
394 + inst = inst.return;
395 + // TODO: If this is a HostRoot we might want to bail out.
396 + // That is depending on if we want nested subtrees (layers) to bubble
397 + // events to their parent. We could also go through parentNode on the
398 + // host node but that wouldn't work for React Native and doesn't let us
399 + // do the portal feature.
400 + } while (inst && inst.tag !== HostComponent);
401 + if (inst) {
402 + return inst;
403 + }
404 + return null;
405 +}
406 +
407 +/**
408 + * Simulates the traversal of a two-phase, capture/bubble event dispatch.
409 + */
410 +export function traverseTwoPhase(inst, fn, arg) {
411 + const path = [];
412 + while (inst) {
413 + path.push(inst);
414 + inst = getParent(inst);
415 + }
416 + let i;
417 + for (i = path.length; i-- > 0; ) {
418 + fn(path[i], 'captured', arg);
419 + }
420 + for (i = 0; i < path.length; i++) {
421 + fn(path[i], 'bubbled', arg);
422 + }
423 +}
424 +
425 function shouldPreventMouseEvent(name, type, props) {
426 switch (name) {
427 case 'onClick':
packages/react-native-renderer/src/ReactNativeBridgeEventPlugin.js
+73 -2
@@ -9,7 +9,6 @@
9
10 import type {AnyNativeEvent} from 'legacy-events/PluginModuleType';
11 import type {EventSystemFlags} from 'legacy-events/EventSystemFlags';
12 -import {accumulateDirectDispatches} from 'legacy-events/EventPropagators';
12 import type {TopLevelType} from 'legacy-events/TopLevelEventTypes';
13 import SyntheticEvent from 'legacy-events/SyntheticEvent';
14 import invariant from 'shared/invariant';
@@ -18,8 +17,8 @@ import invariant from 'shared/invariant';
17 import {ReactNativeViewConfigRegistry} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
18 import accumulateInto from 'legacy-events/accumulateInto';
19 import getListener from 'legacy-events/getListener';
21 -import {traverseTwoPhase} from 'react-reconciler/src/ReactTreeTraversal';
20 import forEachAccumulated from 'legacy-events/forEachAccumulated';
21 +import {HostComponent} from 'react-reconciler/src/ReactWorkTags';
22
23 const {
24 customBubblingEventTypes,
@@ -51,6 +50,39 @@ function accumulateDirectionalDispatches(inst, phase, event) {
50 }
51 }
52
53 +function getParent(inst) {
54 + do {
55 + inst = inst.return;
56 + // TODO: If this is a HostRoot we might want to bail out.
57 + // That is depending on if we want nested subtrees (layers) to bubble
58 + // events to their parent. We could also go through parentNode on the
59 + // host node but that wouldn't work for React Native and doesn't let us
60 + // do the portal feature.
61 + } while (inst && inst.tag !== HostComponent);
62 + if (inst) {
63 + return inst;
64 + }
65 + return null;
66 +}
67 +
68 +/**
69 + * Simulates the traversal of a two-phase, capture/bubble event dispatch.
70 + */
71 +export function traverseTwoPhase(inst: Object, fn: Function, arg: Function) {
72 + const path = [];
73 + while (inst) {
74 + path.push(inst);
75 + inst = getParent(inst);
76 + }
77 + let i;
78 + for (i = path.length; i-- > 0; ) {
79 + fn(path[i], 'captured', arg);
80 + }
81 + for (i = 0; i < path.length; i++) {
82 + fn(path[i], 'bubbled', arg);
83 + }
84 +}
85 +
86 function accumulateTwoPhaseDispatchesSingle(event) {
87 if (event && event.dispatchConfig.phasedRegistrationNames) {
88 traverseTwoPhase(event._targetInst, accumulateDirectionalDispatches, event);
@@ -60,6 +92,45 @@ function accumulateTwoPhaseDispatchesSingle(event) {
92 function accumulateTwoPhaseDispatches(events) {
93 forEachAccumulated(events, accumulateTwoPhaseDispatchesSingle);
94 }
95 +
96 +/**
97 + * Accumulates without regard to direction, does not look for phased
98 + * registration names. Same as `accumulateDirectDispatchesSingle` but without
99 + * requiring that the `dispatchMarker` be the same as the dispatched ID.
100 + */
101 +function accumulateDispatches(
102 + inst: Object,
103 + ignoredDirection: ?boolean,
104 + event: Object,
105 +): void {
106 + if (inst && event && event.dispatchConfig.registrationName) {
107 + const registrationName = event.dispatchConfig.registrationName;
108 + const listener = getListener(inst, registrationName);
109 + if (listener) {
110 + event._dispatchListeners = accumulateInto(
111 + event._dispatchListeners,
112 + listener,
113 + );
114 + event._dispatchInstances = accumulateInto(event._dispatchInstances, inst);
115 + }
116 + }
117 +}
118 +
119 +/**
120 + * Accumulates dispatches on an `SyntheticEvent`, but only for the
121 + * `dispatchMarker`.
122 + * @param {SyntheticEvent} event
123 + */
124 +function accumulateDirectDispatchesSingle(event: Object) {
125 + if (event && event.dispatchConfig.registrationName) {
126 + accumulateDispatches(event._targetInst, null, event);
127 + }
128 +}
129 +
130 +function accumulateDirectDispatches(events: ?(Array<Object> | Object)) {
131 + forEachAccumulated(events, accumulateDirectDispatchesSingle);
132 +}
133 +
134 // End of inline
135 type PropagationPhases = 'bubbled' | 'captured';
136
packages/react-reconciler/src/ReactTreeTraversal.js deleted
-146
@@ -1,146 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - */
7 -
8 -import {HostComponent} from './ReactWorkTags';
9 -
10 -function getParent(inst) {
11 - do {
12 - inst = inst.return;
13 - // TODO: If this is a HostRoot we might want to bail out.
14 - // That is depending on if we want nested subtrees (layers) to bubble
15 - // events to their parent. We could also go through parentNode on the
16 - // host node but that wouldn't work for React Native and doesn't let us
17 - // do the portal feature.
18 - } while (inst && inst.tag !== HostComponent);
19 - if (inst) {
20 - return inst;
21 - }
22 - return null;
23 -}
24 -
25 -/**
26 - * Return the lowest common ancestor of A and B, or null if they are in
27 - * different trees.
28 - */
29 -export function getLowestCommonAncestor(instA, instB) {
30 - let depthA = 0;
31 - for (let tempA = instA; tempA; tempA = getParent(tempA)) {
32 - depthA++;
33 - }
34 - let depthB = 0;
35 - for (let tempB = instB; tempB; tempB = getParent(tempB)) {
36 - depthB++;
37 - }
38 -
39 - // If A is deeper, crawl up.
40 - while (depthA - depthB > 0) {
41 - instA = getParent(instA);
42 - depthA--;
43 - }
44 -
45 - // If B is deeper, crawl up.
46 - while (depthB - depthA > 0) {
47 - instB = getParent(instB);
48 - depthB--;
49 - }
50 -
51 - // Walk in lockstep until we find a match.
52 - let depth = depthA;
53 - while (depth--) {
54 - if (instA === instB || instA === instB.alternate) {
55 - return instA;
56 - }
57 - instA = getParent(instA);
58 - instB = getParent(instB);
59 - }
60 - return null;
61 -}
62 -
63 -/**
64 - * Return if A is an ancestor of B.
65 - */
66 -export function isAncestor(instA, instB) {
67 - while (instB) {
68 - if (instA === instB || instA === instB.alternate) {
69 - return true;
70 - }
71 - instB = getParent(instB);
72 - }
73 - return false;
74 -}
75 -
76 -/**
77 - * Return the parent instance of the passed-in instance.
78 - */
79 -export function getParentInstance(inst) {
80 - return getParent(inst);
81 -}
82 -
83 -/**
84 - * Simulates the traversal of a two-phase, capture/bubble event dispatch.
85 - */
86 -export function traverseTwoPhase(inst, fn, arg) {
87 - const path = [];
88 - while (inst) {
89 - path.push(inst);
90 - inst = getParent(inst);
91 - }
92 - let i;
93 - for (i = path.length; i-- > 0; ) {
94 - fn(path[i], 'captured', arg);
95 - }
96 - for (i = 0; i < path.length; i++) {
97 - fn(path[i], 'bubbled', arg);
98 - }
99 -}
100 -
101 -/**
102 - * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
103 - * should would receive a `mouseEnter` or `mouseLeave` event.
104 - *
105 - * Does not invoke the callback on the nearest common ancestor because nothing
106 - * "entered" or "left" that element.
107 - */
108 -export function traverseEnterLeave(from, to, fn, argFrom, argTo) {
109 - const common = from && to ? getLowestCommonAncestor(from, to) : null;
110 - const pathFrom = [];
111 - while (true) {
112 - if (!from) {
113 - break;
114 - }
115 - if (from === common) {
116 - break;
117 - }
118 - const alternate = from.alternate;
119 - if (alternate !== null && alternate === common) {
120 - break;
121 - }
122 - pathFrom.push(from);
123 - from = getParent(from);
124 - }
125 - const pathTo = [];
126 - while (true) {
127 - if (!to) {
128 - break;
129 - }
130 - if (to === common) {
131 - break;
132 - }
133 - const alternate = to.alternate;
134 - if (alternate !== null && alternate === common) {
135 - break;
136 - }
137 - pathTo.push(to);
138 - to = getParent(to);
139 - }
140 - for (let i = 0; i < pathFrom.length; i++) {
141 - fn(pathFrom[i], 'bubbled', argFrom);
142 - }
143 - for (let i = pathTo.length; i-- > 0; ) {
144 - fn(pathTo[i], 'captured', argTo);
145 - }
146 -}