From c3118282179a7a5ada1556bf3c8293de72c93455 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 14 Nov 2025 23:33:31 +0800 Subject: [PATCH] add add_attrs_if_missing callback --- src/browser/parser/Parser.zig | 30 ++++++++++++++++++++++++++++++ src/browser/parser/html5ever.zig | 3 +++ src/html5ever/lib.rs | 6 ++++++ src/html5ever/sink.rs | 26 ++++++++++---------------- src/html5ever/types.rs | 6 ++++++ 5 files changed, 55 insertions(+), 16 deletions(-) diff --git a/src/browser/parser/Parser.zig b/src/browser/parser/Parser.zig index f7cd5c55..1515d600 100644 --- a/src/browser/parser/Parser.zig +++ b/src/browser/parser/Parser.zig @@ -64,6 +64,8 @@ const Error = struct { append, create_element, create_comment, + append_doctype_to_document, + add_attrs_if_missing, }; }; @@ -80,6 +82,7 @@ pub fn parse(self: *Parser, html: []const u8) void { popCallback, createCommentCallback, appendDoctypeToDocument, + addAttrsIfMissingCallback, ); } @@ -96,6 +99,7 @@ pub fn parseFragment(self: *Parser, html: []const u8) void { popCallback, createCommentCallback, appendDoctypeToDocument, + addAttrsIfMissingCallback, ); } @@ -129,6 +133,7 @@ pub const Streaming = struct { popCallback, createCommentCallback, appendDoctypeToDocument, + addAttrsIfMissingCallback, ) orelse return error.ParserCreationFailed; } @@ -215,6 +220,31 @@ fn _appendDoctypeToDocument(self: *Parser, name: []const u8) !void { _ = name; } +fn addAttrsIfMissingCallback(ctx: *anyopaque, target_ref: *anyopaque, attributes: h5e.AttributeIterator) callconv(.c) void { + const self: *Parser = @ptrCast(@alignCast(ctx)); + self._addAttrsIfMissingCallback(getNode(target_ref), attributes) catch |err| { + self.err = .{ .err = err, .source = .add_attrs_if_missing }; + }; +} +fn _addAttrsIfMissingCallback(self: *Parser, node: *Node, attributes: h5e.AttributeIterator) !void { + const element = node.as(Element); + const page = self.page; + + const attr_list = element._attributes orelse blk: { + const a = try page.arena.create(@import("../webapi/element/Attribute.zig").List); + a.* = .{}; + element._attributes = a; + break :blk a; + }; + + while (attributes.next()) |attr| { + const name = attr.name.local.slice(); + const value = attr.value.slice(); + // putNew only adds if the attribute doesn't already exist + try attr_list.putNew(name, value, page); + } +} + fn getDataCallback(ctx: *anyopaque) callconv(.c) *anyopaque { const pn: *ParsedNode = @ptrCast(@alignCast(ctx)); // For non-elements, data is null. But, we expect this to only ever diff --git a/src/browser/parser/html5ever.zig b/src/browser/parser/html5ever.zig index 52985290..d03fbd8b 100644 --- a/src/browser/parser/html5ever.zig +++ b/src/browser/parser/html5ever.zig @@ -30,6 +30,7 @@ pub extern "c" fn html5ever_parse_document( popCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void, createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque, appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void, + addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void, ) void; pub extern "c" fn html5ever_parse_fragment( @@ -44,6 +45,7 @@ pub extern "c" fn html5ever_parse_fragment( popCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void, createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque, appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void, + addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void, ) void; pub extern "c" fn html5ever_attribute_iterator_next(ctx: *anyopaque) Nullable(Attribute); @@ -67,6 +69,7 @@ pub extern "c" fn html5ever_streaming_parser_create( popCallback: *const fn (ctx: *anyopaque, node_ref: *anyopaque) callconv(.c) void, createCommentCallback: *const fn (ctx: *anyopaque, StringSlice) callconv(.c) ?*anyopaque, appendDoctypeToDocument: *const fn (ctx: *anyopaque, StringSlice, StringSlice, StringSlice) callconv(.c) void, + addAttrsIfMissingCallback: *const fn (ctx: *anyopaque, target_ref: *anyopaque, AttributeIterator) callconv(.c) void, ) ?*anyopaque; pub extern "c" fn html5ever_streaming_parser_feed( diff --git a/src/html5ever/lib.rs b/src/html5ever/lib.rs index 6128b58c..5ef4de21 100644 --- a/src/html5ever/lib.rs +++ b/src/html5ever/lib.rs @@ -44,6 +44,7 @@ pub extern "C" fn html5ever_parse_document( pop_callback: PopCallback, create_comment_callback: CreateCommentCallback, append_doctype_to_document: AppendDoctypeToDocumentCallback, + add_attrs_if_missing_callback: AddAttrsIfMissingCallback, ) -> () { if html.is_null() || len == 0 { return (); @@ -63,6 +64,7 @@ pub extern "C" fn html5ever_parse_document( create_element_callback: create_element_callback, create_comment_callback: create_comment_callback, append_doctype_to_document: append_doctype_to_document, + add_attrs_if_missing_callback: add_attrs_if_missing_callback, }; let bytes = unsafe { std::slice::from_raw_parts(html, len) }; @@ -84,6 +86,7 @@ pub extern "C" fn html5ever_parse_fragment( pop_callback: PopCallback, create_comment_callback: CreateCommentCallback, append_doctype_to_document: AppendDoctypeToDocumentCallback, + add_attrs_if_missing_callback: AddAttrsIfMissingCallback, ) -> () { if html.is_null() || len == 0 { return (); @@ -103,6 +106,7 @@ pub extern "C" fn html5ever_parse_fragment( create_element_callback: create_element_callback, create_comment_callback: create_comment_callback, append_doctype_to_document: append_doctype_to_document, + add_attrs_if_missing_callback: add_attrs_if_missing_callback, }; let bytes = unsafe { std::slice::from_raw_parts(html, len) }; @@ -183,6 +187,7 @@ pub extern "C" fn html5ever_streaming_parser_create( pop_callback: PopCallback, create_comment_callback: CreateCommentCallback, append_doctype_to_document: AppendDoctypeToDocumentCallback, + add_attrs_if_missing_callback: AddAttrsIfMissingCallback, ) -> *mut c_void { let arena = Box::new(typed_arena::Arena::new()); @@ -205,6 +210,7 @@ pub extern "C" fn html5ever_streaming_parser_create( create_element_callback: create_element_callback, create_comment_callback: create_comment_callback, append_doctype_to_document: append_doctype_to_document, + add_attrs_if_missing_callback: add_attrs_if_missing_callback, }; // Create a parser which implements TendrilSink for streaming parsing diff --git a/src/html5ever/sink.rs b/src/html5ever/sink.rs index 21d3a47e..9976450f 100644 --- a/src/html5ever/sink.rs +++ b/src/html5ever/sink.rs @@ -55,6 +55,7 @@ pub struct Sink<'arena> { pub create_element_callback: CreateElementCallback, pub create_comment_callback: CreateCommentCallback, pub append_doctype_to_document: AppendDoctypeToDocumentCallback, + pub add_attrs_if_missing_callback: AddAttrsIfMissingCallback, } impl<'arena> TreeSink for Sink<'arena> { @@ -120,19 +121,6 @@ impl<'arena> TreeSink for Sink<'arena> { let data = self.arena.alloc(ElementData::new(name.clone(), flags)); unsafe { - let mut c_attrs: Vec = Vec::with_capacity(attrs.len()); - - for attr in attrs.iter() { - let v: &str = &attr.value; - c_attrs.push(CAttribute { - name: CQualName::create(&attr.name), - value: StringSlice { - ptr: v.as_ptr(), - len: attr.value.len(), - }, - }) - } - let mut attribute_iterator = CAttributeIterator { vec: attrs, pos: 0 }; return (self.create_element_callback)( @@ -226,9 +214,15 @@ impl<'arena> TreeSink for Sink<'arena> { } fn add_attrs_if_missing(&self, target: &Ref, attrs: Vec) { - _ = target; - _ = attrs; - panic!("add_attrs_if_missing"); + unsafe { + let mut attribute_iterator = CAttributeIterator { vec: attrs, pos: 0 }; + + (self.add_attrs_if_missing_callback)( + self.ctx, + *target, + &mut attribute_iterator as *mut _ as *mut c_void, + ); + } } fn remove_from_parent(&self, target: &Ref) { diff --git a/src/html5ever/types.rs b/src/html5ever/types.rs index f87c8723..fa9c6cbc 100644 --- a/src/html5ever/types.rs +++ b/src/html5ever/types.rs @@ -51,6 +51,12 @@ pub type ParseErrorCallback = unsafe extern "C" fn(ctx: Ref, str: StringSlice) - pub type PopCallback = unsafe extern "C" fn(ctx: Ref, node: Ref) -> (); +pub type AddAttrsIfMissingCallback = unsafe extern "C" fn( + ctx: Ref, + target: Ref, + attributes: *mut c_void, +) -> (); + pub type Ref = *const c_void; #[repr(C)]