webapi: add a number of DOMTokenList setters

While the token list itself is readonly, the attribute itself is settable. For
example, HTMLOutputElement's htmlFor says:

"Although the htmlFor property itself is read-only in the sense that you can't
replace the DOMTokenList object, you can still assign to the htmlFor property
directly"
This commit is contained in:
Karl Seguin committed 2026-09-28 09:59:04 +08:00
1 parent a9738c7df5
commit 8f568c1382
6 files changed
+69 -6

No files matched your search

+29
View File
@@ -21,3 +21,32 @@
testing.expectError('TypeError', () => $('#d1').classList.supports('foo'));
}
</script>
<a id=a1 rel="noopener"></a>
<area id=ar1 rel="noopener">
<output id=o1 for="x"></output>
<iframe id=if1></iframe>
<svg><a id=sa1 rel="noopener"></a></svg>
<script id=put_forwards_value>
{
// [PutForwards=value]: assigning sets the attribute and keeps the same list.
const cases = [
[$('#l1'), 'relList', 'rel'],
[$('#l1'), 'sizes', 'sizes'],
[$('#a1'), 'relList', 'rel'],
[$('#ar1'), 'relList', 'rel'],
[$('#o1'), 'htmlFor', 'for'],
[$('#if1'), 'sandbox', 'sandbox'],
[$('#sa1'), 'relList', 'rel'],
];
for (const [el, prop, attr] of cases) {
const list = el[prop];
el[prop] = 'one two';
testing.expectEqual('one two', el.getAttribute(attr));
testing.expectEqual(true, list === el[prop]);
testing.expectEqual(2, list.length);
testing.expectEqual('two', list.item(1));
}
}
</script>
+6 -1
View File
@@ -248,7 +248,7 @@ pub const JsApi = struct {
pub const rel = reflect.string("rel");
pub const @"type" = reflect.string("type");
pub const text = bridge.accessor(Anchor.getText, Anchor.setText, .{ .ce_reactions = true });
pub const relList = bridge.accessor(_getRelList, null, .{ .null_as_undefined = true });
pub const relList = bridge.accessor(_getRelList, _setRelList, .{ .null_as_undefined = true, .ce_reactions = true });
pub const toString = bridge.function(Anchor.getHref, .{});
fn _getRelList(self: *Anchor, frame: *Frame) !?*@import("../../collections.zig").DOMTokenList {
@@ -260,6 +260,11 @@ pub const JsApi = struct {
}
return element.getRelList(frame);
}
fn _setRelList(self: *Anchor, value: lp.String, frame: *Frame) !void {
const list = try _getRelList(self, frame) orelse return;
try list.setValue(value, frame);
}
};
const testing = @import("../../../../testing.zig");
+7 -1
View File
@@ -26,6 +26,7 @@ const DOMTokenList = @import("../../collections.zig").DOMTokenList;
const HtmlElement = @import("../Html.zig");
const String = lp.String;
const Area = @This();
pub const Proto = HtmlElement;
@@ -197,6 +198,11 @@ pub fn getRelList(self: *Area, frame: *Frame) !?*DOMTokenList {
return element.getRelList(frame);
}
fn setRelList(self: *Area, value: String, frame: *Frame) !void {
const list = try self.getRelList(frame) orelse return;
try list.setValue(value, frame);
}
fn getResolvedHref(self: *Area, frame: *Frame) !?[:0]const u8 {
const href = self.asElement().getAttributeInterned("href") orelse return null;
if (href.len == 0) {
@@ -244,7 +250,7 @@ pub const JsApi = struct {
pub const rel = reflect.string("rel");
pub const referrerPolicy = reflect.referrerPolicy();
pub const toString = bridge.function(Area.getHref, .{});
pub const relList = bridge.accessor(Area.getRelList, null, .{ .null_as_undefined = true });
pub const relList = bridge.accessor(Area.getRelList, Area.setRelList, .{ .null_as_undefined = true, .ce_reactions = true });
};
const testing = @import("../../../../testing.zig");
+13 -2
View File
@@ -28,6 +28,7 @@ const DOMTokenList = @import("../../collections.zig").DOMTokenList;
const HtmlElement = @import("../Html.zig");
const String = lp.String;
const Link = @This();
pub const Proto = HtmlElement;
@@ -91,6 +92,11 @@ fn getSizes(self: *Link, frame: *Frame) !?*DOMTokenList {
return element.getTokenList(.sizes, frame);
}
fn setSizes(self: *Link, value: String, frame: *Frame) !void {
const list = try self.getSizes(frame) orelse return;
try list.setValue(value, frame);
}
pub fn getRelList(self: *Link, frame: *Frame) !?*DOMTokenList {
const element = self.asElement();
// relList is only valid for HTML <link> elements, not SVG or MathML
@@ -100,6 +106,11 @@ pub fn getRelList(self: *Link, frame: *Frame) !?*DOMTokenList {
return element.getRelList(frame);
}
fn setRelList(self: *Link, value: String, frame: *Frame) !void {
const list = try self.getRelList(frame) orelse return;
try list.setValue(value, frame);
}
pub fn linkAddedCallback(self: *Link, frame: *Frame) !void {
// if we're planning on navigating to another frame, don't trigger load event.
if (frame.isGoingAway()) {
@@ -176,8 +187,8 @@ pub const JsApi = struct {
pub const @"type" = reflect.string("type");
pub const rev = reflect.string("rev");
pub const target = reflect.string("target");
pub const relList = bridge.accessor(Link.getRelList, null, .{ .null_as_undefined = true });
pub const sizes = bridge.accessor(Link.getSizes, null, .{ .null_as_undefined = true });
pub const relList = bridge.accessor(Link.getRelList, Link.setRelList, .{ .null_as_undefined = true, .ce_reactions = true });
pub const sizes = bridge.accessor(Link.getSizes, Link.setSizes, .{ .null_as_undefined = true, .ce_reactions = true });
};
// Parser-created <link> elements are void (no closing tag) so they never
+7 -1
View File
@@ -27,6 +27,7 @@ const DOMTokenList = @import("../../collections.zig").DOMTokenList;
const HtmlElement = @import("../Html.zig");
const String = lp.String;
const Output = @This();
pub const Proto = HtmlElement;
@@ -53,6 +54,11 @@ fn getHtmlFor(self: *Output, frame: *Frame) !?*DOMTokenList {
return element.getTokenList(.@"for", frame);
}
fn setHtmlFor(self: *Output, value: String, frame: *Frame) !void {
const list = try self.getHtmlFor(frame) orelse return;
try list.setValue(value, frame);
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Output);
@@ -66,5 +72,5 @@ pub const JsApi = struct {
pub const name = reflect.string("name");
pub const labels = bridge.accessor(Output.getLabels, null, .{});
pub const htmlFor = bridge.accessor(Output.getHtmlFor, null, .{ .null_as_undefined = true });
pub const htmlFor = bridge.accessor(Output.getHtmlFor, Output.setHtmlFor, .{ .null_as_undefined = true, .ce_reactions = true });
};
+7 -1
View File
@@ -28,6 +28,7 @@ const DOMTokenList = @import("../../collections.zig").DOMTokenList;
const Graphics = @import("Graphics.zig");
const String = lp.String;
const A = @This();
pub const Proto = Graphics;
@@ -45,6 +46,11 @@ pub fn getRelList(self: *A, frame: *Frame) !*DOMTokenList {
return self.asElement().getRelList(frame);
}
fn setRelList(self: *A, value: String, frame: *Frame) !void {
const list = try self.getRelList(frame);
try list.setValue(value, frame);
}
pub const JsApi = struct {
pub const bridge = js.Bridge(A);
@@ -59,5 +65,5 @@ pub const JsApi = struct {
return AnimatedString.getOrCreate(self.asElement(), .href, frame);
}
pub const relList = bridge.accessor(A.getRelList, null, .{});
pub const relList = bridge.accessor(A.getRelList, A.setRelList, .{ .ce_reactions = true });
};