Skip to content

Commit 9f205e2

Browse files
committed
[frontend] route devtools content through scramjet
1 parent 237a785 commit 9f205e2

File tree

5 files changed

+135
-50
lines changed

5 files changed

+135
-50
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "dreamlandjs"]
22
path = dreamlandjs
33
url = https://github.com/MercuryWorkshop/dreamlandjs
4+
[submodule "chii"]
5+
path = chii
6+
url = https://github.com/liriliri/chii

chii

Submodule chii added at a191f45

frontend/public/sw.js

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,39 +23,58 @@ self.addEventListener("message", ({ data }) => {
2323
}
2424
});
2525

26-
// scramjet.addEventListener("request", (e) => {
27-
// if (playgroundData && e.url.href.startsWith(playgroundData.origin)) {
28-
// const headers = {};
29-
// const origin = playgroundData.origin;
30-
// if (e.url.href === origin + "/") {
31-
// headers["content-type"] = "text/html";
32-
// e.response = new Response(playgroundData.html, {
33-
// headers,
34-
// });
35-
// } else if (e.url.href === origin + "/style.css") {
36-
// headers["content-type"] = "text/css";
37-
// e.response = new Response(playgroundData.css, {
38-
// headers,
39-
// });
40-
// } else if (e.url.href === origin + "/script.js") {
41-
// headers["content-type"] = "application/javascript";
42-
// e.response = new Response(playgroundData.js, {
43-
// headers,
44-
// });
45-
// } else {
46-
// e.response = new Response("empty response", {
47-
// headers,
48-
// });
49-
// }
50-
// e.response.rawHeaders = headers;
51-
// e.response.rawResponse = {
52-
// body: e.response.body,
53-
// headers: headers,
54-
// status: e.response.status,
55-
// statusText: e.response.statusText,
56-
// };
57-
// e.response.finalURL = e.url.toString();
58-
// } else {
59-
// return;
60-
// }
61-
// });
26+
scramjet.addEventListener("request", (e) => {
27+
if (e.url.href.startsWith("https://fake-devtools.invalid")) {
28+
// route the fake origin devtools requests to the local static files
29+
e.response = (async () => {
30+
let response = await fetch("/chi/" + e.url.pathname);
31+
32+
let rawHeaders = {};
33+
for (const [key, value] of response.headers.entries()) {
34+
rawHeaders[key.toLowerCase()] = value;
35+
}
36+
response.rawHeaders = rawHeaders;
37+
response.rawResponse = {
38+
body: response.body,
39+
headers: rawHeaders,
40+
status: response.status,
41+
statusText: response.statusText,
42+
};
43+
response.finalURL = e.url.toString();
44+
return response;
45+
})();
46+
} else if (playgroundData && e.url.href.startsWith(playgroundData.origin)) {
47+
const headers = {};
48+
const origin = playgroundData.origin;
49+
if (e.url.href === origin + "/") {
50+
headers["content-type"] = "text/html";
51+
e.response = new Response(playgroundData.html, {
52+
headers,
53+
});
54+
} else if (e.url.href === origin + "/style.css") {
55+
headers["content-type"] = "text/css";
56+
e.response = new Response(playgroundData.css, {
57+
headers,
58+
});
59+
} else if (e.url.href === origin + "/script.js") {
60+
headers["content-type"] = "application/javascript";
61+
e.response = new Response(playgroundData.js, {
62+
headers,
63+
});
64+
} else {
65+
e.response = new Response("empty response", {
66+
headers,
67+
});
68+
}
69+
e.response.rawHeaders = headers;
70+
e.response.rawResponse = {
71+
body: e.response.body,
72+
headers: headers,
73+
status: e.response.status,
74+
statusText: e.response.statusText,
75+
};
76+
e.response.finalURL = e.url.toString();
77+
} else {
78+
return;
79+
}
80+
});

frontend/src/Tab.tsx

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -113,24 +113,85 @@ export class Tab extends StatefulClass {
113113
}
114114

