Make "Safe" variants of Attribute work on String

This commit is contained in:
Karl Seguin
2026-01-23 07:16:05 +08:00
parent 54c45a0cfd
commit 16ef487871
40 changed files with 240 additions and 186 deletions

View File

@@ -1037,7 +1037,7 @@ pub fn scriptAddedCallback(self: *Page, comptime from_parser: bool, script: *Ele
self._script_manager.addFromElement(from_parser, script, "parsing") catch |err| { self._script_manager.addFromElement(from_parser, script, "parsing") catch |err| {
log.err(.page, "page.scriptAddedCallback", .{ log.err(.page, "page.scriptAddedCallback", .{
.err = err, .err = err,
.src = script.asElement().getAttributeSafe("src"), .src = script.asElement().getAttributeSafe(comptime .literal("src")),
}); });
}; };
} }
@@ -1122,7 +1122,7 @@ pub fn getElementByIdFromNode(self: *Page, node: *Node, id: []const u8) ?*Elemen
} }
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(node, .{}); var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(node, .{});
while (tw.next()) |el| { while (tw.next()) |el| {
const element_id = el.getAttributeSafe("id") orelse continue; const element_id = el.getAttributeSafe(comptime .literal("id")) orelse continue;
if (std.mem.eql(u8, element_id, id)) { if (std.mem.eql(u8, element_id, id)) {
return el; return el;
} }
@@ -1696,7 +1696,7 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const
// If page's base url is not already set, fill it with the base // If page's base url is not already set, fill it with the base
// tag. // tag.
if (self.base_url == null) { if (self.base_url == null) {
if (n.as(Element).getAttributeSafe("href")) |href| { if (n.as(Element).getAttributeSafe(comptime .literal("href"))) |href| {
self.base_url = try URL.resolve(self.arena, self.url, href, .{}); self.base_url = try URL.resolve(self.arena, self.url, href, .{});
} }
} }
@@ -2307,7 +2307,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts
if (parent.is(Element)) |parent_el| { if (parent.is(Element)) |parent_el| {
if (self._element_shadow_roots.get(parent_el)) |shadow_root| { if (self._element_shadow_roots.get(parent_el)) |shadow_root| {
// Signal slot changes for any affected slots // Signal slot changes for any affected slots
const slot_name = el.getAttributeSafe("slot") orelse ""; const slot_name = el.getAttributeSafe(comptime .literal("slot")) orelse "";
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(shadow_root.asNode(), .{}); var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(shadow_root.asNode(), .{});
while (tw.next()) |slot_el| { while (tw.next()) |slot_el| {
if (slot_el.is(Element.Html.Slot)) |slot| { if (slot_el.is(Element.Html.Slot)) |slot| {
@@ -2346,7 +2346,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts
// the ID map and invoking disconnectedCallback for custom elements // the ID map and invoking disconnectedCallback for custom elements
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(child, .{}); var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(child, .{});
while (tw.next()) |el| { while (tw.next()) |el| {
if (el.getAttributeSafe("id")) |id| { if (el.getAttributeSafe(comptime .literal("id"))) |id| {
self.removeElementIdWithMaps(id_maps.?, id); self.removeElementIdWithMaps(id_maps.?, id);
} }
@@ -2480,7 +2480,7 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
// For main document parsing, we know nodes are connected (fast path) // For main document parsing, we know nodes are connected (fast path)
// For fragment parsing (innerHTML), we need to check connectivity // For fragment parsing (innerHTML), we need to check connectivity
if (child.isConnected() or child.isInShadowTree()) { if (child.isConnected() or child.isInShadowTree()) {
if (el.getAttributeSafe("id")) |id| { if (el.getAttributeSafe(comptime .literal("id"))) |id| {
try self.addElementId(parent, el, id); try self.addElementId(parent, el, id);
} }
try Element.Html.Custom.invokeConnectedCallbackOnElement(true, el, self); try Element.Html.Custom.invokeConnectedCallbackOnElement(true, el, self);
@@ -2519,7 +2519,7 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(child, .{}); var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(child, .{});
while (tw.next()) |el| { while (tw.next()) |el| {
if (el.getAttributeSafe("id")) |id| { if (el.getAttributeSafe(comptime .literal("id"))) |id| {
try self.addElementId(el.asNode()._parent.?, el, id); try self.addElementId(el.asNode()._parent.?, el, id);
} }
@@ -2622,7 +2622,7 @@ fn updateElementAssignedSlot(self: *Page, element: *Element) void {
const parent_el = parent.is(Element) orelse return; const parent_el = parent.is(Element) orelse return;
const shadow_root = self._element_shadow_roots.get(parent_el) orelse return; const shadow_root = self._element_shadow_roots.get(parent_el) orelse return;
const slot_name = element.getAttributeSafe("slot") orelse ""; const slot_name = element.getAttributeSafe(comptime .literal("slot")) orelse "";
// Recursively search through the shadow root for a matching slot // Recursively search through the shadow root for a matching slot
if (findMatchingSlot(shadow_root.asNode(), slot_name)) |slot| { if (findMatchingSlot(shadow_root.asNode(), slot_name)) |slot| {
@@ -2919,7 +2919,7 @@ pub fn handleClick(self: *Page, target: *Node) !void {
switch (html_element._type) { switch (html_element._type) {
.anchor => |anchor| { .anchor => |anchor| {
const href = element.getAttributeSafe("href") orelse return; const href = element.getAttributeSafe(comptime .literal("href")) orelse return;
if (href.len == 0) { if (href.len == 0) {
return; return;
} }
@@ -3015,7 +3015,7 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
const form = form_ orelse return; const form = form_ orelse return;
if (submitter_) |submitter| { if (submitter_) |submitter| {
if (submitter.getAttributeSafe("disabled") != null) { if (submitter.getAttributeSafe(comptime .literal("disabled")) != null) {
return; return;
} }
} }
@@ -3028,13 +3028,13 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
const transfer_arena = self._session.transfer_arena; const transfer_arena = self._session.transfer_arena;
const encoding = form_element.getAttributeSafe("enctype"); const encoding = form_element.getAttributeSafe(comptime .literal("enctype"));
var buf = std.Io.Writer.Allocating.init(transfer_arena); var buf = std.Io.Writer.Allocating.init(transfer_arena);
try form_data.write(encoding, &buf.writer); try form_data.write(encoding, &buf.writer);
const method = form_element.getAttributeSafe("method") orelse ""; const method = form_element.getAttributeSafe(comptime .literal("method")) orelse "";
var action = form_element.getAttributeSafe("action") orelse self.url; var action = form_element.getAttributeSafe(comptime .literal("action")) orelse self.url;
var opts = NavigateOpts{ var opts = NavigateOpts{
.reason = .form, .reason = .form,

View File

@@ -152,14 +152,14 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
script_element._executed = true; script_element._executed = true;
const element = script_element.asElement(); const element = script_element.asElement();
if (element.getAttributeSafe("nomodule") != null) { if (element.getAttributeSafe(comptime .literal("nomodule")) != null) {
// these scripts should only be loaded if we don't support modules // these scripts should only be loaded if we don't support modules
// but since we do support modules, we can just skip them. // but since we do support modules, we can just skip them.
return; return;
} }
const kind: Script.Kind = blk: { const kind: Script.Kind = blk: {
const script_type = element.getAttributeSafe("type") orelse break :blk .javascript; const script_type = element.getAttributeSafe(comptime .literal("type")) orelse break :blk .javascript;
if (script_type.len == 0) { if (script_type.len == 0) {
break :blk .javascript; break :blk .javascript;
} }
@@ -186,7 +186,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
var source: Script.Source = undefined; var source: Script.Source = undefined;
var remote_url: ?[:0]const u8 = null; var remote_url: ?[:0]const u8 = null;
const base_url = page.base(); const base_url = page.base();
if (element.getAttributeSafe("src")) |src| { if (element.getAttributeSafe(comptime .literal("src"))) |src| {
if (try parseDataURI(page.arena, src)) |data_uri| { if (try parseDataURI(page.arena, src)) |data_uri| {
source = .{ .@"inline" = data_uri }; source = .{ .@"inline" = data_uri };
} else { } else {
@@ -217,12 +217,12 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
break :blk if (kind == .module) .@"defer" else .normal; break :blk if (kind == .module) .@"defer" else .normal;
} }
if (element.getAttributeSafe("async") != null) { if (element.getAttributeSafe(comptime .literal("async")) != null) {
break :blk .async; break :blk .async;
} }
// Check for defer or module (before checking dynamic script default) // Check for defer or module (before checking dynamic script default)
if (kind == .module or element.getAttributeSafe("defer") != null) { if (kind == .module or element.getAttributeSafe(comptime .literal("defer")) != null) {
break :blk .@"defer"; break :blk .@"defer";
} }

View File

@@ -110,7 +110,7 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
// to render that "active" content, so when we're trying to render // to render that "active" content, so when we're trying to render
// it, we don't want to skip it. // it, we don't want to skip it.
if ((comptime force_slot == false) and opts.shadow == .rendered) { if ((comptime force_slot == false) and opts.shadow == .rendered) {
if (el.getAttributeSafe("slot")) |_| { if (el.getAttributeSafe(comptime .literal("slot"))) |_| {
// Skip - will be rendered by the Slot if it's the active container // Skip - will be rendered by the Slot if it's the active container
return; return;
} }
@@ -253,12 +253,12 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool {
if (std.mem.eql(u8, tag_name, "noscript")) return true; if (std.mem.eql(u8, tag_name, "noscript")) return true;
if (std.mem.eql(u8, tag_name, "link")) { if (std.mem.eql(u8, tag_name, "link")) {
if (el.getAttributeSafe("as")) |as| { if (el.getAttributeSafe(comptime .literal("as"))) |as| {
if (std.mem.eql(u8, as, "script")) return true; if (std.mem.eql(u8, as, "script")) return true;
} }
if (el.getAttributeSafe("rel")) |rel| { if (el.getAttributeSafe(comptime .literal("rel"))) |rel| {
if (std.mem.eql(u8, rel, "modulepreload") or std.mem.eql(u8, rel, "preload")) { if (std.mem.eql(u8, rel, "modulepreload") or std.mem.eql(u8, rel, "preload")) {
if (el.getAttributeSafe("as")) |as| { if (el.getAttributeSafe(comptime .literal("as"))) |as| {
if (std.mem.eql(u8, as, "script")) return true; if (std.mem.eql(u8, as, "script")) return true;
} }
} }
@@ -270,7 +270,7 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool {
if (std.mem.eql(u8, tag_name, "style")) return true; if (std.mem.eql(u8, tag_name, "style")) return true;
if (std.mem.eql(u8, tag_name, "link")) { if (std.mem.eql(u8, tag_name, "link")) {
if (el.getAttributeSafe("rel")) |rel| { if (el.getAttributeSafe(comptime .literal("rel"))) |rel| {
if (std.mem.eql(u8, rel, "stylesheet")) return true; if (std.mem.eql(u8, rel, "stylesheet")) return true;
} }
} }

View File

@@ -199,7 +199,7 @@ pub fn getElementById(self: *Document, id: []const u8, page: *Page) ?*Element {
if (self._removed_ids.remove(id)) { if (self._removed_ids.remove(id)) {
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{}); var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
while (tw.next()) |el| { while (tw.next()) |el| {
const element_id = el.getAttributeSafe("id") orelse continue; const element_id = el.getAttributeSafe(comptime .literal("id")) orelse continue;
if (std.mem.eql(u8, element_id, id)) { if (std.mem.eql(u8, element_id, id)) {
// we ignore this error to keep getElementById easy to call // we ignore this error to keep getElementById easy to call
// if it really failed, then we're out of memory and nothing's // if it really failed, then we're out of memory and nothing's

View File

@@ -74,7 +74,7 @@ pub fn getElementById(self: *DocumentFragment, id: []const u8) ?*Element {
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{}); var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
while (tw.next()) |el| { while (tw.next()) |el| {
if (el.getAttributeSafe("id")) |element_id| { if (el.getAttributeSafe(comptime .literal("id"))) |element_id| {
if (std.mem.eql(u8, element_id, id)) { if (std.mem.eql(u8, element_id, id)) {
return el; return el;
} }

View File

@@ -427,7 +427,7 @@ pub fn setInnerHTML(self: *Element, html: []const u8, page: *Page) !void {
} }
pub fn getId(self: *const Element) []const u8 { pub fn getId(self: *const Element) []const u8 {
return self.getAttributeSafe("id") orelse ""; return self.getAttributeSafe(comptime .literal("id")) orelse "";
} }
pub fn setId(self: *Element, value: []const u8, page: *Page) !void { pub fn setId(self: *Element, value: []const u8, page: *Page) !void {
@@ -435,7 +435,7 @@ pub fn setId(self: *Element, value: []const u8, page: *Page) !void {
} }
pub fn getSlot(self: *const Element) []const u8 { pub fn getSlot(self: *const Element) []const u8 {
return self.getAttributeSafe("slot") orelse ""; return self.getAttributeSafe(comptime .literal("slot")) orelse "";
} }
pub fn setSlot(self: *Element, value: []const u8, page: *Page) !void { pub fn setSlot(self: *Element, value: []const u8, page: *Page) !void {
@@ -443,7 +443,7 @@ pub fn setSlot(self: *Element, value: []const u8, page: *Page) !void {
} }
pub fn getDir(self: *const Element) []const u8 { pub fn getDir(self: *const Element) []const u8 {
return self.getAttributeSafe("dir") orelse ""; return self.getAttributeSafe(comptime .literal("dir")) orelse "";
} }
pub fn setDir(self: *Element, value: []const u8, page: *Page) !void { pub fn setDir(self: *Element, value: []const u8, page: *Page) !void {
@@ -451,7 +451,7 @@ pub fn setDir(self: *Element, value: []const u8, page: *Page) !void {
} }
pub fn getClassName(self: *const Element) []const u8 { pub fn getClassName(self: *const Element) []const u8 {
return self.getAttributeSafe("class") orelse ""; return self.getAttributeSafe(comptime .literal("class")) orelse "";
} }
pub fn setClassName(self: *Element, value: []const u8, page: *Page) !void { pub fn setClassName(self: *Element, value: []const u8, page: *Page) !void {
@@ -484,7 +484,7 @@ pub fn getAttributeNS(
return self.getAttribute(local_name, page); return self.getAttribute(local_name, page);
} }
pub fn getAttributeSafe(self: *const Element, name: []const u8) ?[]const u8 { pub fn getAttributeSafe(self: *const Element, name: String) ?[]const u8 {
const attributes = self._attributes orelse return null; const attributes = self._attributes orelse return null;
return attributes.getSafe(name); return attributes.getSafe(name);
} }
@@ -495,7 +495,7 @@ pub fn hasAttribute(self: *const Element, name: []const u8, page: *Page) !bool {
return value != null; return value != null;
} }
pub fn hasAttributeSafe(self: *const Element, name: []const u8) bool { pub fn hasAttributeSafe(self: *const Element, name: String) bool {
const attributes = self._attributes orelse return false; const attributes = self._attributes orelse return false;
return attributes.hasSafe(name); return attributes.hasSafe(name);
} }
@@ -666,7 +666,7 @@ pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList {
if (!gop.found_existing) { if (!gop.found_existing) {
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{ gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
._element = self, ._element = self,
._attribute_name = "class", ._attribute_name = comptime .literal("class"),
}); });
} }
return gop.value_ptr.*; return gop.value_ptr.*;
@@ -677,7 +677,7 @@ pub fn getRelList(self: *Element, page: *Page) !*collections.DOMTokenList {
if (!gop.found_existing) { if (!gop.found_existing) {
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{ gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
._element = self, ._element = self,
._attribute_name = "rel", ._attribute_name = comptime .literal("rel"),
}); });
} }
return gop.value_ptr.*; return gop.value_ptr.*;
@@ -919,10 +919,10 @@ fn getElementDimensions(self: *Element, page: *Page) !struct { width: f64, heigh
if (width == 5.0) width = 1920.0; if (width == 5.0) width = 1920.0;
if (height == 5.0) height = 100_000_000.0; if (height == 5.0) height = 100_000_000.0;
} else if (tag == .img or tag == .iframe) { } else if (tag == .img or tag == .iframe) {
if (self.getAttributeSafe("width")) |w| { if (self.getAttributeSafe(comptime .literal("width"))) |w| {
width = std.fmt.parseFloat(f64, w) catch width; width = std.fmt.parseFloat(f64, w) catch width;
} }
if (self.getAttributeSafe("height")) |h| { if (self.getAttributeSafe(comptime .literal("height"))) |h| {
height = std.fmt.parseFloat(f64, h) catch height; height = std.fmt.parseFloat(f64, h) catch height;
} }
} }

View File

@@ -84,7 +84,7 @@ pub fn getElementById(self: *ShadowRoot, id: []const u8, page: *Page) ?*Element
// Do a tree walk to find another element with this ID // Do a tree walk to find another element with this ID
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{}); var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
while (tw.next()) |el| { while (tw.next()) |el| {
const element_id = el.getAttributeSafe("id") orelse continue; const element_id = el.getAttributeSafe(comptime .literal("id")) orelse continue;
if (std.mem.eql(u8, element_id, id)) { if (std.mem.eql(u8, element_id, id)) {
// we ignore this error to keep getElementById easy to call // we ignore this error to keep getElementById easy to call
// if it really failed, then we're out of memory and nothing's // if it really failed, then we're out of memory and nothing's

View File

@@ -18,8 +18,9 @@
const std = @import("std"); const std = @import("std");
const log = @import("../../../log.zig"); const log = @import("../../../log.zig");
const js = @import("../../js/js.zig"); const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Element = @import("../Element.zig"); const Element = @import("../Element.zig");
const GenericIterator = @import("iterator.zig").Entry; const GenericIterator = @import("iterator.zig").Entry;
@@ -31,7 +32,7 @@ pub const DOMTokenList = @This();
// is that lists tend to be very short (often just 1 item). // is that lists tend to be very short (often just 1 item).
_element: *Element, _element: *Element,
_attribute_name: []const u8, _attribute_name: String,
pub const KeyIterator = GenericIterator(Iterator, "0"); pub const KeyIterator = GenericIterator(Iterator, "0");
pub const ValueIterator = GenericIterator(Iterator, "1"); pub const ValueIterator = GenericIterator(Iterator, "1");
@@ -160,7 +161,7 @@ pub fn getValue(self: *const DOMTokenList) []const u8 {
} }
pub fn setValue(self: *DOMTokenList, value: []const u8, page: *Page) !void { pub fn setValue(self: *DOMTokenList, value: []const u8, page: *Page) !void {
try self._element.setAttribute(self._attribute_name, value, page); try self._element.setAttribute(self._attribute_name.str(), value, page);
} }
pub fn keys(self: *DOMTokenList, page: *Page) !*KeyIterator { pub fn keys(self: *DOMTokenList, page: *Page) !*KeyIterator {
@@ -226,7 +227,7 @@ fn validateToken(token: []const u8) !void {
fn updateAttribute(self: *DOMTokenList, tokens: Lookup, page: *Page) !void { fn updateAttribute(self: *DOMTokenList, tokens: Lookup, page: *Page) !void {
const joined = try std.mem.join(page.call_arena, " ", tokens.keys()); const joined = try std.mem.join(page.call_arena, " ", tokens.keys());
try self._element.setAttribute(self._attribute_name, joined, page); try self._element.setAttribute(self._attribute_name.str(), joined, page);
} }
const Iterator = struct { const Iterator = struct {

View File

@@ -111,7 +111,7 @@ pub fn getByName(self: *HTMLAllCollection, name: []const u8, page: *Page) ?*Elem
while (tw.next()) |node| { while (tw.next()) |node| {
if (node.is(Element)) |el| { if (node.is(Element)) |el| {
if (el.getAttributeSafe("name")) |attr_name| { if (el.getAttributeSafe(comptime .literal("name"))) |attr_name| {
if (std.mem.eql(u8, attr_name, name)) { if (std.mem.eql(u8, attr_name, name)) {
return el; return el;
} }

View File

@@ -59,12 +59,12 @@ pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, page: *Pag
var it = try self.iterator(); var it = try self.iterator();
while (it.next()) |element| { while (it.next()) |element| {
const is_match = blk: { const is_match = blk: {
if (element.getAttributeSafe("id")) |id| { if (element.getAttributeSafe(comptime .literal("id"))) |id| {
if (std.mem.eql(u8, id, name)) { if (std.mem.eql(u8, id, name)) {
break :blk true; break :blk true;
} }
} }
if (element.getAttributeSafe("name")) |elem_name| { if (element.getAttributeSafe(comptime .literal("name"))) |elem_name| {
if (std.mem.eql(u8, elem_name, name)) { if (std.mem.eql(u8, elem_name, name)) {
break :blk true; break :blk true;
} }

View File

@@ -69,7 +69,7 @@ pub fn getValue(self: *RadioNodeList) ![]const u8 {
if (!input.getChecked()) { if (!input.getChecked()) {
continue; continue;
} }
return element.getAttributeSafe("value") orelse "on"; return element.getAttributeSafe(comptime .literal("value")) orelse "on";
} }
return ""; return "";
} }
@@ -82,7 +82,7 @@ pub fn setValue(self: *RadioNodeList, value: []const u8, page: *Page) !void {
continue; continue;
} }
const input_value = element.getAttributeSafe("value"); const input_value = element.getAttributeSafe(comptime .literal("value"));
const matches_value = blk: { const matches_value = blk: {
if (std.mem.eql(u8, value, "on")) { if (std.mem.eql(u8, value, "on")) {
break :blk input_value == null or (input_value != null and std.mem.eql(u8, input_value.?, "on")); break :blk input_value == null or (input_value != null and std.mem.eql(u8, input_value.?, "on"));
@@ -99,12 +99,12 @@ pub fn setValue(self: *RadioNodeList, value: []const u8, page: *Page) !void {
} }
fn matches(self: *const RadioNodeList, element: *Element) bool { fn matches(self: *const RadioNodeList, element: *Element) bool {
if (element.getAttributeSafe("id")) |id| { if (element.getAttributeSafe(comptime .literal("id"))) |id| {
if (std.mem.eql(u8, id, self._name)) { if (std.mem.eql(u8, id, self._name)) {
return true; return true;
} }
} }
if (element.getAttributeSafe("name")) |elem_name| { if (element.getAttributeSafe(comptime .literal("name"))) |elem_name| {
if (std.mem.eql(u8, elem_name, self._name)) { if (std.mem.eql(u8, elem_name, self._name)) {
return true; return true;
} }

View File

@@ -187,7 +187,7 @@ pub fn NodeLive(comptime mode: Mode) type {
// (like length or getAtIndex) // (like length or getAtIndex)
var tw = self._tw.clone(); var tw = self._tw.clone();
while (self.nextTw(&tw)) |element| { while (self.nextTw(&tw)) |element| {
const element_name = element.getAttributeSafe("name") orelse continue; const element_name = element.getAttributeSafe(comptime .literal("name")) orelse continue;
if (std.mem.eql(u8, element_name, name)) { if (std.mem.eql(u8, element_name, name)) {
return element; return element;
} }
@@ -228,7 +228,7 @@ pub fn NodeLive(comptime mode: Mode) type {
} }
const el = node.is(Element) orelse return false; const el = node.is(Element) orelse return false;
const class_attr = el.getAttributeSafe("class") orelse return false; const class_attr = el.getAttributeSafe(comptime .literal("class")) orelse return false;
for (self._filter) |class_name| { for (self._filter) |class_name| {
if (!Selector.classAttributeContains(class_attr, class_name)) { if (!Selector.classAttributeContains(class_attr, class_name)) {
return false; return false;
@@ -238,7 +238,7 @@ pub fn NodeLive(comptime mode: Mode) type {
}, },
.name => { .name => {
const el = node.is(Element) orelse return false; const el = node.is(Element) orelse return false;
const name_attr = el.getAttributeSafe("name") orelse return false; const name_attr = el.getAttributeSafe(comptime .literal("name")) orelse return false;
return std.mem.eql(u8, name_attr, self._filter); return std.mem.eql(u8, name_attr, self._filter);
}, },
.all_elements => return node._type == .element, .all_elements => return node._type == .element,
@@ -258,14 +258,14 @@ pub fn NodeLive(comptime mode: Mode) type {
const el = node.is(Element) orelse return false; const el = node.is(Element) orelse return false;
const Anchor = Element.Html.Anchor; const Anchor = Element.Html.Anchor;
if (el.is(Anchor) == null) return false; if (el.is(Anchor) == null) return false;
return el.hasAttributeSafe("href"); return el.hasAttributeSafe(comptime .literal("href"));
}, },
.anchors => { .anchors => {
// Anchors are <a> elements with name attribute // Anchors are <a> elements with name attribute
const el = node.is(Element) orelse return false; const el = node.is(Element) orelse return false;
const Anchor = Element.Html.Anchor; const Anchor = Element.Html.Anchor;
if (el.is(Anchor) == null) return false; if (el.is(Anchor) == null) return false;
return el.hasAttributeSafe("name"); return el.hasAttributeSafe(comptime .literal("name"));
}, },
.form => { .form => {
const el = node.is(Element) orelse return false; const el = node.is(Element) orelse return false;
@@ -273,8 +273,8 @@ pub fn NodeLive(comptime mode: Mode) type {
return false; return false;
} }
if (el.getAttributeSafe("form")) |form_attr| { if (el.getAttributeSafe(comptime .literal("form"))) |form_attr| {
const form_id = self._filter.asElement().getAttributeSafe("id") orelse return false; const form_id = self._filter.asElement().getAttributeSafe(comptime .literal("id")) orelse return false;
return std.mem.eql(u8, form_attr, form_id); return std.mem.eql(u8, form_attr, form_id);
} }

View File

@@ -170,13 +170,13 @@ pub const List = struct {
} }
// meant for internal usage, where the name is known to be properly cased // meant for internal usage, where the name is known to be properly cased
pub fn getSafe(self: *const List, name: []const u8) ?[]const u8 { pub fn getSafe(self: *const List, name: String) ?[]const u8 {
const entry = self.getEntryWithNormalizedName(name) orelse return null; const entry = self.getEntryWithNormalizedName(name) orelse return null;
return entry._value.str(); return entry._value.str();
} }
// meant for internal usage, where the name is known to be properly cased // meant for internal usage, where the name is known to be properly cased
pub fn hasSafe(self: *const List, name: []const u8) bool { pub fn hasSafe(self: *const List, name: String) bool {
return self.getEntryWithNormalizedName(name) != null; return self.getEntryWithNormalizedName(name) != null;
} }
@@ -197,7 +197,7 @@ pub const List = struct {
} }
pub fn putSafe(self: *List, name: []const u8, value: []const u8, element: *Element, page: *Page) !*Entry { pub fn putSafe(self: *List, name: []const u8, value: []const u8, element: *Element, page: *Page) !*Entry {
const entry = self.getEntryWithNormalizedName(name); const entry = self.getEntryWithNormalizedNameOld(name);
return self._put(.{ .entry = entry, .normalized = name }, value, element, page); return self._put(.{ .entry = entry, .normalized = name }, value, element, page);
} }
@@ -331,11 +331,24 @@ pub const List = struct {
return .{ return .{
.normalized = normalized, .normalized = normalized,
.entry = self.getEntryWithNormalizedName(normalized), .entry = self.getEntryWithNormalizedNameOld(normalized),
}; };
} }
fn getEntryWithNormalizedName(self: *const List, name: []const u8) ?*Entry { fn getEntryWithNormalizedName(self: *const List, name: String) ?*Entry {
var node = self._list.first;
while (node) |n| {
var e = Entry.fromNode(n);
if (e._name.eql(name)) {
return e;
}
node = n.next;
}
return null;
}
// TODO remove when we're done making everything String-based
fn getEntryWithNormalizedNameOld(self: *const List, name: []const u8) ?*Entry {
var node = self._list.first; var node = self._list.first;
while (node) |n| { while (node) |n| {
var e = Entry.fromNode(n); var e = Entry.fromNode(n);

View File

@@ -40,7 +40,7 @@ pub fn asNode(self: *Anchor) *Node {
pub fn getHref(self: *Anchor, page: *Page) ![]const u8 { pub fn getHref(self: *Anchor, page: *Page) ![]const u8 {
const element = self.asElement(); const element = self.asElement();
const href = element.getAttributeSafe("href") orelse return ""; const href = element.getAttributeSafe(comptime .literal("href")) orelse return "";
if (href.len == 0) { if (href.len == 0) {
return ""; return "";
} }
@@ -52,7 +52,7 @@ pub fn setHref(self: *Anchor, value: []const u8, page: *Page) !void {
} }
pub fn getTarget(self: *Anchor) []const u8 { pub fn getTarget(self: *Anchor) []const u8 {
return self.asElement().getAttributeSafe("target") orelse ""; return self.asElement().getAttributeSafe(comptime .literal("target")) orelse "";
} }
pub fn setTarget(self: *Anchor, value: []const u8, page: *Page) !void { pub fn setTarget(self: *Anchor, value: []const u8, page: *Page) !void {
@@ -167,7 +167,7 @@ pub fn setProtocol(self: *Anchor, value: []const u8, page: *Page) !void {
} }
pub fn getType(self: *Anchor) []const u8 { pub fn getType(self: *Anchor) []const u8 {
return self.asElement().getAttributeSafe("type") orelse ""; return self.asElement().getAttributeSafe(comptime .literal("type")) orelse "";
} }
pub fn setType(self: *Anchor, value: []const u8, page: *Page) !void { pub fn setType(self: *Anchor, value: []const u8, page: *Page) !void {
@@ -175,7 +175,7 @@ pub fn setType(self: *Anchor, value: []const u8, page: *Page) !void {
} }
pub fn getName(self: *const Anchor) []const u8 { pub fn getName(self: *const Anchor) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse "";
} }
pub fn setName(self: *Anchor, value: []const u8, page: *Page) !void { pub fn setName(self: *Anchor, value: []const u8, page: *Page) !void {
@@ -191,7 +191,7 @@ pub fn setText(self: *Anchor, value: []const u8, page: *Page) !void {
} }
fn getResolvedHref(self: *Anchor, page: *Page) !?[:0]const u8 { fn getResolvedHref(self: *Anchor, page: *Page) !?[:0]const u8 {
const href = self.asElement().getAttributeSafe("href") orelse return null; const href = self.asElement().getAttributeSafe(comptime .literal("href")) orelse return null;
if (href.len == 0) { if (href.len == 0) {
return null; return null;
} }

View File

@@ -49,7 +49,7 @@ pub const JsApi = struct {
pub const Build = struct { pub const Build = struct {
pub fn complete(node: *Node, page: *Page) !void { pub fn complete(node: *Node, page: *Page) !void {
const el = node.as(Element); const el = node.as(Element);
const on_load = el.getAttributeSafe("onload") orelse return; const on_load = el.getAttributeSafe(comptime .literal("onload")) orelse return;
if (page.js.stringToPersistedFunction(on_load)) |func| { if (page.js.stringToPersistedFunction(on_load)) |func| {
page.window._on_load = func; page.window._on_load = func;
} else |err| { } else |err| {

View File

@@ -39,7 +39,7 @@ pub fn asNode(self: *Button) *Node {
} }
pub fn getDisabled(self: *const Button) bool { pub fn getDisabled(self: *const Button) bool {
return self.asConstElement().getAttributeSafe("disabled") != null; return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null;
} }
pub fn setDisabled(self: *Button, disabled: bool, page: *Page) !void { pub fn setDisabled(self: *Button, disabled: bool, page: *Page) !void {
@@ -51,7 +51,7 @@ pub fn setDisabled(self: *Button, disabled: bool, page: *Page) !void {
} }
pub fn getName(self: *const Button) []const u8 { pub fn getName(self: *const Button) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse "";
} }
pub fn setName(self: *Button, name: []const u8, page: *Page) !void { pub fn setName(self: *Button, name: []const u8, page: *Page) !void {
@@ -59,7 +59,7 @@ pub fn setName(self: *Button, name: []const u8, page: *Page) !void {
} }
pub fn getType(self: *const Button) []const u8 { pub fn getType(self: *const Button) []const u8 {
return self.asConstElement().getAttributeSafe("type") orelse "submit"; return self.asConstElement().getAttributeSafe(comptime .literal("type")) orelse "submit";
} }
pub fn setType(self: *Button, typ: []const u8, page: *Page) !void { pub fn setType(self: *Button, typ: []const u8, page: *Page) !void {
@@ -67,7 +67,7 @@ pub fn setType(self: *Button, typ: []const u8, page: *Page) !void {
} }
pub fn getValue(self: *const Button) []const u8 { pub fn getValue(self: *const Button) []const u8 {
return self.asConstElement().getAttributeSafe("value") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("value")) orelse "";
} }
pub fn setValue(self: *Button, value: []const u8, page: *Page) !void { pub fn setValue(self: *Button, value: []const u8, page: *Page) !void {
@@ -75,7 +75,7 @@ pub fn setValue(self: *Button, value: []const u8, page: *Page) !void {
} }
pub fn getRequired(self: *const Button) bool { pub fn getRequired(self: *const Button) bool {
return self.asConstElement().getAttributeSafe("required") != null; return self.asConstElement().getAttributeSafe(comptime .literal("required")) != null;
} }
pub fn setRequired(self: *Button, required: bool, page: *Page) !void { pub fn setRequired(self: *Button, required: bool, page: *Page) !void {
@@ -90,7 +90,7 @@ pub fn getForm(self: *Button, page: *Page) ?*Form {
const element = self.asElement(); const element = self.asElement();
// If form attribute exists, ONLY use that (even if it references nothing) // If form attribute exists, ONLY use that (even if it references nothing)
if (element.getAttributeSafe("form")) |form_id| { if (element.getAttributeSafe(comptime .literal("form"))) |form_id| {
if (page.document.getElementById(form_id, page)) |form_element| { if (page.document.getElementById(form_id, page)) |form_element| {
return form_element.is(Form); return form_element.is(Form);
} }

View File

@@ -40,7 +40,7 @@ pub fn asNode(self: *Canvas) *Node {
} }
pub fn getWidth(self: *const Canvas) u32 { pub fn getWidth(self: *const Canvas) u32 {
const attr = self.asConstElement().getAttributeSafe("width") orelse return 300; const attr = self.asConstElement().getAttributeSafe(comptime .literal("width")) orelse return 300;
return std.fmt.parseUnsigned(u32, attr, 10) catch 300; return std.fmt.parseUnsigned(u32, attr, 10) catch 300;
} }
@@ -50,7 +50,7 @@ pub fn setWidth(self: *Canvas, value: u32, page: *Page) !void {
} }
pub fn getHeight(self: *const Canvas) u32 { pub fn getHeight(self: *const Canvas) u32 {
const attr = self.asConstElement().getAttributeSafe("height") orelse return 150; const attr = self.asConstElement().getAttributeSafe(comptime .literal("height")) orelse return 150;
return std.fmt.parseUnsigned(u32, attr, 10) catch 150; return std.fmt.parseUnsigned(u32, attr, 10) catch 150;
} }

View File

@@ -174,7 +174,7 @@ fn invokeCallbackOnElement(element: *Element, definition: *CustomElementDefiniti
// Check if element has "is" attribute and attach customized built-in definition // Check if element has "is" attribute and attach customized built-in definition
pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void { pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void {
const is_value = element.getAttributeSafe("is") orelse return; const is_value = element.getAttributeSafe(comptime .literal("is")) orelse return;
const custom_elements = page.window.getCustomElements(); const custom_elements = page.window.getCustomElements();
const definition = custom_elements._definitions.get(is_value) orelse return; const definition = custom_elements._definitions.get(is_value) orelse return;

View File

@@ -36,7 +36,7 @@ pub fn asNode(self: *Data) *Node {
} }
pub fn getValue(self: *Data) []const u8 { pub fn getValue(self: *Data) []const u8 {
return self.asElement().getAttributeSafe("value") orelse ""; return self.asElement().getAttributeSafe(comptime .literal("value")) orelse "";
} }
pub fn setValue(self: *Data, value: []const u8, page: *Page) !void { pub fn setValue(self: *Data, value: []const u8, page: *Page) !void {

View File

@@ -20,7 +20,7 @@ pub fn asNode(self: *Dialog) *Node {
} }
pub fn getOpen(self: *const Dialog) bool { pub fn getOpen(self: *const Dialog) bool {
return self.asConstElement().getAttributeSafe("open") != null; return self.asConstElement().getAttributeSafe(comptime .literal("open")) != null;
} }
pub fn setOpen(self: *Dialog, open: bool, page: *Page) !void { pub fn setOpen(self: *Dialog, open: bool, page: *Page) !void {
@@ -32,7 +32,7 @@ pub fn setOpen(self: *Dialog, open: bool, page: *Page) !void {
} }
pub fn getReturnValue(self: *const Dialog) []const u8 { pub fn getReturnValue(self: *const Dialog) []const u8 {
return self.asConstElement().getAttributeSafe("returnvalue") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("returnvalue")) orelse "";
} }
pub fn setReturnValue(self: *Dialog, value: []const u8, page: *Page) !void { pub fn setReturnValue(self: *Dialog, value: []const u8, page: *Page) !void {

View File

@@ -43,7 +43,7 @@ pub fn asNode(self: *Form) *Node {
} }
pub fn getName(self: *const Form) []const u8 { pub fn getName(self: *const Form) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse "";
} }
pub fn setName(self: *Form, name: []const u8, page: *Page) !void { pub fn setName(self: *Form, name: []const u8, page: *Page) !void {
@@ -51,7 +51,7 @@ pub fn setName(self: *Form, name: []const u8, page: *Page) !void {
} }
pub fn getMethod(self: *const Form) []const u8 { pub fn getMethod(self: *const Form) []const u8 {
const method = self.asConstElement().getAttributeSafe("method") orelse return "get"; const method = self.asConstElement().getAttributeSafe(comptime .literal("method")) orelse return "get";
if (std.ascii.eqlIgnoreCase(method, "post")) { if (std.ascii.eqlIgnoreCase(method, "post")) {
return "post"; return "post";
@@ -68,7 +68,7 @@ pub fn setMethod(self: *Form, method: []const u8, page: *Page) !void {
} }
pub fn getElements(self: *Form, page: *Page) !*collections.HTMLFormControlsCollection { pub fn getElements(self: *Form, page: *Page) !*collections.HTMLFormControlsCollection {
const form_id = self.asElement().getAttributeSafe("id"); const form_id = self.asElement().getAttributeSafe(comptime .literal("id"));
const root = if (form_id != null) const root = if (form_id != null)
self.asNode().getRootNode(null) // Has ID: walk entire document to find form=ID controls self.asNode().getRootNode(null) // Has ID: walk entire document to find form=ID controls
else else

View File

@@ -36,7 +36,7 @@ pub fn asNode(self: *Image) *Node {
pub fn getSrc(self: *const Image, page: *Page) ![]const u8 { pub fn getSrc(self: *const Image, page: *Page) ![]const u8 {
const element = self.asConstElement(); const element = self.asConstElement();
const src = element.getAttributeSafe("src") orelse return ""; const src = element.getAttributeSafe(comptime .literal("src")) orelse return "";
if (src.len == 0) { if (src.len == 0) {
return ""; return "";
} }
@@ -50,7 +50,7 @@ pub fn setSrc(self: *Image, value: []const u8, page: *Page) !void {
} }
pub fn getAlt(self: *const Image) []const u8 { pub fn getAlt(self: *const Image) []const u8 {
return self.asConstElement().getAttributeSafe("alt") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("alt")) orelse "";
} }
pub fn setAlt(self: *Image, value: []const u8, page: *Page) !void { pub fn setAlt(self: *Image, value: []const u8, page: *Page) !void {
@@ -58,7 +58,7 @@ pub fn setAlt(self: *Image, value: []const u8, page: *Page) !void {
} }
pub fn getWidth(self: *const Image) u32 { pub fn getWidth(self: *const Image) u32 {
const attr = self.asConstElement().getAttributeSafe("width") orelse return 0; const attr = self.asConstElement().getAttributeSafe(comptime .literal("width")) orelse return 0;
return std.fmt.parseUnsigned(u32, attr, 10) catch 0; return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
} }
@@ -68,7 +68,7 @@ pub fn setWidth(self: *Image, value: u32, page: *Page) !void {
} }
pub fn getHeight(self: *const Image) u32 { pub fn getHeight(self: *const Image) u32 {
const attr = self.asConstElement().getAttributeSafe("height") orelse return 0; const attr = self.asConstElement().getAttributeSafe(comptime .literal("height")) orelse return 0;
return std.fmt.parseUnsigned(u32, attr, 10) catch 0; return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
} }
@@ -78,7 +78,7 @@ pub fn setHeight(self: *Image, value: u32, page: *Page) !void {
} }
pub fn getCrossOrigin(self: *const Image) ?[]const u8 { pub fn getCrossOrigin(self: *const Image) ?[]const u8 {
return self.asConstElement().getAttributeSafe("crossorigin"); return self.asConstElement().getAttributeSafe(comptime .literal("crossorigin"));
} }
pub fn setCrossOrigin(self: *Image, value: ?[]const u8, page: *Page) !void { pub fn setCrossOrigin(self: *Image, value: ?[]const u8, page: *Page) !void {
@@ -89,7 +89,7 @@ pub fn setCrossOrigin(self: *Image, value: ?[]const u8, page: *Page) !void {
} }
pub fn getLoading(self: *const Image) []const u8 { pub fn getLoading(self: *const Image) []const u8 {
return self.asConstElement().getAttributeSafe("loading") orelse "eager"; return self.asConstElement().getAttributeSafe(comptime .literal("loading")) orelse "eager";
} }
pub fn setLoading(self: *Image, value: []const u8, page: *Page) !void { pub fn setLoading(self: *Image, value: []const u8, page: *Page) !void {

View File

@@ -156,7 +156,7 @@ pub fn setDefaultChecked(self: *Input, checked: bool, page: *Page) !void {
pub fn getDisabled(self: *const Input) bool { pub fn getDisabled(self: *const Input) bool {
// TODO: Also check for disabled fieldset ancestors // TODO: Also check for disabled fieldset ancestors
// (but not if we're inside a <legend> of that fieldset) // (but not if we're inside a <legend> of that fieldset)
return self.asConstElement().getAttributeSafe("disabled") != null; return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null;
} }
pub fn setDisabled(self: *Input, disabled: bool, page: *Page) !void { pub fn setDisabled(self: *Input, disabled: bool, page: *Page) !void {
@@ -168,7 +168,7 @@ pub fn setDisabled(self: *Input, disabled: bool, page: *Page) !void {
} }
pub fn getName(self: *const Input) []const u8 { pub fn getName(self: *const Input) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse "";
} }
pub fn setName(self: *Input, name: []const u8, page: *Page) !void { pub fn setName(self: *Input, name: []const u8, page: *Page) !void {
@@ -176,7 +176,7 @@ pub fn setName(self: *Input, name: []const u8, page: *Page) !void {
} }
pub fn getAccept(self: *const Input) []const u8 { pub fn getAccept(self: *const Input) []const u8 {
return self.asConstElement().getAttributeSafe("accept") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("accept")) orelse "";
} }
pub fn setAccept(self: *Input, accept: []const u8, page: *Page) !void { pub fn setAccept(self: *Input, accept: []const u8, page: *Page) !void {
@@ -184,7 +184,7 @@ pub fn setAccept(self: *Input, accept: []const u8, page: *Page) !void {
} }
pub fn getAlt(self: *const Input) []const u8 { pub fn getAlt(self: *const Input) []const u8 {
return self.asConstElement().getAttributeSafe("alt") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("alt")) orelse "";
} }
pub fn setAlt(self: *Input, alt: []const u8, page: *Page) !void { pub fn setAlt(self: *Input, alt: []const u8, page: *Page) !void {
@@ -192,7 +192,7 @@ pub fn setAlt(self: *Input, alt: []const u8, page: *Page) !void {
} }
pub fn getMaxLength(self: *const Input) i32 { pub fn getMaxLength(self: *const Input) i32 {
const attr = self.asConstElement().getAttributeSafe("maxlength") orelse return -1; const attr = self.asConstElement().getAttributeSafe(comptime .literal("maxlength")) orelse return -1;
return std.fmt.parseInt(i32, attr, 10) catch -1; return std.fmt.parseInt(i32, attr, 10) catch -1;
} }
@@ -206,7 +206,7 @@ pub fn setMaxLength(self: *Input, max_length: i32, page: *Page) !void {
} }
pub fn getSize(self: *const Input) i32 { pub fn getSize(self: *const Input) i32 {
const attr = self.asConstElement().getAttributeSafe("size") orelse return 20; const attr = self.asConstElement().getAttributeSafe(comptime .literal("size")) orelse return 20;
const parsed = std.fmt.parseInt(i32, attr, 10) catch return 20; const parsed = std.fmt.parseInt(i32, attr, 10) catch return 20;
return if (parsed == 0) 20 else parsed; return if (parsed == 0) 20 else parsed;
} }
@@ -225,7 +225,7 @@ pub fn setSize(self: *Input, size: i32, page: *Page) !void {
} }
pub fn getSrc(self: *const Input, page: *Page) ![]const u8 { pub fn getSrc(self: *const Input, page: *Page) ![]const u8 {
const src = self.asConstElement().getAttributeSafe("src") orelse return ""; const src = self.asConstElement().getAttributeSafe(comptime .literal("src")) orelse return "";
// If attribute is explicitly set (even if empty), resolve it against the base URL // If attribute is explicitly set (even if empty), resolve it against the base URL
return @import("../../URL.zig").resolve(page.call_arena, page.base(), src, .{}); return @import("../../URL.zig").resolve(page.call_arena, page.base(), src, .{});
} }
@@ -236,7 +236,7 @@ pub fn setSrc(self: *Input, src: []const u8, page: *Page) !void {
} }
pub fn getReadonly(self: *const Input) bool { pub fn getReadonly(self: *const Input) bool {
return self.asConstElement().getAttributeSafe("readonly") != null; return self.asConstElement().getAttributeSafe(comptime .literal("readonly")) != null;
} }
pub fn setReadonly(self: *Input, readonly: bool, page: *Page) !void { pub fn setReadonly(self: *Input, readonly: bool, page: *Page) !void {
@@ -248,7 +248,7 @@ pub fn setReadonly(self: *Input, readonly: bool, page: *Page) !void {
} }
pub fn getRequired(self: *const Input) bool { pub fn getRequired(self: *const Input) bool {
return self.asConstElement().getAttributeSafe("required") != null; return self.asConstElement().getAttributeSafe(comptime .literal("required")) != null;
} }
pub fn setRequired(self: *Input, required: bool, page: *Page) !void { pub fn setRequired(self: *Input, required: bool, page: *Page) !void {
@@ -379,7 +379,7 @@ pub fn getForm(self: *Input, page: *Page) ?*Form {
const element = self.asElement(); const element = self.asElement();
// If form attribute exists, ONLY use that (even if it references nothing) // If form attribute exists, ONLY use that (even if it references nothing)
if (element.getAttributeSafe("form")) |form_id| { if (element.getAttributeSafe(comptime .literal("form"))) |form_id| {
if (page.document.getElementById(form_id, page)) |form_element| { if (page.document.getElementById(form_id, page)) |form_element| {
return form_element.is(Form); return form_element.is(Form);
} }
@@ -402,7 +402,7 @@ pub fn getForm(self: *Input, page: *Page) ?*Form {
fn uncheckRadioGroup(self: *Input, page: *Page) !void { fn uncheckRadioGroup(self: *Input, page: *Page) !void {
const element = self.asElement(); const element = self.asElement();
const name = element.getAttributeSafe("name") orelse return; const name = element.getAttributeSafe(comptime .literal("name")) orelse return;
if (name.len == 0) { if (name.len == 0) {
return; return;
} }
@@ -420,7 +420,7 @@ fn uncheckRadioGroup(self: *Input, page: *Page) !void {
continue; continue;
} }
const other_name = other_element.getAttributeSafe("name") orelse continue; const other_name = other_element.getAttributeSafe(comptime .literal("name")) orelse continue;
if (!std.mem.eql(u8, name, other_name)) { if (!std.mem.eql(u8, name, other_name)) {
continue; continue;
} }
@@ -481,14 +481,14 @@ pub const Build = struct {
const element = self.asElement(); const element = self.asElement();
// Store initial values from attributes // Store initial values from attributes
self._default_value = element.getAttributeSafe("value"); self._default_value = element.getAttributeSafe(comptime .literal("value"));
self._default_checked = element.getAttributeSafe("checked") != null; self._default_checked = element.getAttributeSafe(comptime .literal("checked")) != null;
// Current state starts equal to default // Current state starts equal to default
self._value = self._default_value; self._value = self._default_value;
self._checked = self._default_checked; self._checked = self._default_checked;
self._input_type = if (element.getAttributeSafe("type")) |type_attr| self._input_type = if (element.getAttributeSafe(comptime .literal("type"))) |type_attr|
Type.fromString(type_attr) Type.fromString(type_attr)
else else
.text; .text;

View File

@@ -36,7 +36,7 @@ pub fn asNode(self: *Link) *Node {
pub fn getHref(self: *Link, page: *Page) ![]const u8 { pub fn getHref(self: *Link, page: *Page) ![]const u8 {
const element = self.asElement(); const element = self.asElement();
const href = element.getAttributeSafe("href") orelse return ""; const href = element.getAttributeSafe(comptime .literal("href")) orelse return "";
if (href.len == 0) { if (href.len == 0) {
return ""; return "";
} }
@@ -50,7 +50,7 @@ pub fn setHref(self: *Link, value: []const u8, page: *Page) !void {
} }
pub fn getRel(self: *Link) []const u8 { pub fn getRel(self: *Link) []const u8 {
return self.asElement().getAttributeSafe("rel") orelse return ""; return self.asElement().getAttributeSafe(comptime .literal("rel")) orelse return "";
} }
pub fn setRel(self: *Link, value: []const u8, page: *Page) !void { pub fn setRel(self: *Link, value: []const u8, page: *Page) !void {

View File

@@ -219,7 +219,7 @@ pub fn setCurrentTime(self: *Media, value: f64) void {
pub fn getSrc(self: *const Media, page: *Page) ![]const u8 { pub fn getSrc(self: *const Media, page: *Page) ![]const u8 {
const element = self.asConstElement(); const element = self.asConstElement();
const src = element.getAttributeSafe("src") orelse return ""; const src = element.getAttributeSafe(comptime .literal("src")) orelse return "";
if (src.len == 0) { if (src.len == 0) {
return ""; return "";
} }
@@ -232,7 +232,7 @@ pub fn setSrc(self: *Media, value: []const u8, page: *Page) !void {
} }
pub fn getAutoplay(self: *const Media) bool { pub fn getAutoplay(self: *const Media) bool {
return self.asConstElement().getAttributeSafe("autoplay") != null; return self.asConstElement().getAttributeSafe(comptime .literal("autoplay")) != null;
} }
pub fn setAutoplay(self: *Media, value: bool, page: *Page) !void { pub fn setAutoplay(self: *Media, value: bool, page: *Page) !void {
@@ -244,7 +244,7 @@ pub fn setAutoplay(self: *Media, value: bool, page: *Page) !void {
} }
pub fn getControls(self: *const Media) bool { pub fn getControls(self: *const Media) bool {
return self.asConstElement().getAttributeSafe("controls") != null; return self.asConstElement().getAttributeSafe(comptime .literal("controls")) != null;
} }
pub fn setControls(self: *Media, value: bool, page: *Page) !void { pub fn setControls(self: *Media, value: bool, page: *Page) !void {
@@ -256,7 +256,7 @@ pub fn setControls(self: *Media, value: bool, page: *Page) !void {
} }
pub fn getLoop(self: *const Media) bool { pub fn getLoop(self: *const Media) bool {
return self.asConstElement().getAttributeSafe("loop") != null; return self.asConstElement().getAttributeSafe(comptime .literal("loop")) != null;
} }
pub fn setLoop(self: *Media, value: bool, page: *Page) !void { pub fn setLoop(self: *Media, value: bool, page: *Page) !void {
@@ -268,7 +268,7 @@ pub fn setLoop(self: *Media, value: bool, page: *Page) !void {
} }
pub fn getPreload(self: *const Media) []const u8 { pub fn getPreload(self: *const Media) []const u8 {
return self.asConstElement().getAttributeSafe("preload") orelse "auto"; return self.asConstElement().getAttributeSafe(comptime .literal("preload")) orelse "auto";
} }
pub fn setPreload(self: *Media, value: []const u8, page: *Page) !void { pub fn setPreload(self: *Media, value: []const u8, page: *Page) !void {

View File

@@ -37,7 +37,7 @@ pub fn asNode(self: *Meta) *Node {
} }
pub fn getName(self: *Meta) []const u8 { pub fn getName(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe("name") orelse return ""; return self.asElement().getAttributeSafe(comptime .literal("name")) orelse return "";
} }
pub fn setName(self: *Meta, value: []const u8, page: *Page) !void { pub fn setName(self: *Meta, value: []const u8, page: *Page) !void {
@@ -45,7 +45,7 @@ pub fn setName(self: *Meta, value: []const u8, page: *Page) !void {
} }
pub fn getHttpEquiv(self: *Meta) []const u8 { pub fn getHttpEquiv(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe("http-equiv") orelse return ""; return self.asElement().getAttributeSafe(comptime .literal("http-equiv")) orelse return "";
} }
pub fn setHttpEquiv(self: *Meta, value: []const u8, page: *Page) !void { pub fn setHttpEquiv(self: *Meta, value: []const u8, page: *Page) !void {
@@ -53,7 +53,7 @@ pub fn setHttpEquiv(self: *Meta, value: []const u8, page: *Page) !void {
} }
pub fn getContent(self: *Meta) []const u8 { pub fn getContent(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe("content") orelse return ""; return self.asElement().getAttributeSafe(comptime .literal("content")) orelse return "";
} }
pub fn setContent(self: *Meta, value: []const u8, page: *Page) !void { pub fn setContent(self: *Meta, value: []const u8, page: *Page) !void {
@@ -61,7 +61,7 @@ pub fn setContent(self: *Meta, value: []const u8, page: *Page) !void {
} }
pub fn getMedia(self: *Meta) []const u8 { pub fn getMedia(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe("media") orelse return ""; return self.asElement().getAttributeSafe(comptime .literal("media")) orelse return "";
} }
pub fn setMedia(self: *Meta, value: []const u8, page: *Page) !void { pub fn setMedia(self: *Meta, value: []const u8, page: *Page) !void {

View File

@@ -94,7 +94,7 @@ pub fn setDisabled(self: *Option, disabled: bool, page: *Page) !void {
} }
pub fn getName(self: *const Option) []const u8 { pub fn getName(self: *const Option) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse "";
} }
pub fn setName(self: *Option, name: []const u8, page: *Page) !void { pub fn setName(self: *Option, name: []const u8, page: *Page) !void {
@@ -124,14 +124,14 @@ pub const Build = struct {
const element = self.asElement(); const element = self.asElement();
// Check for value attribute // Check for value attribute
self._value = element.getAttributeSafe("value"); self._value = element.getAttributeSafe(comptime .literal("value"));
// Check for selected attribute // Check for selected attribute
self._default_selected = element.getAttributeSafe("selected") != null; self._default_selected = element.getAttributeSafe(comptime .literal("selected")) != null;
self._selected = self._default_selected; self._selected = self._default_selected;
// Check for disabled attribute // Check for disabled attribute
self._disabled = element.getAttributeSafe("disabled") != null; self._disabled = element.getAttributeSafe(comptime .literal("disabled")) != null;
} }
pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, _: *Page) !void { pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, _: *Page) !void {

View File

@@ -54,14 +54,14 @@ pub fn getSrc(self: *const Script, page: *Page) ![]const u8 {
pub fn setSrc(self: *Script, src: []const u8, page: *Page) !void { pub fn setSrc(self: *Script, src: []const u8, page: *Page) !void {
const element = self.asElement(); const element = self.asElement();
try element.setAttributeSafe("src", src, page); try element.setAttributeSafe("src", src, page);
self._src = element.getAttributeSafe("src") orelse unreachable; self._src = element.getAttributeSafe(comptime .literal("src")) orelse unreachable;
if (element.asNode().isConnected()) { if (element.asNode().isConnected()) {
try page.scriptAddedCallback(false, self); try page.scriptAddedCallback(false, self);
} }
} }
pub fn getType(self: *const Script) []const u8 { pub fn getType(self: *const Script) []const u8 {
return self.asConstElement().getAttributeSafe("type") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("type")) orelse "";
} }
pub fn setType(self: *Script, value: []const u8, page: *Page) !void { pub fn setType(self: *Script, value: []const u8, page: *Page) !void {
@@ -69,7 +69,7 @@ pub fn setType(self: *Script, value: []const u8, page: *Page) !void {
} }
pub fn getNonce(self: *const Script) []const u8 { pub fn getNonce(self: *const Script) []const u8 {
return self.asConstElement().getAttributeSafe("nonce") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("nonce")) orelse "";
} }
pub fn setNonce(self: *Script, value: []const u8, page: *Page) !void { pub fn setNonce(self: *Script, value: []const u8, page: *Page) !void {
@@ -93,7 +93,7 @@ pub fn setOnError(self: *Script, cb: ?js.Function.Global) void {
} }
pub fn getNoModule(self: *const Script) bool { pub fn getNoModule(self: *const Script) bool {
return self.asConstElement().getAttributeSafe("nomodule") != null; return self.asConstElement().getAttributeSafe(comptime .literal("nomodule")) != null;
} }
pub fn setInnerText(self: *Script, text: []const u8, page: *Page) !void { pub fn setInnerText(self: *Script, text: []const u8, page: *Page) !void {
@@ -127,9 +127,9 @@ pub const Build = struct {
pub fn complete(node: *Node, page: *Page) !void { pub fn complete(node: *Node, page: *Page) !void {
const self = node.as(Script); const self = node.as(Script);
const element = self.asElement(); const element = self.asElement();
self._src = element.getAttributeSafe("src") orelse ""; self._src = element.getAttributeSafe(comptime .literal("src")) orelse "";
if (element.getAttributeSafe("onload")) |on_load| { if (element.getAttributeSafe(comptime .literal("onload"))) |on_load| {
if (page.js.stringToPersistedFunction(on_load)) |func| { if (page.js.stringToPersistedFunction(on_load)) |func| {
self._on_load = func; self._on_load = func;
} else |err| { } else |err| {
@@ -137,7 +137,7 @@ pub const Build = struct {
} }
} }
if (element.getAttributeSafe("onerror")) |on_error| { if (element.getAttributeSafe(comptime .literal("onerror"))) |on_error| {
if (page.js.stringToPersistedFunction(on_error)) |func| { if (page.js.stringToPersistedFunction(on_error)) |func| {
self._on_error = func; self._on_error = func;
} else |err| { } else |err| {

View File

@@ -121,7 +121,7 @@ pub fn setSelectedIndex(self: *Select, index: i32) !void {
} }
pub fn getMultiple(self: *const Select) bool { pub fn getMultiple(self: *const Select) bool {
return self.asConstElement().getAttributeSafe("multiple") != null; return self.asConstElement().getAttributeSafe(comptime .literal("multiple")) != null;
} }
pub fn setMultiple(self: *Select, multiple: bool, page: *Page) !void { pub fn setMultiple(self: *Select, multiple: bool, page: *Page) !void {
@@ -133,7 +133,7 @@ pub fn setMultiple(self: *Select, multiple: bool, page: *Page) !void {
} }
pub fn getDisabled(self: *const Select) bool { pub fn getDisabled(self: *const Select) bool {
return self.asConstElement().getAttributeSafe("disabled") != null; return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null;
} }
pub fn setDisabled(self: *Select, disabled: bool, page: *Page) !void { pub fn setDisabled(self: *Select, disabled: bool, page: *Page) !void {
@@ -145,7 +145,7 @@ pub fn setDisabled(self: *Select, disabled: bool, page: *Page) !void {
} }
pub fn getName(self: *const Select) []const u8 { pub fn getName(self: *const Select) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse "";
} }
pub fn setName(self: *Select, name: []const u8, page: *Page) !void { pub fn setName(self: *Select, name: []const u8, page: *Page) !void {
@@ -153,7 +153,7 @@ pub fn setName(self: *Select, name: []const u8, page: *Page) !void {
} }
pub fn getSize(self: *const Select) u32 { pub fn getSize(self: *const Select) u32 {
const s = self.asConstElement().getAttributeSafe("size") orelse return 0; const s = self.asConstElement().getAttributeSafe(comptime .literal("size")) orelse return 0;
const trimmed = std.mem.trimLeft(u8, s, &std.ascii.whitespace); const trimmed = std.mem.trimLeft(u8, s, &std.ascii.whitespace);
@@ -176,7 +176,7 @@ pub fn setSize(self: *Select, size: u32, page: *Page) !void {
} }
pub fn getRequired(self: *const Select) bool { pub fn getRequired(self: *const Select) bool {
return self.asConstElement().getAttributeSafe("required") != null; return self.asConstElement().getAttributeSafe(comptime .literal("required")) != null;
} }
pub fn setRequired(self: *Select, required: bool, page: *Page) !void { pub fn setRequired(self: *Select, required: bool, page: *Page) !void {
@@ -218,7 +218,7 @@ pub fn getForm(self: *Select, page: *Page) ?*Form {
const element = self.asElement(); const element = self.asElement();
// If form attribute exists, ONLY use that (even if it references nothing) // If form attribute exists, ONLY use that (even if it references nothing)
if (element.getAttributeSafe("form")) |form_id| { if (element.getAttributeSafe(comptime .literal("form"))) |form_id| {
if (page.document.getElementById(form_id, page)) |form_element| { if (page.document.getElementById(form_id, page)) |form_element| {
return form_element.is(Form); return form_element.is(Form);
} }

View File

@@ -25,7 +25,7 @@ pub fn asNode(self: *Slot) *Node {
} }
pub fn getName(self: *const Slot) []const u8 { pub fn getName(self: *const Slot) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse "";
} }
pub fn setName(self: *Slot, name: []const u8, page: *Page) !void { pub fn setName(self: *Slot, name: []const u8, page: *Page) !void {
@@ -131,7 +131,7 @@ fn isAssignedToSlot(node: *Node, slot_name: []const u8) bool {
// Check if a node should be assigned to a slot with the given name // Check if a node should be assigned to a slot with the given name
if (node.is(Element)) |element| { if (node.is(Element)) |element| {
// Get the slot attribute from the element // Get the slot attribute from the element
const node_slot = element.getAttributeSafe("slot") orelse ""; const node_slot = element.getAttributeSafe(comptime .literal("slot")) orelse "";
// Match if: // Match if:
// - Both are empty (default slot) // - Both are empty (default slot)

View File

@@ -39,7 +39,7 @@ pub fn asNode(self: *Style) *Node {
// Attribute-backed properties // Attribute-backed properties
pub fn getBlocking(self: *const Style) []const u8 { pub fn getBlocking(self: *const Style) []const u8 {
return self.asConstElement().getAttributeSafe("blocking") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("blocking")) orelse "";
} }
pub fn setBlocking(self: *Style, value: []const u8, page: *Page) !void { pub fn setBlocking(self: *Style, value: []const u8, page: *Page) !void {
@@ -47,7 +47,7 @@ pub fn setBlocking(self: *Style, value: []const u8, page: *Page) !void {
} }
pub fn getMedia(self: *const Style) []const u8 { pub fn getMedia(self: *const Style) []const u8 {
return self.asConstElement().getAttributeSafe("media") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("media")) orelse "";
} }
pub fn setMedia(self: *Style, value: []const u8, page: *Page) !void { pub fn setMedia(self: *Style, value: []const u8, page: *Page) !void {
@@ -55,7 +55,7 @@ pub fn setMedia(self: *Style, value: []const u8, page: *Page) !void {
} }
pub fn getType(self: *const Style) []const u8 { pub fn getType(self: *const Style) []const u8 {
return self.asConstElement().getAttributeSafe("type") orelse "text/css"; return self.asConstElement().getAttributeSafe(comptime .literal("type")) orelse "text/css";
} }
pub fn setType(self: *Style, value: []const u8, page: *Page) !void { pub fn setType(self: *Style, value: []const u8, page: *Page) !void {
@@ -63,7 +63,7 @@ pub fn setType(self: *Style, value: []const u8, page: *Page) !void {
} }
pub fn getDisabled(self: *const Style) bool { pub fn getDisabled(self: *const Style) bool {
return self.asConstElement().getAttributeSafe("disabled") != null; return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null;
} }
pub fn setDisabled(self: *Style, disabled: bool, page: *Page) !void { pub fn setDisabled(self: *Style, disabled: bool, page: *Page) !void {

View File

@@ -84,7 +84,7 @@ pub fn setDefaultValue(self: *TextArea, value: []const u8, page: *Page) !void {
} }
pub fn getDisabled(self: *const TextArea) bool { pub fn getDisabled(self: *const TextArea) bool {
return self.asConstElement().getAttributeSafe("disabled") != null; return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null;
} }
pub fn setDisabled(self: *TextArea, disabled: bool, page: *Page) !void { pub fn setDisabled(self: *TextArea, disabled: bool, page: *Page) !void {
@@ -96,7 +96,7 @@ pub fn setDisabled(self: *TextArea, disabled: bool, page: *Page) !void {
} }
pub fn getName(self: *const TextArea) []const u8 { pub fn getName(self: *const TextArea) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse "";
} }
pub fn setName(self: *TextArea, name: []const u8, page: *Page) !void { pub fn setName(self: *TextArea, name: []const u8, page: *Page) !void {
@@ -104,7 +104,7 @@ pub fn setName(self: *TextArea, name: []const u8, page: *Page) !void {
} }
pub fn getRequired(self: *const TextArea) bool { pub fn getRequired(self: *const TextArea) bool {
return self.asConstElement().getAttributeSafe("required") != null; return self.asConstElement().getAttributeSafe(comptime .literal("required")) != null;
} }
pub fn setRequired(self: *TextArea, required: bool, page: *Page) !void { pub fn setRequired(self: *TextArea, required: bool, page: *Page) !void {
@@ -221,7 +221,7 @@ pub fn getForm(self: *TextArea, page: *Page) ?*Form {
const element = self.asElement(); const element = self.asElement();
// If form attribute exists, ONLY use that (even if it references nothing) // If form attribute exists, ONLY use that (even if it references nothing)
if (element.getAttributeSafe("form")) |form_id| { if (element.getAttributeSafe(comptime .literal("form"))) |form_id| {
if (page.document.getElementById(form_id, page)) |form_element| { if (page.document.getElementById(form_id, page)) |form_element| {
return form_element.is(Form); return form_element.is(Form);
} }

View File

@@ -53,7 +53,7 @@ pub fn getVideoHeight(_: *const Video) u32 {
pub fn getPoster(self: *const Video, page: *Page) ![]const u8 { pub fn getPoster(self: *const Video, page: *Page) ![]const u8 {
const element = self.asConstElement(); const element = self.asConstElement();
const poster = element.getAttributeSafe("poster") orelse return ""; const poster = element.getAttributeSafe(comptime .literal("poster")) orelse return "";
if (poster.len == 0) { if (poster.len == 0) {
return ""; return "";
} }

View File

@@ -127,7 +127,7 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, page: *Pa
var elements = try form.getElements(page); var elements = try form.getElements(page);
var it = try elements.iterator(); var it = try elements.iterator();
while (it.next()) |element| { while (it.next()) |element| {
if (element.getAttributeSafe("disabled") != null) { if (element.getAttributeSafe(comptime .literal("disabled")) != null) {
continue; continue;
} }
@@ -139,7 +139,7 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, page: *Pa
continue; continue;
} }
const name = element.getAttributeSafe("name"); const name = element.getAttributeSafe(comptime .literal("name"));
const x_key = if (name) |n| try std.fmt.allocPrint(arena, "{s}.x", .{n}) else "x"; const x_key = if (name) |n| try std.fmt.allocPrint(arena, "{s}.x", .{n}) else "x";
const y_key = if (name) |n| try std.fmt.allocPrint(arena, "{s}.y", .{n}) else "y"; const y_key = if (name) |n| try std.fmt.allocPrint(arena, "{s}.y", .{n}) else "y";
try list.append(arena, x_key, "0"); try list.append(arena, x_key, "0");
@@ -148,7 +148,7 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, page: *Pa
} }
} }
const name = element.getAttributeSafe("name") orelse continue; const name = element.getAttributeSafe(comptime .literal("name")) orelse continue;
const value = blk: { const value = blk: {
if (element.is(Form.Input)) |input| { if (element.is(Form.Input)) |input| {
const input_type = input._input_type; const input_type = input._input_type;

View File

@@ -412,11 +412,11 @@ fn matchesCompound(el: *Node.Element, compound: Selector.Compound, scope: *Node,
fn matchesPart(el: *Node.Element, part: Part, scope: *Node, page: *Page) bool { fn matchesPart(el: *Node.Element, part: Part, scope: *Node, page: *Page) bool {
switch (part) { switch (part) {
.id => |id| { .id => |id| {
const element_id = el.getAttributeSafe("id") orelse return false; const element_id = el.getAttributeSafe(comptime .literal("id")) orelse return false;
return std.mem.eql(u8, element_id, id); return std.mem.eql(u8, element_id, id);
}, },
.class => |cls| { .class => |cls| {
const class_attr = el.getAttributeSafe("class") orelse return false; const class_attr = el.getAttributeSafe(comptime .literal("class")) orelse return false;
return Selector.classAttributeContains(class_attr, cls); return Selector.classAttributeContains(class_attr, cls);
}, },
.tag => |tag| { .tag => |tag| {
@@ -526,10 +526,10 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N
return input.getChecked(); return input.getChecked();
}, },
.disabled => { .disabled => {
return el.getAttributeSafe("disabled") != null; return el.getAttributeSafe(comptime .literal("disabled")) != null;
}, },
.enabled => { .enabled => {
return el.getAttributeSafe("disabled") == null; return el.getAttributeSafe(comptime .literal("disabled")) == null;
}, },
.indeterminate => return false, .indeterminate => return false,
@@ -537,19 +537,19 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N
.valid => return false, .valid => return false,
.invalid => return false, .invalid => return false,
.required => { .required => {
return el.getAttributeSafe("required") != null; return el.getAttributeSafe(comptime .literal("required")) != null;
}, },
.optional => { .optional => {
return el.getAttributeSafe("required") == null; return el.getAttributeSafe(comptime .literal("required")) == null;
}, },
.in_range => return false, .in_range => return false,
.out_of_range => return false, .out_of_range => return false,
.placeholder_shown => return false, .placeholder_shown => return false,
.read_only => { .read_only => {
return el.getAttributeSafe("readonly") != null; return el.getAttributeSafe(comptime .literal("readonly")) != null;
}, },
.read_write => { .read_write => {
return el.getAttributeSafe("readonly") == null; return el.getAttributeSafe(comptime .literal("readonly")) == null;
}, },
.default => return false, .default => return false,
@@ -571,10 +571,10 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N
.visited => return false, .visited => return false,
.any_link => { .any_link => {
if (el.getTag() != .anchor) return false; if (el.getTag() != .anchor) return false;
return el.getAttributeSafe("href") != null; return el.getAttributeSafe(comptime .literal("href")) != null;
}, },
.target => { .target => {
const element_id = el.getAttributeSafe("id") orelse return false; const element_id = el.getAttributeSafe(comptime .literal("id")) orelse return false;
const location = page.document._location orelse return false; const location = page.document._location orelse return false;
const hash = location.getHash(); const hash = location.getHash();
if (hash.len <= 1) return false; if (hash.len <= 1) return false;

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const String = @import("../../../string.zig").String;
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
@@ -46,6 +47,7 @@ const ParseError = error{
UnknownPseudoClass, UnknownPseudoClass,
InvalidTagSelector, InvalidTagSelector,
InvalidSelector, InvalidSelector,
StringTooLarge,
}; };
pub fn parseList(arena: Allocator, input: []const u8, page: *Page) ParseError![]const Selector.Selector { pub fn parseList(arena: Allocator, input: []const u8, page: *Page) ParseError![]const Selector.Selector {
@@ -846,7 +848,7 @@ fn attribute(self: *Parser, arena: Allocator, page: *Page) !Selector.Attribute {
const attr_name = try self.attributeName(); const attr_name = try self.attributeName();
// Normalize the name to lowercase for fast matching (consistent with Attribute.normalizeNameForLookup) // Normalize the name to lowercase for fast matching (consistent with Attribute.normalizeNameForLookup)
const normalized = try Attribute.normalizeNameForLookup(attr_name, page); const normalized = try Attribute.normalizeNameForLookup(attr_name, page);
const name = try arena.dupe(u8, normalized); const name = try String.init(arena, normalized, .{});
var case_insensitive = false; var case_insensitive = false;
_ = self.skipSpaces(); _ = self.skipSpaces();

View File

@@ -18,6 +18,8 @@
const std = @import("std"); const std = @import("std");
const String = @import("../../../string.zig").String;
const Parser = @import("Parser.zig"); const Parser = @import("Parser.zig");
const Node = @import("../Node.zig"); const Node = @import("../Node.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
@@ -117,7 +119,7 @@ pub const Part = union(enum) {
}; };
pub const Attribute = struct { pub const Attribute = struct {
name: []const u8, name: String,
matcher: AttributeMatcher, matcher: AttributeMatcher,
case_insensitive: bool, case_insensitive: bool,
}; };

View File

@@ -289,7 +289,7 @@ pub const Writer = struct {
}, },
.input => { .input => {
const input = el.as(DOMNode.Element.Html.Input); const input = el.as(DOMNode.Element.Html.Input);
const is_disabled = el.hasAttributeSafe("disabled"); const is_disabled = el.hasAttributeSafe(comptime .literal("disabled"));
switch (input._input_type) { switch (input._input_type) {
.text, .email, .tel, .url, .search, .password, .number => { .text, .email, .tel, .url, .search, .password, .number => {
@@ -305,8 +305,8 @@ pub const Writer = struct {
try self.writeAXProperty(.{ .name = .settable, .value = .{ .booleanOrUndefined = true } }, w); try self.writeAXProperty(.{ .name = .settable, .value = .{ .booleanOrUndefined = true } }, w);
} }
try self.writeAXProperty(.{ .name = .multiline, .value = .{ .boolean = false } }, w); try self.writeAXProperty(.{ .name = .multiline, .value = .{ .boolean = false } }, w);
try self.writeAXProperty(.{ .name = .readonly, .value = .{ .boolean = el.hasAttributeSafe("readonly") } }, w); try self.writeAXProperty(.{ .name = .readonly, .value = .{ .boolean = el.hasAttributeSafe(comptime .literal("readonly")) } }, w);
try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe("required") } }, w); try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe(comptime .literal("required")) } }, w);
}, },
.button, .submit, .reset, .image => { .button, .submit, .reset, .image => {
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w); try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
@@ -319,14 +319,14 @@ pub const Writer = struct {
if (!is_disabled) { if (!is_disabled) {
try self.writeAXProperty(.{ .name = .focusable, .value = .{ .booleanOrUndefined = true } }, w); try self.writeAXProperty(.{ .name = .focusable, .value = .{ .booleanOrUndefined = true } }, w);
} }
const is_checked = el.hasAttributeSafe("checked"); const is_checked = el.hasAttributeSafe(comptime .literal("checked"));
try self.writeAXProperty(.{ .name = .checked, .value = .{ .token = if (is_checked) "true" else "false" } }, w); try self.writeAXProperty(.{ .name = .checked, .value = .{ .token = if (is_checked) "true" else "false" } }, w);
}, },
else => {}, else => {},
} }
}, },
.textarea => { .textarea => {
const is_disabled = el.hasAttributeSafe("disabled"); const is_disabled = el.hasAttributeSafe(comptime .literal("disabled"));
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w); try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
if (!is_disabled) { if (!is_disabled) {
@@ -337,11 +337,11 @@ pub const Writer = struct {
try self.writeAXProperty(.{ .name = .settable, .value = .{ .booleanOrUndefined = true } }, w); try self.writeAXProperty(.{ .name = .settable, .value = .{ .booleanOrUndefined = true } }, w);
} }
try self.writeAXProperty(.{ .name = .multiline, .value = .{ .boolean = true } }, w); try self.writeAXProperty(.{ .name = .multiline, .value = .{ .boolean = true } }, w);
try self.writeAXProperty(.{ .name = .readonly, .value = .{ .boolean = el.hasAttributeSafe("readonly") } }, w); try self.writeAXProperty(.{ .name = .readonly, .value = .{ .boolean = el.hasAttributeSafe(comptime .literal("readonly")) } }, w);
try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe("required") } }, w); try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe(comptime .literal("required")) } }, w);
}, },
.select => { .select => {
const is_disabled = el.hasAttributeSafe("disabled"); const is_disabled = el.hasAttributeSafe(comptime .literal("disabled"));
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w); try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
if (!is_disabled) { if (!is_disabled) {
@@ -385,7 +385,7 @@ pub const Writer = struct {
} }
}, },
.button => { .button => {
const is_disabled = el.hasAttributeSafe("disabled"); const is_disabled = el.hasAttributeSafe(comptime .literal("disabled"));
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w); try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
if (!is_disabled) { if (!is_disabled) {
try self.writeAXProperty(.{ .name = .focusable, .value = .{ .booleanOrUndefined = true } }, w); try self.writeAXProperty(.{ .name = .focusable, .value = .{ .booleanOrUndefined = true } }, w);
@@ -629,10 +629,10 @@ pub const AXRole = enum(u8) {
}, },
.textarea => .textbox, .textarea => .textbox,
.select => { .select => {
if (el.getAttributeSafe("multiple") != null) { if (el.getAttributeSafe(comptime .literal("multiple")) != null) {
return .listbox; return .listbox;
} }
if (el.getAttributeSafe("size")) |size| { if (el.getAttributeSafe(comptime .literal("size"))) |size| {
if (!std.ascii.eqlIgnoreCase(size, "1")) { if (!std.ascii.eqlIgnoreCase(size, "1")) {
return .listbox; return .listbox;
} }
@@ -649,7 +649,7 @@ pub const AXRole = enum(u8) {
// Interactive Elements // Interactive Elements
.anchor, .area => { .anchor, .area => {
if (el.getAttributeSafe("href") == null) { if (el.getAttributeSafe(comptime .literal("href")) == null) {
return .none; return .none;
} }
@@ -669,7 +669,7 @@ pub const AXRole = enum(u8) {
.thead, .tbody, .tfoot => .rowgroup, .thead, .tbody, .tfoot => .rowgroup,
.tr => .row, .tr => .row,
.th => { .th => {
if (el.getAttributeSafe("scope")) |scope| { if (el.getAttributeSafe(comptime .literal("scope"))) |scope| {
if (std.ascii.eqlIgnoreCase(scope, "row")) { if (std.ascii.eqlIgnoreCase(scope, "row")) {
return .rowheader; return .rowheader;
} }
@@ -722,7 +722,7 @@ pub fn fromNode(dom: *DOMNode) AXNode {
break :blk null; break :blk null;
} }
const elt = dom.as(DOMNode.Element); const elt = dom.as(DOMNode.Element);
break :blk elt.getAttributeSafe("role"); break :blk elt.getAttributeSafe(comptime .literal("role"));
}, },
}; };
} }
@@ -759,7 +759,7 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource {
}, },
.element => |el| { .element => |el| {
// Handle aria-labelledby attribute (highest priority) // Handle aria-labelledby attribute (highest priority)
if (el.getAttributeSafe("aria-labelledby")) |labelledby| { if (el.getAttributeSafe(.literal("aria-labelledby"))) |labelledby| {
// Get the document to look up elements by ID // Get the document to look up elements by ID
const doc = node.ownerDocument(page) orelse return null; const doc = node.ownerDocument(page) orelse return null;
@@ -786,12 +786,12 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource {
} }
} }
if (el.getAttributeSafe("aria-label")) |aria_label| { if (el.getAttributeSafe(comptime .literal("aria-label"))) |aria_label| {
try w.write(aria_label); try w.write(aria_label);
return .aria_label; return .aria_label;
} }
if (el.getAttributeSafe("alt")) |alt| { if (el.getAttributeSafe(comptime .literal("alt"))) |alt| {
try w.write(alt); try w.write(alt);
return .alt; return .alt;
} }
@@ -836,12 +836,12 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource {
}, },
} }
if (el.getAttributeSafe("title")) |title| { if (el.getAttributeSafe(comptime .literal("title"))) |title| {
try w.write(title); try w.write(title);
return .title; return .title;
} }
if (el.getAttributeSafe("placeholder")) |placeholder| { if (el.getAttributeSafe(comptime .literal("placeholder"))) |placeholder| {
try w.write(placeholder); try w.write(placeholder);
return .placeholder; return .placeholder;
} }
@@ -857,17 +857,17 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource {
} }
fn isHidden(elt: *DOMNode.Element) bool { fn isHidden(elt: *DOMNode.Element) bool {
if (elt.getAttributeSafe("aria-hidden")) |value| { if (elt.getAttributeSafe(comptime .literal("aria-hidden"))) |value| {
if (std.mem.eql(u8, value, "true")) { if (std.mem.eql(u8, value, "true")) {
return true; return true;
} }
} }
if (elt.hasAttributeSafe("hidden")) { if (elt.hasAttributeSafe(comptime .literal("hidden"))) {
return true; return true;
} }
if (elt.hasAttributeSafe("inert")) { if (elt.hasAttributeSafe(comptime .literal("inert"))) {
return true; return true;
} }
@@ -940,7 +940,7 @@ fn isIgnore(self: AXNode, page: *Page) bool {
// zig fmt: on // zig fmt: on
.img => { .img => {
// Check for empty decorative images // Check for empty decorative images
const alt_ = elt.getAttributeSafe("alt"); const alt_ = elt.getAttributeSafe(comptime .literal("alt"));
if (alt_ == null or alt_.?.len == 0) { if (alt_ == null or alt_.?.len == 0) {
return true; return true;
} }
@@ -967,9 +967,9 @@ fn isIgnore(self: AXNode, page: *Page) bool {
// Generic containers with no semantic value // Generic containers with no semantic value
if (tag == .div or tag == .span) { if (tag == .div or tag == .span) {
const has_role = elt.hasAttributeSafe("role"); const has_role = elt.hasAttributeSafe(comptime .literal("role"));
const has_aria_label = elt.hasAttributeSafe("aria-label"); const has_aria_label = elt.hasAttributeSafe(comptime .literal("aria-label"));
const has_aria_labelledby = elt.hasAttributeSafe("aria-labelledby"); const has_aria_labelledby = elt.hasAttributeSafe(.literal("aria-labelledby"));
if (!has_role and !has_aria_label and !has_aria_labelledby) { if (!has_role and !has_aria_label and !has_aria_labelledby) {
// Check if it has any non-ignored children // Check if it has any non-ignored children

View File

@@ -34,6 +34,44 @@ pub const String = packed struct {
pub const empty = String{ .len = 0, .payload = .{ .content = @splat(0) } }; pub const empty = String{ .len = 0, .payload = .{ .content = @splat(0) } };
pub const deleted = String{ .len = tombstone, .payload = .{ .content = @splat(0) } }; pub const deleted = String{ .len = tombstone, .payload = .{ .content = @splat(0) } };
// Create a String from a string literal. For strings with len <= 12, the
// this can be done at comptime: comptime String.literal("id");
// For strings with len > 12, this must be done at runtime. This is because,
// at comptime, we do not have a ptr for data and thus can't store it.
pub fn literal(input: anytype) String {
if (@inComptime()) {
const l = input.len;
if (l > 12) {
@compileError("Comptime string must be <= 12 bytes (SSO only): " ++ input);
}
var content: [12]u8 = @splat(0);
@memcpy(content[0..l], input);
return .{ .len = @intCast(l), .payload = .{ .content = content } };
}
// Runtime path - handle both String and []const u8
if (@TypeOf(input) == String) {
return input;
}
const l = input.len;
if (l <= 12) {
var content: [12]u8 = @splat(0);
@memcpy(content[0..l], input);
return .{ .len = @intCast(l), .payload = .{ .content = content } };
}
return .{
.len = @intCast(l),
.payload = .{ .heap = .{
.prefix = input[0..4].*,
.ptr = input.ptr,
} },
};
}
pub const InitOpts = struct { pub const InitOpts = struct {
dupe: bool = true, dupe: bool = true,
}; };
@@ -47,13 +85,11 @@ pub const String = packed struct {
@memcpy(content[0..l], input); @memcpy(content[0..l], input);
return .{ .len = @intCast(l), .payload = .{ .content = content } }; return .{ .len = @intCast(l), .payload = .{ .content = content } };
} }
var prefix: [4]u8 = @splat(0);
@memcpy(&prefix, input[0..4]);
return .{ return .{
.len = @intCast(l), .len = @intCast(l),
.payload = .{ .heap = .{ .payload = .{ .heap = .{
.prefix = prefix, .prefix = input[0..4].*,
.ptr = (intern(input) orelse (if (opts.dupe) (try allocator.dupe(u8, input)) else input)).ptr, .ptr = (intern(input) orelse (if (opts.dupe) (try allocator.dupe(u8, input)) else input)).ptr,
} }, } },
}; };