From 0b7962ffa1dcf862ee866f5dd447c9286d681bac Mon Sep 17 00:00:00 2001 From: Victor Varaschin Date: Wed, 20 Mar 2019 18:58:16 -0400 Subject: [PATCH 1/2] Keeps conexion open from App to the original extension script on every refresh --- package.json | 4 +-- src/app/components/App.jsx | 23 ++++++++------- src/browser/chrome/background.js | 46 ++++++++++++++++------------- src/browser/chrome/extension.js | 17 +++-------- src/browser/chrome/manifest.json | 5 +--- src/browser/chrome/page_action.html | 3 +- 6 files changed, 46 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index b102d20..d0dd887 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/app/components/App.jsx b/src/app/components/App.jsx index 4e67cb1..4811088 100644 --- a/src/app/components/App.jsx +++ b/src/app/components/App.jsx @@ -71,7 +71,7 @@ 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 (port.name !== 'injected-app' || this.portToExtension) return; this.portToExtension = port; @@ -90,7 +90,7 @@ 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(); @@ -140,18 +140,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. @@ -260,6 +260,7 @@ class App extends Component { if (isPlayingIndex === 0) return; if (!this.portToExtension) return console.error('No connection on stored port.'); + console.log('Sending timetravel PAST to extension'); this.portToExtension.postMessage({ type: 'TIMETRAVEL', direction: 'backwards', @@ -278,11 +279,12 @@ class App extends Component { resetApp() { if (this.justStartedRecording) { - console.log('not reseting...'); this.justStartedRecording = false; return; } - console.log('reseting...'); + + this.justStartedRecording = false; + this.hasInjectedScript = false; this.setState({ data: [], searchField: '', @@ -294,6 +296,7 @@ class App extends Component { action: {}, state: {}, prevState: {}, + eventTimes: [], }); } diff --git a/src/browser/chrome/background.js b/src/browser/chrome/background.js index e449eff..aea4d85 100644 --- a/src/browser/chrome/background.js +++ b/src/browser/chrome/background.js @@ -2,8 +2,8 @@ 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; @@ -15,19 +15,26 @@ chrome.tabs.onUpdated.addListener((id, info, tab) => { 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') @@ -57,18 +64,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 }); + }); }); }); diff --git a/src/browser/chrome/extension.js b/src/browser/chrome/extension.js index c4c3d40..71c02ef 100644 --- a/src/browser/chrome/extension.js +++ b/src/browser/chrome/extension.js @@ -6,22 +6,13 @@ port.onMessage.addListener((msg) => { // This is where we get messages from the App component. // We get an object { type: 'TIMETRAVEL', direction: 'forward' } + console.log('Got msg to timetravel: ', msg); 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); }); }()); diff --git a/src/browser/chrome/manifest.json b/src/browser/chrome/manifest.json index 36df29a..99b74df 100644 --- a/src/browser/chrome/manifest.json +++ b/src/browser/chrome/manifest.json @@ -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" diff --git a/src/browser/chrome/page_action.html b/src/browser/chrome/page_action.html index 6a9c4c5..40b416a 100644 --- a/src/browser/chrome/page_action.html +++ b/src/browser/chrome/page_action.html @@ -8,7 +8,8 @@ Page Action - + Test - goto google + \ No newline at end of file From 337511f858c381535be1de2dbb8796d16daffdea Mon Sep 17 00:00:00 2001 From: Victor Varaschin Date: Wed, 20 Mar 2019 20:28:49 -0400 Subject: [PATCH 2/2] fixed playback button --- src/app/components/App.jsx | 20 +++++++++++++------- src/browser/chrome/background.js | 3 +-- src/browser/chrome/extension.js | 1 - src/browser/chrome/scripts/parser.js | 9 ++++++--- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/app/components/App.jsx b/src/app/components/App.jsx index 4811088..4797d87 100644 --- a/src/app/components/App.jsx +++ b/src/app/components/App.jsx @@ -71,6 +71,10 @@ 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 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; @@ -91,10 +95,9 @@ class App extends Component { 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], @@ -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 }); @@ -260,7 +265,7 @@ class App extends Component { if (isPlayingIndex === 0) return; if (!this.portToExtension) return console.error('No connection on stored port.'); - console.log('Sending timetravel PAST to extension'); + this.portToExtension.postMessage({ type: 'TIMETRAVEL', direction: 'backwards', @@ -279,7 +284,8 @@ class App extends Component { resetApp() { if (this.justStartedRecording) { - this.justStartedRecording = false; + // hacky: some pages will fire update twice on the background script + setTimeout(() => this.justStartedRecording = false, 50); return; } diff --git a/src/browser/chrome/background.js b/src/browser/chrome/background.js index aea4d85..1b10580 100644 --- a/src/browser/chrome/background.js +++ b/src/browser/chrome/background.js @@ -7,7 +7,7 @@ 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, { @@ -24,7 +24,6 @@ chrome.tabs.onUpdated.addListener((id, info, tab) => { { action: 'refresh_devtool', tabId: tab.id }, 'devtools', ); - }); function handleRequest(request) { diff --git a/src/browser/chrome/extension.js b/src/browser/chrome/extension.js index 71c02ef..d0579a0 100644 --- a/src/browser/chrome/extension.js +++ b/src/browser/chrome/extension.js @@ -6,7 +6,6 @@ port.onMessage.addListener((msg) => { // This is where we get messages from the App component. // We get an object { type: 'TIMETRAVEL', direction: 'forward' } - console.log('Got msg to timetravel: ', msg); window.postMessage(msg); }); diff --git a/src/browser/chrome/scripts/parser.js b/src/browser/chrome/scripts/parser.js index a2375cd..55c54a4 100644 --- a/src/browser/chrome/scripts/parser.js +++ b/src/browser/chrome/scripts/parser.js @@ -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) { @@ -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) {