Skip to content

Commit 90d232d

Browse files
authored
Merge pull request #9 from nus3/web-driver-bidi
Web driver bidi
2 parents 511d561 + 6805123 commit 90d232d

File tree

6 files changed

+326
-0
lines changed

6 files changed

+326
-0
lines changed

src/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ <h1>Top Page</h1>
1616
<li><a href="./scroll-driven/">Scroll driven animations</a></li>
1717
<li><a href="./scheduler-yield/">scheduler.yield</a></li>
1818
<li><a href="./selectlist/">selectlist</a></li>
19+
<li><a href="./web-driver-bidi/">WebDriver BiDi</a></li>
1920
</ul>
2021
</main>
2122
<script type="module" src="./main.ts"></script>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export class EventEmitter {
2+
#listeners;
3+
4+
constructor() {
5+
this.#listeners = new Map();
6+
}
7+
8+
on(eventName, listener) {
9+
if (!this.#listeners.has(eventName)) {
10+
this.#listeners.set(eventName, new Set());
11+
}
12+
this.#listeners.get(eventName).add(listener);
13+
}
14+
15+
off(eventName, listener) {
16+
if (!this.#listeners.has(eventName)) {
17+
return;
18+
}
19+
this.#listeners.get(eventName).delete(listener);
20+
}
21+
22+
emit(eventName, data) {
23+
const listeners = this.#listeners.get(eventName);
24+
if (!listeners) {
25+
return;
26+
}
27+
28+
for (const listener of listeners) {
29+
try {
30+
listener(eventName, data);
31+
} catch (error) {
32+
console.error(`Error in event listener: ${error}`);
33+
}
34+
}
35+
}
36+
}

src/web-driver-bidi/index.html

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>WebDriver BiDi Client | nus3 UI Labs</title>
7+
</head>
8+
<body>
9+
<header>
10+
<h1>WebDriver BiDi Client</h1>
11+
</header>
12+
<main>
13+
<section>
14+
<h2 style="margin-bottom: 0">How to start</h2>
15+
<div class="flow-section">
16+
<ol>
17+
<li>
18+
Start Firefox with --remote-debugging-port
19+
--remote-allow-origins=https://nus3.github.io
20+
</li>
21+
<li>
22+
Input the URL of the Firefox remote debugging websocket
23+
server(Default: localhost:9222)
24+
</li>
25+
<li>Click connect button</li>
26+
</ol>
27+
<ul>
28+
<li>
29+
<a
30+
href="https://wiki.mozilla.org/Firefox/CommandLineOptions#Using_command_line_options"
31+
target="_blank"
32+
rel="noopener noreferrer"
33+
>
34+
How to start Firefox in cli
35+
</a>
36+
</li>
37+
<li>
38+
<a
39+
href="https://firefox-source-docs.mozilla.org/remote/cdp/Usage.html"
40+
target="_blank"
41+
rel="noopener noreferrer"
42+
>
43+
Usage --remote-debugging-port
44+
</a>
45+
</li>
46+
</ul>
47+
</div>
48+
</section>
49+
<section id="form" class="form">
50+
<h2 style="margin: 0">Operate</h2>
51+
<label>
52+
URL
53+
<input
54+
name="url"
55+
type="text"
56+
autocomplete="off"
57+
value="localhost:9222/session"
58+
/>
59+
<button id="connect" type="button">connect</button>
60+
</label>
61+
<button id="navigate" type="button">navigate</button>
62+
</section>
63+
</main>
64+
<section class="result-wrapper">
65+
<h2 style="margin: 0">Traffic</h2>
66+
<div id="result" class="result"></div>
67+
</section>
68+
<footer>
69+
<a href="../">TOP</a>
70+
</footer>
71+
<script type="module" src="./main.js"></script>
72+
</body>
73+
</html>

src/web-driver-bidi/main.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import './style.css'
2+
import { EventEmitter } from "./event-emitter";
3+
4+
let socket = null
5+
let requestId = 1
6+
// TODO: なんでeventEmitterを使ったら動くのか調べるところから
7+
const eventEmitter = new EventEmitter()
8+
9+
function addLog(log) {
10+
const p = document.createElement('p')
11+
p.textContent = log
12+
document.querySelector('#result').appendChild(p)
13+
}
14+
15+
function reset() {
16+
if (socket) {
17+
socket.close()
18+
socket = null
19+
}
20+
document.querySelector('#result').textContent = ''
21+
}
22+
23+
function formatMessage(msg) {
24+
const obj = JSON.parse(msg)
25+
return JSON.stringify(obj, null, 2)
26+
}
27+
28+
function onMessage (e) {
29+
console.log('Message received from server:', e)
30+
eventEmitter.emit('websocket-message', JSON.parse(e.data))
31+
// addLog(`Message received from server: ${formatMessage(e.data)}`)
32+
}
33+
34+
function connect() {
35+
reset()
36+
37+
const input = document.querySelector('input[name="url"]')
38+
let url = ''
39+
if (input.value.includes('/session')) {
40+
url = `ws://${input.value}`
41+
} else {
42+
url = `ws://${input.value}/session`
43+
}
44+
socket = new WebSocket(url)
45+
46+
socket.onopen = (e) => {
47+
console.log('WebSocket connection opened')
48+
// addLog('WebSocket connection opened')
49+
}
50+
socket.onmessage = onMessage
51+
socket.onclose = (e) => {
52+
console.log('WebSocket connection closed')
53+
// addLog('WebSocket connection closed')
54+
}
55+
}
56+
57+
const btn = document.querySelector('#connect')
58+
btn.addEventListener('click', connect)
59+
60+
function sendMessage(msg) {
61+
if (!socket) return null
62+
63+
const id = requestId++
64+
msg.id = id
65+
console.log(`Message sent to server:`, msg)
66+
// addLog(`Message sent to server: ${formatMessage(msg)}`)
67+
socket.send(JSON.stringify(msg))
68+
return id
69+
}
70+
71+
// リモートエンドにコマンドを送り、帰ってきたIDをみて、同一のIDの場合にresolveする
72+
function sendCommand(method, params) {
73+
const id = sendMessage({ method, params })
74+
75+
return new Promise((resolve) => {
76+
const listener = (eventName, data) => {
77+
if (data.id === id) {
78+
eventEmitter.off('websocket-message', listener)
79+
// socket.removeEventListener('message', listener)
80+
resolve(data)
81+
}
82+
}
83+
eventEmitter.on('websocket-message', listener)
84+
// socket.addEventListener('message', listener)
85+
})
86+
}
87+
88+
const navigateBtn = document.querySelector('#navigate')
89+
navigateBtn.addEventListener('click', async () => {
90+
await sendCommand("session.new", { capabilities: {} });
91+
92+
const res = await sendCommand("browsingContext.getTree", {}, {});
93+
console.log('browsingContext.getTree', res);
94+
95+
const context = res.result.contexts[0].context;
96+
await sendCommand("browsingContext.navigate", {
97+
context,
98+
url: "https://example.com",
99+
wait: "complete"
100+
});
101+
})

