Skip to content

Commit 7fa00c1

Browse files
committed
[frontend] connect to playwright & implement CDP server
1 parent d8f6ce9 commit 7fa00c1

File tree

6 files changed

+195
-35
lines changed

6 files changed

+195
-35
lines changed

CONTRIBUTOR_LICENSING.md

Lines changed: 0 additions & 33 deletions
This file was deleted.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@ktibow/iconset-ion": "^8.0.13",
1818
"@mercuryworkshop/epoxy-transport": "^2.1.27",
1919
"@mercuryworkshop/scramjet": "workspace:scramjet",
20+
"devtools-protocol": "^0.0.1339468",
2021
"dreamland": "workspace:dreamland",
2122
"html-to-image": "^1.11.13",
2223
"html2canvas": "^1.4.1",

frontend/src/CDP.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
export function startCDP(message: (message: string) => void): CDPServer {
2+
const server = new CDPServer(message);
3+
4+
return server;
5+
}
6+
7+
export class ErrorWithCode extends Error {
8+
code: number;
9+
constructor(code: number, message: string) {
10+
super(message);
11+
this.code = code;
12+
13+
Object.setPrototypeOf(this, new.target.prototype);
14+
}
15+
}
16+
17+
let server: CDPServer;
18+
class CDPServer {
19+
id = 0;
20+
constructor(public sendMessage: (message: string) => void) {
21+
console.log("starting cdp server");
22+
server = this;
23+
}
24+
25+
async message(message: string) {
26+
const msg = JSON.parse(message);
27+
const resultMsg: any = {
28+
id: msg.id,
29+
};
30+
31+
try {
32+
resultMsg.result = await this.callMethod(msg.method, msg.params);
33+
} catch (e) {
34+
console.error("CDP error", e);
35+
if (e instanceof ErrorWithCode) {
36+
resultMsg.error = {
37+
message: e.message,
38+
code: e.code,
39+
};
40+
} else if (e instanceof Error) {
41+
resultMsg.error = {
42+
message: e.message,
43+
};
44+
}
45+
}
46+
console.log(resultMsg);
47+
48+
this.sendMessage(JSON.stringify(resultMsg));
49+
}
50+
51+
emit<T>(method: string, params: T) {
52+
const msg = JSON.stringify({
53+
method,
54+
params,
55+
});
56+
this.sendMessage(msg);
57+
}
58+
59+
async callMethod(method: string, params: any) {
60+
const [domainName, methodName] = method.split(".");
61+
const domain: any = (Scopes as any)[domainName];
62+
if (domain) {
63+
if (domain[methodName]) {
64+
return domain[methodName](params) || {};
65+
}
66+
}
67+
68+
throw Error(`${method} unimplemented`);
69+
}
70+
}
71+
72+
import type Protocol from "devtools-protocol";
73+
import { browser } from "./main";
74+
const Scopes = {
75+
Browser: {
76+
getVersion(): Protocol.Browser.GetVersionResponse {
77+
return {
78+
protocolVersion: "0.0.0",
79+
product: "scramjet",
80+
revision: "0",
81+
userAgent: navigator.userAgent,
82+
jsVersion: "0.0.0",
83+
};
84+
},
85+
setDownloadBehavior(params: Protocol.Browser.SetDownloadBehaviorRequest) {
86+
console.log(params);
87+
},
88+
},
89+
90+
Runtime: {
91+
runIfWaitingForDebugger() {
92+
console.log("?");
93+
},
94+
},
95+
96+
Target: {
97+
async createTarget(
98+
params: Protocol.Target.CreateTargetRequest
99+
): Promise<Protocol.Target.CreateTargetResponse> {
100+
console.log("creating new target");
101+
const tab = browser.newTab(new URL("https://example.com"));
102+
103+
server.emit<Protocol.Target.AttachedToTargetEvent>(
104+
"Target.attachedToTarget",
105+
{
106+
sessionId: "0",
107+
waitingForDebugger: false,
108+
targetInfo: {
109+
browserContextId: "0",
110+
targetId: String(tab.id),
111+
type: "page",
112+
title: "test",
113+
url: "https://google.com",
114+
attached: false,
115+
canAccessOpener: false,
116+
},
117+
}
118+
);
119+
120+
await new Promise((resolve) => setTimeout(resolve, 1000));
121+
122+
return {
123+
targetId: String(tab.id),
124+
};
125+
},
126+
127+
setAutoAttach(params: Protocol.Target.SetAutoAttachRequest) {
128+
console.log("Target.setAutoAttach", params);
129+
},
130+
getTargetInfo(
131+
params: Protocol.Target.GetTargetInfoRequest
132+
): Protocol.Target.GetTargetInfoResponse {
133+
return {
134+
targetInfo: {
135+
targetId: "0",
136+
/**
137+
* List of types: https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/devtools_agent_host_impl.cc?ss=chromium&q=f:devtools%20-f:out%20%22::kTypeTab%5B%5D%22
138+
*/
139+
type: "tab",
140+
title: "test",
141+
url: "https://google.com",
142+
/**
143+
* Whether the target has an attached client.
144+
*/
145+
attached: false,
146+
/**
147+
* Opener target Id
148+
*/
149+
// openerId?: TargetID;
150+
/**
151+
* Whether the target has access to the originating window.
152+
*/
153+
canAccessOpener: false,
154+
/**
155+
* Frame id of originating window (is only set if target has an opener).
156+
*/
157+
// openerFrameId?: Page.FrameId;
158+
// browserContextId?: Browser.BrowserContextID;
159+
/**
160+
* Provides additional details for specific target types. For example, for
161+
* the type of "page", this may be set to "prerender".
162+
*/
163+
// subtype?: string;
164+
},
165+
};
166+
},
167+
},
168+
};

frontend/src/main.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { createMenu } from "./components/Menu";
88
let app = document.getElementById("app")!;
99
import { Shell } from "./components/Shell";
1010
import { App } from "./App";
11+
import { startCDP } from "./CDP";
1112

1213
const { ScramjetController } = $scramjetLoadController();
1314
export const scramjet = new ScramjetController({
@@ -52,6 +53,30 @@ function mount() {
5253
built.addEventListener("contextmenu", (e) => {
5354
e.preventDefault();
5455
});
56+
57+
let playwrightWindow = window.open(
58+
"http://localhost:5013",
59+
"playwright",
60+
"width=400,height=300,left=100,top=100,resizable=yes,scrollbars=no,menubar=no,toolbar=no,location=no,status=no"
61+
)!;
62+
let server = startCDP((message: string) => {
63+
playwrightWindow.postMessage(
64+
{
65+
type: "scramjet$playwrightcdp",
66+
message: message,
67+
},
68+
"*"
69+
);
70+
});
71+
window.addEventListener("message", (event: MessageEvent) => {
72+
if (!event.data || !event.data.type) return;
73+
if (event.data.type != "scramjet$playwrightcdp") return;
74+
75+
server.message(event.data.message);
76+
});
77+
window.addEventListener("beforeunload", () => {
78+
playwrightWindow.close();
79+
});
5580
} catch (e) {
5681
let err = e as any;
5782
app.replaceWith(

pnpm-workspace.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@ packages:
77
- "dreamlandjs"
88
- "chii"
99
- "chobitsu"
10-
- "playwright"

0 commit comments

Comments
 (0)