mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-24 05:33:16 +00:00
Merge branch 'main' into mcp
This commit is contained in:
4
.github/workflows/wpt.yml
vendored
4
.github/workflows/wpt.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
|||||||
name: build wpt runner
|
name: build wpt runner
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 90
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
|
|
||||||
# use a self host runner.
|
# use a self host runner.
|
||||||
runs-on: lpd-bench-hetzner
|
runs-on: lpd-bench-hetzner
|
||||||
timeout-minutes: 90
|
timeout-minutes: 120
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ pub fn init(allocator: Allocator, config: *const Config) !*App {
|
|||||||
app.telemetry = try Telemetry.init(app, config.mode);
|
app.telemetry = try Telemetry.init(app, config.mode);
|
||||||
errdefer app.telemetry.deinit();
|
errdefer app.telemetry.deinit();
|
||||||
|
|
||||||
app.arena_pool = ArenaPool.init(allocator);
|
app.arena_pool = ArenaPool.init(allocator, 512, 1024 * 16);
|
||||||
errdefer app.arena_pool.deinit();
|
errdefer app.arena_pool.deinit();
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ const Entry = struct {
|
|||||||
arena: ArenaAllocator,
|
arena: ArenaAllocator,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(allocator: Allocator) ArenaPool {
|
pub fn init(allocator: Allocator, free_list_max: u16, retain_bytes: usize) ArenaPool {
|
||||||
return .{
|
return .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.free_list_max = 512, // TODO make configurable
|
.free_list_max = free_list_max,
|
||||||
.retain_bytes = 1024 * 16, // TODO make configurable
|
.retain_bytes = retain_bytes,
|
||||||
.entry_pool = std.heap.MemoryPool(Entry).init(allocator),
|
.entry_pool = .init(allocator),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,3 +99,114 @@ pub fn reset(_: *const ArenaPool, allocator: Allocator, retain: usize) void {
|
|||||||
const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(allocator.ptr));
|
const arena: *std.heap.ArenaAllocator = @ptrCast(@alignCast(allocator.ptr));
|
||||||
_ = arena.reset(.{ .retain_with_limit = retain });
|
_ = arena.reset(.{ .retain_with_limit = retain });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
test "arena pool - basic acquire and use" {
|
||||||
|
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
|
||||||
|
defer pool.deinit();
|
||||||
|
|
||||||
|
const alloc = try pool.acquire();
|
||||||
|
const buf = try alloc.alloc(u8, 64);
|
||||||
|
@memset(buf, 0xAB);
|
||||||
|
try testing.expectEqual(@as(u8, 0xAB), buf[0]);
|
||||||
|
|
||||||
|
pool.release(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "arena pool - reuse entry after release" {
|
||||||
|
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
|
||||||
|
defer pool.deinit();
|
||||||
|
|
||||||
|
const alloc1 = try pool.acquire();
|
||||||
|
try testing.expectEqual(@as(u16, 0), pool.free_list_len);
|
||||||
|
|
||||||
|
pool.release(alloc1);
|
||||||
|
try testing.expectEqual(@as(u16, 1), pool.free_list_len);
|
||||||
|
|
||||||
|
// The same entry should be returned from the free list.
|
||||||
|
const alloc2 = try pool.acquire();
|
||||||
|
try testing.expectEqual(@as(u16, 0), pool.free_list_len);
|
||||||
|
try testing.expectEqual(alloc1.ptr, alloc2.ptr);
|
||||||
|
|
||||||
|
pool.release(alloc2);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "arena pool - multiple concurrent arenas" {
|
||||||
|
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
|
||||||
|
defer pool.deinit();
|
||||||
|
|
||||||
|
const a1 = try pool.acquire();
|
||||||
|
const a2 = try pool.acquire();
|
||||||
|
const a3 = try pool.acquire();
|
||||||
|
|
||||||
|
// All three must be distinct arenas.
|
||||||
|
try testing.expect(a1.ptr != a2.ptr);
|
||||||
|
try testing.expect(a2.ptr != a3.ptr);
|
||||||
|
try testing.expect(a1.ptr != a3.ptr);
|
||||||
|
|
||||||
|
_ = try a1.alloc(u8, 16);
|
||||||
|
_ = try a2.alloc(u8, 32);
|
||||||
|
_ = try a3.alloc(u8, 48);
|
||||||
|
|
||||||
|
pool.release(a1);
|
||||||
|
pool.release(a2);
|
||||||
|
pool.release(a3);
|
||||||
|
|
||||||
|
try testing.expectEqual(@as(u16, 3), pool.free_list_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "arena pool - free list respects max limit" {
|
||||||
|
// Cap the free list at 1 so the second release discards its arena.
|
||||||
|
var pool = ArenaPool.init(testing.allocator, 1, 1024 * 16);
|
||||||
|
defer pool.deinit();
|
||||||
|
|
||||||
|
const a1 = try pool.acquire();
|
||||||
|
const a2 = try pool.acquire();
|
||||||
|
|
||||||
|
pool.release(a1);
|
||||||
|
try testing.expectEqual(@as(u16, 1), pool.free_list_len);
|
||||||
|
|
||||||
|
// The free list is full; a2's arena should be destroyed, not queued.
|
||||||
|
pool.release(a2);
|
||||||
|
try testing.expectEqual(@as(u16, 1), pool.free_list_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "arena pool - reset clears memory without releasing" {
|
||||||
|
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
|
||||||
|
defer pool.deinit();
|
||||||
|
|
||||||
|
const alloc = try pool.acquire();
|
||||||
|
|
||||||
|
const buf = try alloc.alloc(u8, 128);
|
||||||
|
@memset(buf, 0xFF);
|
||||||
|
|
||||||
|
// reset() frees arena memory but keeps the allocator in-flight.
|
||||||
|
pool.reset(alloc, 0);
|
||||||
|
|
||||||
|
// The free list must stay empty; the allocator was not released.
|
||||||
|
try testing.expectEqual(@as(u16, 0), pool.free_list_len);
|
||||||
|
|
||||||
|
// Allocating again through the same arena must still work.
|
||||||
|
const buf2 = try alloc.alloc(u8, 64);
|
||||||
|
@memset(buf2, 0x00);
|
||||||
|
try testing.expectEqual(@as(u8, 0x00), buf2[0]);
|
||||||
|
|
||||||
|
pool.release(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "arena pool - deinit with entries in free list" {
|
||||||
|
// Verifies that deinit properly cleans up free-listed arenas (no leaks
|
||||||
|
// detected by the test allocator).
|
||||||
|
var pool = ArenaPool.init(testing.allocator, 512, 1024 * 16);
|
||||||
|
|
||||||
|
const a1 = try pool.acquire();
|
||||||
|
const a2 = try pool.acquire();
|
||||||
|
_ = try a1.alloc(u8, 256);
|
||||||
|
_ = try a2.alloc(u8, 512);
|
||||||
|
pool.release(a1);
|
||||||
|
pool.release(a2);
|
||||||
|
try testing.expectEqual(@as(u16, 2), pool.free_list_len);
|
||||||
|
|
||||||
|
pool.deinit();
|
||||||
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ pub const PageRemove = struct {};
|
|||||||
|
|
||||||
pub const PageNavigate = struct {
|
pub const PageNavigate = struct {
|
||||||
req_id: u32,
|
req_id: u32,
|
||||||
page_id: u32,
|
frame_id: u32,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
url: [:0]const u8,
|
url: [:0]const u8,
|
||||||
opts: Page.NavigateOpts,
|
opts: Page.NavigateOpts,
|
||||||
@@ -113,7 +113,7 @@ pub const PageNavigate = struct {
|
|||||||
|
|
||||||
pub const PageNavigated = struct {
|
pub const PageNavigated = struct {
|
||||||
req_id: u32,
|
req_id: u32,
|
||||||
page_id: u32,
|
frame_id: u32,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
url: [:0]const u8,
|
url: [:0]const u8,
|
||||||
opts: Page.NavigatedOpts,
|
opts: Page.NavigatedOpts,
|
||||||
@@ -121,18 +121,18 @@ pub const PageNavigated = struct {
|
|||||||
|
|
||||||
pub const PageNetworkIdle = struct {
|
pub const PageNetworkIdle = struct {
|
||||||
req_id: u32,
|
req_id: u32,
|
||||||
page_id: u32,
|
frame_id: u32,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PageNetworkAlmostIdle = struct {
|
pub const PageNetworkAlmostIdle = struct {
|
||||||
req_id: u32,
|
req_id: u32,
|
||||||
page_id: u32,
|
frame_id: u32,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PageFrameCreated = struct {
|
pub const PageFrameCreated = struct {
|
||||||
page_id: u32,
|
frame_id: u32,
|
||||||
parent_id: u32,
|
parent_id: u32,
|
||||||
timestamp: u64,
|
timestamp: u64,
|
||||||
};
|
};
|
||||||
@@ -319,7 +319,7 @@ test "Notification" {
|
|||||||
|
|
||||||
// noop
|
// noop
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
.page_id = 0,
|
.frame_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 4,
|
.timestamp = 4,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
@@ -330,7 +330,7 @@ test "Notification" {
|
|||||||
|
|
||||||
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
.page_id = 0,
|
.frame_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 4,
|
.timestamp = 4,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
@@ -340,7 +340,7 @@ test "Notification" {
|
|||||||
|
|
||||||
notifier.unregisterAll(&tc);
|
notifier.unregisterAll(&tc);
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
.page_id = 0,
|
.frame_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 10,
|
.timestamp = 10,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
@@ -351,25 +351,25 @@ test "Notification" {
|
|||||||
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
||||||
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
.page_id = 0,
|
.frame_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 10,
|
.timestamp = 10,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
.opts = .{},
|
.opts = .{},
|
||||||
});
|
});
|
||||||
notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 6, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigated, &.{ .frame_id = 0, .req_id = 1, .timestamp = 6, .url = undefined, .opts = .{} });
|
||||||
try testing.expectEqual(14, tc.page_navigate);
|
try testing.expectEqual(14, tc.page_navigate);
|
||||||
try testing.expectEqual(6, tc.page_navigated);
|
try testing.expectEqual(6, tc.page_navigated);
|
||||||
|
|
||||||
notifier.unregisterAll(&tc);
|
notifier.unregisterAll(&tc);
|
||||||
notifier.dispatch(.page_navigate, &.{
|
notifier.dispatch(.page_navigate, &.{
|
||||||
.page_id = 0,
|
.frame_id = 0,
|
||||||
.req_id = 1,
|
.req_id = 1,
|
||||||
.timestamp = 100,
|
.timestamp = 100,
|
||||||
.url = undefined,
|
.url = undefined,
|
||||||
.opts = .{},
|
.opts = .{},
|
||||||
});
|
});
|
||||||
notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigated, &.{ .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||||
try testing.expectEqual(14, tc.page_navigate);
|
try testing.expectEqual(14, tc.page_navigate);
|
||||||
try testing.expectEqual(6, tc.page_navigated);
|
try testing.expectEqual(6, tc.page_navigated);
|
||||||
|
|
||||||
@@ -377,27 +377,27 @@ test "Notification" {
|
|||||||
// unregister
|
// unregister
|
||||||
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
|
||||||
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
|
||||||
notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigate, &.{ .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||||
notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigated, &.{ .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||||
try testing.expectEqual(114, tc.page_navigate);
|
try testing.expectEqual(114, tc.page_navigate);
|
||||||
try testing.expectEqual(1006, tc.page_navigated);
|
try testing.expectEqual(1006, tc.page_navigated);
|
||||||
|
|
||||||
notifier.unregister(.page_navigate, &tc);
|
notifier.unregister(.page_navigate, &tc);
|
||||||
notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigate, &.{ .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||||
notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigated, &.{ .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||||
try testing.expectEqual(114, tc.page_navigate);
|
try testing.expectEqual(114, tc.page_navigate);
|
||||||
try testing.expectEqual(2006, tc.page_navigated);
|
try testing.expectEqual(2006, tc.page_navigated);
|
||||||
|
|
||||||
notifier.unregister(.page_navigated, &tc);
|
notifier.unregister(.page_navigated, &tc);
|
||||||
notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigate, &.{ .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||||
notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigated, &.{ .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||||
try testing.expectEqual(114, tc.page_navigate);
|
try testing.expectEqual(114, tc.page_navigate);
|
||||||
try testing.expectEqual(2006, tc.page_navigated);
|
try testing.expectEqual(2006, tc.page_navigated);
|
||||||
|
|
||||||
// already unregistered, try anyways
|
// already unregistered, try anyways
|
||||||
notifier.unregister(.page_navigated, &tc);
|
notifier.unregister(.page_navigated, &tc);
|
||||||
notifier.dispatch(.page_navigate, &.{ .page_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigate, &.{ .frame_id = 0, .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
|
||||||
notifier.dispatch(.page_navigated, &.{ .page_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
notifier.dispatch(.page_navigated, &.{ .frame_id = 0, .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
|
||||||
try testing.expectEqual(114, tc.page_navigate);
|
try testing.expectEqual(114, tc.page_navigate);
|
||||||
try testing.expectEqual(2006, tc.page_navigated);
|
try testing.expectEqual(2006, tc.page_navigated);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,38 +211,9 @@ pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, co
|
|||||||
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
|
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
|
||||||
}
|
}
|
||||||
|
|
||||||
event._target = target;
|
|
||||||
event._dispatch_target = target; // Store original target for composedPath()
|
|
||||||
var was_handled = false;
|
|
||||||
|
|
||||||
defer if (was_handled) {
|
|
||||||
var ls: js.Local.Scope = undefined;
|
|
||||||
self.page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
ls.local.runMicrotasks();
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (target._type) {
|
switch (target._type) {
|
||||||
.node => |node| try self.dispatchNode(node, event, &was_handled, opts),
|
.node => |node| try self.dispatchNode(node, event, opts),
|
||||||
.xhr,
|
else => try self.dispatchDirect(target, event, null, .{ .context = "dispatch" }),
|
||||||
.window,
|
|
||||||
.abort_signal,
|
|
||||||
.media_query_list,
|
|
||||||
.message_port,
|
|
||||||
.text_track_cue,
|
|
||||||
.navigation,
|
|
||||||
.screen,
|
|
||||||
.screen_orientation,
|
|
||||||
.visual_viewport,
|
|
||||||
.file_reader,
|
|
||||||
.generic,
|
|
||||||
=> {
|
|
||||||
const list = self.lookup.get(.{
|
|
||||||
.event_target = @intFromPtr(target),
|
|
||||||
.type_string = event._type_string,
|
|
||||||
}) orelse return;
|
|
||||||
try self.dispatchAll(list, target, event, &was_handled, opts);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,16 +222,22 @@ pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, co
|
|||||||
// property is just a shortcut for calling addEventListener, but they are distinct.
|
// property is just a shortcut for calling addEventListener, but they are distinct.
|
||||||
// An event set via property cannot be removed by removeEventListener. If you
|
// An event set via property cannot be removed by removeEventListener. If you
|
||||||
// set both the property and add a listener, they both execute.
|
// set both the property and add a listener, they both execute.
|
||||||
const DispatchWithFunctionOptions = struct {
|
const DispatchDirectOptions = struct {
|
||||||
context: []const u8,
|
context: []const u8,
|
||||||
inject_target: bool = true,
|
inject_target: bool = true,
|
||||||
};
|
};
|
||||||
pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *Event, function_: ?js.Function, comptime opts: DispatchWithFunctionOptions) !void {
|
|
||||||
|
// Direct dispatch for non-DOM targets (Window, XHR, AbortSignal) or DOM nodes with
|
||||||
|
// property handlers. No propagation - just calls the handler and registered listeners.
|
||||||
|
// Handler can be: null, ?js.Function.Global, ?js.Function.Temp, or js.Function
|
||||||
|
pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event, handler: anytype, comptime opts: DispatchDirectOptions) !void {
|
||||||
|
const page = self.page;
|
||||||
|
|
||||||
event.acquireRef();
|
event.acquireRef();
|
||||||
defer event.deinit(false, self.page);
|
defer event.deinit(false, page);
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
log.debug(.event, "dispatchWithFunction", .{ .type = event._type_string.str(), .context = opts.context, .has_function = function_ != null });
|
log.debug(.event, "dispatchDirect", .{ .type = event._type_string, .context = opts.context });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comptime opts.inject_target) {
|
if (comptime opts.inject_target) {
|
||||||
@@ -269,14 +246,15 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
|
|||||||
}
|
}
|
||||||
|
|
||||||
var was_dispatched = false;
|
var was_dispatched = false;
|
||||||
defer if (was_dispatched) {
|
|
||||||
var ls: js.Local.Scope = undefined;
|
|
||||||
self.page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
ls.local.runMicrotasks();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (function_) |func| {
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer {
|
||||||
|
ls.local.runMicrotasks();
|
||||||
|
ls.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getFunction(handler, &ls.local)) |func| {
|
||||||
event._current_target = target;
|
event._current_target = target;
|
||||||
if (func.callWithThis(void, target, .{event})) {
|
if (func.callWithThis(void, target, .{event})) {
|
||||||
was_dispatched = true;
|
was_dispatched = true;
|
||||||
@@ -286,17 +264,126 @@ pub fn dispatchWithFunction(self: *EventManager, target: *EventTarget, event: *E
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listeners reigstered via addEventListener
|
||||||
const list = self.lookup.get(.{
|
const list = self.lookup.get(.{
|
||||||
.event_target = @intFromPtr(target),
|
.event_target = @intFromPtr(target),
|
||||||
.type_string = event._type_string,
|
.type_string = event._type_string,
|
||||||
}) orelse return;
|
}) orelse return;
|
||||||
try self.dispatchAll(list, target, event, &was_dispatched, .{});
|
|
||||||
|
// This is a slightly simplified version of what you'll find in dispatchPhase
|
||||||
|
// It is simpler because, for direct dispatching, we know there's no ancestors
|
||||||
|
// and only the single target phase.
|
||||||
|
|
||||||
|
// Track dispatch depth for deferred removal
|
||||||
|
self.dispatch_depth += 1;
|
||||||
|
defer {
|
||||||
|
const dispatch_depth = self.dispatch_depth;
|
||||||
|
// Only destroy deferred listeners when we exit the outermost dispatch
|
||||||
|
if (dispatch_depth == 1) {
|
||||||
|
for (self.deferred_removals.items) |removal| {
|
||||||
|
removal.list.remove(&removal.listener.node);
|
||||||
|
self.listener_pool.destroy(removal.listener);
|
||||||
|
}
|
||||||
|
self.deferred_removals.clearRetainingCapacity();
|
||||||
|
} else {
|
||||||
|
self.dispatch_depth = dispatch_depth - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the last listener in the list as sentinel - listeners added during dispatch will be after it
|
||||||
|
const last_node = list.last orelse return;
|
||||||
|
const last_listener: *Listener = @alignCast(@fieldParentPtr("node", last_node));
|
||||||
|
|
||||||
|
// Iterate through the list, stopping after we've encountered the last_listener
|
||||||
|
var node = list.first;
|
||||||
|
var is_done = false;
|
||||||
|
while (node) |n| {
|
||||||
|
if (is_done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listener: *Listener = @alignCast(@fieldParentPtr("node", n));
|
||||||
|
is_done = (listener == last_listener);
|
||||||
|
node = n.next;
|
||||||
|
|
||||||
|
// Skip removed listeners
|
||||||
|
if (listener.removed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the listener has an aborted signal, remove it and skip
|
||||||
|
if (listener.signal) |signal| {
|
||||||
|
if (signal.getAborted()) {
|
||||||
|
self.removeListener(list, listener);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove "once" listeners BEFORE calling them so nested dispatches don't see them
|
||||||
|
if (listener.once) {
|
||||||
|
self.removeListener(list, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
was_dispatched = true;
|
||||||
|
event._current_target = target;
|
||||||
|
|
||||||
|
switch (listener.function) {
|
||||||
|
.value => |value| try ls.toLocal(value).callWithThis(void, target, .{event}),
|
||||||
|
.string => |string| {
|
||||||
|
const str = try page.call_arena.dupeZ(u8, string.str());
|
||||||
|
try ls.local.eval(str, null);
|
||||||
|
},
|
||||||
|
.object => |obj_global| {
|
||||||
|
const obj = ls.toLocal(obj_global);
|
||||||
|
if (try obj.getFunction("handleEvent")) |handleEvent| {
|
||||||
|
try handleEvent.callWithThis(void, obj, .{event});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event._stop_immediate_propagation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled: *bool, comptime opts: DispatchOpts) !void {
|
fn getFunction(handler: anytype, local: *const js.Local) ?js.Function {
|
||||||
|
const T = @TypeOf(handler);
|
||||||
|
const ti = @typeInfo(T);
|
||||||
|
|
||||||
|
if (ti == .null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (ti == .optional) {
|
||||||
|
return getFunction(handler orelse return null, local);
|
||||||
|
}
|
||||||
|
return switch (T) {
|
||||||
|
js.Function => handler,
|
||||||
|
js.Function.Temp => local.toLocal(handler),
|
||||||
|
js.Function.Global => local.toLocal(handler),
|
||||||
|
else => @compileError("handler must be null or \\??js.Function(\\.(Temp|Global))?"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts: DispatchOpts) !void {
|
||||||
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
const ShadowRoot = @import("webapi/ShadowRoot.zig");
|
||||||
|
|
||||||
|
{
|
||||||
|
const et = target.asEventTarget();
|
||||||
|
event._target = et;
|
||||||
|
event._dispatch_target = et; // Store original target for composedPath()
|
||||||
|
}
|
||||||
|
|
||||||
const page = self.page;
|
const page = self.page;
|
||||||
|
var was_handled = false;
|
||||||
|
|
||||||
|
defer if (was_handled) {
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
ls.local.runMicrotasks();
|
||||||
|
};
|
||||||
|
|
||||||
const activation_state = ActivationState.create(event, target, page);
|
const activation_state = ActivationState.create(event, target, page);
|
||||||
|
|
||||||
// Defer runs even on early return - ensures event phase is reset
|
// Defer runs even on early return - ensures event phase is reset
|
||||||
@@ -374,7 +461,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
|||||||
.event_target = @intFromPtr(current_target),
|
.event_target = @intFromPtr(current_target),
|
||||||
.type_string = event._type_string,
|
.type_string = event._type_string,
|
||||||
})) |list| {
|
})) |list| {
|
||||||
try self.dispatchPhase(list, current_target, event, was_handled, comptime .init(true, opts));
|
try self.dispatchPhase(list, current_target, event, &was_handled, comptime .init(true, opts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +473,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
|||||||
blk: {
|
blk: {
|
||||||
// Get inline handler (e.g., onclick property) for this target
|
// Get inline handler (e.g., onclick property) for this target
|
||||||
if (self.getInlineHandler(target_et, event)) |inline_handler| {
|
if (self.getInlineHandler(target_et, event)) |inline_handler| {
|
||||||
was_handled.* = true;
|
was_handled = true;
|
||||||
event._current_target = target_et;
|
event._current_target = target_et;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
var ls: js.Local.Scope = undefined;
|
||||||
@@ -408,7 +495,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
|||||||
.type_string = event._type_string,
|
.type_string = event._type_string,
|
||||||
.event_target = @intFromPtr(target_et),
|
.event_target = @intFromPtr(target_et),
|
||||||
})) |list| {
|
})) |list| {
|
||||||
try self.dispatchPhase(list, target_et, event, was_handled, comptime .init(null, opts));
|
try self.dispatchPhase(list, target_et, event, &was_handled, comptime .init(null, opts));
|
||||||
if (event._stop_propagation) {
|
if (event._stop_propagation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -425,7 +512,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, was_handled:
|
|||||||
.type_string = event._type_string,
|
.type_string = event._type_string,
|
||||||
.event_target = @intFromPtr(current_target),
|
.event_target = @intFromPtr(current_target),
|
||||||
})) |list| {
|
})) |list| {
|
||||||
try self.dispatchPhase(list, current_target, event, was_handled, comptime .init(false, opts));
|
try self.dispatchPhase(list, current_target, event, &was_handled, comptime .init(false, opts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -549,11 +636,6 @@ fn dispatchPhase(self: *EventManager, list: *std.DoublyLinkedList, current_targe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-Node dispatching (XHR, Window without propagation)
|
|
||||||
fn dispatchAll(self: *EventManager, list: *std.DoublyLinkedList, current_target: *EventTarget, event: *Event, was_handled: *bool, comptime opts: DispatchOpts) !void {
|
|
||||||
return self.dispatchPhase(list, current_target, event, was_handled, comptime .init(null, opts));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getInlineHandler(self: *EventManager, target: *EventTarget, event: *Event) ?js.Function.Global {
|
fn getInlineHandler(self: *EventManager, target: *EventTarget, event: *Event) ?js.Function.Global {
|
||||||
const global_event_handlers = @import("webapi/global_event_handlers.zig");
|
const global_event_handlers = @import("webapi/global_event_handlers.zig");
|
||||||
const handler_type = global_event_handlers.fromEventType(event._type_string.str()) orelse return null;
|
const handler_type = global_event_handlers.fromEventType(event._type_string.str()) orelse return null;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ const Page = @This();
|
|||||||
|
|
||||||
// This is the "id" of the frame. It can be re-used from page-to-page, e.g.
|
// This is the "id" of the frame. It can be re-used from page-to-page, e.g.
|
||||||
// when navigating.
|
// when navigating.
|
||||||
id: u32,
|
_frame_id: u32,
|
||||||
|
|
||||||
_session: *Session,
|
_session: *Session,
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ _element_namespace_uris: Element.NamespaceUriLookup = .empty,
|
|||||||
/// ```js
|
/// ```js
|
||||||
/// img.setAttribute("onload", "(() => { ... })()");
|
/// img.setAttribute("onload", "(() => { ... })()");
|
||||||
/// ```
|
/// ```
|
||||||
_element_attr_listeners: GlobalEventHandlersLookup = .empty,
|
_event_target_attr_listeners: GlobalEventHandlersLookup = .empty,
|
||||||
|
|
||||||
// Blob URL registry for URL.createObjectURL/revokeObjectURL
|
// Blob URL registry for URL.createObjectURL/revokeObjectURL
|
||||||
_blob_urls: std.StringHashMapUnmanaged(*Blob) = .{},
|
_blob_urls: std.StringHashMapUnmanaged(*Blob) = .{},
|
||||||
@@ -242,7 +242,7 @@ _type: enum { root, frame }, // only used for logs right now
|
|||||||
_req_id: u32 = 0,
|
_req_id: u32 = 0,
|
||||||
_navigated_options: ?NavigatedOpts = null,
|
_navigated_options: ?NavigatedOpts = null,
|
||||||
|
|
||||||
pub fn init(self: *Page, id: u32, session: *Session, parent: ?*Page) !void {
|
pub fn init(self: *Page, frame_id: u32, session: *Session, parent: ?*Page) !void {
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
log.debug(.page, "page.init", .{});
|
log.debug(.page, "page.init", .{});
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,6 @@ pub fn init(self: *Page, id: u32, session: *Session, parent: ?*Page) !void {
|
|||||||
})).asDocument();
|
})).asDocument();
|
||||||
|
|
||||||
self.* = .{
|
self.* = .{
|
||||||
.id = id,
|
|
||||||
.js = undefined,
|
.js = undefined,
|
||||||
.parent = parent,
|
.parent = parent,
|
||||||
.arena = page_arena,
|
.arena = page_arena,
|
||||||
@@ -270,6 +269,7 @@ pub fn init(self: *Page, id: u32, session: *Session, parent: ?*Page) !void {
|
|||||||
.window = undefined,
|
.window = undefined,
|
||||||
.arena_pool = arena_pool,
|
.arena_pool = arena_pool,
|
||||||
.call_arena = call_arena,
|
.call_arena = call_arena,
|
||||||
|
._frame_id = frame_id,
|
||||||
._session = session,
|
._session = session,
|
||||||
._factory = factory,
|
._factory = factory,
|
||||||
._pending_loads = 1, // always 1 for the ScriptManager
|
._pending_loads = 1, // always 1 for the ScriptManager
|
||||||
@@ -465,7 +465,7 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
self.documentIsComplete();
|
self.documentIsComplete();
|
||||||
|
|
||||||
session.notification.dispatch(.page_navigate, &.{
|
session.notification.dispatch(.page_navigate, &.{
|
||||||
.page_id = self.id,
|
.frame_id = self._frame_id,
|
||||||
.req_id = req_id,
|
.req_id = req_id,
|
||||||
.opts = opts,
|
.opts = opts,
|
||||||
.url = request_url,
|
.url = request_url,
|
||||||
@@ -481,7 +481,7 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
});
|
});
|
||||||
|
|
||||||
session.notification.dispatch(.page_navigated, &.{
|
session.notification.dispatch(.page_navigated, &.{
|
||||||
.page_id = self.id,
|
.frame_id = self._frame_id,
|
||||||
.req_id = req_id,
|
.req_id = req_id,
|
||||||
.opts = .{
|
.opts = .{
|
||||||
.cdp_id = opts.cdp_id,
|
.cdp_id = opts.cdp_id,
|
||||||
@@ -517,7 +517,7 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
// We dispatch page_navigate event before sending the request.
|
// We dispatch page_navigate event before sending the request.
|
||||||
// It ensures the event page_navigated is not dispatched before this one.
|
// It ensures the event page_navigated is not dispatched before this one.
|
||||||
session.notification.dispatch(.page_navigate, &.{
|
session.notification.dispatch(.page_navigate, &.{
|
||||||
.page_id = self.id,
|
.frame_id = self._frame_id,
|
||||||
.req_id = req_id,
|
.req_id = req_id,
|
||||||
.opts = opts,
|
.opts = opts,
|
||||||
.url = self.url,
|
.url = self.url,
|
||||||
@@ -535,7 +535,7 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi
|
|||||||
http_client.request(.{
|
http_client.request(.{
|
||||||
.ctx = self,
|
.ctx = self,
|
||||||
.url = self.url,
|
.url = self.url,
|
||||||
.page_id = self.id,
|
.frame_id = self._frame_id,
|
||||||
.method = opts.method,
|
.method = opts.method,
|
||||||
.headers = headers,
|
.headers = headers,
|
||||||
.body = opts.body,
|
.body = opts.body,
|
||||||
@@ -712,7 +712,7 @@ pub fn documentIsComplete(self: *Page) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self._session.notification.dispatch(.page_navigated, &.{
|
self._session.notification.dispatch(.page_navigated, &.{
|
||||||
.page_id = self.id,
|
.frame_id = self._frame_id,
|
||||||
.req_id = self._req_id,
|
.req_id = self._req_id,
|
||||||
.opts = self._navigated_options.?,
|
.opts = self._navigated_options.?,
|
||||||
.url = self.url,
|
.url = self.url,
|
||||||
@@ -726,27 +726,23 @@ fn _documentIsComplete(self: *Page) !void {
|
|||||||
// Run load events before window.load.
|
// Run load events before window.load.
|
||||||
try self.dispatchLoad();
|
try self.dispatchLoad();
|
||||||
|
|
||||||
var ls: JS.Local.Scope = undefined;
|
|
||||||
self.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
// Dispatch window.load event.
|
// Dispatch window.load event.
|
||||||
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
|
const event = try Event.initTrusted(comptime .wrap("load"), .{}, self);
|
||||||
// This event is weird, it's dispatched directly on the window, but
|
// This event is weird, it's dispatched directly on the window, but
|
||||||
// with the document as the target.
|
// with the document as the target.
|
||||||
event._target = self.document.asEventTarget();
|
event._target = self.document.asEventTarget();
|
||||||
try self._event_manager.dispatchWithFunction(
|
try self._event_manager.dispatchDirect(
|
||||||
self.window.asEventTarget(),
|
self.window.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
ls.toLocal(self.window._on_load),
|
self.window._on_load,
|
||||||
.{ .inject_target = false, .context = "page load" },
|
.{ .inject_target = false, .context = "page load" },
|
||||||
);
|
);
|
||||||
|
|
||||||
const pageshow_event = (try PageTransitionEvent.initTrusted(comptime .wrap("pageshow"), .{}, self)).asEvent();
|
const pageshow_event = (try PageTransitionEvent.initTrusted(comptime .wrap("pageshow"), .{}, self)).asEvent();
|
||||||
try self._event_manager.dispatchWithFunction(
|
try self._event_manager.dispatchDirect(
|
||||||
self.window.asEventTarget(),
|
self.window.asEventTarget(),
|
||||||
pageshow_event,
|
pageshow_event,
|
||||||
ls.toLocal(self.window._on_pageshow),
|
self.window._on_pageshow,
|
||||||
.{ .context = "page show" },
|
.{ .context = "page show" },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -968,17 +964,17 @@ pub fn iframeAddedCallback(self: *Page, iframe: *Element.Html.IFrame) !void {
|
|||||||
iframe._executed = true;
|
iframe._executed = true;
|
||||||
|
|
||||||
const session = self._session;
|
const session = self._session;
|
||||||
const page_id = session.nextPageId();
|
const frame_id = session.nextFrameId();
|
||||||
const page_frame = try self.arena.create(Page);
|
const page_frame = try self.arena.create(Page);
|
||||||
try Page.init(page_frame, page_id, session, self);
|
try Page.init(page_frame, frame_id, session, self);
|
||||||
|
|
||||||
self._pending_loads += 1;
|
self._pending_loads += 1;
|
||||||
page_frame.iframe = iframe;
|
page_frame.iframe = iframe;
|
||||||
iframe._content_window = page_frame.window;
|
iframe._content_window = page_frame.window;
|
||||||
|
|
||||||
self._session.notification.dispatch(.page_frame_created, &.{
|
self._session.notification.dispatch(.page_frame_created, &.{
|
||||||
.page_id = page_id,
|
.frame_id = frame_id,
|
||||||
.parent_id = self.id,
|
.parent_id = self._frame_id,
|
||||||
.timestamp = timestamp(.monotonic),
|
.timestamp = timestamp(.monotonic),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1331,8 +1327,8 @@ pub fn deliverSlotchangeEvents(self: *Page) void {
|
|||||||
pub fn notifyNetworkIdle(self: *Page) void {
|
pub fn notifyNetworkIdle(self: *Page) void {
|
||||||
lp.assert(self._notified_network_idle == .done, "Page.notifyNetworkIdle", .{});
|
lp.assert(self._notified_network_idle == .done, "Page.notifyNetworkIdle", .{});
|
||||||
self._session.notification.dispatch(.page_network_idle, &.{
|
self._session.notification.dispatch(.page_network_idle, &.{
|
||||||
.page_id = self.id,
|
|
||||||
.req_id = self._req_id,
|
.req_id = self._req_id,
|
||||||
|
.frame_id = self._frame_id,
|
||||||
.timestamp = timestamp(.monotonic),
|
.timestamp = timestamp(.monotonic),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1340,8 +1336,8 @@ pub fn notifyNetworkIdle(self: *Page) void {
|
|||||||
pub fn notifyNetworkAlmostIdle(self: *Page) void {
|
pub fn notifyNetworkAlmostIdle(self: *Page) void {
|
||||||
lp.assert(self._notified_network_almost_idle == .done, "Page.notifyNetworkAlmostIdle", .{});
|
lp.assert(self._notified_network_almost_idle == .done, "Page.notifyNetworkAlmostIdle", .{});
|
||||||
self._session.notification.dispatch(.page_network_almost_idle, &.{
|
self._session.notification.dispatch(.page_network_almost_idle, &.{
|
||||||
.page_id = self.id,
|
|
||||||
.req_id = self._req_id,
|
.req_id = self._req_id,
|
||||||
|
.frame_id = self._frame_id,
|
||||||
.timestamp = timestamp(.monotonic),
|
.timestamp = timestamp(.monotonic),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3127,20 +3123,13 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
|
|||||||
const form_element = form.asElement();
|
const form_element = form.asElement();
|
||||||
|
|
||||||
if (submit_opts.fire_event) {
|
if (submit_opts.fire_event) {
|
||||||
const onsubmit_handler = try form.asHtmlElement().getOnSubmit(self);
|
|
||||||
const submit_event = try Event.initTrusted(comptime .wrap("submit"), .{ .bubbles = true, .cancelable = true }, self);
|
const submit_event = try Event.initTrusted(comptime .wrap("submit"), .{ .bubbles = true, .cancelable = true }, self);
|
||||||
|
|
||||||
var ls: JS.Local.Scope = undefined;
|
// so submit_event is still valid when we check _prevent_default
|
||||||
self.js.localScope(&ls);
|
submit_event.acquireRef();
|
||||||
defer ls.deinit();
|
defer submit_event.deinit(false, self);
|
||||||
|
|
||||||
try self._event_manager.dispatchWithFunction(
|
|
||||||
form_element.asEventTarget(),
|
|
||||||
submit_event,
|
|
||||||
ls.toLocal(onsubmit_handler),
|
|
||||||
.{ .context = "form submit" },
|
|
||||||
);
|
|
||||||
|
|
||||||
|
try self._event_manager.dispatch(form_element.asEventTarget(), submit_event);
|
||||||
// If the submit event was prevented, don't submit the form
|
// If the submit event was prevented, don't submit the form
|
||||||
if (submit_event._prevent_default) {
|
if (submit_event._prevent_default) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
|
|||||||
.url = url,
|
.url = url,
|
||||||
.ctx = script,
|
.ctx = script,
|
||||||
.method = .GET,
|
.method = .GET,
|
||||||
.page_id = page.id,
|
.frame_id = page._frame_id,
|
||||||
.headers = try self.getHeaders(url),
|
.headers = try self.getHeaders(url),
|
||||||
.blocking = is_blocking,
|
.blocking = is_blocking,
|
||||||
.cookie_jar = &page._session.cookie_jar,
|
.cookie_jar = &page._session.cookie_jar,
|
||||||
@@ -384,7 +384,7 @@ pub fn preloadImport(self: *ScriptManager, url: [:0]const u8, referrer: []const
|
|||||||
.url = url,
|
.url = url,
|
||||||
.ctx = script,
|
.ctx = script,
|
||||||
.method = .GET,
|
.method = .GET,
|
||||||
.page_id = page.id,
|
.frame_id = page._frame_id,
|
||||||
.headers = try self.getHeaders(url),
|
.headers = try self.getHeaders(url),
|
||||||
.cookie_jar = &page._session.cookie_jar,
|
.cookie_jar = &page._session.cookie_jar,
|
||||||
.resource_type = .script,
|
.resource_type = .script,
|
||||||
@@ -487,7 +487,7 @@ pub fn getAsyncImport(self: *ScriptManager, url: [:0]const u8, cb: ImportAsync.C
|
|||||||
try self.client.request(.{
|
try self.client.request(.{
|
||||||
.url = url,
|
.url = url,
|
||||||
.method = .GET,
|
.method = .GET,
|
||||||
.page_id = page.id,
|
.frame_id = page._frame_id,
|
||||||
.headers = try self.getHeaders(url),
|
.headers = try self.getHeaders(url),
|
||||||
.ctx = script,
|
.ctx = script,
|
||||||
.resource_type = .script,
|
.resource_type = .script,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ navigation: Navigation,
|
|||||||
|
|
||||||
page: ?Page,
|
page: ?Page,
|
||||||
|
|
||||||
page_id_gen: u32,
|
frame_id_gen: u32,
|
||||||
|
|
||||||
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
|
pub fn init(self: *Session, browser: *Browser, notification: *Notification) !void {
|
||||||
const allocator = browser.app.allocator;
|
const allocator = browser.app.allocator;
|
||||||
@@ -65,7 +65,7 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
|
|||||||
.page = null,
|
.page = null,
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.history = .{},
|
.history = .{},
|
||||||
.page_id_gen = 0,
|
.frame_id_gen = 0,
|
||||||
// The prototype (EventTarget) for Navigation is created when a Page is created.
|
// The prototype (EventTarget) for Navigation is created when a Page is created.
|
||||||
.navigation = .{ ._proto = undefined },
|
.navigation = .{ ._proto = undefined },
|
||||||
.storage_shed = .{},
|
.storage_shed = .{},
|
||||||
@@ -93,7 +93,7 @@ pub fn createPage(self: *Session) !*Page {
|
|||||||
|
|
||||||
self.page = @as(Page, undefined);
|
self.page = @as(Page, undefined);
|
||||||
const page = &self.page.?;
|
const page = &self.page.?;
|
||||||
try Page.init(page, self.nextPageId(), self, null);
|
try Page.init(page, self.nextFrameId(), self, null);
|
||||||
|
|
||||||
// Creates a new NavigationEventTarget for this page.
|
// Creates a new NavigationEventTarget for this page.
|
||||||
try self.navigation.onNewPage(page);
|
try self.navigation.onNewPage(page);
|
||||||
@@ -131,7 +131,7 @@ pub fn replacePage(self: *Session) !*Page {
|
|||||||
lp.assert(self.page != null, "Session.replacePage null page", .{});
|
lp.assert(self.page != null, "Session.replacePage null page", .{});
|
||||||
|
|
||||||
var current = self.page.?;
|
var current = self.page.?;
|
||||||
const page_id = current.id;
|
const frame_id = current._frame_id;
|
||||||
const parent = current.parent;
|
const parent = current.parent;
|
||||||
current.deinit();
|
current.deinit();
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ pub fn replacePage(self: *Session) !*Page {
|
|||||||
|
|
||||||
self.page = @as(Page, undefined);
|
self.page = @as(Page, undefined);
|
||||||
const page = &self.page.?;
|
const page = &self.page.?;
|
||||||
try Page.init(page, page_id, self, parent);
|
try Page.init(page, frame_id, self, parent);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,9 +153,9 @@ pub const WaitResult = enum {
|
|||||||
cdp_socket,
|
cdp_socket,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn findPage(self: *Session, id: u32) ?*Page {
|
pub fn findPage(self: *Session, frame_id: u32) ?*Page {
|
||||||
const page = self.currentPage() orelse return null;
|
const page = self.currentPage() orelse return null;
|
||||||
return if (page.id == id) page else null;
|
return if (page._frame_id == frame_id) page else null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
|
pub fn wait(self: *Session, wait_ms: u32) WaitResult {
|
||||||
@@ -347,20 +347,20 @@ fn processScheduledNavigation(self: *Session, current_page: *Page) !*Page {
|
|||||||
current_page._queued_navigation = null;
|
current_page._queued_navigation = null;
|
||||||
defer browser.arena_pool.release(qn.arena);
|
defer browser.arena_pool.release(qn.arena);
|
||||||
|
|
||||||
const page_id, const parent = blk: {
|
const frame_id, const parent = blk: {
|
||||||
const page = &self.page.?;
|
const page = &self.page.?;
|
||||||
const page_id = page.id;
|
const frame_id = page._frame_id;
|
||||||
const parent = page.parent;
|
const parent = page.parent;
|
||||||
|
|
||||||
browser.http_client.abort();
|
browser.http_client.abort();
|
||||||
self.removePage();
|
self.removePage();
|
||||||
|
|
||||||
break :blk .{ page_id, parent };
|
break :blk .{ frame_id, parent };
|
||||||
};
|
};
|
||||||
|
|
||||||
self.page = @as(Page, undefined);
|
self.page = @as(Page, undefined);
|
||||||
const page = &self.page.?;
|
const page = &self.page.?;
|
||||||
try Page.init(page, page_id, self, parent);
|
try Page.init(page, frame_id, self, parent);
|
||||||
|
|
||||||
// Creates a new NavigationEventTarget for this page.
|
// Creates a new NavigationEventTarget for this page.
|
||||||
try self.navigation.onNewPage(page);
|
try self.navigation.onNewPage(page);
|
||||||
@@ -377,8 +377,8 @@ fn processScheduledNavigation(self: *Session, current_page: *Page) !*Page {
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nextPageId(self: *Session) u32 {
|
pub fn nextFrameId(self: *Session) u32 {
|
||||||
const id = self.page_id_gen +% 1;
|
const id = self.frame_id_gen +% 1;
|
||||||
self.page_id_gen = id;
|
self.frame_id_gen = id;
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,9 +328,13 @@ fn nameToString(local: *const Local, comptime T: type, name: *const v8.Name) !T
|
|||||||
fn handleError(comptime T: type, comptime F: type, local: *const Local, err: anyerror, info: anytype, comptime opts: CallOpts) void {
|
fn handleError(comptime T: type, comptime F: type, local: *const Local, err: anyerror, info: anytype, comptime opts: CallOpts) void {
|
||||||
const isolate = local.isolate;
|
const isolate = local.isolate;
|
||||||
|
|
||||||
if (comptime @import("builtin").mode == .Debug and @TypeOf(info) == FunctionCallbackInfo) {
|
if (comptime IS_DEBUG and @TypeOf(info) == FunctionCallbackInfo) {
|
||||||
if (log.enabled(.js, .warn)) {
|
if (log.enabled(.js, .debug)) {
|
||||||
logFunctionCallError(local, @typeName(T), @typeName(F), err, info);
|
const DOMException = @import("../webapi/DOMException.zig");
|
||||||
|
if (DOMException.fromError(err) == null) {
|
||||||
|
// This isn't a DOMException, let's log it
|
||||||
|
logFunctionCallError(local, @typeName(T), @typeName(F), err, info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +364,7 @@ fn handleError(comptime T: type, comptime F: type, local: *const Local, err: any
|
|||||||
// this can add as much as 10 seconds of compilation time.
|
// this can add as much as 10 seconds of compilation time.
|
||||||
fn logFunctionCallError(local: *const Local, type_name: []const u8, func: []const u8, err: anyerror, info: FunctionCallbackInfo) void {
|
fn logFunctionCallError(local: *const Local, type_name: []const u8, func: []const u8, err: anyerror, info: FunctionCallbackInfo) void {
|
||||||
const args_dump = serializeFunctionArgs(local, info) catch "failed to serialize args";
|
const args_dump = serializeFunctionArgs(local, info) catch "failed to serialize args";
|
||||||
log.info(.js, "function call error", .{
|
log.debug(.js, "function call error", .{
|
||||||
.type = type_name,
|
.type = type_name,
|
||||||
.func = func,
|
.func = func,
|
||||||
.err = err,
|
.err = err,
|
||||||
|
|||||||
@@ -252,3 +252,34 @@
|
|||||||
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script id=xhr_abort_callback_nobody>
|
||||||
|
testing.async(async (restore) => {
|
||||||
|
const req = new XMLHttpRequest();
|
||||||
|
let abortFired = false;
|
||||||
|
let errorFired = false;
|
||||||
|
let loadEndFired = false;
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
req.onabort = () => { abortFired = true; };
|
||||||
|
req.onerror = () => { errorFired = true; };
|
||||||
|
req.onloadend = () => {
|
||||||
|
loadEndFired = true;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
req.open('GET', 'http://127.0.0.1:9582/xhr_empty');
|
||||||
|
req.onreadystatechange = (e) => {
|
||||||
|
req.abort();
|
||||||
|
}
|
||||||
|
req.send();
|
||||||
|
});
|
||||||
|
|
||||||
|
restore();
|
||||||
|
testing.expectEqual(true, abortFired);
|
||||||
|
testing.expectEqual(true, errorFired);
|
||||||
|
testing.expectEqual(true, loadEndFired);
|
||||||
|
testing.expectEqual(XMLHttpRequest.UNSENT, req.readyState);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub fn getSignal(self: *const AbortController) *AbortSignal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, page: *Page) !void {
|
pub fn abort(self: *AbortController, reason_: ?js.Value.Global, page: *Page) !void {
|
||||||
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page.js.local.?, page);
|
try self._signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ pub fn asEventTarget(self: *AbortSignal) *EventTarget {
|
|||||||
return self._proto;
|
return self._proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort(self: *AbortSignal, reason_: ?Reason, local: *const js.Local, page: *Page) !void {
|
pub fn abort(self: *AbortSignal, reason_: ?Reason, page: *Page) !void {
|
||||||
if (self._aborted) {
|
if (self._aborted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,10 +77,10 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, local: *const js.Local, page:
|
|||||||
|
|
||||||
// Dispatch abort event
|
// Dispatch abort event
|
||||||
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, page);
|
const event = try Event.initTrusted(comptime .wrap("abort"), .{}, page);
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
local.toLocal(self._on_abort),
|
self._on_abort,
|
||||||
.{ .context = "abort signal" },
|
.{ .context = "abort signal" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ pub fn abort(self: *AbortSignal, reason_: ?Reason, local: *const js.Local, page:
|
|||||||
// Static method to create an already-aborted signal
|
// Static method to create an already-aborted signal
|
||||||
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
pub fn createAborted(reason_: ?js.Value.Global, page: *Page) !*AbortSignal {
|
||||||
const signal = try init(page);
|
const signal = try init(page);
|
||||||
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page.js.local.?, page);
|
try signal.abort(if (reason_) |r| .{ .js_val = r } else null, page);
|
||||||
return signal;
|
return signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,11 +136,7 @@ const TimeoutCallback = struct {
|
|||||||
|
|
||||||
fn run(ctx: *anyopaque) !?u32 {
|
fn run(ctx: *anyopaque) !?u32 {
|
||||||
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
const self: *TimeoutCallback = @ptrCast(@alignCast(ctx));
|
||||||
var ls: js.Local.Scope = undefined;
|
self.signal.abort(.{ .string = "TimeoutError" }, self.page) catch |err| {
|
||||||
self.page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
self.signal.abort(.{ .string = "TimeoutError" }, &ls.local, self.page) catch |err| {
|
|
||||||
log.warn(.app, "abort signal timeout", .{ .err = err });
|
log.warn(.app, "abort signal timeout", .{ .err = err });
|
||||||
};
|
};
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -183,12 +183,7 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
|||||||
|
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
try self.dispatch(.load_start, .{ .loaded = 0, .total = blob.getSize() }, page);
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.dispatch(.load_start, .{ .loaded = 0, .total = blob.getSize() }, local, page);
|
|
||||||
if (self._aborted) {
|
if (self._aborted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -196,7 +191,7 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
|||||||
// Perform the read (synchronous since data is in memory)
|
// Perform the read (synchronous since data is in memory)
|
||||||
const data = blob._slice;
|
const data = blob._slice;
|
||||||
const size = data.len;
|
const size = data.len;
|
||||||
try self.dispatch(.progress, .{ .loaded = size, .total = size }, local, page);
|
try self.dispatch(.progress, .{ .loaded = size, .total = size }, page);
|
||||||
if (self._aborted) {
|
if (self._aborted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -216,8 +211,8 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
|||||||
|
|
||||||
self._ready_state = .done;
|
self._ready_state = .done;
|
||||||
|
|
||||||
try self.dispatch(.load, .{ .loaded = size, .total = size }, local, page);
|
try self.dispatch(.load, .{ .loaded = size, .total = size }, page);
|
||||||
try self.dispatch(.load_end, .{ .loaded = size, .total = size }, local, page);
|
try self.dispatch(.load_end, .{ .loaded = size, .total = size }, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort(self: *FileReader) !void {
|
pub fn abort(self: *FileReader) !void {
|
||||||
@@ -231,17 +226,12 @@ pub fn abort(self: *FileReader) !void {
|
|||||||
|
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
try self.dispatch(.abort, null, page);
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.dispatch(.abort, null, local, page);
|
try self.dispatch(.load_end, null, page);
|
||||||
|
|
||||||
try self.dispatch(.load_end, null, local, page);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Progress, local: *const js.Local, page: *Page) !void {
|
fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Progress, page: *Page) !void {
|
||||||
const field, const typ = comptime blk: {
|
const field, const typ = comptime blk: {
|
||||||
break :blk switch (event_type) {
|
break :blk switch (event_type) {
|
||||||
.abort => .{ "_on_abort", "abort" },
|
.abort => .{ "_on_abort", "abort" },
|
||||||
@@ -260,10 +250,10 @@ fn dispatch(self: *FileReader, comptime event_type: DispatchType, progress_: ?Pr
|
|||||||
page,
|
page,
|
||||||
)).asEvent();
|
)).asEvent();
|
||||||
|
|
||||||
return page._event_manager.dispatchWithFunction(
|
return page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
local.toLocal(@field(self, field)),
|
@field(self, field),
|
||||||
.{ .context = "FileReader " ++ typ },
|
.{ .context = "FileReader " ++ typ },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,10 +80,10 @@ fn goInner(delta: i32, page: *Page) !void {
|
|||||||
if (entry._url) |url| {
|
if (entry._url) |url| {
|
||||||
if (try page.isSameOrigin(url)) {
|
if (try page.isSameOrigin(url)) {
|
||||||
const event = (try PopStateEvent.initTrusted(comptime .wrap("popstate"), .{ .state = entry._state.value }, page)).asEvent();
|
const event = (try PopStateEvent.initTrusted(comptime .wrap("popstate"), .{ .state = entry._state.value }, page)).asEvent();
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchDirect(
|
||||||
page.window.asEventTarget(),
|
page.window.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
page.js.toLocal(page.window._on_popstate),
|
page.window._on_popstate,
|
||||||
.{ .context = "Pop State" },
|
.{ .context = "Pop State" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,14 +131,10 @@ const PostMessageCallback = struct {
|
|||||||
return null;
|
return null;
|
||||||
}).asEvent();
|
}).asEvent();
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
page._event_manager.dispatchDirect(
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
page._event_manager.dispatchWithFunction(
|
|
||||||
self.port.asEventTarget(),
|
self.port.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
ls.toLocal(self.port._on_message),
|
self.port._on_message,
|
||||||
.{ .context = "MessagePort message" },
|
.{ .context = "MessagePort message" },
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
log.err(.dom, "MessagePort.postMessage", .{ .err = err });
|
||||||
|
|||||||
@@ -556,10 +556,10 @@ pub fn unhandledPromiseRejection(self: *Window, rejection: js.PromiseRejection,
|
|||||||
.promise = try rejection.promise().temp(),
|
.promise = try rejection.promise().temp(),
|
||||||
}, page)).asEvent();
|
}, page)).asEvent();
|
||||||
|
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
rejection.local.toLocal(self._on_unhandled_rejection),
|
self._on_unhandled_rejection,
|
||||||
.{ .inject_target = true, .context = "window.unhandledrejection" },
|
.{ .inject_target = true, .context = "window.unhandledrejection" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ pub fn getAttributeFunction(
|
|||||||
page: *Page,
|
page: *Page,
|
||||||
) !?js.Function.Global {
|
) !?js.Function.Global {
|
||||||
const element = self.asElement();
|
const element = self.asElement();
|
||||||
if (page._element_attr_listeners.get(.{ .target = element.asEventTarget(), .handler = listener_type })) |cached| {
|
if (page._event_target_attr_listeners.get(.{ .target = element.asEventTarget(), .handler = listener_type })) |cached| {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,7 +404,7 @@ pub fn getAttributeFunction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hasAttributeFunction(self: *HtmlElement, listener_type: GlobalEventHandler, page: *const Page) bool {
|
pub fn hasAttributeFunction(self: *HtmlElement, listener_type: GlobalEventHandler, page: *const Page) bool {
|
||||||
return page._element_attr_listeners.contains(.{ .target = self.asEventTarget(), .handler = listener_type });
|
return page._event_target_attr_listeners.contains(.{ .target = self.asEventTarget(), .handler = listener_type });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setAttributeListener(
|
fn setAttributeListener(
|
||||||
@@ -421,7 +421,7 @@ fn setAttributeListener(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (listener_callback) |cb| {
|
if (listener_callback) |cb| {
|
||||||
try page._element_attr_listeners.put(page.arena, .{
|
try page._event_target_attr_listeners.put(page.arena, .{
|
||||||
.target = self.asEventTarget(),
|
.target = self.asEventTarget(),
|
||||||
.handler = listener_type,
|
.handler = listener_type,
|
||||||
}, cb);
|
}, cb);
|
||||||
@@ -429,7 +429,7 @@ fn setAttributeListener(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The listener is null, remove existing listener.
|
// The listener is null, remove existing listener.
|
||||||
_ = page._element_attr_listeners.remove(.{
|
_ = page._event_target_attr_listeners.remove(.{
|
||||||
.target = self.asEventTarget(),
|
.target = self.asEventTarget(),
|
||||||
.handler = listener_type,
|
.handler = listener_type,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -440,14 +440,10 @@ pub fn updateCurrentEntry(self: *Navigation, options: UpdateCurrentEntryOptions,
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch(self: *Navigation, func: js.Function.Global, event: *Event, page: *Page) !void {
|
pub fn dispatch(self: *Navigation, func: js.Function.Global, event: *Event, page: *Page) !void {
|
||||||
var ls: js.Local.Scope = undefined;
|
return page._event_manager.dispatchDirect(
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
return page._event_manager.dispatchWithFunction(
|
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
ls.toLocal(func),
|
func,
|
||||||
.{ .context = "Navigation" },
|
.{ .context = "Navigation" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
|
|||||||
|
|
||||||
try http_client.request(.{
|
try http_client.request(.{
|
||||||
.ctx = fetch,
|
.ctx = fetch,
|
||||||
.page_id = page.id,
|
|
||||||
.url = request._url,
|
.url = request._url,
|
||||||
.method = request._method,
|
.method = request._method,
|
||||||
|
.frame_id = page._frame_id,
|
||||||
.body = request._body,
|
.body = request._body,
|
||||||
.headers = headers,
|
.headers = headers,
|
||||||
.resource_type = .fetch,
|
.resource_type = .fetch,
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ pub fn open(self: *XMLHttpRequest, method_: []const u8, url: [:0]const u8) !void
|
|||||||
const page = self._page;
|
const page = self._page;
|
||||||
self._method = try parseMethod(method_);
|
self._method = try parseMethod(method_);
|
||||||
self._url = try URL.resolve(self._arena, page.base(), url, .{ .always_dupe = true, .encode = true });
|
self._url = try URL.resolve(self._arena, page.base(), url, .{ .always_dupe = true, .encode = true });
|
||||||
try self.stateChanged(.opened, page.js.local.?, page);
|
try self.stateChanged(.opened, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setRequestHeader(self: *XMLHttpRequest, name: []const u8, value: []const u8, page: *Page) !void {
|
pub fn setRequestHeader(self: *XMLHttpRequest, name: []const u8, value: []const u8, page: *Page) !void {
|
||||||
@@ -223,9 +223,9 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
|
|||||||
try http_client.request(.{
|
try http_client.request(.{
|
||||||
.ctx = self,
|
.ctx = self,
|
||||||
.url = self._url,
|
.url = self._url,
|
||||||
.page_id = page.id,
|
|
||||||
.method = self._method,
|
.method = self._method,
|
||||||
.headers = headers,
|
.headers = headers,
|
||||||
|
.frame_id = page._frame_id,
|
||||||
.body = self._request_body,
|
.body = self._request_body,
|
||||||
.cookie_jar = if (cookie_support) &page._session.cookie_jar else null,
|
.cookie_jar = if (cookie_support) &page._session.cookie_jar else null,
|
||||||
.resource_type = .xhr,
|
.resource_type = .xhr,
|
||||||
@@ -397,11 +397,10 @@ fn httpHeaderDoneCallback(transfer: *Http.Transfer) !bool {
|
|||||||
var ls: js.Local.Scope = undefined;
|
var ls: js.Local.Scope = undefined;
|
||||||
page.js.localScope(&ls);
|
page.js.localScope(&ls);
|
||||||
defer ls.deinit();
|
defer ls.deinit();
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.stateChanged(.headers_received, local, page);
|
try self.stateChanged(.headers_received, page);
|
||||||
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, local, page);
|
try self._proto.dispatch(.load_start, .{ .loaded = 0, .total = self._response_len orelse 0 }, page);
|
||||||
try self.stateChanged(.loading, local, page);
|
try self.stateChanged(.loading, page);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -412,14 +411,10 @@ fn httpDataCallback(transfer: *Http.Transfer, data: []const u8) !void {
|
|||||||
|
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
|
|
||||||
try self._proto.dispatch(.progress, .{
|
try self._proto.dispatch(.progress, .{
|
||||||
.total = self._response_len orelse 0,
|
.total = self._response_len orelse 0,
|
||||||
.loaded = self._response_data.items.len,
|
.loaded = self._response_data.items.len,
|
||||||
}, &ls.local, page);
|
}, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn httpDoneCallback(ctx: *anyopaque) !void {
|
fn httpDoneCallback(ctx: *anyopaque) !void {
|
||||||
@@ -438,22 +433,17 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
|
|||||||
|
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
try self.stateChanged(.done, page);
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.stateChanged(.done, local, page);
|
|
||||||
|
|
||||||
const loaded = self._response_data.items.len;
|
const loaded = self._response_data.items.len;
|
||||||
try self._proto.dispatch(.load, .{
|
try self._proto.dispatch(.load, .{
|
||||||
.total = loaded,
|
.total = loaded,
|
||||||
.loaded = loaded,
|
.loaded = loaded,
|
||||||
}, local, page);
|
}, page);
|
||||||
try self._proto.dispatch(.load_end, .{
|
try self._proto.dispatch(.load_end, .{
|
||||||
.total = loaded,
|
.total = loaded,
|
||||||
.loaded = loaded,
|
.loaded = loaded,
|
||||||
}, local, page);
|
}, page);
|
||||||
|
|
||||||
page.js.weakRef(self);
|
page.js.weakRef(self);
|
||||||
}
|
}
|
||||||
@@ -495,17 +485,12 @@ fn _handleError(self: *XMLHttpRequest, err: anyerror) !void {
|
|||||||
if (new_state != self._ready_state) {
|
if (new_state != self._ready_state) {
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
|
|
||||||
var ls: js.Local.Scope = undefined;
|
try self.stateChanged(new_state, page);
|
||||||
page.js.localScope(&ls);
|
|
||||||
defer ls.deinit();
|
|
||||||
const local = &ls.local;
|
|
||||||
|
|
||||||
try self.stateChanged(new_state, local, page);
|
|
||||||
if (is_abort) {
|
if (is_abort) {
|
||||||
try self._proto.dispatch(.abort, null, local, page);
|
try self._proto.dispatch(.abort, null, page);
|
||||||
}
|
}
|
||||||
try self._proto.dispatch(.err, null, local, page);
|
try self._proto.dispatch(.err, null, page);
|
||||||
try self._proto.dispatch(.load_end, null, local, page);
|
try self._proto.dispatch(.load_end, null, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
const level: log.Level = if (err == error.Abort) .debug else .err;
|
const level: log.Level = if (err == error.Abort) .debug else .err;
|
||||||
@@ -516,7 +501,7 @@ fn _handleError(self: *XMLHttpRequest, err: anyerror) !void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stateChanged(self: *XMLHttpRequest, state: ReadyState, local: *const js.Local, page: *Page) !void {
|
fn stateChanged(self: *XMLHttpRequest, state: ReadyState, page: *Page) !void {
|
||||||
if (state == self._ready_state) {
|
if (state == self._ready_state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -524,10 +509,10 @@ fn stateChanged(self: *XMLHttpRequest, state: ReadyState, local: *const js.Local
|
|||||||
self._ready_state = state;
|
self._ready_state = state;
|
||||||
|
|
||||||
const event = try Event.initTrusted(.wrap("readystatechange"), .{}, page);
|
const event = try Event.initTrusted(.wrap("readystatechange"), .{}, page);
|
||||||
try page._event_manager.dispatchWithFunction(
|
try page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
local.toLocal(self._on_ready_state_change),
|
self._on_ready_state_change,
|
||||||
.{ .context = "XHR state change" },
|
.{ .context = "XHR state change" },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub fn asEventTarget(self: *XMLHttpRequestEventTarget) *EventTarget {
|
|||||||
return self._proto;
|
return self._proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchType, progress_: ?Progress, local: *const js.Local, page: *Page) !void {
|
pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchType, progress_: ?Progress, page: *Page) !void {
|
||||||
const field, const typ = comptime blk: {
|
const field, const typ = comptime blk: {
|
||||||
break :blk switch (event_type) {
|
break :blk switch (event_type) {
|
||||||
.abort => .{ "_on_abort", "abort" },
|
.abort => .{ "_on_abort", "abort" },
|
||||||
@@ -63,10 +63,10 @@ pub fn dispatch(self: *XMLHttpRequestEventTarget, comptime event_type: DispatchT
|
|||||||
page,
|
page,
|
||||||
)).asEvent();
|
)).asEvent();
|
||||||
|
|
||||||
return page._event_manager.dispatchWithFunction(
|
return page._event_manager.dispatchDirect(
|
||||||
self.asEventTarget(),
|
self.asEventTarget(),
|
||||||
event,
|
event,
|
||||||
local.toLocal(@field(self, field)),
|
@field(self, field),
|
||||||
.{ .context = "XHR " ++ typ },
|
.{ .context = "XHR " ++ typ },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -438,15 +438,10 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
|||||||
const browser = &self.cdp.browser;
|
const browser = &self.cdp.browser;
|
||||||
const env = &browser.env;
|
const env = &browser.env;
|
||||||
|
|
||||||
// Drain microtasks makes sure we don't have inspector's callback
|
|
||||||
// in progress before deinit.
|
|
||||||
env.runMicrotasks();
|
|
||||||
|
|
||||||
// resetContextGroup detach the inspector from all contexts.
|
// resetContextGroup detach the inspector from all contexts.
|
||||||
// It append async tasks, so we make sure we run the message loop
|
// It appends async tasks, so we make sure we run the message loop
|
||||||
// before deinit it.
|
// before deinit it.
|
||||||
env.inspector.?.resetContextGroup();
|
env.inspector.?.resetContextGroup();
|
||||||
_ = env.pumpMessageLoop();
|
|
||||||
env.inspector.?.stopSession();
|
env.inspector.?.stopSession();
|
||||||
|
|
||||||
// abort all intercepted requests before closing the sesion/page
|
// abort all intercepted requests before closing the sesion/page
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ pub fn requestIntercept(bc: anytype, intercept: *const Notification.RequestInter
|
|||||||
|
|
||||||
try bc.cdp.sendEvent("Fetch.requestPaused", .{
|
try bc.cdp.sendEvent("Fetch.requestPaused", .{
|
||||||
.requestId = &id.toInterceptId(transfer.id),
|
.requestId = &id.toInterceptId(transfer.id),
|
||||||
.frameId = &id.toFrameId(transfer.req.page_id),
|
.frameId = &id.toFrameId(transfer.req.frame_id),
|
||||||
.request = network.TransferAsRequestWriter.init(transfer),
|
.request = network.TransferAsRequestWriter.init(transfer),
|
||||||
.resourceType = switch (transfer.req.resource_type) {
|
.resourceType = switch (transfer.req.resource_type) {
|
||||||
.script => "Script",
|
.script => "Script",
|
||||||
@@ -397,7 +397,7 @@ pub fn requestAuthRequired(bc: anytype, intercept: *const Notification.RequestAu
|
|||||||
|
|
||||||
try bc.cdp.sendEvent("Fetch.authRequired", .{
|
try bc.cdp.sendEvent("Fetch.authRequired", .{
|
||||||
.requestId = &id.toInterceptId(transfer.id),
|
.requestId = &id.toInterceptId(transfer.id),
|
||||||
.frameId = &id.toFrameId(transfer.req.page_id),
|
.frameId = &id.toFrameId(transfer.req.frame_id),
|
||||||
.request = network.TransferAsRequestWriter.init(transfer),
|
.request = network.TransferAsRequestWriter.init(transfer),
|
||||||
.resourceType = switch (transfer.req.resource_type) {
|
.resourceType = switch (transfer.req.resource_type) {
|
||||||
.script => "Script",
|
.script => "Script",
|
||||||
|
|||||||
@@ -237,8 +237,8 @@ pub fn httpRequestStart(bc: anytype, msg: *const Notification.RequestStart) !voi
|
|||||||
|
|
||||||
const transfer = msg.transfer;
|
const transfer = msg.transfer;
|
||||||
const req = &transfer.req;
|
const req = &transfer.req;
|
||||||
const page_id = req.page_id;
|
const frame_id = req.frame_id;
|
||||||
const page = bc.session.findPage(page_id) orelse return;
|
const page = bc.session.findPage(frame_id) orelse return;
|
||||||
|
|
||||||
// Modify request with extra CDP headers
|
// Modify request with extra CDP headers
|
||||||
for (bc.extra_headers.items) |extra| {
|
for (bc.extra_headers.items) |extra| {
|
||||||
@@ -249,7 +249,7 @@ pub fn httpRequestStart(bc: anytype, msg: *const Notification.RequestStart) !voi
|
|||||||
try bc.cdp.sendEvent("Network.requestWillBeSent", .{
|
try bc.cdp.sendEvent("Network.requestWillBeSent", .{
|
||||||
.loaderId = &id.toLoaderId(transfer.id),
|
.loaderId = &id.toLoaderId(transfer.id),
|
||||||
.requestId = &id.toRequestId(transfer.id),
|
.requestId = &id.toRequestId(transfer.id),
|
||||||
.frameId = &id.toFrameId(page_id),
|
.frameId = &id.toFrameId(frame_id),
|
||||||
.type = req.resource_type.string(),
|
.type = req.resource_type.string(),
|
||||||
.documentURL = page.url,
|
.documentURL = page.url,
|
||||||
.request = TransferAsRequestWriter.init(transfer),
|
.request = TransferAsRequestWriter.init(transfer),
|
||||||
@@ -270,7 +270,7 @@ pub fn httpResponseHeaderDone(arena: Allocator, bc: anytype, msg: *const Notific
|
|||||||
try bc.cdp.sendEvent("Network.responseReceived", .{
|
try bc.cdp.sendEvent("Network.responseReceived", .{
|
||||||
.loaderId = &id.toLoaderId(transfer.id),
|
.loaderId = &id.toLoaderId(transfer.id),
|
||||||
.requestId = &id.toRequestId(transfer.id),
|
.requestId = &id.toRequestId(transfer.id),
|
||||||
.frameId = &id.toFrameId(transfer.req.page_id),
|
.frameId = &id.toFrameId(transfer.req.frame_id),
|
||||||
.response = TransferAsResponseWriter.init(arena, msg.transfer),
|
.response = TransferAsResponseWriter.init(arena, msg.transfer),
|
||||||
.hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo
|
.hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void {
|
|||||||
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
|
||||||
|
|
||||||
if (page._load_state == .complete) {
|
if (page._load_state == .complete) {
|
||||||
const frame_id = &id.toFrameId(page.id);
|
const frame_id = &id.toFrameId(page._frame_id);
|
||||||
const loader_id = &id.toLoaderId(page._req_id);
|
const loader_id = &id.toLoaderId(page._req_id);
|
||||||
|
|
||||||
const now = timestampF(.monotonic);
|
const now = timestampF(.monotonic);
|
||||||
@@ -239,7 +239,7 @@ pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void
|
|||||||
const session_id = bc.session_id orelse return;
|
const session_id = bc.session_id orelse return;
|
||||||
bc.reset();
|
bc.reset();
|
||||||
|
|
||||||
const frame_id = &id.toFrameId(event.page_id);
|
const frame_id = &id.toFrameId(event.frame_id);
|
||||||
const loader_id = &id.toLoaderId(event.req_id);
|
const loader_id = &id.toLoaderId(event.req_id);
|
||||||
|
|
||||||
var cdp = bc.cdp;
|
var cdp = bc.cdp;
|
||||||
@@ -308,7 +308,7 @@ pub fn pageFrameCreated(bc: anytype, event: *const Notification.PageFrameCreated
|
|||||||
const session_id = bc.session_id orelse return;
|
const session_id = bc.session_id orelse return;
|
||||||
|
|
||||||
const cdp = bc.cdp;
|
const cdp = bc.cdp;
|
||||||
const frame_id = &id.toFrameId(event.page_id);
|
const frame_id = &id.toFrameId(event.frame_id);
|
||||||
|
|
||||||
try cdp.sendEvent("Page.frameAttached", .{ .params = .{
|
try cdp.sendEvent("Page.frameAttached", .{ .params = .{
|
||||||
.frameId = frame_id,
|
.frameId = frame_id,
|
||||||
@@ -319,7 +319,7 @@ pub fn pageFrameCreated(bc: anytype, event: *const Notification.PageFrameCreated
|
|||||||
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{
|
||||||
.name = "init",
|
.name = "init",
|
||||||
.frameId = frame_id,
|
.frameId = frame_id,
|
||||||
.loaderId = &id.toLoaderId(event.page_id),
|
.loaderId = &id.toLoaderId(event.frame_id),
|
||||||
.timestamp = event.timestamp,
|
.timestamp = event.timestamp,
|
||||||
}, .{ .session_id = session_id });
|
}, .{ .session_id = session_id });
|
||||||
}
|
}
|
||||||
@@ -331,7 +331,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
|
|||||||
const session_id = bc.session_id orelse return;
|
const session_id = bc.session_id orelse return;
|
||||||
|
|
||||||
const timestamp = event.timestamp;
|
const timestamp = event.timestamp;
|
||||||
const frame_id = &id.toFrameId(event.page_id);
|
const frame_id = &id.toFrameId(event.frame_id);
|
||||||
const loader_id = &id.toLoaderId(event.req_id);
|
const loader_id = &id.toLoaderId(event.req_id);
|
||||||
|
|
||||||
var cdp = bc.cdp;
|
var cdp = bc.cdp;
|
||||||
@@ -478,11 +478,11 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pageNetworkIdle(bc: anytype, event: *const Notification.PageNetworkIdle) !void {
|
pub fn pageNetworkIdle(bc: anytype, event: *const Notification.PageNetworkIdle) !void {
|
||||||
return sendPageLifecycle(bc, "networkIdle", event.timestamp, &id.toFrameId(event.page_id), &id.toLoaderId(event.req_id));
|
return sendPageLifecycle(bc, "networkIdle", event.timestamp, &id.toFrameId(event.frame_id), &id.toLoaderId(event.req_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pageNetworkAlmostIdle(bc: anytype, event: *const Notification.PageNetworkAlmostIdle) !void {
|
pub fn pageNetworkAlmostIdle(bc: anytype, event: *const Notification.PageNetworkAlmostIdle) !void {
|
||||||
return sendPageLifecycle(bc, "networkAlmostIdle", event.timestamp, &id.toFrameId(event.page_id), &id.toLoaderId(event.req_id));
|
return sendPageLifecycle(bc, "networkAlmostIdle", event.timestamp, &id.toFrameId(event.frame_id), &id.toLoaderId(event.req_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sendPageLifecycle(bc: anytype, name: []const u8, timestamp: u64, frame_id: []const u8, loader_id: []const u8) !void {
|
fn sendPageLifecycle(bc: anytype, name: []const u8, timestamp: u64, frame_id: []const u8, loader_id: []const u8) !void {
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ fn createTarget(cmd: anytype) !void {
|
|||||||
const page = try bc.session.createPage();
|
const page = try bc.session.createPage();
|
||||||
|
|
||||||
// the target_id == the frame_id of the "root" page
|
// the target_id == the frame_id of the "root" page
|
||||||
const frame_id = id.toFrameId(page.id);
|
const frame_id = id.toFrameId(page._frame_id);
|
||||||
bc.target_id = frame_id;
|
bc.target_id = frame_id;
|
||||||
const target_id = &bc.target_id.?;
|
const target_id = &bc.target_id.?;
|
||||||
{
|
{
|
||||||
@@ -421,7 +421,7 @@ fn setAutoAttach(cmd: anytype) !void {
|
|||||||
if (bc.target_id == null) {
|
if (bc.target_id == null) {
|
||||||
if (bc.session.currentPage()) |page| {
|
if (bc.session.currentPage()) |page| {
|
||||||
// the target_id == the frame_id of the "root" page
|
// the target_id == the frame_id of the "root" page
|
||||||
bc.target_id = id.toFrameId(page.id);
|
bc.target_id = id.toFrameId(page._frame_id);
|
||||||
try doAttachtoTarget(cmd, &bc.target_id.?);
|
try doAttachtoTarget(cmd, &bc.target_id.?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,27 +174,59 @@ pub fn newHeaders(self: *const Client) !Net.Headers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort(self: *Client) void {
|
pub fn abort(self: *Client) void {
|
||||||
while (self.handles.in_use.first) |node| {
|
self._abort(true, 0);
|
||||||
const conn: *Net.Connection = @fieldParentPtr("node", node);
|
}
|
||||||
var transfer = Transfer.fromConnection(conn) catch |err| {
|
|
||||||
log.err(.http, "get private info", .{ .err = err, .source = "abort" });
|
pub fn abortFrame(self: *Client, frame_id: u32) void {
|
||||||
continue;
|
self._abort(false, frame_id);
|
||||||
};
|
}
|
||||||
transfer.kill();
|
|
||||||
|
// Written this way so that both abort and abortFrame can share the same code
|
||||||
|
// but abort can avoid the frame_id check at comptime.
|
||||||
|
fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
|
||||||
|
{
|
||||||
|
var q = &self.handles.in_use;
|
||||||
|
var n = q.first;
|
||||||
|
while (n) |node| {
|
||||||
|
n = node.next;
|
||||||
|
const conn: *Net.Connection = @fieldParentPtr("node", node);
|
||||||
|
var transfer = Transfer.fromConnection(conn) catch |err| {
|
||||||
|
log.err(.http, "get private info", .{ .err = err, .source = "abort" });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (comptime abort_all) {
|
||||||
|
transfer.kill();
|
||||||
|
} else if (transfer.req.frame_id == frame_id) {
|
||||||
|
q.remove(node);
|
||||||
|
transfer.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (comptime IS_DEBUG) {
|
|
||||||
|
if (comptime IS_DEBUG and abort_all) {
|
||||||
std.debug.assert(self.active == 0);
|
std.debug.assert(self.active == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
var n = self.queue.first;
|
{
|
||||||
while (n) |node| {
|
var q = &self.queue;
|
||||||
n = node.next;
|
var n = q.first;
|
||||||
const transfer: *Transfer = @fieldParentPtr("_node", node);
|
while (n) |node| {
|
||||||
transfer.kill();
|
n = node.next;
|
||||||
|
const transfer: *Transfer = @fieldParentPtr("_node", node);
|
||||||
|
if (comptime abort_all) {
|
||||||
|
transfer.kill();
|
||||||
|
} else if (transfer.req.frame_id == frame_id) {
|
||||||
|
q.remove(node);
|
||||||
|
transfer.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.queue = .{};
|
|
||||||
|
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime abort_all) {
|
||||||
|
self.queue = .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comptime IS_DEBUG and abort_all) {
|
||||||
std.debug.assert(self.handles.in_use.first == null);
|
std.debug.assert(self.handles.in_use.first == null);
|
||||||
std.debug.assert(self.handles.available.len() == self.handles.connections.len);
|
std.debug.assert(self.handles.available.len() == self.handles.connections.len);
|
||||||
|
|
||||||
@@ -320,7 +352,7 @@ fn fetchRobotsThenProcessRequest(self: *Client, robots_url: [:0]const u8, req: R
|
|||||||
.method = .GET,
|
.method = .GET,
|
||||||
.headers = headers,
|
.headers = headers,
|
||||||
.blocking = false,
|
.blocking = false,
|
||||||
.page_id = req.page_id,
|
.frame_id = req.frame_id,
|
||||||
.cookie_jar = req.cookie_jar,
|
.cookie_jar = req.cookie_jar,
|
||||||
.notification = req.notification,
|
.notification = req.notification,
|
||||||
.resource_type = .fetch,
|
.resource_type = .fetch,
|
||||||
@@ -496,8 +528,12 @@ fn waitForInterceptedResponse(self: *Client, transfer: *Transfer) !bool {
|
|||||||
// cases, the interecptor is expected to call resume to continue the transfer
|
// cases, the interecptor is expected to call resume to continue the transfer
|
||||||
// or transfer.abort() to abort it.
|
// or transfer.abort() to abort it.
|
||||||
fn process(self: *Client, transfer: *Transfer) !void {
|
fn process(self: *Client, transfer: *Transfer) !void {
|
||||||
if (self.handles.get()) |conn| {
|
// libcurl doesn't allow recursive calls, if we're in a `perform()` operation
|
||||||
return self.makeRequest(conn, transfer);
|
// then we _have_ to queue this.
|
||||||
|
if (self.handles.performing == false) {
|
||||||
|
if (self.handles.get()) |conn| {
|
||||||
|
return self.makeRequest(conn, transfer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.queue.append(&transfer._node);
|
self.queue.append(&transfer._node);
|
||||||
@@ -789,9 +825,14 @@ fn processMessages(self: *Client) !bool {
|
|||||||
if (msg.err) |err| {
|
if (msg.err) |err| {
|
||||||
requestFailed(transfer, err, true);
|
requestFailed(transfer, err, true);
|
||||||
} else blk: {
|
} else blk: {
|
||||||
// In case of request w/o data, we need to call the header done
|
// make sure the transfer can't be immediately aborted from a callback
|
||||||
// callback now.
|
// since we still need it here.
|
||||||
|
transfer._performing = true;
|
||||||
|
defer transfer._performing = false;
|
||||||
|
|
||||||
if (!transfer._header_done_called) {
|
if (!transfer._header_done_called) {
|
||||||
|
// In case of request w/o data, we need to call the header done
|
||||||
|
// callback now.
|
||||||
const proceed = transfer.headerDoneCallback(&msg.conn) catch |err| {
|
const proceed = transfer.headerDoneCallback(&msg.conn) catch |err| {
|
||||||
log.err(.http, "header_done_callback", .{ .err = err });
|
log.err(.http, "header_done_callback", .{ .err = err });
|
||||||
requestFailed(transfer, err, true);
|
requestFailed(transfer, err, true);
|
||||||
@@ -855,7 +896,7 @@ pub const RequestCookie = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Request = struct {
|
pub const Request = struct {
|
||||||
page_id: u32,
|
frame_id: u32,
|
||||||
method: Method,
|
method: Method,
|
||||||
url: [:0]const u8,
|
url: [:0]const u8,
|
||||||
headers: Net.Headers,
|
headers: Net.Headers,
|
||||||
@@ -937,6 +978,7 @@ pub const Transfer = struct {
|
|||||||
// number of times the transfer has been tried.
|
// number of times the transfer has been tried.
|
||||||
// incremented by reset func.
|
// incremented by reset func.
|
||||||
_tries: u8 = 0,
|
_tries: u8 = 0,
|
||||||
|
_performing: bool = false,
|
||||||
|
|
||||||
// for when a Transfer is queued in the client.queue
|
// for when a Transfer is queued in the client.queue
|
||||||
_node: std.DoublyLinkedList.Node = .{},
|
_node: std.DoublyLinkedList.Node = .{},
|
||||||
@@ -1041,13 +1083,9 @@ pub const Transfer = struct {
|
|||||||
|
|
||||||
pub fn abort(self: *Transfer, err: anyerror) void {
|
pub fn abort(self: *Transfer, err: anyerror) void {
|
||||||
requestFailed(self, err, true);
|
requestFailed(self, err, true);
|
||||||
if (self._conn == null) {
|
|
||||||
self.deinit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = self.client;
|
const client = self.client;
|
||||||
if (client.handles.performing) {
|
if (self._performing or client.handles.performing) {
|
||||||
// We're currently in a curl_multi_perform. We cannot call endTransfer
|
// We're currently in a curl_multi_perform. We cannot call endTransfer
|
||||||
// as that calls curl_multi_remove_handle, and you can't do that
|
// as that calls curl_multi_remove_handle, and you can't do that
|
||||||
// from a curl callback. Instead, we flag this transfer and all of
|
// from a curl callback. Instead, we flag this transfer and all of
|
||||||
|
|||||||
@@ -561,6 +561,14 @@ fn testHTTPHandler(req: *std.http.Server.Request) !void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, path, "/xhr_empty")) {
|
||||||
|
return req.respond("", .{
|
||||||
|
.extra_headers = &.{
|
||||||
|
.{ .name = "Content-Type", .value = "text/html; charset=utf-8" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (std.mem.eql(u8, path, "/xhr/json")) {
|
if (std.mem.eql(u8, path, "/xhr/json")) {
|
||||||
return req.respond("{\"over\":\"9000!!!\",\"updated_at\":1765867200000}", .{
|
return req.respond("{\"over\":\"9000!!!\",\"updated_at\":1765867200000}", .{
|
||||||
.extra_headers = &.{
|
.extra_headers = &.{
|
||||||
|
|||||||
Reference in New Issue
Block a user