Skip to content

Commit fa382ca

Browse files
feat: deepen headed websocket indexeddb and webgl
1 parent 7e6a7ac commit fa382ca

18 files changed

+1288
-45
lines changed

docs/FULL_BROWSER_MASTER_TRACKER.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ Current state inside Gate 1:
242242
definitions and indexed lookups persistent across tabs and browser restart,
243243
with focused DOM tests plus a bounded headed probe proving indexed entries
244244
survive restart and can still be read back by index name and key
245+
- that same headed IndexedDB path now also supports object-store and index
246+
cursor iteration, with focused DOM tests plus a bounded headed cross-tab
247+
probe proving seeded cursor rows can be read back in sorted order from a
248+
sibling tab through both `objectStore.openCursor()` and `index.openCursor()`
245249
- headed `fetch(...)` now honors credentials policy correctly on authenticated
246250
pages, with bounded localhost probes proving:
247251
- default same-origin fetch keeps cookie plus inherited auth
@@ -398,11 +402,21 @@ Current state inside Gate 1:
398402
`drawArrays(TRIANGLES, ...)`, with focused DOM tests plus a bounded headed
399403
screenshot probe proving a red triangle reaches the real destination canvas
400404
surface
405+
- that same headed Win32 `webgl` path now also supports a first indexed-draw
406+
and uniform-color slice for `getUniformLocation`, `uniform4f`,
407+
`ELEMENT_ARRAY_BUFFER`, and `drawElements(TRIANGLES, ..., UNSIGNED_SHORT, ...)`,
408+
with bounded headed screenshot coverage proving a uniform-colored indexed
409+
triangle reaches the real destination canvas surface
401410
- the headed browser runtime now also exposes a first real `WebSocket`
402411
browser-API slice with `CONNECTING` -> `OPEN` -> `CLOSED` state transitions,
403412
`send`, `close`, `onopen`, `onmessage`, `onerror`, and `onclose`, with a
404413
focused localhost DOM test plus a bounded headed echo probe proving text
405414
frames round-trip on the live headed surface path
415+
- that same headed `WebSocket` runtime now also covers binary echo plus richer
416+
close semantics through `binaryType`, binary `message` payloads, and
417+
`CloseEvent` `code` / `reason` / `wasClean`, with a bounded headed localhost
418+
probe proving binary frames round-trip and server-initiated close details
419+
reach page JS on the live headed surface path
406420
- the current headed painter now also keeps simple block paragraphs with mixed
407421
direct text plus inline child elements on one shared inline row instead of
408422
splitting the direct text into a separate label band above the inline chips,

src/browser/js/bridge.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,7 @@ pub const JsApis = flattenTypes(&.{
830830
@import("../webapi/encoding/TextEncoderStream.zig"),
831831
@import("../webapi/encoding/TextDecoderStream.zig"),
832832
@import("../webapi/Event.zig"),
833+
@import("../webapi/event/CloseEvent.zig"),
833834
@import("../webapi/event/CompositionEvent.zig"),
834835
@import("../webapi/event/CustomEvent.zig"),
835836
@import("../webapi/event/ErrorEvent.zig"),

src/browser/tests/canvas/webgl_rendering_context.html

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,64 @@
173173
gl.drawArrays(gl.TRIANGLES, 0, 3);
174174
}
175175
</script>
176+
177+
<script id=WebGLRenderingContext#shader_program_drawElements_uniform4f>
178+
{
179+
const element = document.createElement("canvas");
180+
element.width = 64;
181+
element.height = 64;
182+
const gl = element.getContext("webgl");
183+
184+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
185+
gl.shaderSource(vertexShader, `
186+
attribute vec2 a_position;
187+
void main() {
188+
gl_Position = vec4(a_position, 0.0, 1.0);
189+
}
190+
`);
191+
gl.compileShader(vertexShader);
192+
testing.expectEqual(true, gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS));
193+
194+
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
195+
gl.shaderSource(fragmentShader, `
196+
uniform vec4 u_color;
197+
void main() {
198+
gl_FragColor = u_color;
199+
}
200+
`);
201+
gl.compileShader(fragmentShader);
202+
testing.expectEqual(true, gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS));
203+
204+
const program = gl.createProgram();
205+
gl.attachShader(program, vertexShader);
206+
gl.attachShader(program, fragmentShader);
207+
gl.linkProgram(program);
208+
testing.expectEqual(true, gl.getProgramParameter(program, gl.LINK_STATUS));
209+
210+
const vertices = gl.createBuffer();
211+
gl.bindBuffer(gl.ARRAY_BUFFER, vertices);
212+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
213+
-0.75, -0.75,
214+
0.75, -0.75,
215+
0.00, 0.80,
216+
]), gl.STATIC_DRAW);
217+
218+
const elements = gl.createBuffer();
219+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elements);
220+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2]), gl.STATIC_DRAW);
221+
222+
const location = gl.getAttribLocation(program, "a_position");
223+
testing.expectEqual(0, location);
224+
const colorLocation = gl.getUniformLocation(program, "u_color");
225+
testing.expectEqual(true, !!colorLocation);
226+
227+
gl.useProgram(program);
228+
gl.viewport(0, 0, 64, 64);
229+
gl.clearColor(1, 1, 1, 1);
230+
gl.clear(gl.COLOR_BUFFER_BIT);
231+
gl.enableVertexAttribArray(location);
232+
gl.vertexAttribPointer(location, 2, gl.FLOAT, false, 0, 0);
233+
gl.uniform4f(colorLocation, 0.0, 0.0, 1.0, 1.0);
234+
gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);
235+
}
236+
</script>

