Skip to content

Commit b40c053

Browse files
committed
moved script injection to context menu click event listener
1 parent 4e5f5ec commit b40c053

File tree

8 files changed

+274
-16
lines changed

8 files changed

+274
-16
lines changed

src/app/FrontendTypes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,7 @@ export interface Snapshots {
379379
component3: number;
380380
'all others': number;
381381
}
382+
383+
export interface ErrorContainerProps {
384+
port: chrome.runtime.Port | null;
385+
}

src/app/components/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function App(): JSX.Element {
1515
<ThemeProvider theme={theme}>
1616
<Router>
1717
{/* we wrap our application with the <Router> tag so that all components that are nested will have the react-router context */}
18-
{console.log('App reloaded')}
18+
{console.log('App reloaded', new Date().toLocaleString())}
1919
<MainContainer />
2020
</Router>
2121
</ThemeProvider>
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/* eslint-disable max-len */
2+
import React, { useState, useEffect, useRef } from 'react';
3+
import { launchContentScript } from '../slices/mainSlice';
4+
import Loader from '../components/Loader';
5+
import ErrorMsg from '../components/ErrorMsg';
6+
import { useDispatch, useSelector } from 'react-redux';
7+
import { MainState, RootState, ErrorContainerProps } from '../FrontendTypes';
8+
import { current } from '@reduxjs/toolkit';
9+
/*
10+
This is the loading screen that a user may get when first initalizing the application. This page checks:
11+
12+
1. if the content script has been launched on the current tab
13+
2. if React Dev Tools has been installed
14+
3. if target tab contains a compatible React app
15+
*/
16+
17+
function ErrorContainer(props: ErrorContainerProps): JSX.Element {
18+
const dispatch = useDispatch();
19+
const { tabs, currentTitle, currentTab }: MainState = useSelector(
20+
(state: RootState) => state.main,
21+
);
22+
const [loadingArray, setLoading] = useState([true, true, true]); // We create a local state "loadingArray" and set it to an array with three true elements. These will be used as hooks for error checking against a 'status' object that is declared later in a few lines. 'loadingArray' is used later in the return statement to display a spinning loader icon if it's true. If it's false, either a checkmark icon or an exclamation icon will be displayed to the user.
23+
const titleTracker = useRef(currentTitle); // useRef returns an object with a property 'initialValue' and a value of whatever was passed in. This allows us to reference a value that's not needed for rendering
24+
const timeout = useRef(null);
25+
const { port } = props;
26+
console.log(
27+
'ErrorContainer state variables: tabs status: ',
28+
JSON.stringify(tabs[currentTab]?.status),
29+
'currentTab: ',
30+
currentTab,
31+
'currentTitle: ',
32+
currentTitle,
33+
);
34+
// function that launches the main app
35+
function launch(): void {
36+
dispatch(launchContentScript(tabs[currentTab]));
37+
}
38+
39+
let status = {
40+
// We create a status object that we may use later if tabs[currentTab] exists
41+
contentScriptLaunched: false,
42+
reactDevToolsInstalled: false,
43+
targetPageisaReactApp: false,
44+
};
45+
46+
if (tabs[currentTab]) {
47+
// If we do have a tabs[currentTab] object, we replace the status obj we declared above with the properties of the tabs[currentTab].status
48+
Object.assign(status, tabs[currentTab].status);
49+
}
50+
51+
// hook that sets timer while waiting for a snapshot from the background script, resets if the tab changes/reloads
52+
useEffect(() => {
53+
if (tabs[currentTab])
54+
console.log('ErrorContainer useEffect fired, ', JSON.stringify(tabs[currentTab]?.status));
55+
// We declare a function
56+
function setLoadingArray(i: number, value: boolean) {
57+
// 'setLoadingArray' checks an element in our 'loadingArray' local state and compares it with passed in boolean argument. If they don't match, we update our local state replacing the selected element with the boolean argument
58+
if (loadingArray[i] !== value) {
59+
// this conditional helps us avoid unecessary state changes if the element and the value are already the same
60+
const loadingArrayClone = [...loadingArray];
61+
loadingArrayClone[i] = value;
62+
setLoading(loadingArrayClone);
63+
}
64+
}
65+
66+
if (titleTracker.current !== currentTitle) {
67+
// if the current tab changes/reloads, we reset loadingArray to it's default [true, true, true]
68+
titleTracker.current = currentTitle;
69+
setLoadingArray(0, true);
70+
setLoadingArray(1, true);
71+
setLoadingArray(2, true);
72+
73+
if (timeout.current) {
74+
// if there is a current timeout set, we clear it
75+
clearTimeout(timeout.current);
76+
timeout.current = null;
77+
}
78+
}
79+
80+
if (!status.contentScriptLaunched) {
81+
// if content script hasnt been launched/found, set a timer or immediately update 'loadingArray' state
82+
83+
if (loadingArray[0] === true) {
84+
// if loadingArray[0] is true, then that means our timeout.current is still null so we now set it to a setTimeout function that will flip loadingArray[0] to false after 3 seconds
85+
timeout.current = setTimeout(() => {
86+
setLoadingArray(0, false);
87+
}, 3000); // increased from 1500
88+
}
89+
} else {
90+
setLoadingArray(0, false); // if status.contentScriptLaunched is true, that means timeout.current !== null. This means that useEffect was triggered previously.
91+
}
92+
93+
// The next two if statements are written in a way to allow the checking of 'content script hook', 'reactDevTools check', and 'target page is a react app' to be run in chronological order.
94+
if (loadingArray[0] === false && status.contentScriptLaunched === true) {
95+
timeout.current = setTimeout(() => {
96+
setLoadingArray(1, false);
97+
}, 3000); // increased from 1500
98+
// let devTools;
99+
// while (!devTools) {
100+
// devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
101+
// }
102+
// console.log('ErrorContainer react devtools: ', devTools);
103+
// // If React Devtools is not installed, object will be undefined.
104+
// if (!devTools) return;
105+
// // If React Devtools is installed, send a message to front end.
106+
107+
// console.log('ErrorContainer react devtools check passed');
108+
// status.reactDevToolsInstalled = true;
109+
110+
// console.log('ErrorContainer attempting reinitalize');
111+
// port.postMessage({
112+
// action: 'reinitialize',
113+
// tabId: currentTab,
114+
// });
115+
setLoadingArray(1, false);
116+
}
117+
if (loadingArray[1] === false && status.reactDevToolsInstalled === true) {
118+
setLoadingArray(2, false);
119+
}
120+
121+
// Unload async function when Error Container is unmounted
122+
return () => {
123+
clearTimeout(timeout.current);
124+
};
125+
}, [status, currentTitle, timeout, loadingArray]); // within our dependency array, we're keeping track of if the status, currentTitle/tab, timeout, or loadingArray changes and we re-run the useEffect hook if they do
126+
127+
return (
128+
<div className='error-container'>
129+
<img src='../assets/whiteBlackSquareLogo.png' alt='Reactime Logo' height='50px' />
130+
131+
<h2>Launching Reactime on tab: {currentTitle}</h2>
132+
133+
<div className='loaderChecks'>
134+
<p>Checking if content script has been launched on current tab</p>
135+
<Loader loading={loadingArray[0]} result={status.contentScriptLaunched} />
136+
137+
<p>Checking if React Dev Tools has been installed</p>
138+
<Loader loading={loadingArray[1]} result={status.reactDevToolsInstalled} />
139+
140+
<p>Checking if target is a compatible React app</p>
141+
<Loader loading={loadingArray[2]} result={status.targetPageisaReactApp} />
142+
</div>
143+
144+
<br />
145+
<div className='errorMsg'>
146+
<ErrorMsg loadingArray={loadingArray} status={status} launchContent={launch} />
147+
</div>
148+
<br />
149+
<a
150+
href='https://github.com/open-source-labs/reactime'
151+
target='_blank'
152+
rel='noopener noreferrer'
153+
>
154+
Please visit the Reactime Github for more info.
155+
</a>
156+
</div>
157+
);
158+
}
159+
160+
export default ErrorContainer;

src/app/containers/ErrorContainer.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { launchContentScript } from '../slices/mainSlice';
44
import Loader from '../components/Loader';
55
import ErrorMsg from '../components/ErrorMsg';
66
import { useDispatch, useSelector } from 'react-redux';
7-
import { MainState, RootState } from '../FrontendTypes';
7+
import { MainState, RootState, ErrorContainerProps } from '../FrontendTypes';
8+
import { current } from '@reduxjs/toolkit';
89
/*
910
This is the loading screen that a user may get when first initalizing the application. This page checks:
1011
@@ -13,15 +14,15 @@ This is the loading screen that a user may get when first initalizing the applic
1314
3. if target tab contains a compatible React app
1415
*/
1516

16-
function ErrorContainer(): JSX.Element {
17+
function ErrorContainer(props: ErrorContainerProps): JSX.Element {
1718
const dispatch = useDispatch();
1819
const { tabs, currentTitle, currentTab }: MainState = useSelector(
1920
(state: RootState) => state.main,
2021
);
2122
const [loadingArray, setLoading] = useState([true, true, true]); // We create a local state "loadingArray" and set it to an array with three true elements. These will be used as hooks for error checking against a 'status' object that is declared later in a few lines. 'loadingArray' is used later in the return statement to display a spinning loader icon if it's true. If it's false, either a checkmark icon or an exclamation icon will be displayed to the user.
2223
const titleTracker = useRef(currentTitle); // useRef returns an object with a property 'initialValue' and a value of whatever was passed in. This allows us to reference a value that's not needed for rendering
2324
const timeout = useRef(null);
24-
25+
const { port } = props;
2526
console.log(
2627
'ErrorContainer state variables: tabs status: ',
2728
JSON.stringify(tabs[currentTab]?.status),
@@ -35,7 +36,7 @@ function ErrorContainer(): JSX.Element {
3536
dispatch(launchContentScript(tabs[currentTab]));
3637
}
3738

38-
const status = {
39+
let status = {
3940
// We create a status object that we may use later if tabs[currentTab] exists
4041
contentScriptLaunched: false,
4142
reactDevToolsInstalled: false,
@@ -91,6 +92,26 @@ function ErrorContainer(): JSX.Element {
9192

9293
// The next two if statements are written in a way to allow the checking of 'content script hook', 'reactDevTools check', and 'target page is a react app' to be run in chronological order.
9394
if (loadingArray[0] === false && status.contentScriptLaunched === true) {
95+
timeout.current = setTimeout(() => {
96+
setLoadingArray(1, false);
97+
}, 3000); // increased from 1500
98+
// let devTools;
99+
// while (!devTools) {
100+
// devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
101+
// }
102+
// console.log('ErrorContainer react devtools: ', devTools);
103+
// // If React Devtools is not installed, object will be undefined.
104+
// if (!devTools) return;
105+
// // If React Devtools is installed, send a message to front end.
106+
107+
// console.log('ErrorContainer react devtools check passed');
108+
// status.reactDevToolsInstalled = true;
109+
110+
// console.log('ErrorContainer attempting reinitalize');
111+
// port.postMessage({
112+
// action: 'reinitialize',
113+
// tabId: currentTab,
114+
// });
94115
setLoadingArray(1, false);
95116
}
96117
if (loadingArray[1] === false && status.reactDevToolsInstalled === true) {

src/app/containers/MainContainer.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ function MainContainer(): JSX.Element {
2828
const dispatch = useDispatch();
2929

3030
const { currentTab, tabs, port }: MainState = useSelector((state: RootState) => state.main);
31+
console.log(
32+
'Redux state at render: ',
33+
useSelector((state: RootState) => state.main),
34+
);
3135
console.log(
3236
'MainContainer state at render: tabs: ',
3337
JSON.stringify(tabs[currentTab]?.status),
@@ -183,7 +187,8 @@ function MainContainer(): JSX.Element {
183187
//@ts-ignore
184188
!tabs[currentTab].status.targetPageisaReactApp
185189
) {
186-
return <ErrorContainer />;
190+
// @ts-ignore
191+
return <ErrorContainer port={port} />;
187192
}
188193

189194
const { currLocation, viewIndex, sliderIndex, snapshots, hierarchy, webMetrics } =

src/backend/index.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,45 @@ window.addEventListener('message', async ({ data: { action, payload } }: MsgData
5858
await timeJump(payload); // * This sets state with given payload
5959
}
6060
break;
61+
case 'reinitialize':
62+
// console.log('backend reinitialize received, performing checks again');
63+
// let devTools;
64+
// while (!devTools) {
65+
// devTools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
66+
// }
67+
// console.log('backend react devtools: ', devTools);
68+
// // If React Devtools is not installed, object will be undefined.
69+
// if (!devTools) return;
70+
// // If React Devtools is installed, send a message to front end.
71+
72+
// console.log(
73+
// 'backend react devtools check passed, sending devToolsInstalled to contentScript',
74+
// );
75+
// window.postMessage(
76+
// {
77+
// action: 'devToolsInstalled',
78+
// payload: 'devToolsInstalled',
79+
// },
80+
// '*',
81+
// );
82+
83+
// const reactInstance = devTools.renderers.get(1);
84+
// // If target application is not a React App, this will return undefined.
85+
// if (!reactInstance) {
86+
// return;
87+
// }
88+
// console.log('backend react instance check passed, sending aReactApp to contentScript');
89+
// // If target application is a React App, send a message to front end.
90+
// window.postMessage(
91+
// {
92+
// action: 'aReactApp',
93+
// payload: 'aReactApp',
94+
// },
95+
// '*',
96+
// );
97+
98+
linkFiberInit();
99+
break;
61100
default:
62101
break;
63102
}

src/extension/background.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,14 @@ chrome.runtime.onConnect.addListener((port) => {
280280
chrome.tabs.sendMessage(tabId, msg);
281281
return true;
282282

283+
case 'reinitialize':
284+
console.log(
285+
'background reinitialize message received, forwarding to content script at tabId',
286+
tabId,
287+
);
288+
chrome.tabs.sendMessage(tabId, msg);
289+
return true;
290+
283291
default:
284292
return true;
285293
}
@@ -559,8 +567,6 @@ chrome.contextMenus.onClicked.addListener(({ menuItemId }) => {
559567

560568
// // this was a test to see if I could dynamically set the left property to be the 0 origin of the invoked DISPLAY (as opposed to invoked window).
561569
// // this would allow you to split your screen, keep the browser open on the right side, and reactime always opens at the top left corner.
562-
// // currently, invokedScreenLeft is the left of the invoked window. To get around the issue of reactime covering the refresh button (currently needed for debugging as of 12.19.23), added a vertical offset, topOffset.
563-
// // this just pushes the top down by a fixed amount that is enough to surpass most people's bookmarks bar.
564570
// chrome.system.display.getInfo((displayUnitInfo) => {
565571
// console.log(displayUnitInfo);
566572
// });
@@ -569,25 +575,43 @@ chrome.contextMenus.onClicked.addListener(({ menuItemId }) => {
569575
console.log('onContext click window properties', window);
570576
const invokedScreenHeight = window.height || 1000;
571577
const invokedScreenTop = window.top || 0;
572-
const invokedScreenLeft = -400;
578+
const invokedScreenLeft = window.left || 0;
579+
const invokedScreenWidth = Math.max(Math.trunc(window.width / 2), 1000) || 1000; // set reactime window to half of chrome window, with a min of 1000px
573580
const options = {
574581
type: 'panel',
575582
left: invokedScreenLeft,
576583
top: invokedScreenTop,
577-
width: 1000,
584+
width: invokedScreenWidth,
578585
height: invokedScreenHeight,
579586
url: chrome.runtime.getURL('panel.html'),
580587
};
581588
if (menuItemId === 'reactime') chrome.windows.create(options);
582589
});
583590
//JR 12.20.23
584591
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
585-
console.log('onContext click tab info', tabs);
592+
console.log('onContext click tab info', tabs, new Date().toLocaleString());
586593
if (tabs.length) {
587594
const invokedTab = tabs[0];
588595
const invokedTabId = invokedTab.id;
589596
const invokedTabTitle = invokedTabTitle;
590597
tabsObj[invokedTabId] = createTabObj(invokedTabTitle);
598+
599+
// inject backend script
600+
const injectScript = (file, tab) => {
601+
const htmlBody = document.getElementsByTagName('body')[0];
602+
const script = document.createElement('script');
603+
script.setAttribute('type', 'text/javascript');
604+
script.setAttribute('src', file);
605+
// eslint-disable-next-line prefer-template
606+
htmlBody.appendChild(script);
607+
};
608+
609+
console.log(invokedTabId);
610+
chrome.scripting.executeScript({
611+
target: { tabId: invokedTabId },
612+
function: injectScript,
613+
args: [chrome.runtime.getURL('bundles/backend.bundle.js'), invokedTabId],
614+
});
591615
}
592616
});
593617
});

0 commit comments

Comments
 (0)