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
55 changes: 18 additions & 37 deletions src/app/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class App extends Component {
isRecording: true,
};

this.port = null;
this.addActionToView = this.addActionToView.bind(this);
this.toTheFuture = this.toTheFuture.bind(this);
this.toThePast = this.toThePast.bind(this);
Expand All @@ -52,11 +53,13 @@ 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((portFromExtension) => {
this.port = portFromExtension;

portFromExtension.onMessage.addListener((msg) => {
const newData = {
action: msg.action,
state: msg.state,
id: this.state.length,
id: this.state.data.length,
};
this.setState((state) => ({
data: [...state.data, newData]
Expand All @@ -67,15 +70,13 @@ class App extends Component {

// functionality to change 'play' button to 'stop'
setIsPlaying() {
console.log('setIsPlaying:', this.state.isPlaying)
let { isPlaying } = this.state;
isPlaying = !isPlaying;
this.setState({ isPlaying });
}

// functionality to change 'record' button to 'pause'
setIsRecording() {
console.log('setIsRecording:', this.state.isRecording)
this.setState(state => ({
isRecording: !state.isRecording,
}));
Expand All @@ -96,41 +97,21 @@ class App extends Component {


// function to travel to the FUTURE
toTheFuture(e) {
if (this.state.action) {
for (let i = 0; i < data.length - 1; i += 1) {
// clicking next returns next piece of data
if (data[i].id === this.state.id) {
const { action, id, payload, state } = data[i + 1];
this.setState({action, id, payload, state});
}
// if we're at the last action stop there
// don't let user go any further
if (data[i].id === undefined) {
const { action, id, payload, state } = data[data.length -1 ];
this.setState({action, id, payload, state});
}
}
}
toTheFuture() {
if (!this.port) return console.error('No connection on stored port.');
this.port.postMessage({
type: 'TIMETRAVEL',
direction: 'forward',
});
}

// function to travel to the PAST
toThePast(e) {
if (this.state.action) {
for (let i = data.length - 1; i >= 0; i -= 1) {
// clicking next returns next piece of data
if (data[i].id === this.state.id) {
const { action, id, payload, state } = data[i - 1];
this.setState({action, id, payload, state});
}
// if we're at the last action stop there
// don't let user go any further
if (this.state.action === undefined) {
const { action, id, payload, state } = data[0];
this.setState({action, id, payload, state});
}
}
}
toThePast() {
if (!this.port) return console.error('No connection on stored port.');
this.port.postMessage({
type: 'TIMETRAVEL',
direction: 'backwards',
});
}

render() {
Expand All @@ -153,7 +134,7 @@ class App extends Component {
left={
(
<Events
data={data}
data={data}
addAction={this.addActionToView}
toTheFuture={this.toTheFuture}
toThePast={this.toThePast}
Expand All @@ -176,6 +157,6 @@ class App extends Component {
</>
);
}
}
}

export default App;
7 changes: 0 additions & 7 deletions src/app/styles/Events.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,3 @@ export const TimeTravelContainer = styled.div`
display: flex;
justify-content: space-evenly;
`;

export const TimeTravelContainer = styled.div`
border-top: 1px solid white;
padding-top: 5%;
display: flex;
justify-content: space-evenly;
`;
19 changes: 7 additions & 12 deletions src/browser/chrome/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,22 @@ const port = chrome.runtime.connect({
name: 'Injected-Background Connection',
});

port.onMessage.addListener((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;

switch (msg.data.type) {
case 'DISPATCH':
port.postMessage(msg.data.data);
break;
case 'EFFECT':
console.log('Received effect: ', msg.data);
break;
default:
console.log('default');
}
// if (!status.isAppTurnedOn) return;
if (msg.data.type === 'DISPATCH') port.postMessage(msg.data.data);
});
});
10 changes: 9 additions & 1 deletion src/browser/chrome/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>", "http://*/*", "https://*/*"],
"js": ["scripts/inject_script_tags.js"],
"run_at":"document_start"
}
],
"permissions": [
"<all_urls>",
"tabs",
Expand All @@ -17,7 +24,8 @@
"webRequestBlocking"
],
"web_accessible_resources": [

"scripts/linked_list.js",
"scripts/time_travel.js"
],
"devtools_page": "devtools.html"
}
15 changes: 15 additions & 0 deletions src/browser/chrome/scripts/inject_script_tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// We add a <script> tag to the DOM with our script files as the src attribute.
// We need this because our content-injected scripts are executed in an "isolated
// world" environment. BUT for the scripts below, we want the edited-React libraries
// to have access to the their functionalities.
const linkedListScript = document.createElement('script');
linkedListScript.src = chrome.runtime.getURL('scripts/linked_list.js');
(document.head || document.documentElement).appendChild(linkedListScript);

const timeTravelScript = document.createElement('script');
timeTravelScript.src = chrome.runtime.getURL('scripts/time_travel.js');
(document.head || document.documentElement).appendChild(timeTravelScript);

linkedListScript.onload = timeTravelScript.onload = function removeScriptTag() {
this.remove();
};
29 changes: 29 additions & 0 deletions src/browser/chrome/scripts/linked_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class DoublyLinkedListNode {
constructor(value, next = null, previous = null) {
this.value = value;
this.next = next;
this.previous = previous;
}
}

class DoublyLinkedList {
constructor() {
this.tail = null;
this.current = null;
}

append(fiberNode) {
const newDLLNode = new DoublyLinkedListNode(fiberNode);

if (!this.head) {
this.tail = newDLLNode;
this.current = newDLLNode;
} else {
this.tail.next = newDLLNode;
newDLLNode.previous = this.tail;
this.tail = newDLLNode;
}

return this;
}
}
109 changes: 109 additions & 0 deletions src/browser/chrome/scripts/time_travel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
let timeTravelTracker = [];
let timeTravelTrackerIndex = null;

// TODO: we need a function to get a reference to the root container instance.
const root2 = document.querySelector('#content');

function timeTravel(direction) {
if (timeTravelTrackerIndex === null) {
// First time we call this function. We need to remove the PLACEMENT entry
// from position [0]. This is originated from the initial mount of the App.
timeTravelTracker = timeTravelTracker.slice(1);
timeTravelTrackerIndex = timeTravelTracker.length - 1;
}

const diff = direction === 'forward' ? 1 : -1;

if ((diff === 1 && timeTravelTrackerIndex === timeTravelTracker.length - 1)
|| (diff === -1 && timeTravelTrackerIndex === 0)) {
return;
}

if (diff === 1 && timeTravelTrackerIndex !== 0) timeTravelTrackerIndex += 1;

while (true) {
console.log('doing work for ', timeTravelTrackerIndex);
const { primaryEffectTag, effect } = timeTravelTracker[timeTravelTrackerIndex];

switch(primaryEffectTag) {
case 'UPDATE': {
const { current } = timeTravelTracker[timeTravelTrackerIndex];

// if we are moving forwards, we need to commitWork() the same
// way the function was originally called.
if (diff === 1) {
commitWork(current, effect);
break;
}

// TODO: hacky solution. There must be some other edge-cases, so
// we should perform this check in a more generalized way.
if (effect.tag !== 6) {
// if the fiberNode is a HostText (tag = 6), the effect does NOT
// have a updateQueue. Otherwise, we need to get the .updateQueue
// value that represents this backwards transformation. This value
// is returned by the prepareUpdate function.
const rootContainerInstance = root2;
const payload = prepareUpdate(
effect.stateNode,
effect.type,
effect.memoizedProps,
current.memoizedProps,
rootContainerInstance,
{},
);

current.updateQueue = payload;
}

commitWork(effect, current);
break;
}
case 'PLACEMENT': {
if (diff === 1) {
commitPlacement(effect);
} else {
// commitDeletion() will call unmountHostComponents(), which recursively
// deletes all host nodes from the parent. This means that
// effect.return = null. BUT we need that reference for later calls
// on commitPlacement() on this same node. This is why we need to clone
// the effect fiberNode and call commitDeletion() on that instead.
const effectCopy = _.cloneDeep(effect);
commitDeletion(effectCopy);
}
break;
}
case 'DELETION': {
if (diff === 1) {
// Refer to case 'PLACEMENT' as to why we need to clone.
const effectCopy = _.cloneDeep(effect);
commitDeletion(effectCopy);
} else {
commitPlacement(effect);
}
break;
}
default:
break;
}

// break points for the while loop
if (timeTravelTrackerIndex + diff === timeTravelTracker.length
|| (diff === -1 && timeTravelTrackerIndex === 0)
|| (diff === 1 && timeTravelTracker[timeTravelTrackerIndex].actionDispatched)) {
return;
}

timeTravelTrackerIndex += diff;

if (diff === -1 && timeTravelTracker[timeTravelTrackerIndex].actionDispatched) {
return;
}
}
}

window.addEventListener('message', (msg) => {
if (msg.data.type === 'TIMETRAVEL') {
timeTravel(msg.data.direction);
}
});