115115
function injectDevtools(client: ScramjetClient, tab: Tab) {
116-
const devtoolsUrl = "/chi";
117-
let devtoolsScript = document.createElement("script");
116+
// right here there's three contexts, the main window, the page frame, and the empty devtools frame
117+
// chobitsu is injected into the page frame (loaded from https://fake-devtools.invalid/target.js), and then will try and inject
118+
// itself into the devtools frame through the `ChiiDevtoolsIframe` global variable
119+
// so first we need to make sure that the devtools frame is in a scramjet context so that the page frame will use proxied apis
120+
// then we set up communication between the devtools frame and the page frame
121+
// then, hand off the devtools frame to the page frame so that it can inject itself into it, but cannot use the globals to escape
122+
123+
// we're loading the entire bundle through scramjet so that eval() will be rewritten
124+
// to start, apply the same hack as window.open to scramjetify the devtools frame
125+
const contentwindow = tab.devtoolsFrame!.contentWindow;
126+
const ctor: any = client.constructor;
127+
const devtoolsFrameClient: ScramjetClient = new ctor(contentwindow);
128+
// TODO: move this to frame creation
129+
scramjet.createFrame(tab.devtoolsFrame!);
130+
devtoolsFrameClient.hook();
131+
// the fake origin is defined in sw.js
132+
const devtoolsUrl = "https://fake-devtools.invalid";
133+
// make sure to create the element through the proxied document
134+
let devtoolsScript = client.global.document.createElement("script");
135+
devtoolsScript.setAttribute("src", devtoolsUrl + "/target.js");
118136
devtoolsScript.setAttribute("embedded", "true");
119-
window.addEventListener("message", (event) => {
120-
console.log(event);
121-
// tab.devtoolsFrame?.contentWindow?.postMessage(event.data, event.origin);
122-
// client.natives.call("window.postMessage", client.global, [
123-
// event.data,
124-
// event.origin,
125-
// ]);
126-
client.global.window.postMessage(event.data);
137+
138+
devtoolsFrameClient.frame!.addEventListener("contextInit", () => {
139+
// devtools frontend will try to access the parent window's postMessage
140+
// this won't work, i manually patched it to use this global instead
141+
//@ts-expect-error
142+
tab.devtoolsFrame.contentWindow.parentPostMessage = (
143+
data: any,
144+
origin: string
145+
) => {
146+
if (!JSON.parse(data).method) {
147+
console.error("ignoring some data");
148+
return;
149+
}
150+
151+
// oh god
152+
// i will fix this later
153+
// we're not supposed to be postmessaging from a non-scramjet context into a scramjet one
154+
// so we need to trick the page content's scramjetclient into thinking this window is also a scramjet context
155+
//@ts-expect-error
156+
window[Symbol.for("scramjet client global")] = {
157+
// for our fake scramjet client in the main window, set the origin to the fake devtools url so that the event origin check will suceed
158+
url: { origin: devtoolsUrl },
159+
};
160+
//@ts-expect-error
161+
client.global.window.$scramjet$setrealm({}).postMessage(data);
162+
//@ts-expect-error
163+
delete window[Symbol.for("scramjet client global")];
164+
};
127165
});
128166

129-
// devtoolsScript.setAttribute("cdn", devtoolsUrl);
130-
devtoolsScript.setAttribute("src", devtoolsUrl + "/target.js");
131-
//@ts-expect-error i'm not typing this
132-
client.global.ChiiDevtoolsIframe = tab.devtoolsFrame;
167+
// VERY IMPORTANT: GIVE CHII THE *PROXIED* VERSION OF THE DEVTOOLS FRAME, AND NO REAL CTORS
168+
// this is needed for the interceptors to work - but also stops sbx
169+
//@ts-expect-error
170+
client.global.ChiiDevtoolsIframe = {
171+
contentWindow: devtoolsFrameClient.global,
172+
// TODO this is STILL sbx annoyingly because the functions don't have their ctors intercepted
173+
set src(value) {
174+
devtoolsFrameClient.url = value;
175+
},
176+
get src() {
177+
return devtoolsFrameClient.url;
178+
},
179+
};
133180
client.global.document.head.appendChild(devtoolsScript);
181+
182+
// unproxied version
183+
// const devtoolsUrl = "/chi";
184+
// let devtoolsScript = document.createElement("script");
185+
// devtoolsScript.setAttribute("src", devtoolsUrl + "/target.js");
186+
// devtoolsScript.setAttribute("embedded", "true");
187+
188+
// window.addEventListener("message", (event) => {
189+
// console.log(event.data);
190+
// client.global.window.postMessage(event.data, event.origin);
191+
// });
192+
193+
// client.global.ChiiDevtoolsIframe = tab.devtoolsFrame;
194+
// client.global.document.head.appendChild(devtoolsScript);
134195
}
135196

136197
function copyImageToClipboard(img: HTMLImageElement) {

frontend/src/main.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const scramjet = new ScramjetController({
2121
},
2222
flags: {
2323
rewriterLogs: false,
24+
captureErrors: false,
2425
naiiveRewriter: false,
2526
},
2627
siteFlags: {

0 commit comments

Comments
 (0)