src/browser/tests/indexed_db.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@
2525
request.onsuccess = () => resolve(request.result);
2626
});
2727
}
28+
29+
function iterateCursor(request, step) {
30+
return new Promise((resolve, reject) => {
31+
const values = [];
32+
request.onerror = () => reject(new Error(request.error || 'cursor failed'));
33+
request.onsuccess = () => {
34+
const cursor = request.result;
35+
if (!cursor) {
36+
resolve(values);
37+
return;
38+
}
39+
values.push(step(cursor));
40+
cursor.continue();
41+
};
42+
});
43+
}
2844
</script>
2945

3046
<script id="indexed_db_open_upgrade">
@@ -129,3 +145,34 @@
129145
db2.close();
130146
});
131147
</script>
148+
149+
<script id="indexed_db_cursor_iteration">
150+
testing.async(async () => {
151+
const db = await openDb('lp-cursor', 1, (upgradeDb) => {
152+
const store = upgradeDb.createObjectStore('users');
153+
store.createIndex('by_email', 'email');
154+
});
155+
156+
const tx = db.transaction('users');
157+
const store = tx.objectStore('users');
158+
await requestResult(store.put({ name: 'Grace', email: 'grace@example.com' }, 'user-2'));
159+
await requestResult(store.put({ name: 'Ada', email: 'ada@example.com' }, 'user-1'));
160+
await requestResult(store.put({ name: 'Linus', email: 'linus@example.com' }, 'user-3'));
161+
162+
const storeRows = await iterateCursor(store.openCursor(), (cursor) => {
163+
return `${cursor.primaryKey}:${cursor.value.name}`;
164+
});
165+
testing.expectEqual(['user-1:Ada', 'user-2:Grace', 'user-3:Linus'], storeRows);
166+
167+
const emailRows = await iterateCursor(store.index('by_email').openCursor(), (cursor) => {
168+
return `${cursor.key}:${cursor.primaryKey}:${cursor.value.name}`;
169+
});
170+
testing.expectEqual([
171+
'ada@example.com:user-1:Ada',
172+
'grace@example.com:user-2:Grace',
173+
'linus@example.com:user-3:Linus',
174+
], emailRows);
175+
176+
db.close();
177+
});
178+
</script>

