@@ -33,7 +33,12 @@ import {
33
33
USE_EVENT_SYSTEM ,
34
34
} from './EventSystemFlags' ;
35
35
36
- import { HostRoot , HostPortal } from 'react-reconciler/src/ReactWorkTags' ;
36
+ import {
37
+ HostRoot ,
38
+ HostPortal ,
39
+ ScopeComponent ,
40
+ HostComponent ,
41
+ } from 'react-reconciler/src/ReactWorkTags' ;
37
42
38
43
import {
39
44
addTrappedEventListener ,
@@ -86,10 +91,12 @@ import {
86
91
import { COMMENT_NODE } from '../shared/HTMLNodeType' ;
87
92
import { topLevelEventsToDispatchConfig } from './DOMEventProperties' ;
88
93
import { batchedEventUpdates } from './ReactDOMUpdateBatching' ;
94
+ import getListener from './getListener' ;
89
95
90
96
import {
91
97
enableLegacyFBSupport ,
92
98
enableUseEventAPI ,
99
+ enableScopeAPI ,
93
100
} from 'shared/ReactFeatureFlags' ;
94
101
import {
95
102
invokeGuardedCallbackAndCatchFirstError ,
@@ -153,7 +160,7 @@ const isArray = Array.isArray;
153
160
const PossiblyWeakMap = ( ( typeof WeakMap === 'function' ? WeakMap : Map ) : any ) ;
154
161
155
162
// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
156
- export const eventTargetEventListenerStore : WeakMap <
163
+ const eventTargetEventListenerStore : WeakMap <
157
164
EventTarget ,
158
165
Map <
159
166
DOMTopLevelEventType ,
@@ -162,7 +169,7 @@ export const eventTargetEventListenerStore: WeakMap<
162
169
> = new PossiblyWeakMap ( ) ;
163
170
164
171
// $FlowFixMe: Flow cannot handle polymorphic WeakMaps
165
- export const reactScopeListenerStore : WeakMap <
172
+ const reactScopeListenerStore : WeakMap <
166
173
ReactScopeMethods ,
167
174
Map <
168
175
DOMTopLevelEventType ,
@@ -672,3 +679,303 @@ export function detachListenerFromReactScope(listener: ReactDOMListener): void {
672
679
}
673
680
}
674
681
}
682
+
683
+ export function accumulateTwoPhaseListeners (
684
+ event : ReactSyntheticEvent ,
685
+ accumulateUseEventListeners ?: boolean ,
686
+ ) : void {
687
+ const phasedRegistrationNames = event . dispatchConfig . phasedRegistrationNames ;
688
+ const dispatchListeners = [ ] ;
689
+ const dispatchInstances : Array < Fiber | null > = [ ] ;
690
+ const dispatchCurrentTargets = [ ] ;
691
+
692
+ const { bubbled, captured} = phasedRegistrationNames ;
693
+ // If we are not handling EventTarget only phase, then we're doing the
694
+ // usual two phase accumulation using the React fiber tree to pick up
695
+ // all relevant useEvent and on* prop events.
696
+ let instance = event . _targetInst ;
697
+ let lastHostComponent = null ;
698
+
699
+ // Accumulate all instances and listeners via the target -> root path.
700
+ while ( instance !== null ) {
701
+ const { stateNode, tag} = instance ;
702
+ // Handle listeners that are on HostComponents (i.e. <div>)
703
+ if ( tag === HostComponent && stateNode !== null ) {
704
+ const currentTarget = stateNode ;
705
+ lastHostComponent = currentTarget ;
706
+ // For useEvent listenrs
707
+ if ( enableUseEventAPI && accumulateUseEventListeners ) {
708
+ // useEvent event listeners
709
+ const targetType = event . type ;
710
+ const listeners = getListenersFromTarget ( currentTarget ) ;
711
+
712
+ if ( listeners !== null ) {
713
+ const listenersArr = Array . from ( listeners ) ;
714
+ for ( let i = 0 ; i < listenersArr . length ; i ++ ) {
715
+ const listener = listenersArr [ i ] ;
716
+ const {
717
+ callback,
718
+ event : { capture, type} ,
719
+ } = listener ;
720
+ if ( type === targetType ) {
721
+ if ( capture === true ) {
722
+ dispatchListeners . unshift ( callback ) ;
723
+ dispatchInstances . unshift ( instance ) ;
724
+ dispatchCurrentTargets . unshift ( currentTarget ) ;
725
+ } else {
726
+ dispatchListeners . push ( callback ) ;
727
+ dispatchInstances . push ( instance ) ;
728
+ dispatchCurrentTargets . push ( currentTarget ) ;
729
+ }
730
+ }
731
+ }
732
+ }
733
+ }
734
+ // Standard React on* listeners, i.e. onClick prop
735
+ if ( captured !== null ) {
736
+ const captureListener = getListener ( instance , captured ) ;
737
+ if ( captureListener != null ) {
738
+ // Capture listeners/instances should go at the start, so we
739
+ // unshift them to the start of the array.
740
+ dispatchListeners . unshift ( captureListener ) ;
741
+ dispatchInstances . unshift ( instance ) ;
742
+ dispatchCurrentTargets . unshift ( currentTarget ) ;
743
+ }
744
+ }
745
+ if ( bubbled !== null ) {
746
+ const bubbleListener = getListener ( instance , bubbled ) ;
747
+ if ( bubbleListener != null ) {
748
+ // Bubble listeners/instances should go at the end, so we
749
+ // push them to the end of the array.
750
+ dispatchListeners . push ( bubbleListener ) ;
751
+ dispatchInstances . push ( instance ) ;
752
+ dispatchCurrentTargets . push ( currentTarget ) ;
753
+ }
754
+ }
755
+ }
756
+ if (
757
+ enableUseEventAPI &&
758
+ enableScopeAPI &&
759
+ accumulateUseEventListeners &&
760
+ tag === ScopeComponent &&
761
+ lastHostComponent !== null
762
+ ) {
763
+ const reactScope = stateNode . methods ;
764
+ const eventTypeMap = reactScopeListenerStore . get ( reactScope ) ;
765
+ if ( eventTypeMap !== undefined ) {
766
+ const type = ( ( event . type : any ) : DOMTopLevelEventType ) ;
767
+ const listeners = eventTypeMap . get ( type ) ;
768
+ if ( listeners !== undefined ) {
769
+ const captureListeners = Array . from ( listeners . captured ) ;
770
+ const bubbleListeners = Array . from ( listeners . bubbled ) ;
771
+ const lastCurrentTarget = ( ( lastHostComponent : any ) : Element ) ;
772
+
773
+ for ( let i = 0 ; i < captureListeners . length ; i ++ ) {
774
+ const listener = captureListeners [ i ] ;
775
+ const { callback } = listener ;
776
+ dispatchListeners . unshift ( callback ) ;
777
+ dispatchInstances . unshift ( instance ) ;
778
+ dispatchCurrentTargets . unshift ( lastCurrentTarget ) ;
779
+ }
780
+ for (let i = 0; i < bubbleListeners . length ; i ++ ) {
781
+ const listener = bubbleListeners [ i ] ;
782
+ const { callback} = listener ;
783
+ dispatchListeners . push ( callback ) ;
784
+ dispatchInstances . push ( instance ) ;
785
+ dispatchCurrentTargets . push ( lastCurrentTarget ) ;
786
+ }
787
+ }
788
+ }
789
+ }
790
+ instance = instance . return ;
791
+ }
792
+
793
+ // To prevent allocation to the event unless we actually
794
+ // have listeners we check the length of one of the arrays.
795
+ if ( dispatchListeners . length > 0) {
796
+ event . _dispatchListeners = dispatchListeners ;
797
+ event . _dispatchInstances = dispatchInstances ;
798
+ event . _dispatchCurrentTargets = dispatchCurrentTargets ;
799
+ }
800
+ }
801
+
802
+ export function accumulateEventTargetListeners (
803
+ event : ReactSyntheticEvent ,
804
+ currentTarget : EventTarget ,
805
+ ) : void {
806
+ const dispatchListeners = [ ] ;
807
+ const dispatchInstances : Array < Fiber | null > = [ ] ;
808
+ const dispatchCurrentTargets = [ ] ;
809
+
810
+ const eventTypeMap = eventTargetEventListenerStore . get ( currentTarget ) ;
811
+ if ( eventTypeMap !== undefined ) {
812
+ const type = ( ( event . type : any ) : DOMTopLevelEventType ) ;
813
+ const listeners = eventTypeMap . get ( type ) ;
814
+ if ( listeners !== undefined ) {
815
+ const isCapturePhase = ( event : any ) . eventPhase === 1 ;
816
+
817
+ if ( isCapturePhase ) {
818
+ const captureListeners = Array . from ( listeners . captured ) ;
819
+
820
+ for ( let i = captureListeners . length - 1 ; i >= 0 ; i -- ) {
821
+ const listener = captureListeners [ i ] ;
822
+ const { callback} = listener ;
823
+ dispatchListeners . push ( callback ) ;
824
+ // EventTarget listeners do not have instances, as there
825
+ // is no backing Fiber instance for them (window, document etc).
826
+ dispatchInstances . push ( null ) ;
827
+ dispatchCurrentTargets . push ( currentTarget ) ;
828
+ }
829
+ } else {
830
+ const bubbleListeners = Array . from ( listeners . bubbled ) ;
831
+
832
+ for ( let i = 0 ; i < bubbleListeners . length ; i ++ ) {
833
+ const listener = bubbleListeners [ i ] ;
834
+ const { callback } = listener ;
835
+ dispatchListeners . push ( callback ) ;
836
+ // EventTarget listeners do not have instances, as there
837
+ // is no backing Fiber instance for them (window, document etc).
838
+ dispatchInstances . push ( null ) ;
839
+ dispatchCurrentTargets . push ( currentTarget ) ;
840
+ }
841
+ }
842
+ }
843
+ }
844
+ // To prevent allocation to the event unless we actually
845
+ // have listeners we check the length of one of the arrays.
846
+ if ( dispatchListeners . length > 0 ) {
847
+ event . _dispatchListeners = dispatchListeners ;
848
+ event . _dispatchInstances = dispatchInstances ;
849
+ event . _dispatchCurrentTargets = dispatchCurrentTargets ;
850
+ }
851
+ }
852
+
853
+ function getParent ( inst : Fiber | null ) : Fiber | null {
854
+ if ( inst === null ) {
855
+ return null ;
856
+ }
857
+ do {
858
+ inst = inst . return ;
859
+ // TODO: If this is a HostRoot we might want to bail out.
860
+ // That is depending on if we want nested subtrees (layers) to bubble
861
+ // events to their parent. We could also go through parentNode on the
862
+ // host node but that wouldn't work for React Native and doesn't let us
863
+ // do the portal feature.
864
+ } while (inst && inst . tag !== HostComponent ) ;
865
+ if ( inst ) {
866
+ return inst ;
867
+ }
868
+ return null;
869
+ }
870
+
871
+ /**
872
+ * Return the lowest common ancestor of A and B, or null if they are in
873
+ * different trees.
874
+ */
875
+ function getLowestCommonAncestor ( instA : Fiber , instB : Fiber ) : Fiber | null {
876
+ let nodeA = instA ;
877
+ let nodeB = instB ;
878
+ let depthA = 0 ;
879
+ for ( let tempA = nodeA ; tempA ; tempA = getParent ( tempA ) ) {
880
+ depthA ++ ;
881
+ }
882
+ let depthB = 0;
883
+ for (let tempB = nodeB; tempB; tempB = getParent(tempB)) {
884
+ depthB ++ ;
885
+ }
886
+
887
+ // If A is deeper, crawl up.
888
+ while (depthA - depthB > 0 ) {
889
+ nodeA = getParent ( nodeA ) ;
890
+ depthA -- ;
891
+ }
892
+
893
+ // If B is deeper, crawl up.
894
+ while (depthB - depthA > 0 ) {
895
+ nodeB = getParent ( nodeB ) ;
896
+ depthB -- ;
897
+ }
898
+
899
+ // Walk in lockstep until we find a match.
900
+ let depth = depthA;
901
+ while (depth--) {
902
+ if ( nodeA === nodeB || ( nodeB !== null && nodeA === nodeB . alternate ) ) {
903
+ return nodeA ;
904
+ }
905
+ nodeA = getParent(nodeA);
906
+ nodeB = getParent(nodeB);
907
+ }
908
+ return null ;
909
+ }
910
+
911
+ function accumulateEnterLeaveListenersForEvent (
912
+ event : ReactSyntheticEvent ,
913
+ target : Fiber ,
914
+ common : Fiber | null ,
915
+ capture : boolean ,
916
+ ) : void {
917
+ const registrationName = event . dispatchConfig . registrationName ;
918
+ if ( registrationName === undefined ) {
919
+ return;
920
+ }
921
+ const dispatchListeners = [ ] ;
922
+ const dispatchInstances : Array < Fiber | null > = [ ] ;
923
+ const dispatchCurrentTargets = [ ] ;
924
+
925
+ let instance = target ;
926
+ while ( instance !== null ) {
927
+ if ( instance === common ) {
928
+ break ;
929
+ }
930
+ const { alternate, stateNode, tag} = instance ;
931
+ if ( alternate !== null && alternate === common ) {
932
+ break;
933
+ }
934
+ if ( tag === HostComponent && stateNode !== null ) {
935
+ const currentTarget = stateNode ;
936
+ if ( capture ) {
937
+ const captureListener = getListener ( instance , registrationName ) ;
938
+ if ( captureListener != null ) {
939
+ // Capture listeners/instances should go at the start, so we
940
+ // unshift them to the start of the array.
941
+ dispatchListeners. unshift ( captureListener ) ;
942
+ dispatchInstances . unshift ( instance ) ;
943
+ dispatchCurrentTargets . unshift ( currentTarget ) ;
944
+ }
945
+ } else {
946
+ const bubbleListener = getListener ( instance , registrationName ) ;
947
+ if ( bubbleListener != null ) {
948
+ // Bubble listeners/instances should go at the end, so we
949
+ // push them to the end of the array.
950
+ dispatchListeners . push ( bubbleListener ) ;
951
+ dispatchInstances . push ( instance ) ;
952
+ dispatchCurrentTargets . push ( currentTarget ) ;
953
+ }
954
+ }
955
+ }
956
+ instance = instance . return ;
957
+ }
958
+ // To prevent allocation to the event unless we actually
959
+ // have listeners we check the length of one of the arrays.
960
+ if ( dispatchListeners . length > 0 ) {
961
+ event . _dispatchListeners = dispatchListeners ;
962
+ event . _dispatchInstances = dispatchInstances ;
963
+ event . _dispatchCurrentTargets = dispatchCurrentTargets ;
964
+ }
965
+ }
966
+
967
+ export function accumulateEnterLeaveListeners(
968
+ leaveEvent: ReactSyntheticEvent,
969
+ enterEvent: ReactSyntheticEvent,
970
+ from: Fiber | null,
971
+ to: Fiber | null,
972
+ ): void {
973
+ const common = from && to ? getLowestCommonAncestor ( from , to ) : null ;
974
+
975
+ if ( from !== null ) {
976
+ accumulateEnterLeaveListenersForEvent ( leaveEvent , from , common , false ) ;
977
+ }
978
+ if (to !== null) {
979
+ accumulateEnterLeaveListenersForEvent ( enterEvent , to , common , true ) ;
980
+ }
981
+ }
0 commit comments