Files
browser/src/browser/webapi/element/html/FieldSet.zig
Karl Seguin 89814fd855 Fix ce_reactions audit gaps and iterator-invalidation UAF
popAndInvoke captured the reactions queue as a slice at loop entry. If
firing a reaction triggered a nested scope whose enqueue grew the
underlying ArrayList, the captured slice became dangling and the next
iteration read freed memory. Switch to indexed iteration so the queue
pointer is re-read each step. Repros via wpt/custom-elements/
enqueue-custom-element-callback-reactions-inside-another-callback.html.

The rest of the diff adds .ce_reactions = true to bridge declarations
that were missing it per WebIDL [CEReactions] *and* whose Zig impl
actually performs a DOM/attribute mutation (verified by reading each
setter). Without the flag, the algorithm's enqueue path hits
assertScopeActive and panics. Runtime-state-only setters (Input.value,
Input.checked, Select.value, Option.selected, Media.muted/volume, etc.)
are deliberately left untagged.

Covered:
- CustomElementRegistry.define, .upgrade
- HTMLDocument: body, title, dir
- Document: open, close
- HTMLElement base: insertAdjacentHTML, dir, hidden, lang, tabIndex,
  title, innerText
- Range: insertNode, deleteContents, extractContents,
  surroundContents, createContextualFragment
- Selection: deleteFromDocument
- HTMLOptionsCollection: add, remove
- DOMStringMap (dataset): namedIndexed setter/deleter — required adding
  .ce_reactions to NamedIndexed.Opts in bridge.zig
- ~28 HTMLxxxElement files: every settable attribute that resolves to
  setAttributeSafe/removeAttribute (Anchor, Button, Canvas, Data,
  Details, Dialog, FieldSet, Form, IFrame, Image, Input, Label, Link,
  LI, Media, Meta, OL, OptGroup, Option, Quote, Script, Select, Slot,
  Style, TableCell, Template, TextArea, Time, Track, Video)
2026-05-23 07:52:34 +08:00

55 lines
1.7 KiB
Zig

const js = @import("../../../js/js.zig");
const Frame = @import("../../../Frame.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const HtmlElement = @import("../Html.zig");
const FieldSet = @This();
_proto: *HtmlElement,
pub fn asElement(self: *FieldSet) *Element {
return self._proto._proto;
}
pub fn asNode(self: *FieldSet) *Node {
return self.asElement().asNode();
}
pub fn getDisabled(self: *FieldSet) bool {
return self.asElement().getAttributeSafe(comptime .wrap("disabled")) != null;
}
pub fn setDisabled(self: *FieldSet, value: bool, frame: *Frame) !void {
if (value) {
try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), frame);
} else {
try self.asElement().removeAttribute(comptime .wrap("disabled"), frame);
}
}
pub fn getName(self: *FieldSet) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("name")) orelse "";
}
pub fn setName(self: *FieldSet, value: []const u8, frame: *Frame) !void {
try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(value), frame);
}
pub const JsApi = struct {
pub const bridge = js.Bridge(FieldSet);
pub const Meta = struct {
pub const name = "HTMLFieldSetElement";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const disabled = bridge.accessor(FieldSet.getDisabled, FieldSet.setDisabled, .{ .ce_reactions = true });
pub const name = bridge.accessor(FieldSet.getName, FieldSet.setName, .{ .ce_reactions = true });
};
const testing = @import("../../../../testing.zig");
test "WebApi: HTML.FieldSet" {
try testing.htmlRunner("element/html/fieldset.html", .{});
}