1
+ /**
2
+ * This file contains core module functionality.
3
+ *
4
+ * It exports an anonymous
5
+ * @function
6
+ * that is invoked on
7
+ * @param snap --> Current snapshot
8
+ * @param mode --> Current mode (jumping i.e. time-traveling, locked, or paused)
9
+ * and @returns a function to be invoked on the rootContainer HTMLElement
10
+ *
11
+ * @function updateSnapShotTree
12
+ * --> Middleware #1: Updates snap object with latest snapshot
13
+ *
14
+ * @function sendSnapshot
15
+ * --> Middleware #2: Gets a copy of the current snap.tree and posts a message to the window
16
+ *
17
+ * @function changeSetState
18
+ * @param component : stateNode property on a stateful class component's FiberNode object
19
+ * --> Binds class component setState method to the component
20
+ * --> Injects middleware into class component's setState method
21
+ *
22
+ * @function changeUseState
23
+ * @param component : memoizedState property on a stateful functional component's FiberNode object
24
+ * --> Binds functional component dispatch method to the component
25
+ * --> Injects middleware into component's dispatch method
26
+ * Note: dispatch is hook equivalent to setState()
27
+ *
28
+ * @function traverseHooks
29
+ * @param memoizedState : memoizedState property on a stateful fctnl component's FiberNode object
30
+ * --> Helper function to traverse through memoizedState
31
+ * --> Invokes @changeUseState on each stateful functional component
32
+ *
33
+ * @function createTree
34
+ * @param currentFiber : a FiberNode object
35
+ * --> Recursive function to traverse from FiberRootNode and create
36
+ * an instance of custom Tree class and build up state snapshot
37
+ */
38
+
1
39
/* eslint-disable no-underscore-dangle */
2
40
/* eslint-disable func-names */
3
41
/* eslint-disable no-use-before-define */
4
42
/* eslint-disable no-param-reassign */
5
- // links component state tree to library
6
- // changes the setState method to also update our snapshot
43
+
7
44
const Tree = require ( './tree' ) ;
8
45
const astParser = require ( './astParser' ) ;
9
46
const { saveState } = require ( './masterState' ) ;
@@ -14,80 +51,80 @@ module.exports = (snap, mode) => {
14
51
let concurrent = false ; // flag to check if we are in concurrent mode
15
52
16
53
function sendSnapshot ( ) {
17
- // don 't send messages while jumping or while paused
54
+ // Don 't send messages while jumping or while paused
18
55
// DEV: So that when we are jumping to an old snapshot it
19
- // wouldn't think we want to create new snapshots
20
56
if ( mode . jumping || mode . paused ) return ;
21
57
const payload = snap . tree . getCopy ( ) ;
22
- // console.log('payload', payload);
23
58
window . postMessage ( {
24
59
action : 'recordSnap' ,
25
60
payload,
26
61
} ) ;
27
62
}
28
63
29
64
function changeSetState ( component ) {
30
- // check that setState hasn't been changed yet
31
65
if ( component . setState . linkFiberChanged ) return ;
32
- // make a copy of setState
66
+
67
+ // Persist the old setState and bind to component so we can continue to setState({})
33
68
const oldSetState = component . setState . bind ( component ) ;
34
- // replace component's setState so developer doesn't change syntax
35
- // component.setState = newSetState.bind(component);
69
+
36
70
component . setState = ( state , callback = ( ) => { } ) => {
37
- // don't do anything if state is locked
38
- // UNLESS we are currently jumping through time
71
+ // Don't do anything if state is locked UNLESS we are currently jumping through time
39
72
if ( mode . locked && ! mode . jumping ) return ;
40
- // continue normal setState functionality, except add sending message middleware
73
+ // Continue normal setState functionality, with middleware in callback
41
74
oldSetState ( state , ( ) => {
42
75
updateSnapShotTree ( ) ;
43
76
sendSnapshot ( ) ;
44
77
callback . bind ( component ) ( ) ;
45
78
} ) ;
46
79
} ;
80
+ // Set a custom property to ensure we don't change this method again
47
81
component . setState . linkFiberChanged = true ;
48
82
}
49
83
50
84
function changeUseState ( component ) {
51
85
if ( component . queue . dispatch . linkFiberChanged ) return ;
52
- // store the original dispatch function definition
86
+
87
+ // Persist the old dispatch and bind to component so we can continue to dispatch()
53
88
const oldDispatch = component . queue . dispatch . bind ( component . queue ) ;
54
- // redefine the dispatch function so we can inject our code
89
+
55
90
component . queue . dispatch = ( fiber , queue , action ) => {
56
- // don't do anything if state is locked
57
91
if ( mode . locked && ! mode . jumping ) return ;
58
92
oldDispatch ( fiber , queue , action ) ;
93
+ // * Uncomment setTimeout to prevent snapshot lag-effect
94
+ // * (i.e. getting the prior snapshot on each state change)
59
95
// setTimeout(() => {
60
96
updateSnapShotTree ( ) ;
61
97
sendSnapshot ( ) ;
62
98
// }, 100);
63
99
} ;
100
+ // Set a custom property to ensure we don't change this method again
64
101
component . queue . dispatch . linkFiberChanged = true ;
65
102
}
66
103
67
- // Helper function to traverse through the memoized state
68
104
// TODO: WE NEED TO CLEAN IT UP A BIT
69
105
function traverseHooks ( memoizedState ) {
70
106
// Declare variables and assigned to 0th index and an empty object, respectively
71
107
const memoized = { } ;
72
108
let index = 0 ;
73
109
astHooks = Object . values ( astHooks ) ;
74
- // while memoizedState is truthy, save the value to the object
110
+ // While memoizedState is truthy, save the value to the object
75
111
while ( memoizedState && memoizedState . queue ) {
76
- // prevents useEffect from crashing on load
112
+ // // prevents useEffect from crashing on load
77
113
// if (memoizedState.next.queue === null) { // prevents double pushing snapshot updates
78
114
changeUseState ( memoizedState ) ;
79
115
// }
80
116
// memoized[astHooks[index]] = memoizedState.memoizedState;
81
117
memoized [ astHooks [ index ] ] = memoizedState . memoizedState ;
82
118
// Reassign memoizedState to its next value
83
119
memoizedState = memoizedState . next ;
84
- // Increment the index by 2
120
+ // See astParser.js for explanation of this increment
85
121
index += 2 ;
86
122
}
87
123
return memoized ;
88
124
}
89
125
90
126
function createTree ( currentFiber , tree = new Tree ( 'root' ) ) {
127
+ // Base case: child or sibling pointed to null
91
128
if ( ! currentFiber ) return tree ;
92
129
93
130
const {
@@ -99,19 +136,17 @@ module.exports = (snap, mode) => {
99
136
} = currentFiber ;
100
137
101
138
let nextTree = tree ;
102
- // check if stateful component
139
+
140
+ // Check if stateful component
103
141
if ( stateNode && stateNode . state ) {
104
- // add component to tree
105
- nextTree = tree . appendChild ( stateNode ) ;
106
- // change setState functionality
107
- changeSetState ( stateNode ) ;
142
+ nextTree = tree . appendChild ( stateNode ) ; // Add component to tree
143
+ changeSetState ( stateNode ) ; // Change setState functionality
108
144
}
109
- // Check if the component uses hooks
110
- // console.log("memoizedState", memoizedState);
111
145
146
+ // Check if the component uses hooks
112
147
if (
113
- memoizedState &&
114
- Object . hasOwnProperty . call ( memoizedState , 'baseState' )
148
+ memoizedState
149
+ && Object . hasOwnProperty . call ( memoizedState , 'baseState' )
115
150
) {
116
151
// 'catch-all' for suspense elements (experimental)
117
152
if ( typeof elementType . $$typeof === 'symbol' ) return ;
@@ -123,18 +158,19 @@ module.exports = (snap, mode) => {
123
158
memoizedState . traversed = traverseHooks ( memoizedState ) ;
124
159
nextTree = tree . appendChild ( memoizedState ) ;
125
160
}
126
- // iterate through siblings
161
+
162
+ // Recurse on siblings
127
163
createTree ( sibling , tree ) ;
128
- // iterate through children
164
+ // Recurse on children
129
165
createTree ( child , nextTree ) ;
130
166
131
167
return tree ;
132
168
}
133
- // runs when page initially loads
134
- // but skips 1st hook click
169
+
170
+ // ! BUG: skips 1st hook click
135
171
async function updateSnapShotTree ( ) {
136
172
let current ;
137
- // if concurrent mode, grab current.child'
173
+ // If concurrent mode, grab current.child
138
174
if ( concurrent ) {
139
175
// we need a way to wait for current child to populate
140
176
const promise = new Promise ( ( resolve , reject ) => {
@@ -152,6 +188,7 @@ module.exports = (snap, mode) => {
152
188
}
153
189
154
190
return async container => {
191
+ // Point fiberRoot to FiberRootNode
155
192
if ( container . _internalRoot ) {
156
193
fiberRoot = container . _internalRoot ;
157
194
concurrent = true ;
@@ -160,12 +197,13 @@ module.exports = (snap, mode) => {
160
197
_reactRootContainer : { _internalRoot } ,
161
198
_reactRootContainer,
162
199
} = container ;
163
- // only assign internal root if it actually exists
200
+ // Only assign internal root if it actually exists
164
201
fiberRoot = _internalRoot || _reactRootContainer ;
165
202
}
166
203
167
204
await updateSnapShotTree ( ) ;
168
- // send the initial snapshot once the content script has started up
205
+ // Send the initial snapshot once the content script has started up
206
+ // This message is sent from contentScript.js in chrome extension bundles
169
207
window . addEventListener ( 'message' , ( { data : { action } } ) => {
170
208
if ( action === 'contentScriptStarted' ) sendSnapshot ( ) ;
171
209
} ) ;
0 commit comments