From c80d006568ab893b7d6f39a3af920b4fbc1451ba Mon Sep 17 00:00:00 2001
From: elliesimens
Date: Mon, 2 Dec 2024 20:10:21 -0500
Subject: [PATCH 1/2] corrected bug where snapshots did not render after idle
time. Implemented KeepAlive API and port reconnection, as well as improving
the duplicate snapshot check by timestamping snapshots rather than checking
intervals
---
README.md | 4 +-
src/app/components/Actions/SwitchApp.tsx | 2 +-
src/app/components/StateRoute/History.tsx | 2 +-
src/backend/controllers/createTree.ts | 1 -
src/backend/controllers/timeJump.ts | 1 +
src/extension/background.js | 98 +++++++++++++++++------
src/extension/contentScript.ts | 5 ++
7 files changed, 84 insertions(+), 29 deletions(-)
diff --git a/README.md b/README.md
index 7534d0384..b12b4a9f0 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@
You can view your application's file structure and click on a snapshot to view
your app's state. State can be visualized in a Component Graph, JSON Tree, or
Performance Graph. Snapshot history can be visualized in the History tab.
-The Web Metrics tab provides some useful metrics for site performance. The accessibility tab
+The Web Metrics tab provides some useful metrics for site performance. The accessibility tab
visualizes an app's accessibility tree per state change.
Snapshots can be compared with the previous snapshot, which can be viewed in Diff mode.
@@ -89,7 +89,7 @@ Download the recorded snapshots as a JSON file and upload them to access state t
-### 🔹 Reconnect and Status
+### 🔹 and Status
If Reactime loses its connection to the tab you're monitoring, simply click the "reconnect" button to resume your work. You'll notice a circle located to the right of the button, which will appear as either red (indicating disconnection) or green (signifying a successful reconnection).
diff --git a/src/app/components/Actions/SwitchApp.tsx b/src/app/components/Actions/SwitchApp.tsx
index 03c2c4757..2f24261c3 100644
--- a/src/app/components/Actions/SwitchApp.tsx
+++ b/src/app/components/Actions/SwitchApp.tsx
@@ -53,4 +53,4 @@ const SwitchAppDropdown = (): JSX.Element => {
);
};
-export default SwitchAppDropdown;
+export default SwitchAppDropdown;
\ No newline at end of file
diff --git a/src/app/components/StateRoute/History.tsx b/src/app/components/StateRoute/History.tsx
index a836fb1c4..a096ad0dd 100644
--- a/src/app/components/StateRoute/History.tsx
+++ b/src/app/components/StateRoute/History.tsx
@@ -101,7 +101,7 @@ function History(props: Record): JSX.Element {
delete newObj.state; // delete the state property
}
if (newObj.stateSnaphot) {
- // if our new object has a stateSnaphot property
+ // if our new object has a stateSnaphot propertys
newObj.stateSnaphot = statelessCleaning(obj.stateSnaphot); // run statelessCleaning on the stateSnapshot
}
diff --git a/src/backend/controllers/createTree.ts b/src/backend/controllers/createTree.ts
index 86f6aee4c..8a26bccaa 100644
--- a/src/backend/controllers/createTree.ts
+++ b/src/backend/controllers/createTree.ts
@@ -223,7 +223,6 @@ export default function createTree(currentFiberNode: Fiber): Tree {
const hooksStates = getHooksStateAndUpdateMethod(memoizedState);
// Obtain variable names by parsing the function definition stored in elementType.
const hooksNames = getHooksNames(elementType.toString());
-
// Intialize state & index:
componentData.hooksState = {};
componentData.hooksIndex = [];
diff --git a/src/backend/controllers/timeJump.ts b/src/backend/controllers/timeJump.ts
index 1f0918b12..788b2dfb5 100644
--- a/src/backend/controllers/timeJump.ts
+++ b/src/backend/controllers/timeJump.ts
@@ -24,6 +24,7 @@ export default function timeJumpInitiation(mode: Status) {
* @param targetSnapshot - The target snapshot to re-render. The payload from index.ts is assigned to targetSnapshot
*/
return async function timeJump(targetSnapshot: Tree): Promise {
+ console.log('Time jump initiated with targetSnapshot:', targetSnapshot); // logging to see if the re-rendering bug lives here
mode.jumping = true;
// Reset mode.navigating
delete mode.navigating;
diff --git a/src/extension/background.js b/src/extension/background.js
index 01d3353dc..f1521f922 100644
--- a/src/extension/background.js
+++ b/src/extension/background.js
@@ -267,14 +267,14 @@ function changeCurrLocation(tabObj, rootNode, index, name) {
async function getActiveTab() {
return new Promise((resolve, reject) => {
- chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
+ chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
if (tabs.length > 0) {
resolve(tabs[0].id);
} else {
- reject(new Error('No active tab'))
+ reject(new Error('No active tab'));
}
});
- })
+ });
}
/*
@@ -307,19 +307,18 @@ chrome.runtime.onConnect.addListener(async (port) => {
portsArr.push(port); // push each Reactime communication channel object to the portsArr
// sets the current Title of the Reactime panel
-/**
- * Sends messages to ports in the portsArr array, triggering a tab change action.
- */
+ /**
+ * Sends messages to ports in the portsArr array, triggering a tab change action.
+ */
function sendMessagesToPorts() {
portsArr.forEach((bg, index) => {
- bg.postMessage({
- action: 'changeTab',
- payload: { tabId: activeTab.id, title: activeTab.title },
- });
+ bg.postMessage({
+ action: 'changeTab',
+ payload: { tabId: activeTab.id, title: activeTab.title },
+ });
});
-}
+ }
-
if (portsArr.length > 0 && Object.keys(tabsObj).length > 0) {
//if the activeTab is not set during the onActivate API, run a query to get the tabId and set activeTab
if (!activeTab) {
@@ -330,8 +329,8 @@ chrome.runtime.onConnect.addListener(async (port) => {
activeTab = tab;
sendMessagesToPorts();
}
- });
- };
+ });
+ }
}
if (Object.keys(tabsObj).length > 0) {
@@ -342,11 +341,32 @@ chrome.runtime.onConnect.addListener(async (port) => {
}
// every time devtool is closed, remove the port from portsArr
+ // port.onDisconnect.addListener((e) => {
+ // for (let i = 0; i < portsArr.length; i += 1) {
+ // if (portsArr[i] === e) {
+ // portsArr.splice(i, 1);
+ // chrome.runtime.sendMessage({ action: 'portDisconnect', port: e.name });
+ // break;
+ // }
+ // }
+ // }); //ellie commented out
port.onDisconnect.addListener((e) => {
+ //ellie added reconnection attempt
for (let i = 0; i < portsArr.length; i += 1) {
if (portsArr[i] === e) {
portsArr.splice(i, 1);
- chrome.runtime.sendMessage({ action: 'portDisconnect', port: e.name });
+ setTimeout(async () => {
+ try {
+ const response = await chrome.runtime.sendMessage({
+ action: 'attemptReconnect',
+ });
+ if (response && response.success) {
+ console.log('Port successfully reconnected');
+ }
+ } catch (error) {
+ console.warn('Port reconnection failed:', error);
+ }
+ }, 1000);
break;
}
}
@@ -431,6 +451,7 @@ chrome.runtime.onConnect.addListener(async (port) => {
// sends new tabs obj to devtools
if (portsArr.length > 0) {
+ console.log('Sending snapshots to frontend:', tabsObj[tabId].snapshots); //ellie
portsArr.forEach((bg) =>
bg.postMessage({
action: 'sendSnapshots',
@@ -438,6 +459,7 @@ chrome.runtime.onConnect.addListener(async (port) => {
tabId,
}),
);
+ console.log('Current tabsObj:', tabsObj); //ellie
}
return true; // return true so that port remains open
@@ -602,10 +624,32 @@ chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
// DUPLICATE SNAPSHOT CHECK
// This may be where the bug is coming from that when Reactime fails to collect
// state. If they happen to take the same actual duration, it won't record the snapshot.
- const previousSnap =
- tabsObj[tabId]?.currLocation?.stateSnapshot?.children[0]?.componentData?.actualDuration;
- const incomingSnap = request.payload.children[0].componentData.actualDuration;
- if (previousSnap === incomingSnap) {
+ // const previousSnap = //ellie commented out
+ // tabsObj[tabId]?.currLocation?.stateSnapshot?.children[0]?.componentData?.actualDuration;
+ // const incomingSnap = request.payload.children[0].componentData.actualDuration;
+ // if (previousSnap === incomingSnap) {
+ // break;
+ // }
+ // ellie added new duplicate snapshot check
+ const isDuplicateSnapshot = (previous, incoming) => {
+ if (!previous || !incoming) return false;
+ const prevData = previous?.componentData;
+ const incomingData = incoming?.componentData;
+
+ // Check if both snapshots have required data
+ if (!prevData || !incomingData) return false;
+
+ const timeDiff = Math.abs(
+ (incomingData.timestamp || Date.now()) - (prevData.timestamp || Date.now()),
+ );
+ return prevData.actualDuration === incomingData.actualDuration && timeDiff < 1000;
+ };
+
+ const previousSnap = tabsObj[tabId]?.currLocation?.stateSnapshot?.children[0];
+ const incomingSnap = request.payload.children[0];
+
+ if (isDuplicateSnapshot(previousSnap, incomingSnap)) {
+ console.warn('Duplicate snapshot detected, skipping');
break;
}
@@ -614,6 +658,7 @@ chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
// don't add anything to snapshot storage if tab is reloaded for the initial snapshot
reloaded[tabId] = false;
} else {
+ console.log('Adding new snapshot to snapshots array.'); //ellie
tabsObj[tabId].snapshots.push(request.payload);
// INVOKING buildHierarchy FIGURE OUT WHAT TO PASS IN
if (!tabsObj[tabId][index]) {
@@ -626,11 +671,12 @@ chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
addedAxSnap = 'emptyAxSnap';
tabsObj[tabId].axSnapshots.push(addedAxSnap);
}
-
+ console.log('Updating hierarchy with new snapshot.'); //ellie
sendToHierarchy(
tabsObj[tabId],
new HistoryNode(tabsObj[tabId], request.payload, addedAxSnap),
);
+ console.log('Updated hierarchy:', tabsObj[tabId].hierarchy); //ellie
}
}
@@ -708,10 +754,14 @@ chrome.tabs.onActivated.addListener((info) => {
/**this setInterval is here to make sure that the app does not stop working even
* if chrome pauses to save energy. There is probably a better solution, but v25 did
* not have time to complete.
- */
- setInterval(() => {
- console.log(activeTab)
- }, 10000);
+ */
+ // setInterval(() => {
+ // console.log(activeTab);
+ // }, 10000); //ellie commented out
+ chrome.runtime.onStartup.addListener(() => {
+ //ellie replaced with this
+ chrome.runtime.setKeepAlive(true);
+ });
if (portsArr.length > 0) {
portsArr.forEach((bg) =>
bg.postMessage({
diff --git a/src/extension/contentScript.ts b/src/extension/contentScript.ts
index b1ae376bf..04738f63e 100644
--- a/src/extension/contentScript.ts
+++ b/src/extension/contentScript.ts
@@ -26,7 +26,12 @@ window.addEventListener('message', (msg) => {
// will send snapshots of the test app's link fiber tree.
const { action }: { action: string } = msg.data;
if (action === 'recordSnap') {
+ // if (isRecording) { //ellie commented out
+ // chrome.runtime.sendMessage(msg.data);
+ // }
if (isRecording) {
+ // ellie added, add timestamp to payload
+ msg.data.payload.children[0].componentData.timestamp = Date.now();
chrome.runtime.sendMessage(msg.data);
}
}
From f54e5348467804e85ac9e16281a61c404423606f Mon Sep 17 00:00:00 2001
From: elliesimens
Date: Mon, 2 Dec 2024 20:29:08 -0500
Subject: [PATCH 2/2] improved comments for the bug fixes and cleaned up the
old, refactored code
---
src/extension/background.js | 39 +++++-----------------------------
src/extension/contentScript.ts | 5 +----
2 files changed, 6 insertions(+), 38 deletions(-)
diff --git a/src/extension/background.js b/src/extension/background.js
index f1521f922..40e4ea5fe 100644
--- a/src/extension/background.js
+++ b/src/extension/background.js
@@ -340,18 +340,9 @@ chrome.runtime.onConnect.addListener(async (port) => {
});
}
- // every time devtool is closed, remove the port from portsArr
- // port.onDisconnect.addListener((e) => {
- // for (let i = 0; i < portsArr.length; i += 1) {
- // if (portsArr[i] === e) {
- // portsArr.splice(i, 1);
- // chrome.runtime.sendMessage({ action: 'portDisconnect', port: e.name });
- // break;
- // }
- // }
- // }); //ellie commented out
+ // Handles port disconnection by removing the disconnected port and attempting reconnection after 1s delay
+ // This prevents permanent connection loss during idle periods or temporary disconnects -ellie
port.onDisconnect.addListener((e) => {
- //ellie added reconnection attempt
for (let i = 0; i < portsArr.length; i += 1) {
if (portsArr[i] === e) {
portsArr.splice(i, 1);
@@ -448,10 +439,8 @@ chrome.runtime.onConnect.addListener(async (port) => {
toggleAxRecord = !toggleAxRecord;
await replaceEmptySnap(tabsObj, tabId, toggleAxRecord);
-
// sends new tabs obj to devtools
if (portsArr.length > 0) {
- console.log('Sending snapshots to frontend:', tabsObj[tabId].snapshots); //ellie
portsArr.forEach((bg) =>
bg.postMessage({
action: 'sendSnapshots',
@@ -459,7 +448,6 @@ chrome.runtime.onConnect.addListener(async (port) => {
tabId,
}),
);
- console.log('Current tabsObj:', tabsObj); //ellie
}
return true; // return true so that port remains open
@@ -621,16 +609,7 @@ chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
break;
}
- // DUPLICATE SNAPSHOT CHECK
- // This may be where the bug is coming from that when Reactime fails to collect
- // state. If they happen to take the same actual duration, it won't record the snapshot.
- // const previousSnap = //ellie commented out
- // tabsObj[tabId]?.currLocation?.stateSnapshot?.children[0]?.componentData?.actualDuration;
- // const incomingSnap = request.payload.children[0].componentData.actualDuration;
- // if (previousSnap === incomingSnap) {
- // break;
- // }
- // ellie added new duplicate snapshot check
+ // DUPLICATE SNAPSHOT CHECK -ellie
const isDuplicateSnapshot = (previous, incoming) => {
if (!previous || !incoming) return false;
const prevData = previous?.componentData;
@@ -658,7 +637,6 @@ chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
// don't add anything to snapshot storage if tab is reloaded for the initial snapshot
reloaded[tabId] = false;
} else {
- console.log('Adding new snapshot to snapshots array.'); //ellie
tabsObj[tabId].snapshots.push(request.payload);
// INVOKING buildHierarchy FIGURE OUT WHAT TO PASS IN
if (!tabsObj[tabId][index]) {
@@ -671,12 +649,10 @@ chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
addedAxSnap = 'emptyAxSnap';
tabsObj[tabId].axSnapshots.push(addedAxSnap);
}
- console.log('Updating hierarchy with new snapshot.'); //ellie
sendToHierarchy(
tabsObj[tabId],
new HistoryNode(tabsObj[tabId], request.payload, addedAxSnap),
);
- console.log('Updated hierarchy:', tabsObj[tabId].hierarchy); //ellie
}
}
@@ -751,15 +727,10 @@ chrome.tabs.onActivated.addListener((info) => {
if (!tab.pendingUrl?.match('^chrome-extension')) {
activeTab = tab;
- /**this setInterval is here to make sure that the app does not stop working even
- * if chrome pauses to save energy. There is probably a better solution, but v25 did
- * not have time to complete.
+ /**this setKeepAlive is here to make sure that the app does not stop working even
+ * if chrome pauses to save energy.
*/
- // setInterval(() => {
- // console.log(activeTab);
- // }, 10000); //ellie commented out
chrome.runtime.onStartup.addListener(() => {
- //ellie replaced with this
chrome.runtime.setKeepAlive(true);
});
if (portsArr.length > 0) {
diff --git a/src/extension/contentScript.ts b/src/extension/contentScript.ts
index 04738f63e..ab8e4d3c7 100644
--- a/src/extension/contentScript.ts
+++ b/src/extension/contentScript.ts
@@ -26,11 +26,8 @@ window.addEventListener('message', (msg) => {
// will send snapshots of the test app's link fiber tree.
const { action }: { action: string } = msg.data;
if (action === 'recordSnap') {
- // if (isRecording) { //ellie commented out
- // chrome.runtime.sendMessage(msg.data);
- // }
if (isRecording) {
- // ellie added, add timestamp to payload
+ // add timestamp to payload for the purposes of duplicate screenshot check in backgroundscript -ellie
msg.data.payload.children[0].componentData.timestamp = Date.now();
chrome.runtime.sendMessage(msg.data);
}