src/web-driver-bidi/style.css

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
:root {
2+
--primary-color: hsl(160 55% 55%);
3+
--primary-color-opacity: hsl(160 55% 55% / 0.6);
4+
--primary-color-opacity-low: hsl(160 55% 55% / 0.2);
5+
--primary-color-hover-opacity: hsl(160 55% 55% / 0.8);
6+
--secondary-color: hsl(42 100% 70%);
7+
--secondary-color-opacity: hsl(42 100% 70% / 0.2);
8+
--secondary-color-hover-opacity: hsl(42 100% 70% / 0.8);
9+
--secondary-color-opacity-low: hsl(42 100% 70% / 0.2);
10+
--bg-color: hsl(216 18% 16%);
11+
--bg-color-opacity: hsl(216 18% 16% / 0.4);
12+
--bg-light-color: hsl(216 10% 24%);
13+
--button-shadow: 0px 6px 11px 1px rgba(31, 37, 45, 0.6);
14+
--text-color: hsl(0 0% 93%);
15+
}
16+
17+
body {
18+
margin: 0;
19+
background-color: var(--bg-color);
20+
margin: 0;
21+
color: var(--text-color);
22+
padding: 2rem;
23+
}
24+
25+
h1 {
26+
color: var(--primary-color);
27+
}
28+
29+
ul > li {
30+
margin-bottom: 1rem;
31+
}
32+
33+
ul > li:last-child {
34+
margin-bottom: 0;
35+
}
36+
37+
ol > li {
38+
margin-bottom: 1rem;
39+
}
40+
41+
ol > li:last-child {
42+
margin-bottom: 0;
43+
}
44+
45+
button {
46+
border-radius: 0.25rem;
47+
border-width: 1px;
48+
border-style: solid;
49+
border-color: var(--primary-color);
50+
background-color: var(--primary-color-opacity);
51+
cursor: pointer;
52+
color: inherit;
53+
padding: 0.6rem;
54+
font-size: 1.2rem;
55+
}
56+
57+
button:hover {
58+
background-color: var(--primary-color-hover-opacity);
59+
}
60+
61+
footer {
62+
position: fixed;
63+
inset-block-end: 0;
64+
padding: 2rem;
65+
padding-left: 0;
66+
}
67+
68+
a {
69+
color: var(--secondary-color);
70+
}
71+
72+
label {
73+
display: flex;
74+
gap: 0.8rem;
75+
align-items: center;
76+
}
77+
78+
input {
79+
height: 28px;
80+
background-color: var(--secondary-color-opacity-low);
81+
border-radius: 0.25rem;
82+
border-width: 1px;
83+
border-style: solid;
84+
border-color: var(--secondary-color);
85+
color: var(--text-color);
86+
padding: 0.4rem;
87+
font-size: 1.2rem;
88+
}
89+
90+
.flow-section {
91+
display: flex;
92+
gap: 0.6rem;
93+
border-bottom: 2px solid var(--bg-light-color);
94+
margin-bottom: 2rem;
95+
}
96+
97+
.form {
98+
display: flex;
99+
flex-direction: column;
100+
gap: 0.8rem;
101+
border-bottom: 2px solid var(--bg-light-color);
102+
margin-bottom: 2rem;
103+
padding-bottom: 2rem;
104+
}
105+
106+
.result-wrapper {
107+
display: flex;
108+
flex-direction: column;
109+
gap: 0.4rem;
110+
}
111+
112+
.result > p {
113+
margin: 0;
114+
}

vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default defineConfig({
1919
scrollDriven: resolve(root, 'scroll-driven', 'index.html'),
2020
schedulerYield: resolve(root, 'scheduler-yield', 'index.html'),
2121
selectlist: resolve(root, 'selectlist', 'index.html'),
22+
webDriverBidi: resolve(root, 'web-driver-bidi', 'index.html'),
2223
},
2324
},
2425
},

0 commit comments

Comments
 (0)