Skip to content

Commit 2f88e98

Browse files
committed
Fully refactor timeJump, snapShot, index & linkFiber to work for timeJump functionlity. Need to review remix compatibility
2 parents 6151e7e + ee1c192 commit 2f88e98

File tree

6 files changed

+115
-169
lines changed

6 files changed

+115
-169
lines changed

src/backend/controllers/createTree/createComponentActionsRecord.ts

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,19 @@ const nextJSDefaultComponent = new Set([
5656
'RouteAnnouncer',
5757
'Portal',
5858
]);
59-
// ------------------------CREATE TREE TO SEND TO FRONT END-------------------
59+
// ------------------------CREATE COMPONENT ACTIONS RECORD----------------------
6060
/**
61-
* This is a recursive function that runs after every Fiber commit using the following logic:
61+
* This is a recursive function that runs after Fiber commit, if user navigating to a new route during jumping. This function performs the following logic:
6262
* 1. Traverse from FiberRootNode
63-
* 2. Create an instance of custom Tree class
64-
* 3. Build a new state snapshot
65-
* Every time a state change is made in the accompanying app, the extension creates a Tree “snapshot” of the current state, and adds it to the current “cache” of snapshots in the extension
63+
* 2. If the component is stateful, extract its update methods & push to the `componentActionRecord` array
6664
* @param currentFiberNode A Fiber object
67-
* @param tree A Tree object, default initialized to an instance given 'root' and 'root'
68-
* @param fromSibling A boolean, default initialized to false
69-
* @return An instance of a Tree object
65+
* @param circularComponentTable A table content visited Fiber nodes
7066
*/
7167
// TODO: Not sure why the ritd need to be outside of the createTree function. Want to put inside, but in case this need to be keep track for front end.
7268
export default function createComponentActionsRecord(
7369
currentFiberNode: Fiber,
7470
circularComponentTable: Set<Fiber> = new Set(),
75-
): Tree {
71+
): void {
7672
// Base Case: if has visited, return
7773
if (circularComponentTable.has(currentFiberNode)) {
7874
return;
@@ -95,20 +91,20 @@ export default function createComponentActionsRecord(
9591
elementType?.render?.name ||
9692
elementType?.name ||
9793
'nameless';
98-
console.log('LinkFiber', {
99-
currentFiberNode,
100-
// tag,
101-
// elementType,
102-
componentName:
103-
elementType?._context?.displayName || //For ContextProvider
104-
elementType?._result?.name || //For lazy Component
105-
elementType?.render?.name ||
106-
elementType?.name ||
107-
elementType,
108-
// memoizedState,
109-
// stateNode,
110-
// _debugHookTypes,
111-
});
94+
// console.log('LinkFiber', {
95+
// currentFiberNode,
96+
// // tag,
97+
// // elementType,
98+
// componentName:
99+
// elementType?._context?.displayName || //For ContextProvider
100+
// elementType?._result?.name || //For lazy Component
101+
// elementType?.render?.name ||
102+
// elementType?.name ||
103+
// elementType,
104+
// // memoizedState,
105+
// // stateNode,
106+
// // _debugHookTypes,
107+
// });
112108

113109
// ---------OBTAIN STATE & SET STATE METHODS FROM CLASS COMPONENT-----------
114110
// Check if node is a stateful class component when user use setState.
@@ -119,20 +115,17 @@ export default function createComponentActionsRecord(
119115
stateNode?.state &&
120116
(tag === ClassComponent || tag === IndeterminateComponent)
121117
) {
122-
// Save component's state and setState() function to our record for future
123-
// time-travel state changing. Add record index to snapshot so we can retrieve.
118+
// Save component setState() method to our componentActionsRecord for use during timeJump
124119
componentActionsRecord.saveNew(stateNode);
125120
}
126121

127122
// --------OBTAIN STATE & DISPATCH METHODS FROM FUNCTIONAL COMPONENT--------
128-
// Check if node is a hooks useState function
123+
// Check if node is a stateful class component when user use setState.
124+
// If user use useState to define/manage state, the state object will be stored in memoizedState.queue => grab the state object stored in the memoizedState.queue
129125
if (
130126
!nextJSDefaultComponent.has(componentName) &&
131127
memoizedState &&
132-
(tag === FunctionComponent ||
133-
// tag === ClassComponent || WE SHOULD NOT BE ABLE TO USE HOOK IN CLASS
134-
tag === IndeterminateComponent ||
135-
tag === ContextProvider) //TODOD: Need to figure out why we need context provider
128+
(tag === FunctionComponent || tag === IndeterminateComponent || tag === ContextProvider) //TODO: Need to figure out why we need context provider
136129
) {
137130
if (memoizedState.queue) {
138131
try {

src/backend/controllers/timeJump.ts

Lines changed: 38 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,98 +7,69 @@ import routes from '../models/routes';
77
import componentActionsRecord from '../models/masterState';
88
import { Status } from '../types/backendTypes';
99
import Tree from '../models/tree';
10-
const circularComponentTable = new Set();
10+
11+
// THIS FILE CONTAINS NECCESSARY FUNCTIONALITY FOR TIME-TRAVEL FEATURE
1112

1213
/**
13-
* This file contains necessary functionality for time-travel feature.
14-
*
15-
* The target snapshot portrays some past state we want to travel to `jump` recursively and setState for any stateful component.
1614
*
17-
* @param mode - The current mode (i.e. jumping, time-traveling, or paused)
18-
* @returns A function that takes a `inputTarget` snapshot and a boolean flag checking for `firstCall`, then invokes `initiateJump` on that target snapshot
15+
* This is a closure function to keep track of mode (jumping or not jumping)
16+
* @param mode - The current mode (i.e. jumping)
17+
* @returns an async function that takes an `targetSnapshot`, then invokes `updateReactFiberTree` based on the state provided within that target snapshot
1918
*
2019
*/
21-
export default function timeJump(mode: Status) {
20+
export default function timeJumpInitiation(mode: Status) {
2221
/**
23-
* The target snapshot to re-render
22+
* This function is to reset jumping mode to false when user hover the mouse over the browser body
2423
*/
25-
let target;
26-
/**
27-
* This function is to aid the removeListener for 'popstate'
28-
*/
29-
// IMPORTANT: DO NOT move this function into return function. This function is out here so that it will not be redefined any time the return function is invoked. This is importatnt for removeEventListener for popstate to work.
30-
const popStateHandler = () => {
31-
console.log('POP STATE');
32-
initiateJump(target, mode);
24+
const resetJumpingMode = (): void => {
25+
console.log('timeJump - STOP JUMPING');
26+
mode.jumping = false;
3327
};
34-
3528
/**
36-
* This function that takes a `inputTarget` snapshot and a boolean flag checking for `firstCall`, then invokes `initiateJump` update browser to match states provided by the `inputTarget`
37-
* @param inputTarget - The target snapshot to re-render. The payload from index.ts is assigned to inputTarget
38-
* @param firstCall - A boolean flag checking for `firstCall`
29+
* This function that takes a `targetSnapshot` then invokes `updateReactFiberTree` to update the React Application on the browser to match states provided by the `targetSnapshot`
30+
* @param targetSnapshot - The target snapshot to re-render. The payload from index.ts is assigned to targetSnapshot
3931
*/
40-
return async (inputTarget: Tree, firstCall = false): Promise<void> => {
32+
return async function timeJump(targetSnapshot: Tree): Promise<void> {
33+
console.log('timeJump - START JUMPING');
4134
// Reset mode.navigating
4235
delete mode.navigating;
43-
// Set target for popStateHandler usage:
44-
target = inputTarget;
45-
// Clearn the circularComponentTable
46-
if (firstCall) circularComponentTable.clear();
47-
// Determine if user is navigating to another route
48-
// NOTE: Inside routes.navigate, if user is navigating, we will invoke history.go, which will go back/forth based on # of delta steps. This will trigger a popstate event. Since history.go is an async method, the event listener is the only way to invoke timeJump after we have arrived at the desirable route.
49-
// const navigating: boolean = routes.navigate(inputTarget.route);
50-
// if (navigating) {
51-
// // Remove 'popstate' listener to avoid duplicate listeners
52-
// removeEventListener('popstate', popStateHandler);
53-
// // To invoke initateJump after history.go is complete
54-
// addEventListener('popstate', popStateHandler);
55-
// } else {
56-
// // Intiate the jump immideately if not navigating
57-
// initiateJump(inputTarget, mode);
58-
// }
59-
await initiateJump(inputTarget, mode);
36+
// Traverse the snapshotTree to update ReactFiberTree
37+
updateReactFiberTree(targetSnapshot).then(() => {
38+
// Remove Event listener for mouse over
39+
removeEventListener('mouseover', resetJumpingMode);
40+
// Since in order to change state, user will need to navigate to browser
41+
// => set an event listener to resetJumpingMode when mouse is over the browser
42+
addEventListener('mouseover', resetJumpingMode, { once: true });
43+
});
6044
};
6145
}
6246

6347
/**
64-
* This function initiates the request for jump and will pause the jump when user moves mouse over the body of the document.
65-
* @param target - The target snapshot to re-render
66-
* @param mode - The current mode (i.e. jumping, time-traveling, or paused)
67-
*/
68-
async function initiateJump(target, mode): Promise<void> {
69-
console.log('JUMP', { jumping: mode.jumping });
70-
console.log('JUMP', { target, componentAction: componentActionsRecord.getAllComponents() });
71-
updateTreeState(target).then(() => {
72-
document.body.onmouseover = () => {
73-
console.log('STOP JUMPING');
74-
mode.jumping = false;
75-
console.log('mouseover');
76-
};
77-
});
78-
}
79-
80-
/**
81-
* This recursive function receives the target snapshot and will update the state of the fiber tree if the component is statefu
82-
* @param target - The target snapshot to re-render
48+
* This recursive function receives the target snapshot from front end and will update the state of the fiber tree if the component is stateful
49+
* @param targetSnapshot - Target snapshot portrays some past state we want to travel to.
50+
* @param circularComponentTable - A table contains visited components
51+
*
8352
*/
84-
async function updateTreeState(target): Promise<void> {
85-
if (!target) return;
53+
async function updateReactFiberTree(
54+
targetSnapshot,
55+
circularComponentTable: Set<any> = new Set(),
56+
): Promise<void> {
57+
if (!targetSnapshot) return;
8658
// Base Case: if has visited, return
87-
if (circularComponentTable.has(target)) {
59+
if (circularComponentTable.has(targetSnapshot)) {
8860
return;
8961
} else {
90-
circularComponentTable.add(target);
62+
circularComponentTable.add(targetSnapshot);
9163
}
92-
// console.log(target.name);
9364
// ------------------------STATELESS/ROOT COMPONENT-------------------------
9465
// Since stateless component has no data to update, continue to traverse its child nodes:
95-
if (target.state === 'stateless' || target.state === 'root') {
96-
target.children.forEach((child) => updateTreeState(child));
66+
if (targetSnapshot.state === 'stateless' || targetSnapshot.state === 'root') {
67+
targetSnapshot.children.forEach((child) => updateReactFiberTree(child, circularComponentTable));
9768
return;
9869
}
9970

10071
// Destructure component data:
101-
const { index, state, hooksIndex, hooksState } = target.componentData;
72+
const { index, state, hooksIndex, hooksState } = targetSnapshot.componentData;
10273
// ------------------------STATEFUL CLASS COMPONENT-------------------------
10374
// Check if it is a stateful class component
10475
// Index can be zero => falsy value => DO NOT REMOVE UNDEFINED
@@ -111,7 +82,7 @@ async function updateTreeState(target): Promise<void> {
11182
(prevState) => state,
11283
);
11384
// Iterate through new children after state has been set
114-
target.children.forEach((child) => updateTreeState(child));
85+
targetSnapshot.children.forEach((child) => updateReactFiberTree(child, circularComponentTable));
11586
return;
11687
}
11788

@@ -129,7 +100,7 @@ async function updateTreeState(target): Promise<void> {
129100
await functionalComponent[i].dispatch(Object.values(hooksState)[i]);
130101
}
131102
// Iterate through new children after state has been set
132-
target.children.forEach((child) => updateTreeState(child));
103+
targetSnapshot.children.forEach((child) => updateReactFiberTree(child));
133104
return;
134105
}
135106
}

src/backend/index.ts

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
*/
1010
// regenerator runtime supports async functionality
1111
import 'regenerator-runtime/runtime';
12-
import linkFiberStart from './routers/linkFiber';
13-
import timeJumpStart from './controllers/timeJump';
12+
import linkFiberInitialization from './routers/linkFiber';
13+
import timeJumpInitialization from './controllers/timeJump';
1414
import { Snapshot, Status, MsgData } from './types/backendTypes';
1515
import componentActionsRecord from './models/masterState';
1616
import routes from './models/routes';
@@ -26,37 +26,31 @@ const mode: Status = {
2626
};
2727

2828
// linkFiber is now assigned the default function exported from the file linkFiber.ts
29-
console.log('Index ts', { snapShot: JSON.parse(JSON.stringify(snapShot)) });
30-
const linkFiber = linkFiberStart(snapShot, mode);
29+
30+
console.log('Index ts Initiation');
31+
const linkFiber = linkFiberInitialization(snapShot, mode);
3132
// timeJump is now assigned the default function exported from the file timeJump.ts
32-
const timeJump = timeJumpStart(mode);
33+
const timeJump = timeJumpInitialization(mode);
3334

3435
// * Event listener for time-travel actions
35-
window.addEventListener('message', ({ data: { action, payload } }: MsgData) => {
36+
window.addEventListener('message', async ({ data: { action, payload } }: MsgData) => {
3637
switch (action) {
3738
case 'jumpToSnap':
38-
console.log('Index ts', { payload });
39-
console.log('Index ts', { componentAction: componentActionsRecord.getAllComponents() });
39+
console.log('Index ts - jumpToSnap', { payload });
4040
// Set mode to jumping to prevent snapShot being sent to frontEnd
41+
// NOTE: mode.jumping is set to false inside the timeJump.ts
4142
mode.jumping = true;
42-
// Check if we need to navigate to another route
43-
const needNavigation: boolean = routes.navigate(payload.route);
43+
// Check if we are navigating to another route
44+
const navigating: boolean = routes.navigate(payload.route);
4445
// If need to navigate
45-
if (needNavigation) {
46-
// Pass timeJump function to mode.jumping => which will be invoked during onCommitFiberRoot:
47-
mode.navigating = () => timeJump(payload, true);
48-
// Navigate to the new route
49-
// Navigate to a new route will cause ReactFiber to re-render
50-
// => this in-turn will invoke onCommitFiberRoot inside linkFiber.ts
51-
// window.location.href = payload.route.url;
52-
} else {
53-
timeJump(payload, true); // * This sets state with given payload
46+
if (navigating) {
47+
// Pass timeJump function to mode.navigating => which will be invoked during onCommitFiberRoot:
48+
mode.navigating = () => timeJump(payload);
49+
}
50+
// If not navitating, invoke timeJump immediately to update React Application FiberTree based on the snapshotTree
51+
else {
52+
await timeJump(payload); // * This sets state with given payload
5453
}
55-
break;
56-
57-
case 'setPause':
58-
console.log('PAUSED');
59-
mode.paused = payload;
6054
break;
6155

6256
default:

src/backend/models/routes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ class Routes {
8585
}
8686

8787
/**
88-
* @method navigate
88+
* This method will perform the following:
89+
* 1. Evaluate if user need to navigate to another route
90+
* 2. If navigation is needed, perform navigation and return true
91+
* 3. Else return false
8992
* @param route - The target route in the `routeHistory` stack that is being navigated to.
9093
* @returns A boolean indicating whether or not a new route was navigated to.
9194
*

src/backend/routers/linkFiber.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,13 @@ import {
2121
FiberRoot,
2222
} from '../types/backendTypes';
2323
import { DevTools } from '../types/linkFiberTypes';
24-
import updateSnapShotTree from './snapShot';
24+
import updateAndSendSnapShotTree from './snapShot';
2525

2626
// throttle returns a function that can be called any number of times (possibly in quick succession) but will only invoke the callback at most once every x ms
2727
// getHooksNames - helper function to grab the getters/setters from `elementType`
2828
import throttle from '../controllers/throttle';
2929
import componentActionsRecord from '../models/masterState';
3030
import createComponentActionsRecord from '../controllers/createTree/createComponentActionsRecord';
31-
import timeJump from '../controllers/timeJump';
3231

3332
// Set global variables to use in exported module and helper functions
3433
declare global {
@@ -68,23 +67,30 @@ export default function linkFiber(snapShot: Snapshot, mode: Status): () => void
6867
/**
6968
* @function throttledUpdateSnapshot - a function that will wait for at least MIN_TIME_BETWEEN_UPDATE ms, before updating the tree snapShot being displayed on the Chrome Extension.
7069
*/
71-
const throttledUpdateSnapshot = throttle((fiberRoot) => {
72-
console.log('RERENDER');
73-
// If jumping cause a navigation to a new route:
74-
if (mode.navigating) {
75-
console.log('OBTAIN NEW UPDATE METHOD');
70+
const throttledUpdateSnapshot = throttle(async (fiberRoot) => {
71+
console.log('linkFiber - RERENDER');
72+
// If not jumping
73+
if (!mode.jumping) {
74+
console.log('linkFiber - SEND SNAPSHOT');
75+
// Update and Send SnapShot tree to front end
76+
updateAndSendSnapShotTree(snapShot, fiberRoot);
77+
}
78+
79+
// If navigating to another route during jumping:
80+
else if (mode.navigating) {
81+
console.log('linkFiber - NAVIGATING');
7682
// Reset the array containing update methods:
7783
componentActionsRecord.clear();
7884
// Obtain new update methods for the current route:
7985
const { current } = fiberRoot;
8086
createComponentActionsRecord(current);
81-
// Invoke timeJump to update reactFiber based on the snapshotTree & the newly obtained BOUND update methods
82-
mode.navigating();
87+
// Invoke timeJump, which is stored in mode.navigating, to update React Application FiberTree based on the snapshotTree
88+
await mode.navigating();
8389
}
84-
// Else if not jumping
85-
else if (!mode.jumping) {
86-
// Update and Send SnapShot tree to front end
87-
updateSnapShotTree(snapShot, mode, fiberRoot);
90+
91+
// Else:
92+
else {
93+
console.log('linkFiber - REACT FIBER TREE UPDATED');
8894
}
8995
}, MIN_TIME_BETWEEN_UPDATE);
9096

@@ -96,8 +102,6 @@ export default function linkFiber(snapShot: Snapshot, mode: Status): () => void
96102
// react devtools global hook is a global object that was injected by the React Devtools content script, allows access to fiber nodes and react version
97103
// Obtain React Devtools Object:
98104
const devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
99-
const { onCommitFiberRoot } = devTools;
100-
console.log('onCommit..', onCommitFiberRoot);
101105
// If React Devtools is not installed, object will be undefined.
102106
if (!devTools) {
103107
return;

0 commit comments

Comments
 (0)