src/browser/tests/net/websocket.html

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,39 @@
55
testing.async(async () => {
66
await new Promise((resolve, reject) => {
77
const ws = new WebSocket('ws://127.0.0.1:9593/echo');
8+
let step = 0;
89
testing.expectEqual(WebSocket.CONNECTING, ws.readyState);
910
testing.expectEqual('ws://127.0.0.1:9593/echo', ws.url);
1011
testing.expectEqual(WebSocket.OPEN, ws.OPEN);
12+
testing.expectEqual('blob', ws.binaryType);
13+
ws.binaryType = 'arraybuffer';
14+
testing.expectEqual('arraybuffer', ws.binaryType);
1115

1216
ws.onerror = () => reject(new Error('unexpected websocket error'));
1317
ws.onopen = () => {
1418
testing.expectEqual(WebSocket.OPEN, ws.readyState);
1519
ws.send('ping');
1620
};
1721
ws.onmessage = (event) => {
18-
testing.expectEqual('echo:ping', event.data);
19-
ws.close();
22+
if (step == 0) {
23+
testing.expectEqual('echo:ping', event.data);
24+
step = 1;
25+
ws.send(new Uint8Array([1, 2, 3]));
26+
return;
27+
}
28+
29+
testing.expectEqual(1, step);
30+
testing.expectEqual(true, event.data instanceof ArrayBuffer);
31+
testing.expectEqual(new Uint8Array([1, 2, 3]), new Uint8Array(event.data));
32+
step = 2;
33+
ws.send('close-me');
2034
};
21-
ws.onclose = () => {
35+
ws.onclose = (event) => {
36+
testing.expectEqual(2, step);
37+
testing.expectEqual(true, event instanceof CloseEvent);
38+
testing.expectEqual(4001, event.code);
39+
testing.expectEqual('server-close', event.reason);
40+
testing.expectEqual(true, event.wasClean);
2241
testing.expectEqual(WebSocket.CLOSED, ws.readyState);
2342
resolve();
2443
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<!doctype html>
2+
<html>
3+
<body style="margin:0;background:#ffffff">
4+
<canvas id="main" width="120" height="80"></canvas>
5+
<script>
6+
const canvas = document.getElementById("main");
7+
const gl = canvas.getContext("webgl");
8+
9+
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
10+
gl.shaderSource(vertexShader, `
11+
attribute vec2 a_position;
12+
void main() {
13+
gl_Position = vec4(a_position, 0.0, 1.0);
14+
}
15+
`);
16+
gl.compileShader(vertexShader);
17+
18+
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
19+
gl.shaderSource(fragmentShader, `
20+
uniform vec4 u_color;
21+
void main() {
22+
gl_FragColor = u_color;
23+
}
24+
`);
25+
gl.compileShader(fragmentShader);
26+
27+
const program = gl.createProgram();
28+
gl.attachShader(program, vertexShader);
29+
gl.attachShader(program, fragmentShader);
30+
gl.linkProgram(program);
31+
gl.useProgram(program);
32+
33+
const vertexBuffer = gl.createBuffer();
34+
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
35+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
36+
-0.75, -0.75,
37+
0.75, -0.75,
38+
0.00, 0.80,
39+
]), gl.STATIC_DRAW);
40+
41+
const indexBuffer = gl.createBuffer();
42+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
43+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2]), gl.STATIC_DRAW);
44+
45+
const position = gl.getAttribLocation(program, "a_position");
46+
const color = gl.getUniformLocation(program, "u_color");
47+
gl.viewport(0, 0, canvas.width, canvas.height);
48+
gl.clearColor(1.0, 1.0, 1.0, 1.0);
49+
gl.clear(gl.COLOR_BUFFER_BIT);
50+
gl.enableVertexAttribArray(position);
51+
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
52+
gl.uniform4f(color, 0.0, 0.0, 1.0, 1.0);
53+
gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_SHORT, 0);
54+
document.title = "Canvas WebGL Indexed Ready";
55+
</script>
56+
</body>
57+
</html>

src/browser/webapi/Event.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ pub const EventPhase = enum(u8) {
6565

6666
pub const Type = union(enum) {
6767
generic,
68+
close_event: *@import("event/CloseEvent.zig"),
6869
error_event: *@import("event/ErrorEvent.zig"),
6970
custom_event: *@import("event/CustomEvent.zig"),
7071
message_event: *@import("event/MessageEvent.zig"),
@@ -165,6 +166,7 @@ pub fn as(self: *Event, comptime T: type) *T {
165166
pub fn is(self: *Event, comptime T: type) ?*T {
166167
switch (self._type) {
167168
.generic => return if (T == Event) self else null,
169+
.close_event => |e| return if (T == @import("event/CloseEvent.zig")) e else null,
168170
.error_event => |e| return if (T == @import("event/ErrorEvent.zig")) e else null,
169171
.custom_event => |e| return if (T == @import("event/CustomEvent.zig")) e else null,
170172
.message_event => |e| return if (T == @import("event/MessageEvent.zig")) e else null,

0 commit comments

Comments
 (0)