42
42
/* eslint-disable no-param-reassign */
43
43
44
44
const Tree = require ( './tree' ) ;
45
- const astParser = require ( './astParser' ) ;
46
- const { saveState } = require ( './masterState' ) ;
45
+ const componentActionsRecord = require ( './masterState' ) ;
47
46
48
47
module . exports = ( snap , mode ) => {
49
48
let fiberRoot = null ;
50
49
let astHooks ;
51
50
let concurrent = false ; // flag to check if we are in concurrent mode
52
51
53
- function sendSnapshot ( ) {
52
+ async function sendSnapshot ( ) {
54
53
// Don't send messages while jumping or while paused
55
- // DEV: So that when we are jumping to an old snapshot it
56
54
if ( mode . jumping || mode . paused ) return ;
57
- const payload = snap . tree . getCopy ( ) ;
58
- window . postMessage ( {
59
- action : 'recordSnap' ,
60
- payload,
61
- } ) ;
62
- }
63
-
64
- function changeSetState ( component ) {
65
- if ( component . setState . linkFiberChanged ) return ;
66
-
67
- // Persist the old setState and bind to component so we can continue to setState({})
68
- const oldSetState = component . setState . bind ( component ) ;
69
-
70
- component . setState = ( state , callback = ( ) => { } ) => {
71
- // Don't do anything if state is locked UNLESS we are currently jumping through time
72
- if ( mode . locked && ! mode . jumping ) return ;
73
- // Continue normal setState functionality, with middleware in callback
74
- oldSetState ( state , ( ) => {
75
- updateSnapShotTree ( ) ;
76
- sendSnapshot ( ) ;
77
- callback . bind ( component ) ( ) ;
55
+ // console.log('PAYLOAD: before cleaning', snap.tree);
56
+ const payload = snap . tree . cleanTreeCopy ( ) ; // snap.tree.getCopy();
57
+ // console.log('PAYLOAD: after cleaning', payload);
58
+ try {
59
+ await window . postMessage ( {
60
+ action : 'recordSnap' ,
61
+ payload,
78
62
} ) ;
79
- } ;
80
- // Set a custom property to ensure we don't change this method again
81
- component . setState . linkFiberChanged = true ;
82
- }
83
-
84
- function changeUseState ( component ) {
85
- if ( component . queue . dispatch . linkFiberChanged ) return ;
86
-
87
- // Persist the old dispatch and bind to component so we can continue to dispatch()
88
- const oldDispatch = component . queue . dispatch . bind ( component . queue ) ;
89
-
90
- component . queue . dispatch = ( fiber , queue , action ) => {
91
- if ( mode . locked && ! mode . jumping ) return ;
92
- oldDispatch ( fiber , queue , action ) ;
93
- // * Uncomment setTimeout to prevent snapshot lag-effect
94
- // * (i.e. getting the prior snapshot on each state change)
95
- // setTimeout(() => {
96
- updateSnapShotTree ( ) ;
97
- sendSnapshot ( ) ;
98
- // }, 100);
99
- } ;
100
- // Set a custom property to ensure we don't change this method again
101
- component . queue . dispatch . linkFiberChanged = true ;
63
+ } catch ( e ) {
64
+ console . log ( 'failed to send postMessage:' , e ) ;
65
+ }
102
66
}
103
67
104
- // TODO: WE NEED TO CLEAN IT UP A BIT
68
+ // Carlos: Injects instrumentation to update our state tree every time
69
+ // a hooks component changes state
105
70
function traverseHooks ( memoizedState ) {
106
- // Declare variables and assigned to 0th index and an empty object, respectively
107
- const memoized = { } ;
108
- let index = 0 ;
109
- astHooks = Object . values ( astHooks ) ;
110
- // While memoizedState is truthy, save the value to the object
71
+ const hooksComponents = [ ] ;
111
72
while ( memoizedState && memoizedState . queue ) {
112
- // // prevents useEffect from crashing on load
73
+ // Carlos: these two are legacy comments, we should look into them later
74
+ // prevents useEffect from crashing on load
113
75
// if (memoizedState.next.queue === null) { // prevents double pushing snapshot updates
114
- changeUseState ( memoizedState ) ;
115
- // }
116
- // memoized[astHooks[index]] = memoizedState.memoizedState;
117
- memoized [ astHooks [ index ] ] = memoizedState . memoizedState ;
118
- // Reassign memoizedState to its next value
119
- memoizedState = memoizedState . next ;
120
- // See astParser.js for explanation of this increment
121
- index += 2 ;
76
+ if ( memoizedState . memoizedState ) {
77
+ console . log ( 'memoizedState in traverseHooks is:' , memoizedState ) ;
78
+ hooksComponents . push ( {
79
+ component : memoizedState . queue ,
80
+ state : memoizedState . memoizedState ,
81
+ } ) ;
82
+ }
83
+ // console.log('GOT STATE', memoizedState.memoizedState);
84
+ memoizedState = memoizedState . next !== memoizedState
85
+ ? memoizedState . next : null ;
122
86
}
123
- return memoized ;
87
+ return hooksComponents ;
124
88
}
125
89
90
+ // Carlos: This runs after EVERY Fiber commit. It creates a new snapshot,
91
+ //
126
92
function createTree ( currentFiber , tree = new Tree ( 'root' ) ) {
127
93
// Base case: child or sibling pointed to null
128
94
if ( ! currentFiber ) return tree ;
129
95
96
+ // These have the newest state. We update state and then
97
+ // called updateSnapshotTree()
130
98
const {
131
99
sibling,
132
100
stateNode,
133
101
child,
134
102
memoizedState,
135
103
elementType,
104
+ tag,
136
105
} = currentFiber ;
137
106
138
- let nextTree = tree ;
139
-
140
- // Check if stateful component
141
- if ( stateNode && stateNode . state ) {
142
- nextTree = tree . appendChild ( stateNode ) ; // Add component to tree
143
- changeSetState ( stateNode ) ; // Change setState functionality
107
+ let index ;
108
+ // Check if node is a stateful component
109
+ if ( stateNode && stateNode . state && ( tag === 0 || tag === 1 ) ) {
110
+ // Save component's state and setState() function to our record for future
111
+ // time-travel state changing. Add record index to snapshot so we can retrieve.
112
+ index = componentActionsRecord . saveNew ( stateNode . state , stateNode ) ;
113
+ tree . appendChild ( stateNode . state , elementType . name , index ) ; // Add component to tree
114
+ } else {
115
+ // grab stateless components here
144
116
}
145
117
146
- // Check if the component uses hooks
147
- if (
148
- memoizedState
149
- && Object . hasOwnProperty . call ( memoizedState , 'baseState' )
150
- ) {
151
- // 'catch-all' for suspense elements (experimental)
152
- if ( typeof elementType . $$typeof === 'symbol' ) return ;
153
- // Traverse through the currentFiber and extract the getters/setters
154
- astHooks = astParser ( elementType ) ;
155
- saveState ( astHooks ) ;
156
- // Create a traversed property and assign to the evaluated result of
157
- // invoking traverseHooks with memoizedState
158
- memoizedState . traversed = traverseHooks ( memoizedState ) ;
159
- nextTree = tree . appendChild ( memoizedState ) ;
118
+ // Check if node is a hooks function
119
+ if ( memoizedState && ( tag === 0 || tag === 1 || tag === 10 ) ) {
120
+ if ( memoizedState . queue ) {
121
+ const hooksComponents = traverseHooks ( memoizedState ) ;
122
+ hooksComponents . forEach ( c => {
123
+ if ( elementType . name ) {
124
+ index = componentActionsRecord . saveNew ( c . state , c . component ) ;
125
+ tree . appendChild ( c . state , elementType . name ? elementType . name : 'nameless' , index ) ;
126
+ }
127
+ } ) ;
128
+ }
160
129
}
161
130
162
131
// Recurse on siblings
163
132
createTree ( sibling , tree ) ;
164
133
// Recurse on children
165
- createTree ( child , nextTree ) ;
134
+ if ( tree . children . length > 0 ) {
135
+ createTree ( child , tree . children [ 0 ] ) ;
136
+ } else {
137
+ createTree ( child , tree ) ;
138
+ }
166
139
167
140
return tree ;
168
141
}
169
142
170
143
// ! BUG: skips 1st hook click
171
- async function updateSnapShotTree ( ) {
172
- let current ;
144
+ function updateSnapShotTree ( ) {
145
+ /* let current;
173
146
// If concurrent mode, grab current.child
174
147
if (concurrent) {
175
148
// we need a way to wait for current child to populate
176
149
const promise = new Promise((resolve, reject) => {
177
150
setTimeout(() => resolve(fiberRoot.current.child), 400);
178
151
});
179
-
180
152
current = await promise;
181
-
182
153
current = fiberRoot.current.child;
183
154
} else {
184
155
current = fiberRoot.current;
185
- }
156
+ } */
157
+ const { current } = fiberRoot ; // Carlos: get rid of concurrent mode for now
186
158
187
- snap . tree = createTree ( current ) ;
159
+ // console.log('FIBER COMMITTED, new fiber is:', util.inspect(current, false, 4));
160
+ // fs.appendFile('fiberlog.txt', util.inspect(current, false, 10));
161
+ snap . tree = createTree ( current ) ; // Carlos: pass new hooks state here?
188
162
}
189
163
190
164
return async container => {
@@ -199,15 +173,30 @@ module.exports = (snap, mode) => {
199
173
} = container ;
200
174
// Only assign internal root if it actually exists
201
175
fiberRoot = _internalRoot || _reactRootContainer ;
202
- console . log ( 'linkFiber.js, fiberRoot:' , fiberRoot ) ;
176
+ // console.log('_reactRootContainer is:', _reactRootContainer);
177
+ // console.log('linkFiber.js, fiberRoot:', fiberRoot);
203
178
}
204
-
205
- await updateSnapShotTree ( ) ;
179
+ const devTools = window . __REACT_DEVTOOLS_GLOBAL_HOOK__ ;
180
+ const reactInstance = devTools ? devTools . renderers . get ( 1 ) : null ;
181
+ const overrideHookState = reactInstance ? reactInstance . overrideHookState : null ;
182
+ console . log ( 'DEVTOOLS:' , devTools ) ;
183
+ console . log ( 'roots:' , reactInstance . getCurrentFiber ( ) )
184
+
185
+ if ( reactInstance && reactInstance . version ) {
186
+ devTools . onCommitFiberRoot = ( function ( original ) {
187
+ return function ( ...args ) {
188
+ fiberRoot = args [ 1 ] ;
189
+ updateSnapShotTree ( ) ;
190
+ sendSnapshot ( ) ;
191
+ return original ( ...args ) ;
192
+ } ;
193
+ } ( devTools . onCommitFiberRoot ) ) ;
194
+ }
195
+ updateSnapShotTree ( ) ;
206
196
// Send the initial snapshot once the content script has started up
207
197
// This message is sent from contentScript.js in chrome extension bundles
208
198
window . addEventListener ( 'message' , ( { data : { action } } ) => {
209
199
if ( action === 'contentScriptStarted' ) {
210
- console . log ( 'linkFiber.js received contentScriptStarted message, sending snapshot' ) ;
211
200
sendSnapshot ( ) ;
212
201
}
213
202
} ) ;
0 commit comments