7
7
* @flow
8
8
*/
9
9
10
- import { getCurrentContext , trackContext } from './InteractionZone' ;
10
+ import invariant from 'shared/invariant' ;
11
+ import {
12
+ __onInteractionsScheduled ,
13
+ __onInteractionsStarting ,
14
+ __onInteractionsEnded ,
15
+ } from './InteractionEmitter' ;
11
16
12
- export {
13
- getCurrentContext as getCurrentEvents ,
14
- restoreContext as startContinuation ,
15
- completeContext as stopContinuation ,
16
- wrapForCurrentContext as wrap ,
17
- } from './InteractionZone' ;
18
-
19
- // TODO This package will likely want to override browser APIs (e.g. setTimeout, fetch)
20
- // So that async callbacks are automatically wrapped with the current tracked event info.
21
- // For the initial iteration, async callbacks must be explicitely wrapped with wrap().
17
+ export { registerInteractionObserver } from './InteractionEmitter' ;
22
18
23
19
type Interactions = Array < Interaction > ;
24
20
25
21
export type Interaction = { |
22
+ id : number ,
26
23
name : string ,
27
24
timestamp : number ,
28
25
| } ;
29
26
27
+ export type Continuation = {
28
+ __hasBeenRun : boolean ,
29
+ __id : number ,
30
+ __interactions : Interactions ,
31
+ __prevInteractions : Interactions | null ,
32
+ } ;
33
+
30
34
// Normally we would use the current renderer HostConfig's "now" method,
31
35
// But since interaction-tracking will be a separate package,
32
36
// I instead just copied the approach used by ReactScheduler.
33
37
let now ;
34
38
if ( typeof performance === 'object' && typeof performance . now === 'function' ) {
35
39
const localPerformance = performance ;
36
- now = function ( ) {
37
- return localPerformance . now ( ) ;
38
- } ;
40
+ now = ( ) => localPerformance . now ( ) ;
39
41
} else {
40
42
const localDate = Date ;
41
- now = function ( ) {
42
- return localDate . now ( ) ;
43
- } ;
43
+ now = ( ) => localDate . now ( ) ;
44
+ }
45
+
46
+ let currentContinuation : Continuation | null = null ;
47
+ let globalExecutionID : number = 0 ;
48
+ let globalInteractionID : number = 0 ;
49
+ let interactions : Interactions | null = null ;
50
+
51
+ export function getCurrent ( ) : Interactions | null {
52
+ if ( ! __PROFILE__ ) {
53
+ return null ;
54
+ } else {
55
+ return interactions ;
56
+ }
57
+ }
58
+
59
+ export function reserveContinuation ( ) : Continuation | null {
60
+ if ( ! __PROFILE__ ) {
61
+ return null ;
62
+ }
63
+
64
+ if ( interactions !== null ) {
65
+ const executionID = globalExecutionID ++ ;
66
+
67
+ __onInteractionsScheduled ( interactions , executionID ) ;
68
+
69
+ return {
70
+ __hasBeenRun : false ,
71
+ __id : executionID ,
72
+ __interactions : interactions ,
73
+ __prevInteractions : null ,
74
+ } ;
75
+ } else {
76
+ return null ;
77
+ }
78
+ }
79
+
80
+ export function startContinuation ( continuation : Continuation | null ) : void {
81
+ if ( ! __PROFILE__ ) {
82
+ return ;
83
+ }
84
+
85
+ invariant (
86
+ currentContinuation === null ,
87
+ 'Cannot start a continuation when one is already active.' ,
88
+ ) ;
89
+
90
+ if ( continuation === null ) {
91
+ return ;
92
+ }
93
+
94
+ invariant (
95
+ ! continuation . __hasBeenRun ,
96
+ 'A continuation can only be started once' ,
97
+ ) ;
98
+
99
+ continuation . __hasBeenRun = true ;
100
+ currentContinuation = continuation ;
101
+
102
+ // Continuations should mask (rather than extend) any current interactions.
103
+ // Upon completion of a continuation, previous interactions will be restored.
104
+ continuation . __prevInteractions = interactions ;
105
+ interactions = continuation . __interactions ;
106
+
107
+ __onInteractionsStarting ( interactions , continuation . __id ) ;
108
+ }
109
+
110
+ export function stopContinuation ( continuation : Continuation ) : void {
111
+ if ( ! __PROFILE__ ) {
112
+ return ;
113
+ }
114
+
115
+ invariant (
116
+ currentContinuation === continuation ,
117
+ 'Cannot stop a continuation that is not active.' ,
118
+ ) ;
119
+
120
+ if ( continuation === null ) {
121
+ return ;
122
+ }
123
+
124
+ __onInteractionsEnded ( continuation . __interactions , continuation . __id ) ;
125
+
126
+ currentContinuation = null ;
127
+
128
+ // Restore previous interactions.
129
+ interactions = continuation . __prevInteractions ;
44
130
}
45
131
46
132
export function track ( name : string , callback : Function ) : void {
@@ -50,18 +136,58 @@ export function track(name: string, callback: Function): void {
50
136
}
51
137
52
138
const interaction : Interaction = {
139
+ id : globalInteractionID ++ ,
53
140
name,
54
141
timestamp : now ( ) ,
55
142
} ;
56
143
57
- // Tracked interactions should stack.
58
- // To do that, create a new zone with a concatenated (cloned) array.
59
- let interactions : Interactions | null = getCurrentContext ( ) ;
144
+ const executionID = globalExecutionID ++ ;
145
+ const prevInteractions = interactions ;
146
+
147
+ // Tracked interactions should stack/accumulate.
148
+ // To do that, clone the current interactions array.
149
+ // The previous interactions array will be restored upon completion.
150
+ interactions =
151
+ interactions === null ? [ interaction ] : interactions . concat ( interaction ) ;
152
+
153
+ try {
154
+ __onInteractionsScheduled ( interactions , executionID ) ;
155
+ __onInteractionsStarting ( interactions , executionID ) ;
156
+
157
+ callback ( ) ;
158
+ } finally {
159
+ __onInteractionsEnded ( interactions , executionID ) ;
160
+
161
+ interactions = prevInteractions ;
162
+ }
163
+ }
164
+
165
+ export function wrap ( callback : Function ) : Function {
166
+ if ( ! __PROFILE__ ) {
167
+ return callback ;
168
+ }
169
+
60
170
if ( interactions === null ) {
61
- interactions = [ interaction ] ;
62
- } else {
63
- interactions = interactions . concat ( interaction ) ;
171
+ return callback ;
64
172
}
65
173
66
- trackContext ( interactions , callback ) ;
174
+ const executionID = globalExecutionID ++ ;
175
+ const wrappedInteractions = interactions ;
176
+
177
+ __onInteractionsScheduled ( wrappedInteractions , executionID ) ;
178
+
179
+ return ( ...args ) => {
180
+ const prevInteractions = interactions ;
181
+ interactions = wrappedInteractions ;
182
+
183
+ try {
184
+ __onInteractionsStarting ( interactions , executionID ) ;
185
+
186
+ callback ( ...args ) ;
187
+ } finally {
188
+ __onInteractionsEnded ( interactions , executionID ) ;
189
+
190
+ interactions = prevInteractions ;
191
+ }
192
+ } ;
67
193
}
0 commit comments