Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"esprima": "^4.0.1",
"estraverse": "^4.2.0",
"lodash": "^4.17.11",
"lodash.clonedeep": "^4.5.0"
"estraverse": "^4.2.0"
}
}
39 changes: 24 additions & 15 deletions src/app/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ class App extends Component {
// adds listener to the effects that are gonna be sent from
// our edited useReducer from the 'react' library.
chrome.runtime.onConnect.addListener((port) => {
if (port.name !== 'injected-app') return;
// if our port is already open with the extension script,
// we don't want to change this.portToExtension no more. We want
// to keep every instance of the App associated with the specific
// extension script that can communicated with the injected timeTravel.
if (port.name !== 'injected-app' || this.portToExtension) return;

this.portToExtension = port;

Expand All @@ -90,11 +94,10 @@ class App extends Component {
// search field
const { searchField } = this.state;
const newDataActionType = newData.action.type.toLowerCase();

// get the date everytime an action fires and add it to state

const eventTime = Date.now();


// get the date everytime an action fires and add it to state
if (newDataActionType.includes(searchField.toLowerCase())) {
this.setState(state => ({
data: [...state.data, newData],
Expand Down Expand Up @@ -140,18 +143,18 @@ class App extends Component {
}

setIsRecording() {
// This variable will prevent the app from refreshing when we refresh
// the userpage.
this.justStartedRecording = true;
const { isRecording, hasInjectedScript } = this.state;
const { isRecording } = this.state;
this.setState(state => ({
isRecording: !state.isRecording,
}));

// if we are hitting the pause or re-starting the record session
if (isRecording || hasInjectedScript) return;
if (isRecording || this.hasInjectedScript) return;

this.setState({ hasInjectedScript: true });
// This variable will prevent the app from refreshing when we refresh
// the userpage.
this.justStartedRecording = true;
this.hasInjectedScript = true;

// we query the active window so we can send it to the background script
// so it knows on which URL to run our devtool.
Expand All @@ -167,11 +170,13 @@ class App extends Component {
}

actionInPlay() {
const { data, isPlayingIndex, isPlaying } = this.state;
const { data, isPlayingIndex } = this.state;

setTimeout(() => {
this.toTheFuture();
if (isPlaying && isPlayingIndex + 1 < data.length - 1) {
// We CANT deconstruct isPlaying because we want it to be the value
// when this function gets executed - 1000s later.
if (this.state.isPlaying && isPlayingIndex + 1 < data.length - 1) {
this.actionInPlay();
} else {
this.setState({ isPlaying: false });
Expand Down Expand Up @@ -260,6 +265,7 @@ class App extends Component {
if (isPlayingIndex === 0) return;

if (!this.portToExtension) return console.error('No connection on stored port.');

this.portToExtension.postMessage({
type: 'TIMETRAVEL',
direction: 'backwards',
Expand All @@ -278,11 +284,13 @@ class App extends Component {

resetApp() {
if (this.justStartedRecording) {
console.log('not reseting...');
this.justStartedRecording = false;
// hacky: some pages will fire update twice on the background script
setTimeout(() => this.justStartedRecording = false, 50);
return;
}
console.log('reseting...');

this.justStartedRecording = false;
this.hasInjectedScript = false;
this.setState({
data: [],
searchField: '',
Expand All @@ -294,6 +302,7 @@ class App extends Component {
action: {},
state: {},
prevState: {},
eventTimes: [],
});
}

Expand Down
47 changes: 25 additions & 22 deletions src/browser/chrome/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,38 @@ const parseAndGenerate = require('./scripts/parser');
const injectBundleStr = require('./scripts/inject_bundle');

let ports = [];
let interceptedUrl = '';
let reqIndex = 0;
const interceptedURLs = {};

chrome.tabs.onUpdated.addListener((id, info, tab) => {
if (tab.status !== 'complete' || tab.url.startsWith('chrome')) return;

console.log('refresh completed on: ', tab.url);
// active page action button and inject extension.js
chrome.pageAction.show(tab.id);
chrome.tabs.executeScript(null, {
file: 'extension.js',
runAt: 'document_end',
});

chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
interceptedUrl = '';
if (interceptedURLs.hasOwnProperty(tab.url)) {
delete interceptedURLs[tab.url];
reqIndex = 0;
notifyPorts(
{ action: 'refresh_devtool', tabId: tabs[0].id },
'devtools',
);
});
}

notifyPorts(
{ action: 'refresh_devtool', tabId: tab.id },
'devtools',
);
});

function handleRequest(request) {
// TODO: filter the request from the webRequest call.
if (!interceptedUrl.startsWith(request.initiator)) return { cancel: false };
// We check wether or not the URL should have its requests intercepted
let shouldInterceptUrl = false;
Object.keys(interceptedURLs).forEach((url) => {
if (url.startsWith(request.initiator)) shouldInterceptUrl = true;
});
if (!shouldInterceptUrl) return { cancel: false };

if (request.type === 'script' && !request.url.startsWith('chrome')
&& request.frameId === 0 && ((request.url.slice(-3) === '.js')
Expand Down Expand Up @@ -57,18 +63,15 @@ chrome.runtime.onConnect.addListener((port) => {
if (ports) ports.push(port);

port.onMessage.addListener((msg) => {
if (msg.turnOnDevtool) {
interceptedUrl = msg.url;
addScriptInterception();

// after activating our interception script, we refresh the active tab
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.update(tabs[0].id, { url: tabs[0].url });
});

} else {
console.log('Got a msg not turnOnDevtool: ', msg);
}
if (!msg.turnOnDevtool) return;
console.log('got turn on: ', msg);
interceptedURLs[msg.url] = true;
addScriptInterception();

// after activating our interception script, we refresh the active tab
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.update(tabs[0].id, { url: tabs[0].url });
});
});
});

Expand Down
16 changes: 3 additions & 13 deletions src/browser/chrome/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,9 @@
window.postMessage(msg);
});

port.onDisconnect.addListener(() => console.log('Disconecting...'));

window.addEventListener('message', (msg) => {
// TODO: fix comments. Are we gonna receive msgs from reactDOM here??
// When our injected scripts post messages (both from the 'react'
// and 'react-dom'), we receive it here and send it to our app loaded
// on the DevTool. If storage.isAppTurnedOff is false, it means that
// the user started the application, but stopped recording. So even
// though our injected scripts keep posting messages, we don't want to
// send them over to the App anymore.
chrome.storage.sync.get(['isAppTurnedOn'], (status) => {
// if (!status.isAppTurnedOn) return;
if (msg.data.type === 'DISPATCH') port.postMessage(msg.data.data);
});
// When our injected scripts post messages (from useRedute in 'react'),
// we receive it here and send it to our app loaded on the DevTool.
if (msg.data.type === 'DISPATCH') port.postMessage(msg.data.data);
});
}());
5 changes: 1 addition & 4 deletions src/browser/chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@
"tabs",
"storage",
"webRequest",
"webRequestBlocking",
"webNavigation"
"webRequestBlocking"
],
"web_accessible_resources": [
"scripts/linked_list.js",
"scripts/time_travel.js",
"scripts/deepclone_bundle.js"
],
"devtools_page": "devtools.html"
Expand Down
3 changes: 2 additions & 1 deletion src/browser/chrome/page_action.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
</head>
<body>
Page Action
<button id="color_btn">CHANGE COLOR</button>
<a href="http://www.google.com">Test - goto google</a>
<button id="color_btn">CHANGE COLOR!</button>
<script src="page_action.js"></script>
</body>
</html>
9 changes: 6 additions & 3 deletions src/browser/chrome/scripts/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,16 @@ function commitAllHostEffectsReplacement() {
// method names
const USEREDUCER = 'useReducer';
const COMMITALLHOSTEFFECTS = 'commitAllHostEffects';

// library key inside of bundle
const reactLibraryPath = './node_modules/react/cjs/react.development.js';
const reactDOMLibraryPath = './node_modules/react-dom/cjs/react-dom.development.js';

// get replacer method
let injectableUseReducer = esprima.parseScript(useReducerReplacement.toString());

let injectableCommitAllHostEffects = esprima.parseScript(commitAllHostEffectsReplacement.toString());
let injectableCommitAllHostEffects = esprima.parseScript(
commitAllHostEffectsReplacement.toString(),
);

// traverse ast to find method and replace body with our node's body
function traverseTree(replacementNode, functionName, ast) {
Expand All @@ -136,7 +139,7 @@ function traverseBundledTree(replacementNode, functionName, ast, library) {
if (node.key && node.key.value === library) {
if (node.value.body.body[1].type === 'ExpressionStatement') {
if (node.value.body.body[1].expression.callee.name === 'eval') {
// create new ast
// create new ast
const reactLib = esprima.parseScript(node.value.body.body[1].expression.arguments[0].value);
estraverse.traverse(reactLib, {
enter(libNode) {
Expand Down