Compare commits

..

1 Commits

Author SHA1 Message Date
Karl Seguin
de0a04a58e Relax assertion on httpclient abort
It's ok to still have transfers, as long as whatever transfers still exists
are in an aborted state.
2026-04-02 17:59:17 +08:00
2 changed files with 62 additions and 51 deletions

View File

@@ -235,10 +235,6 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
}
}
if (comptime IS_DEBUG and abort_all) {
std.debug.assert(self.active == 0);
}
{
var q = &self.queue;
var n = q.first;
@@ -259,12 +255,16 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
}
if (comptime IS_DEBUG and abort_all) {
std.debug.assert(self.in_use.first == null);
const running = self.handles.perform() catch |err| {
lp.assert(false, "multi perform in abort", .{ .err = err });
};
std.debug.assert(running == 0);
// Even after an abort_all, we could still have transfers, but, at the
// very least, they should all be flagged as aborted.
var it = self.in_use.first;
var leftover: usize = 0;
while (it) |node| : (it = node.next) {
const conn: *http.Connection = @fieldParentPtr("node", node);
std.debug.assert((Transfer.fromConnection(conn) catch unreachable).aborted);
leftover += 1;
}
std.debug.assert(self.active == leftover);
}
}

View File

@@ -25,7 +25,9 @@ const Element = @import("webapi/Element.zig");
const Node = @import("webapi/Node.zig");
const isAllWhitespace = @import("../string.zig").isAllWhitespace;
pub const Opts = struct {};
pub const Opts = struct {
// Options for future customization (e.g., dialect)
};
const State = struct {
const ListType = enum { ordered, unordered };
@@ -37,6 +39,7 @@ const State = struct {
list_depth: usize = 0,
list_stack: [32]ListState = undefined,
pre_node: ?*Node = null,
in_code: bool = false,
in_table: bool = false,
table_row_index: usize = 0,
table_col_count: usize = 0,
@@ -97,35 +100,27 @@ fn getAnchorLabel(el: *Element) ?[]const u8 {
return el.getAttributeSafe(comptime .wrap("aria-label")) orelse el.getAttributeSafe(comptime .wrap("title"));
}
const ContentInfo = struct {
has_visible: bool,
has_block: bool,
};
fn hasBlockDescendant(root: *Node) bool {
var tw = TreeWalker.FullExcludeSelf.Elements.init(root, .{});
while (tw.next()) |el| {
if (el.getTag().isBlock()) return true;
}
return false;
}
fn analyzeContent(root: *Node) ContentInfo {
var result: ContentInfo = .{ .has_visible = false, .has_block = false };
fn hasVisibleContent(root: *Node) bool {
var tw = TreeWalker.FullExcludeSelf.init(root, .{});
while (tw.next()) |node| {
if (isSignificantText(node)) {
result.has_visible = true;
if (result.has_block) return result;
} else if (node.is(Element)) |el| {
if (isSignificantText(node)) return true;
if (node.is(Element)) |el| {
if (!isVisibleElement(el)) {
tw.skipChildren();
} else {
const tag = el.getTag();
if (tag == .img) {
result.has_visible = true;
if (result.has_block) return result;
}
if (tag.isBlock()) {
result.has_block = true;
if (result.has_visible) return result;
} else if (el.getTag() == .img) {
return true;
}
}
}
}
return result;
return false;
}
const Context = struct {
@@ -175,7 +170,9 @@ const Context = struct {
if (!isVisibleElement(el)) return;
// Ensure block elements start on a new line
// --- Opening Tag Logic ---
// Ensure block elements start on a new line (double newline for paragraphs etc)
if (tag.isBlock() and !self.state.in_table) {
try self.ensureNewline();
if (shouldAddSpacing(tag)) {
@@ -185,6 +182,7 @@ const Context = struct {
try self.ensureNewline();
}
// Prefixes
switch (tag) {
.h1 => try self.writer.writeAll("# "),
.h2 => try self.writer.writeAll("## "),
@@ -227,6 +225,7 @@ const Context = struct {
try self.writer.writeByte('|');
},
.td, .th => {
// Note: leading pipe handled by previous cell closing or tr opening
self.state.last_char_was_newline = false;
try self.writer.writeByte(' ');
},
@@ -242,6 +241,7 @@ const Context = struct {
.code => {
if (self.state.pre_node == null) {
try self.writer.writeByte('`');
self.state.in_code = true;
self.state.last_char_was_newline = false;
}
},
@@ -286,15 +286,16 @@ const Context = struct {
return;
},
.anchor => {
const info = analyzeContent(el.asNode());
const has_content = hasVisibleContent(el.asNode());
const label = getAnchorLabel(el);
const href_raw = el.getAttributeSafe(comptime .wrap("href"));
if (!info.has_visible and label == null and href_raw == null) return;
if (!has_content and label == null and href_raw == null) return;
const has_block = hasBlockDescendant(el.asNode());
const href = if (href_raw) |h| URL.resolve(self.page.call_arena, self.page.base(), h, .{ .encode = true }) catch h else null;
if (info.has_block) {
if (has_block) {
try self.renderChildren(el.asNode());
if (href) |h| {
if (!self.state.last_char_was_newline) try self.writer.writeByte('\n');
@@ -306,12 +307,25 @@ const Context = struct {
return;
}
const standalone = isStandaloneAnchor(el);
if (standalone) {
if (isStandaloneAnchor(el)) {
if (!self.state.last_char_was_newline) try self.writer.writeByte('\n');
}
try self.writer.writeByte('[');
if (info.has_visible) {
if (has_content) {
try self.renderChildren(el.asNode());
} else {
try self.writer.writeAll(label orelse "");
}
try self.writer.writeAll("](");
if (href) |h| {
try self.writer.writeAll(h);
}
try self.writer.writeAll(")\n");
self.state.last_char_was_newline = true;
return;
}
try self.writer.writeByte('[');
if (has_content) {
try self.renderChildren(el.asNode());
} else {
try self.writer.writeAll(label orelse "");
@@ -321,12 +335,7 @@ const Context = struct {
try self.writer.writeAll(h);
}
try self.writer.writeByte(')');
if (standalone) {
try self.writer.writeByte('\n');
self.state.last_char_was_newline = true;
} else {
self.state.last_char_was_newline = false;
}
return;
},
.input => {
@@ -341,8 +350,12 @@ const Context = struct {
else => {},
}
// --- Render Children ---
try self.renderChildren(el.asNode());
// --- Closing Tag Logic ---
// Suffixes
switch (tag) {
.pre => {
if (!self.state.last_char_was_newline) {
@@ -355,6 +368,7 @@ const Context = struct {
.code => {
if (self.state.pre_node == null) {
try self.writer.writeByte('`');
self.state.in_code = false;
self.state.last_char_was_newline = false;
}
},
@@ -397,6 +411,7 @@ const Context = struct {
else => {},
}
// Post-block newlines
if (tag.isBlock() and !self.state.in_table) {
try self.ensureNewline();
}
@@ -439,19 +454,15 @@ const Context = struct {
}
fn escape(self: *Context, text: []const u8) !void {
var start: usize = 0;
for (text, 0..) |c, i| {
for (text) |c| {
switch (c) {
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '#', '+', '-', '!', '|' => {
if (i > start) try self.writer.writeAll(text[start..i]);
try self.writer.writeByte('\\');
try self.writer.writeByte(c);
start = i + 1;
},
else => {},
else => try self.writer.writeByte(c),
}
}
if (start < text.len) try self.writer.writeAll(text[start..]);
}
};