Skip to content

Commit 5a3df8d

Browse files
authored
Merge pull request #10 from salazarm/interaction-tracking-marco
Interaction Tracking -- Observer context switching lifecycle.
2 parents 9830883 + c3662fa commit 5a3df8d

File tree

4 files changed

+477
-133
lines changed

4 files changed

+477
-133
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
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 {Interaction} from './InteractionTracking';
11+
12+
type Interactions = Array<Interaction>;
13+
14+
export type InteractionObserver = {
15+
onInteractionsScheduled: (
16+
interactions: Interactions,
17+
executionID: number,
18+
) => void,
19+
onInteractionsStarting: (
20+
interactions: Interactions,
21+
executionID: number,
22+
) => void,
23+
onInteractionsEnded: (
24+
interactions: Interactions,
25+
executionID: number,
26+
) => void,
27+
};
28+
29+
const observers: Array<InteractionObserver> = [];
30+
31+
export function registerInteractionObserver(
32+
observer: InteractionObserver,
33+
): void {
34+
observers.push(observer);
35+
}
36+
37+
export function __onInteractionsScheduled(
38+
interactions: Interactions,
39+
executionID: number,
40+
): void {
41+
if (!observers.length) {
42+
return;
43+
}
44+
observers.forEach(observer => {
45+
observer.onInteractionsScheduled(interactions, executionID);
46+
});
47+
}
48+
49+
export function __onInteractionsStarting(
50+
interactions: Interactions,
51+
executionID: number,
52+
) {
53+
if (!observers.length) {
54+
return;
55+
}
56+
observers.forEach(observer => {
57+
observer.onInteractionsStarting(interactions, executionID);
58+
});
59+
}
60+
61+
export function __onInteractionsEnded(
62+
interactions: Interactions,
63+
executionID: number,
64+
) {
65+
if (!observers.length) {
66+
return;
67+
}
68+
observers.forEach(observer => {
69+
observer.onInteractionsEnded(interactions, executionID);
70+
});
71+
}

packages/interaction-tracking/src/InteractionTracking.js

Lines changed: 150 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,126 @@
77
* @flow
88
*/
99

10-
import {getCurrentContext, trackContext} from './InteractionZone';
10+
import invariant from 'shared/invariant';
11+
import {
12+
__onInteractionsScheduled,
13+
__onInteractionsStarting,
14+
__onInteractionsEnded,
15+
} from './InteractionEmitter';
1116

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';
2218

2319
type Interactions = Array<Interaction>;
2420

2521
export type Interaction = {|
22+
id: number,
2623
name: string,
2724
timestamp: number,
2825
|};
2926

27+
export type Continuation = {
28+
__hasBeenRun: boolean,
29+
__id: number,
30+
__interactions: Interactions,
31+
__prevInteractions: Interactions | null,
32+
};
33+
3034
// Normally we would use the current renderer HostConfig's "now" method,
3135
// But since interaction-tracking will be a separate package,
3236
// I instead just copied the approach used by ReactScheduler.
3337
let now;
3438
if (typeof performance === 'object' && typeof performance.now === 'function') {
3539
const localPerformance = performance;
36-
now = function() {
37-
return localPerformance.now();
38-
};
40+
now = () => localPerformance.now();
3941
} else {
4042
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;
44130
}
45131

46132
export function track(name: string, callback: Function): void {
@@ -50,18 +136,58 @@ export function track(name: string, callback: Function): void {
50136
}
51137

52138
const interaction: Interaction = {
139+
id: globalInteractionID++,
53140
name,
54141
timestamp: now(),
55142
};
56143

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+
60170
if (interactions === null) {
61-
interactions = [interaction];
62-
} else {
63-
interactions = interactions.concat(interaction);
171+
return callback;
64172
}
65173

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+
};
67193
}

packages/interaction-tracking/src/InteractionZone.js

Lines changed: 0 additions & 60 deletions
This file was deleted.

0 commit comments

Comments
 (0)