Skip to content

Commit 7e4f3d6

Browse files
committed
merged gabriela staging
2 parents bb1c7ae + 8738fc8 commit 7e4f3d6

19 files changed

+1179
-471
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ src/extension/build.crx
1010
src/extension/build.pem
1111
bower_components
1212
sandboxes/manual-tests/NextJS/.next
13+
.vscode

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"eslint.enable": true
3+
}

dev-reactime/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import 'regenerator-runtime/runtime';
77
import { exist } from 'acorn-jsx/xhtml';
88

99
// * State snapshot object initialized here
10-
const snapShot = { tree: null };
10+
const snapShot = {
11+
tree: null,
12+
unfilteredTree: null
13+
};
1114

1215

1316
const mode = {

dev-reactime/linkFiber.js

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/* eslint-disable indent */
2+
/* eslint-disable brace-style */
3+
/* eslint-disable comma-dangle */
4+
/**
5+
* This file contains core module functionality.
6+
*
7+
* It exports an anonymous
8+
* @function
9+
* that is invoked on
10+
* @param snap --> Current snapshot
11+
* @param mode --> Current mode (jumping i.e. time-traveling, locked, or paused)
12+
* and @returns a function to be invoked on the rootContainer HTMLElement
13+
*
14+
* @function updateSnapShotTree
15+
* --> Middleware #1: Updates snap object with latest snapshot
16+
*
17+
* @function sendSnapshot
18+
* --> Middleware #2: Gets a copy of the current snap.tree and posts a message to the window
19+
*
20+
* @function changeSetState
21+
* @param component : stateNode property on a stateful class component's FiberNode object
22+
* --> Binds class component setState method to the component
23+
* --> Injects middleware into class component's setState method
24+
*
25+
* @function changeUseState
26+
* @param component : memoizedState property on a stateful functional component's FiberNode object
27+
* --> Binds functional component dispatch method to the component
28+
* --> Injects middleware into component's dispatch method
29+
* Note: dispatch is hook equivalent to setState()
30+
*
31+
* @function traverseHooks
32+
* @param memoizedState : memoizedState property on a stateful fctnl component's FiberNode object
33+
* --> Helper function to traverse through memoizedState
34+
* --> Invokes @changeUseState on each stateful functional component
35+
*
36+
* @function createTree
37+
* @param currentFiber : a FiberNode object
38+
* --> Recursive function to traverse from FiberRootNode and create
39+
* an instance of custom Tree class and build up state snapshot
40+
*/
41+
42+
/* eslint-disable no-underscore-dangle */
43+
/* eslint-disable func-names */
44+
/* eslint-disable no-use-before-define */
45+
/* eslint-disable no-param-reassign */
46+
47+
const Tree = require('./tree');
48+
const componentActionsRecord = require('./masterState');
49+
50+
module.exports = (snap, mode) => {
51+
let fiberRoot = null;
52+
53+
async function sendSnapshot() {
54+
// Don't send messages while jumping or while paused
55+
if (mode.jumping || mode.paused) return;
56+
console.log('PAYLOAD: before cleaning', snap.tree);
57+
const payload = snap.tree.cleanTreeCopy();// snap.tree.getCopy();
58+
console.log('PAYLOAD: after cleaning', payload);
59+
try {
60+
await window.postMessage({
61+
action: 'recordSnap',
62+
payload,
63+
});
64+
} catch (e) {
65+
console.log('failed to send postMessage:', e);
66+
}
67+
}
68+
69+
// Carlos: Injects instrumentation to update our state tree every time
70+
// a hooks component changes state
71+
function traverseHooks(memoizedState) {
72+
const hooksStates = [];
73+
while (memoizedState && memoizedState.queue) {
74+
// Carlos: these two are legacy comments, we should look into them later
75+
// prevents useEffect from crashing on load
76+
// if (memoizedState.next.queue === null) { // prevents double pushing snapshot updates
77+
if (memoizedState.memoizedState) {
78+
console.log('memoizedState in traverseHooks is:', memoizedState);
79+
hooksStates.push({
80+
component: memoizedState.queue,
81+
state: memoizedState.memoizedState,
82+
});
83+
}
84+
// console.log('GOT STATE', memoizedState.memoizedState);
85+
memoizedState = memoizedState.next !== memoizedState
86+
? memoizedState.next : null;
87+
}
88+
return hooksStates;
89+
}
90+
91+
// Carlos: This runs after EVERY Fiber commit. It creates a new snapshot,
92+
//
93+
function createTree(currentFiber, tree = new Tree('root')) {
94+
// Base case: child or sibling pointed to null
95+
if (!currentFiber) return null;
96+
if (!tree) return tree;
97+
98+
// These have the newest state. We update state and then
99+
// called updateSnapshotTree()
100+
const {
101+
sibling,
102+
stateNode,
103+
child,
104+
memoizedState,
105+
elementType,
106+
tag,
107+
actualDuration,
108+
actualStartTime,
109+
selfBaseDuration,
110+
treeBaseDuration,
111+
} = currentFiber;
112+
113+
let newState = null;
114+
let componentData = {};
115+
let componentFound = false;
116+
117+
// Check if node is a stateful setState component
118+
if (stateNode && stateNode.state && (tag === 0 || tag === 1)) {
119+
console.log('in create tree if');
120+
console.log('this is currentFiber from createTree', currentFiber);
121+
// Save component's state and setState() function to our record for future
122+
// time-travel state changing. Add record index to snapshot so we can retrieve.
123+
componentData.index = componentActionsRecord.saveNew(stateNode.state, stateNode);
124+
newState.state = stateNode.state;
125+
componentFound = true;
126+
}
127+
128+
// Check if node is a hooks useState function
129+
let hooksIndex;
130+
if (memoizedState && (tag === 0 || tag === 1 || tag === 10)) {
131+
console.log('in create tree if');
132+
console.log('this is currentFiber from createTree', currentFiber);
133+
if (memoizedState.queue) {
134+
// Hooks states are stored as a linked list using memoizedState.next,
135+
// so we must traverse through the list and get the states.
136+
// We then store them along with the corresponding memoizedState.queue,
137+
// which includes the dispatch() function we use to change their state.
138+
const hooksStates = traverseHooks(memoizedState);
139+
hooksStates.forEach(state => {
140+
hooksIndex = componentActionsRecord.saveNew(state.state, state.component);
141+
if (newState && newState.hooksState) {
142+
newState.hooksState.push([state.state, hooksIndex]);
143+
} else if (newState) {
144+
newState.hooksState = [[state.state, hooksIndex]];
145+
} else {
146+
newState = { hooksState: [[state.state, hooksIndex]] };
147+
}
148+
componentFound = true;
149+
});
150+
}
151+
} else {
152+
console.log('in create tree else');
153+
console.log('this is currentFiber from createTree', currentFiber);
154+
console.log('this is memoizedState from createTree', memoizedState);
155+
// grab stateless components here
156+
if (elementType) {
157+
if (elementType.name) {
158+
tree.appendChild('stateless', elementType.name, index);
159+
} else {
160+
tree.appendChild('stateless', elementType, index);
161+
}
162+
}
163+
}
164+
165+
// This grabs stateless components
166+
if (!componentFound && (tag === 0 || tag === 1)) {
167+
newState = 'stateless';
168+
}
169+
170+
// Adds performance metrics to the component data
171+
componentData = {
172+
...componentData,
173+
actualDuration,
174+
actualStartTime,
175+
selfBaseDuration,
176+
treeBaseDuration,
177+
};
178+
179+
if (componentFound) {
180+
tree.addChild(newState, elementType.name ? elementType.name : elementType, componentData);
181+
} else if (newState === 'stateless') {
182+
tree.addChild(newState, elementType.name ? elementType.name : elementType, componentData);
183+
}
184+
185+
// Recurse on children
186+
if (child) {
187+
// If this node had state we appended to the children array,
188+
// so attach children to the newly appended child.
189+
// Otherwise, attach children to this same node.
190+
if (tree.children.length > 0) {
191+
createTree(child, tree.children[tree.children.length - 1]);
192+
} else {
193+
createTree(child, tree);
194+
}
195+
}
196+
// Recurse on siblings
197+
if (sibling) createTree(sibling, tree);
198+
199+
return tree;
200+
}
201+
202+
// ! BUG: skips 1st hook click
203+
function updateSnapShotTree() {
204+
/* let current;
205+
// If concurrent mode, grab current.child
206+
if (concurrent) {
207+
// we need a way to wait for current child to populate
208+
const promise = new Promise((resolve, reject) => {
209+
setTimeout(() => resolve(fiberRoot.current.child), 400);
210+
});
211+
current = await promise;
212+
current = fiberRoot.current.child;
213+
} else {
214+
current = fiberRoot.current;
215+
} */
216+
const { current } = fiberRoot; // Carlos: get rid of concurrent mode for now
217+
218+
// console.log('FIBER COMMITTED, new fiber is:', util.inspect(current, false, 4));
219+
// fs.appendFile('fiberlog.txt', util.inspect(current, false, 10));
220+
snap.tree = createTree(current); // Carlos: pass new hooks state here?
221+
}
222+
223+
return async container => {
224+
// Point fiberRoot to FiberRootNode
225+
if (container._internalRoot) {
226+
fiberRoot = container._internalRoot;
227+
} else {
228+
const {
229+
_reactRootContainer: { _internalRoot },
230+
_reactRootContainer,
231+
} = container;
232+
// Only assign internal root if it actually exists
233+
fiberRoot = _internalRoot || _reactRootContainer;
234+
}
235+
const devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
236+
console.log('this is devTools', devTools);
237+
const reactInstance = devTools ? devTools.renderers.get(1) : null;
238+
239+
if (reactInstance && reactInstance.version) {
240+
devTools.onCommitFiberRoot = (function (original) {
241+
return function (...args) {
242+
fiberRoot = args[1];
243+
console.log('this is fiberRoot', fiberRoot);
244+
updateSnapShotTree();
245+
sendSnapshot();
246+
return original(...args);
247+
};
248+
}(devTools.onCommitFiberRoot));
249+
}
250+
updateSnapShotTree();
251+
// Send the initial snapshot once the content script has started up
252+
// This message is sent from contentScript.js in chrome extension bundles
253+
window.addEventListener('message', ({ data: { action } }) => {
254+
if (action === 'contentScriptStarted') {
255+
sendSnapshot();
256+
}
257+
});
258+
};
259+
};

dev-reactime/reactWorkTags.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// eslint-disable-next-line import/prefer-default-export
2+
export const reactWorkTags = [
3+
{ 0: 'FunctionComponent' },
4+
{ 1: 'ClassComponent' },
5+
{ 2: 'IndeterminateComponent' },
6+
{ 3: 'HostRoot' }, // Root of a host tree. Could be nested inside another node.
7+
{ 4: 'HostPortal' }, // A subtree. Could be an entry point to a different renderer.
8+
{ 5: 'HostComponent' },
9+
{ 6: 'HostText' },
10+
];

dev-reactime/timeJump.js

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,11 @@ module.exports = (origin, mode) => {
2020
// Carlos: target is past state we are travelling to
2121

2222
function jump(target, originNode = origin.tree) {
23-
console.log('origin (link to current app state) in jump():', origin);
24-
console.log('target state is: ', target);
2523
// Set the state of the origin tree if the component is stateful
2624
if (!target) return;
27-
const component = componentActionsRecord.getComponentByIndex(target.index);
28-
if (component) {
29-
console.log('time-travel component is true');
30-
if (component.setState) {
31-
console.log('time-travel component setState is true');
32-
}
33-
}
34-
25+
if (target.state === 'stateless') target.children.forEach(child => jump(child));
26+
const component = componentActionsRecord.getComponentByIndex(target.componentData.index);
3527
if (component && component.setState) {
36-
console.log('time-travel calling setState', component);
3728
component.setState(prevState => {
3829
Object.keys(prevState).forEach(key => {
3930
if (target.state[key] === undefined) {
@@ -43,16 +34,24 @@ module.exports = (origin, mode) => {
4334
return target.state;
4435
// Iterate through new children after state has been set
4536
}, () => target.children.forEach(child => jump(child)));
46-
} else if (component && component.dispatch) {
47-
// ** not entering here
48-
console.log('time-travel calling dispatch', component);
49-
component.dispatch(target.state);
37+
}
38+
39+
if (target.state.hooksState) {
40+
target.state.hooksState.forEach(hooksState => {
41+
if (component && component.dispatch) {
42+
const hooksComponent = componentActionsRecord.getComponentByIndex(hooksState[1]);
43+
hooksComponent.dispatch(target.state.hooksState[0]);
44+
}
45+
});
5046
target.children.forEach(child => jump(child));
51-
} else {
52-
console.log('time-travel did not call setState nor dispatch', component);
47+
}
48+
49+
if ((!component || !component.state) && !target.state.hooksState) {
5350
target.children.forEach(child => jump(child));
5451
}
5552

53+
54+
5655
/*
5756
if (originNode.component.setState) {
5857
console.log('stateful component jump, originNode: ', originNode.component);

0 commit comments

Comments
 (0)