diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig
index 89cba801..cce80fe9 100644
--- a/src/browser/EventManager.zig
+++ b/src/browser/EventManager.zig
@@ -130,16 +130,6 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
}
fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
- if (event._bubbles == false) {
- event._event_phase = .at_target;
- const target_et = target.asEventTarget();
- if (self.lookup.getPtr(@intFromPtr(target_et))) |list| {
- try self.dispatchPhase(list, target_et, event, null);
- }
- event._event_phase = .none;
- return;
- }
-
var path_len: usize = 0;
var path_buffer: [128]*EventTarget = undefined;
@@ -150,7 +140,8 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
path_len += 1;
}
- // Even though the window isn't part of the DOM, events bubble to it
+ // Even though the window isn't part of the DOM, events always propagate
+ // through it in the capture phase
if (path_len < path_buffer.len) {
path_buffer[path_len] = self.page.window.asEventTarget();
path_len += 1;
@@ -159,6 +150,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
const path = path_buffer[0..path_len];
// Phase 1: Capturing phase (root → target, excluding target)
+ // This happens for all events, regardless of bubbling
event._event_phase = .capturing_phase;
var i: usize = path_len;
while (i > 1) {
@@ -173,6 +165,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
}
}
+ // Phase 2: At target
event._event_phase = .at_target;
const target_et = target.asEventTarget();
if (self.lookup.getPtr(@intFromPtr(target_et))) |list| {
@@ -183,12 +176,16 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event) !void {
}
}
- event._event_phase = .bubbling_phase;
- for (path[1..]) |current_target| {
- if (self.lookup.getPtr(@intFromPtr(current_target))) |list| {
- try self.dispatchPhase(list, current_target, event, false);
- if (event._stop_propagation) {
- break;
+ // Phase 3: Bubbling phase (target → root, excluding target)
+ // This only happens if the event bubbles
+ if (event._bubbles) {
+ event._event_phase = .bubbling_phase;
+ for (path[1..]) |current_target| {
+ if (self.lookup.getPtr(@intFromPtr(current_target))) |list| {
+ try self.dispatchPhase(list, current_target, event, false);
+ if (event._stop_propagation) {
+ break;
+ }
}
}
}
diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig
index 008fb60f..312b85ba 100644
--- a/src/browser/ScriptManager.zig
+++ b/src/browser/ScriptManager.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see .
const std = @import("std");
+const builtin = @import("builtin");
const js = @import("js/js.zig");
const log = @import("../log.zig");
@@ -31,11 +32,13 @@ const Element = @import("webapi/Element.zig");
const Allocator = std.mem.Allocator;
const ArrayListUnmanaged = std.ArrayListUnmanaged;
+const IS_DEBUG = builtin.mode == .Debug;
+
const ScriptManager = @This();
page: *Page,
-// used to prevent recursive evalutaion
+// used to prevent recursive evaluation
is_evaluating: bool,
// Only once this is true can deferred scripts be run
@@ -45,9 +48,6 @@ static_scripts_done: bool,
// on shutdown/abort, we need to cleanup any pending ones.
asyncs: OrderList,
-// Normal scripts (non-deferred & non-async). These must be executed in order
-scripts: OrderList,
-
// List of deferred scripts. These must be executed in order, but only once
// dom_loaded == true,
deferreds: OrderList,
@@ -85,7 +85,6 @@ pub fn init(page: *Page) ScriptManager {
return .{
.page = page,
.asyncs = .{},
- .scripts = .{},
.deferreds = .{},
.importmap = .empty,
.sync_modules = .empty,
@@ -130,7 +129,6 @@ pub fn reset(self: *ScriptManager) void {
self.importmap = .empty;
self.clearList(&self.asyncs);
- self.clearList(&self.scripts);
self.clearList(&self.deferreds);
self.static_scripts_done = false;
}
@@ -212,57 +210,78 @@ pub fn add(self: *ScriptManager, script_element: *Element.Html.Script, comptime
.is_async = if (remote_url == null) false else element.getAttributeSafe("async") != null,
};
- if (source == .@"inline" and self.scripts.first == null) {
- // inline script with no pending scripts, execute it immediately.
- // (if there is a pending script, then we cannot execute this immediately
- // as it needs to be executed in order)
+ if (source == .@"inline") {
+ // inline script gets executed immediately
return script.eval(page);
}
- const pending_script = try self.script_pool.create();
- errdefer self.script_pool.destroy(pending_script);
- pending_script.* = .{
- .script = script,
- .complete = false,
- .manager = self,
- .node = .{},
+ const pending_script = blk: {
+ // Done in a block this way so that, if something fails in this block
+ // it's cleaned up with these errdefers
+ // BUT, if we need to load/execute the script immediately, cleanup/lifetimes
+ // become the responsibility of the outer block.
+ const pending_script = try self.script_pool.create();
+ errdefer self.script_pool.destroy(pending_script);
+
+ pending_script.* = .{
+ .script = script,
+ .complete = false,
+ .manager = self,
+ .node = .{},
+ };
+ errdefer pending_script.deinit();
+
+ if (comptime IS_DEBUG) {
+ log.debug(.http, "script queue", .{
+ .ctx = ctx,
+ .url = remote_url.?,
+ .stack = page.js.stackTrace() catch "???",
+ });
+ }
+
+ var headers = try self.client.newHeaders();
+ try page.requestCookie(.{}).headersForRequest(page.arena, remote_url.?, &headers);
+
+ try self.client.request(.{
+ .url = remote_url.?,
+ .ctx = pending_script,
+ .method = .GET,
+ .headers = headers,
+ .resource_type = .script,
+ .cookie_jar = &page._session.cookie_jar,
+ .start_callback = if (log.enabled(.http, .debug)) startCallback else null,
+ .header_callback = headerCallback,
+ .data_callback = dataCallback,
+ .done_callback = doneCallback,
+ .error_callback = errorCallback,
+ });
+
+ if (script.is_defer) {
+ // non-blocking loading, track the list this belongs to, and return
+ pending_script.list = &self.deferreds;
+ return;
+ }
+
+ if (script.is_async) {
+ // non-blocking loading, track the list this belongs to, and return
+ pending_script.list = &self.asyncs;
+ return;
+ }
+
+ break :blk pending_script;
};
- if (source == .@"inline") {
- // if we're here, it means that we have pending scripts (i.e. self.scripts
- // is not empty). Because the script is inline, it's complete/ready, but
- // we need to process them in order
- pending_script.complete = true;
- self.scripts.append(&pending_script.node);
- return;
- } else {
- log.debug(.http, "script queue", .{
- .ctx = ctx,
- .url = remote_url.?,
- .stack = page.js.stackTrace() catch "???",
- });
+ defer pending_script.deinit();
+
+ // this is , it needs to block the caller
+ // until it's evaluated
+ var client = self.client;
+ while (true) {
+ if (pending_script.complete) {
+ return pending_script.script.eval(page);
+ }
+ _ = try client.tick(200);
}
-
- pending_script.getList().append(&pending_script.node);
-
- errdefer pending_script.deinit();
-
- var headers = try self.client.newHeaders();
- try page.requestCookie(.{}).headersForRequest(page.arena, remote_url.?, &headers);
-
- try self.client.request(.{
- .url = remote_url.?,
- .ctx = pending_script,
- .method = .GET,
- .headers = headers,
- .resource_type = .script,
- .cookie_jar = &page._session.cookie_jar,
- .start_callback = if (log.enabled(.http, .debug)) startCallback else null,
- .header_callback = headerCallback,
- .data_callback = dataCallback,
- .done_callback = doneCallback,
- .error_callback = errorCallback,
- });
}
// Resolve a module specifier to an valid URL.
@@ -394,6 +413,7 @@ pub fn getAsyncModule(self: *ScriptManager, url: [:0]const u8, cb: AsyncModule.C
.error_callback = AsyncModule.errorCallback,
});
}
+
pub fn pageIsLoaded(self: *ScriptManager) void {
std.debug.assert(self.static_scripts_done == false);
self.static_scripts_done = true;
@@ -415,15 +435,6 @@ fn evaluate(self: *ScriptManager) void {
self.is_evaluating = true;
defer self.is_evaluating = false;
- while (self.scripts.first) |n| {
- var pending_script: *PendingScript = @fieldParentPtr("node", n);
- if (pending_script.complete == false) {
- return;
- }
- defer pending_script.deinit();
- pending_script.script.eval(page);
- }
-
if (self.static_scripts_done == false) {
// We can only execute deferred scripts if
// 1 - all the normal scripts are done
@@ -460,7 +471,6 @@ fn evaluate(self: *ScriptManager) void {
pub fn isDone(self: *const ScriptManager) bool {
return self.asyncs.first == null and // there are no more async scripts
self.static_scripts_done and // and we've finished parsing the HTML to queue all
- self.scripts.first == null and // and there are no more
-->
+
diff --git a/src/browser/tests/net/url_search_params.html b/src/browser/tests/net/url_search_params.html
index 689e9e68..12b98f26 100644
--- a/src/browser/tests/net/url_search_params.html
+++ b/src/browser/tests/net/url_search_params.html
@@ -20,8 +20,8 @@
-
+ -->
diff --git a/src/browser/webapi/EventTarget.zig b/src/browser/webapi/EventTarget.zig
index d808e70f..45379018 100644
--- a/src/browser/webapi/EventTarget.zig
+++ b/src/browser/webapi/EventTarget.zig
@@ -56,6 +56,15 @@ pub fn removeEventListener(self: *EventTarget, typ: []const u8, callback: js.Fun
return page._event_manager.remove(self, typ, callback, use_capture);
}
+pub fn format(self: *EventTarget, writer: *std.Io.Writer) !void {
+ return switch (self._type) {
+ .node => |n| n.format(writer),
+ .window => writer.writeAll(""),
+ .xhr => writer.writeAll(""),
+ .abort_signal => writer.writeAll(""),
+ };
+}
+
pub const JsApi = struct {
pub const bridge = js.Bridge(EventTarget);
diff --git a/src/browser/webapi/storage/storage.zig b/src/browser/webapi/storage/storage.zig
index 8813c092..00e06bf3 100644
--- a/src/browser/webapi/storage/storage.zig
+++ b/src/browser/webapi/storage/storage.zig
@@ -9,7 +9,7 @@ pub fn registerTypes() []const type {
}
pub const Jar = @import("cookie.zig").Jar;
-pub const Cookie =@import("cookie.zig").Cookie;
+pub const Cookie = @import("cookie.zig").Cookie;
pub const Shed = struct {
_origins: std.StringHashMapUnmanaged(*Bucket) = .empty,
diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig
index 7c086f6f..3912b842 100644
--- a/src/cdp/testing.zig
+++ b/src/cdp/testing.zig
@@ -117,11 +117,12 @@ const TestContext = struct {
bc.session_id = sid;
}
- if (opts.html) |html| {
- if (bc.session_id == null) bc.session_id = "SID-X";
- const page = try bc.session.createPage();
- page.window.document = (try Document.init(html)).doc;
- }
+ // @ZIGDOM
+ // if (opts.html) |html| {
+ // if (bc.session_id == null) bc.session_id = "SID-X";
+ // const page = try bc.session.createPage();
+ // page.window._document = (try Document.init(html)).doc;
+ // }
return bc;
}
diff --git a/src/testing.zig b/src/testing.zig
index 7526180e..a4805f98 100644
--- a/src/testing.zig
+++ b/src/testing.zig
@@ -422,9 +422,8 @@ test {
const log = @import("log.zig");
const TestHTTPServer = @import("TestHTTPServer.zig");
-// @ZIGDOM-CDP
-// const Server = @import("Server.zig");
-// var test_cdp_server: ?Server = null;
+const Server = @import("Server.zig");
+var test_cdp_server: ?Server = null;
var test_http_server: ?TestHTTPServer = null;
test "tests:beforeAll" {
@@ -446,12 +445,10 @@ test "tests:beforeAll" {
var wg: std.Thread.WaitGroup = .{};
wg.startMany(2);
- // @ZIGDOM-CDP
- // {
- // const thread = try std.Thread.spawn(.{}, serveCDP, .{&wg});
- // thread.detach();
- // }
- wg.finish(); // @ZIGDOM-CDP REMOVE
+ {
+ const thread = try std.Thread.spawn(.{}, serveCDP, .{&wg});
+ thread.detach();
+ }
test_http_server = TestHTTPServer.init(testHTTPHandler);
{
@@ -465,10 +462,9 @@ test "tests:beforeAll" {
}
test "tests:afterAll" {
- // @ZIGDOM-CDP
- // if (test_cdp_server) |*server| {
- // server.deinit();
- // }
+ if (test_cdp_server) |*server| {
+ server.deinit();
+ }
if (test_http_server) |*server| {
server.deinit();
}
@@ -477,20 +473,19 @@ test "tests:afterAll" {
test_app.deinit();
}
-// @ZIGDOM-CDP
-// fn serveCDP(wg: *std.Thread.WaitGroup) !void {
-// const address = try std.net.Address.parseIp("127.0.0.1", 9583);
-// test_cdp_server = try Server.init(test_app, address);
+fn serveCDP(wg: *std.Thread.WaitGroup) !void {
+ const address = try std.net.Address.parseIp("127.0.0.1", 9583);
+ test_cdp_server = try Server.init(test_app, address);
-// var server = try Server.init(test_app, address);
-// defer server.deinit();
-// wg.finish();
+ var server = try Server.init(test_app, address);
+ defer server.deinit();
+ wg.finish();
-// test_cdp_server.?.run(address, 5) catch |err| {
-// std.debug.print("CDP server error: {}", .{err});
-// return err;
-// };
-// }
+ test_cdp_server.?.run(address, 5) catch |err| {
+ std.debug.print("CDP server error: {}", .{err});
+ return err;
+ };
+}
fn testHTTPHandler(req: *std.http.Server.Request) !void {
const path = req.head.target;