perf: improve common element attribute getters

In main, there's a `getId`, and `getClassName` (etc...) getter on Element. But
these all `orelse ""`, because that's what the WebAPI wants. Internally though,
most code want the optional. The result is that _many_ places do:

```zig
el.getAttributeSafe(comptime .wrap("id"))
```

instead of:

```zig
el.getId()
```

This is a bit tedious AND, it means that when we improve `Element.getId` (1) no
internal caller benefits from it. This commit makes the element getters return
the optional (`?[]const u8`) and updates every callsite to use the new getter.
The `orelse ""` needed by the WebAPI is moved to the JsApi bridge.

(1) https://github.com/lightpanda-io/browser/pull/3457
This commit is contained in:
Karl Seguin committed 2026-09-09 11:22:37 +08:00
1 parent 5a55185ffe
commit a9af75034d
53 files changed
+275 -210

No files matched your search

+5 -5
View File
@@ -201,7 +201,7 @@ fn walk(
var name = try axn.getName(self.frame, self.arena, ctx.label_index);
const has_explicit_label = if (node.is(Element)) |el|
el.getAttributeSafe(comptime .wrap("aria-label")) != null or el.getAttributeSafe(comptime .wrap("title")) != null
el.getAttributeInterned("aria-label") != null or el.getAttributeInterned("title") != null
else
false;
@@ -681,12 +681,12 @@ pub fn getNodeDetails(
if (node.is(Element)) |el| {
tag_name = el.getTagNameLower();
is_disabled = el.isDisabled();
id_attr = el.getAttributeSafe(comptime .wrap("id"));
class_attr = el.getAttributeSafe(comptime .wrap("class"));
id_attr = el.getId();
class_attr = el.getClassName();
selector = try SelectorPath.init(arena, frame).build(el);
placeholder = el.getAttributeSafe(comptime .wrap("placeholder"));
placeholder = el.getAttributeInterned("placeholder");
if (el.getAttributeSafe(comptime .wrap("href"))) |h| {
if (el.getAttributeInterned("href")) |h| {
const URL = lp.URL;
href = URL.resolve(arena, frame.base(), h, .{ .encoding = frame.charset }) catch h;
}
+2 -2
View File
@@ -792,7 +792,7 @@ const ActivationState = struct {
fn findCheckedRadioInGroup(input: *Input, frame: *Frame) !?*Input {
const elem = input.asElement();
const name = elem.getAttributeSafe(comptime .wrap("name")) orelse return null;
const name = elem.getName() orelse return null;
if (name.len == 0) {
return null;
}
@@ -819,7 +819,7 @@ const ActivationState = struct {
continue;
}
const other_name = other_element.getAttributeSafe(comptime .wrap("name")) orelse continue;
const other_name = other_element.getName() orelse continue;
if (!std.mem.eql(u8, name, other_name)) {
continue;
}
+14 -14
View File
@@ -1951,7 +1951,7 @@ pub fn scriptAddedCallback(self: *Frame, comptime from_parser: bool, script: *El
log.err(.frame, "frame.scriptAddedCallback", .{
.err = err,
.url = self.url,
.src = script.asElement().getAttributeSafe(comptime .wrap("src")),
.src = script.asElement().getAttributeInterned("src"),
.type = self._type,
});
};
@@ -1977,7 +1977,7 @@ pub fn iframeAddedCallback(self: *Frame, iframe: *IFrame) !void {
break :blk "about:srcdoc";
}
var src = iframe.asElement().getAttributeSafe(comptime .wrap("src")) orelse "";
var src = iframe.asElement().getAttributeInterned("src") orelse "";
if (src.len == 0) {
src = "about:blank";
}
@@ -2277,7 +2277,7 @@ pub fn getElementByIdFromNode(self: *Frame, node: *Node, id: []const u8) ?*Eleme
// exists, so scan it.
var tw = TreeWalker.Full.Elements.init(node, .{});
while (tw.next()) |el| {
const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue;
const element_id = el.getId() orelse continue;
if (std.mem.eql(u8, element_id, id)) {
return el;
}
@@ -2747,7 +2747,7 @@ pub fn removeNode(self: *Frame, parent: *Node, child: *Node, opts: RemoveNodeOpt
// the ID map and invoking disconnectedCallback for custom elements
var tw = TreeWalker.Full.Elements.init(child, .{});
while (tw.next()) |el| {
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
if (el.getId()) |id| {
self.removeElementIdWithMaps(old_id_maps.?, id);
}
@@ -2790,7 +2790,7 @@ pub fn removeNode(self: *Frame, parent: *Node, child: *Node, opts: RemoveNodeOpt
fn unregisterSubtreeIds(self: *Frame, node: *Node, id_maps: ElementIdMaps) void {
var tw = TreeWalker.Full.Elements.init(node, .{});
while (tw.next()) |el| {
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
if (el.getId()) |id| {
self.removeElementIdWithMaps(id_maps, id);
}
}
@@ -2961,7 +2961,7 @@ pub fn _insertNodeRelative(self: *Frame, comptime from_parser: bool, parent: *No
// For main document parsing we know nodes are connected (fast path);
// for fragment parsing (innerHTML) we check connectivity.
if (child.isConnected() or child.isInShadowTree()) {
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
if (el.getId()) |id| {
try self.addElementId(parent, el, id);
}
try Element.Html.Custom.enqueueConnectedCallbackOnElement(true, el, self);
@@ -3015,7 +3015,7 @@ pub fn _insertNodeRelative(self: *Frame, comptime from_parser: bool, parent: *No
// id to the new parent...
var tw = TreeWalker.Full.Elements.init(child, .{});
while (tw.next()) |el| {
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
if (el.getId()) |id| {
try self.addElementIdWithMaps(new_id_maps, el, id);
}
}
@@ -3035,7 +3035,7 @@ pub fn _insertNodeRelative(self: *Frame, comptime from_parser: bool, parent: *No
var tw = TreeWalker.Full.Elements.init(child, .{});
while (tw.next()) |el| {
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
if (el.getId()) |id| {
try self.addElementIdWithMaps(new_id_maps, el, id);
}
@@ -3533,7 +3533,7 @@ pub fn openBlankTarget(self: *Frame, element: *Element, url: []const u8) !*Frame
}
fn hasRelToken(element: *Element, token: []const u8) bool {
const rel = element.getAttributeSafe(comptime .wrap("rel")) orelse return false;
const rel = element.getAttributeInterned("rel") orelse return false;
var it = std.mem.tokenizeAny(u8, rel, &std.ascii.whitespace);
while (it.next()) |t| {
if (std.ascii.eqlIgnoreCase(t, token)) {
@@ -3547,7 +3547,7 @@ fn findFrameByName(frame: *Frame, name: []const u8) ?*Frame {
for (frame.child_frames.items) |f| {
if (f.iframe) |iframe| {
if (iframe.asNode().isConnected()) {
const frame_name = iframe.asElement().getAttributeSafe(comptime .wrap("name")) orelse "";
const frame_name = iframe.asElement().getName() orelse "";
if (std.mem.eql(u8, frame_name, name)) {
return f;
}
@@ -3578,7 +3578,7 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
}
if (submitter_) |submitter| {
if (submitter.getAttributeSafe(comptime .wrap("disabled")) != null) {
if (submitter.getAttributeInterned("disabled") != null) {
return;
}
}
@@ -3600,7 +3600,7 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
break :blk ft;
}
}
break :blk form_element.getAttributeSafe(comptime .wrap("target"));
break :blk form_element.getAttributeInterned("target");
};
const target: TargetFrame = blk: {
@@ -3691,7 +3691,7 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
if (submit_button) |s| {
if (s.getAttributeSafe(comptime .wrap("formmethod"))) |fm| break :blk fm;
}
break :blk form_element.getAttributeSafe(comptime .wrap("method"));
break :blk form_element.getAttributeInterned("method");
};
const method = Element.Html.Form.normalizeMethod(method_attr, "get");
@@ -3747,7 +3747,7 @@ pub fn submitForm(self: *Frame, submitter_: ?*Element, form_: ?*Element.Html.For
if (submit_button) |s| {
if (s.getAttributeSafe(comptime .wrap("formaction"))) |fa| break :blk fa;
}
break :blk form_element.getAttributeSafe(comptime .wrap("action")) orelse self.url;
break :blk form_element.getAttributeInterned("action") orelse self.url;
};
var opts = NavigateOpts{
+2 -2
View File
@@ -141,7 +141,7 @@ fn display(self: *const RenderTree, el: *Element, is_slotted: bool) ?StyleManage
return .other;
};
if (dump_html.shouldStripElement(el, self.strip, self.frame)) return null;
if (!is_slotted and el.getAttributeSafe(comptime .wrap("slot")) != null) return null;
if (!is_slotted and el.getSlot() != null) return null;
return d;
}
@@ -156,7 +156,7 @@ fn visibleDisplay(el: *Element, frame: *Frame) ?StyleManager.Display {
if (d == .none) {
return null;
}
if (el.getAttributeSafe(comptime .wrap("aria-hidden"))) |v| {
if (el.getAttributeInterned("aria-hidden")) |v| {
if (std.ascii.eqlIgnoreCase(v, "true")) return null;
}
return d;
+5 -5
View File
@@ -107,7 +107,7 @@ const CorsSettings = struct {
// in order to properly set the request_mode and credentials_mode.
fn corsSettings(element: ?*Element, is_module: bool) CorsSettings {
const mode: enum { no_cors, anonymous, use_credentials } = blk: {
const co = if (element) |e| e.getAttributeSafe(comptime .wrap("crossorigin")) else null;
const co = if (element) |e| e.getAttributeInterned("crossorigin") else null;
const value = co orelse {
// Missing-value default: No CORS for classic scripts, Anonymous for modules.
@@ -229,7 +229,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
}
const kind: Script.Extra.FrameExtra.Kind = blk: {
const script_type = element.getAttributeSafe(comptime .wrap("type")) orelse break :blk .javascript;
const script_type = element.getAttributeInterned("type") orelse break :blk .javascript;
if (script_type.len == 0) {
break :blk .javascript;
}
@@ -255,7 +255,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
const frame = self.frame;
const base_url = frame.base();
const src = element.getAttributeSafe(comptime .wrap("src")) orelse {
const src = element.getAttributeInterned("src") orelse {
return self.addInlineScript(script_element, kind);
};
@@ -274,12 +274,12 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
script_element._executed = true;
const mode: Script.Extra.FrameExtra.Mode = blk: {
if (element.getAttributeSafe(comptime .wrap("async")) != null) {
if (element.getAttributeInterned("async") != null) {
break :blk .async;
}
// Check for defer or module (before checking dynamic script default)
if (kind == .module or element.getAttributeSafe(comptime .wrap("defer")) != null) {
if (kind == .module or element.getAttributeInterned("defer") != null) {
break :blk .@"defer";
}
+1 -1
View File
@@ -90,7 +90,7 @@ fn buildStrictPath(self: SelectorPath, target: *Element) !?[]const u8 {
/// else its tag qualified by stable attributes and — when that still matches a
/// sibling — a `:has()` distinguisher (preferred) or positional `:nth-of-type`.
fn localSegment(self: SelectorPath, el: *Element) ![]const u8 {
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
if (el.getId()) |id| {
if (id.len != 0) {
const id_sel = try std.fmt.allocPrint(self.arena, "#{s}", .{try CSS.escape(id, self.frame)});
if (self.isFirstMatch(el, id_sel)) return id_sel;
+6 -6
View File
@@ -638,7 +638,7 @@ fn matchesUaDisplayNoneRule(el: *Element) bool {
const tag = el.getTag();
if (tag.isHiddenByUaStylesheet()) return true;
if (el.hasAttributeSafe(comptime .wrap("hidden"))) return true;
if (el.hasAttributeInterned("hidden")) return true;
// input[type="hidden" i] { display: none !important }
// _input_type is parsed case-insensitively at attribute-set time.
@@ -810,13 +810,13 @@ fn resolve(self: *StyleManager, el: *Element, options: CheckVisibilityOptions, c
.frame = self.frame,
};
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
if (el.getId()) |id| {
if (self.id_rules.get(id)) |rules| {
ctx.checkRules(&rules);
}
}
if (el.getAttributeSafe(comptime .wrap("class"))) |class_attr| {
if (el.getClassName()) |class_attr| {
var it = std.mem.tokenizeAny(u8, class_attr, &std.ascii.whitespace);
while (it.next()) |class| {
if (self.class_rules.get(class)) |rules| {
@@ -916,13 +916,13 @@ fn elementHasPointerEventsNone(self: *StyleManager, el: *Element) bool {
}
}.check;
if (el.getAttributeSafe(comptime .wrap("id"))) |id| {
if (el.getId()) |id| {
if (self.id_rules.get(id)) |rules| {
checkRules(&rules, &result, &best_priority, el, frame);
}
}
if (el.getAttributeSafe(comptime .wrap("class"))) |class_attr| {
if (el.getClassName()) |class_attr| {
var it = std.mem.tokenizeAny(u8, class_attr, &std.ascii.whitespace);
while (it.next()) |class| {
if (self.class_rules.get(class)) |rules| {
@@ -1246,7 +1246,7 @@ fn inlineValue(el: *Element, property_name: String, comptime access: InlineAcces
return styleValue(style, property_name);
}
// No JS-set style object and no style attribute -> nothing inline to read.
const attr = el.getAttributeSafe(comptime .wrap("style")) orelse return null;
const attr = el.getAttributeInterned("style") orelse return null;
switch (access) {
.materialize => {
const style = el.getOrCreateStyle(frame) catch |err| {
+3 -3
View File
@@ -141,7 +141,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
// it, we don't want to skip it.
if ((comptime force_slot == false) and opts.shadow == .rendered) {
if (el.getAttributeSafe(comptime .wrap("slot"))) |_| {
if (el.getSlot()) |_| {
// Skip - will be rendered by the Slot if it's the active container
return;
}
@@ -378,7 +378,7 @@ pub fn shouldStripElement(el: *Node.Element, strip: Opts.Strip, frame: *Frame) b
if (el.getAttributeSafe(comptime .wrap("as"))) |as| {
if (std.mem.eql(u8, as, "script")) return true;
}
if (el.getAttributeSafe(comptime .wrap("rel"))) |rel| {
if (el.getAttributeInterned("rel")) |rel| {
if (std.mem.eql(u8, rel, "modulepreload") or std.mem.eql(u8, rel, "preload")) {
if (el.getAttributeSafe(comptime .wrap("as"))) |as| {
if (std.mem.eql(u8, as, "script")) return true;
@@ -392,7 +392,7 @@ pub fn shouldStripElement(el: *Node.Element, strip: Opts.Strip, frame: *Frame) b
if (std.mem.eql(u8, tag_name, "style")) return true;
if (std.mem.eql(u8, tag_name, "link")) {
if (el.getAttributeSafe(comptime .wrap("rel"))) |rel| {
if (el.getAttributeInterned("rel")) |rel| {
if (std.mem.eql(u8, rel, "stylesheet")) return true;
}
}
+9 -9
View File
@@ -169,7 +169,7 @@ pub fn collectForms(
const fields = try collectFormFields(arena, form, frame);
if (fields.len == 0) continue;
const action_attr = el.getAttributeSafe(comptime .wrap("action"));
const action_attr = el.getAttributeInterned("action");
const method_str = form.getMethod();
try forms.append(arena, .{
@@ -204,12 +204,12 @@ fn collectFormFields(
try fields.append(arena, .{
.node = node,
.tag_name = "input",
.name = el.getAttributeSafe(comptime .wrap("name")),
.name = el.getName(),
.input_type = input._input_type.toString(),
.required = el.getAttributeSafe(comptime .wrap("required")) != null,
.required = el.getAttributeInterned("required") != null,
.disabled = is_disabled,
.value = input.getRedactedValue(),
.placeholder = el.getAttributeSafe(comptime .wrap("placeholder")),
.placeholder = el.getAttributeInterned("placeholder"),
.options = &.{},
});
continue;
@@ -219,12 +219,12 @@ fn collectFormFields(
try fields.append(arena, .{
.node = node,
.tag_name = "textarea",
.name = el.getAttributeSafe(comptime .wrap("name")),
.name = el.getName(),
.input_type = null,
.required = el.getAttributeSafe(comptime .wrap("required")) != null,
.required = el.getAttributeInterned("required") != null,
.disabled = is_disabled,
.value = textarea.getValue(),
.placeholder = el.getAttributeSafe(comptime .wrap("placeholder")),
.placeholder = el.getAttributeInterned("placeholder"),
.options = &.{},
});
continue;
@@ -236,9 +236,9 @@ fn collectFormFields(
try fields.append(arena, .{
.node = node,
.tag_name = "select",
.name = el.getAttributeSafe(comptime .wrap("name")),
.name = el.getName(),
.input_type = null,
.required = el.getAttributeSafe(comptime .wrap("required")) != null,
.required = el.getAttributeInterned("required") != null,
.disabled = is_disabled,
.value = select.getValue(frame),
.placeholder = null,
+1 -1
View File
@@ -411,7 +411,7 @@ pub fn createElementNS(frame: *Frame, namespace: Element.Namespace, name: []cons
// If frames's base url is not already set, fill it with
// the base tag.
if (frame.base_url == null) {
if (n.as(Element).getAttributeSafe(comptime .wrap("href"))) |href| {
if (n.as(Element).getAttributeInterned("href")) |href| {
frame.base_url = try URL.resolve(frame.arena, frame.url, href, .{});
}
}
+7 -7
View File
@@ -331,7 +331,7 @@ fn hasClickActivationBehavior(node: *Node) bool {
};
return switch (html_element._type) {
.anchor => element.getAttributeSafe(comptime .wrap("href")) != null,
.anchor => element.getAttributeInterned("href") != null,
.input, .button, .select, .textarea, .label => true,
.generic => html_element.subtype(Element.Html.Generic)._tag == .summary,
else => false,
@@ -340,7 +340,7 @@ fn hasClickActivationBehavior(node: *Node) bool {
// SVG 2 <a> links via `href`; xlink:href is the deprecated SVG 1.1 spelling.
fn svgAnchorHref(element: *Element) ?[]const u8 {
return element.getAttributeSafe(comptime .wrap("href")) orelse element.getAttributeSafe(comptime .wrap("xlink:href"));
return element.getAttributeInterned("href") orelse element.getAttributeSafe(comptime .wrap("xlink:href"));
}
// Clicks on editable content are for editing: they don't activate the
@@ -462,7 +462,7 @@ pub fn handleClick(frame: *Frame, target: *Node, event_target: *Node) !void {
if (element.is(Element.Svg.Graphics.A) != null) {
const href = svgAnchorHref(element) orelse return;
const target_name = element.getAttributeSafe(comptime .wrap("target")) orelse "";
const target_name = element.getAttributeInterned("target") orelse "";
return followLink(frame, target, element, href, target_name);
}
@@ -471,7 +471,7 @@ pub fn handleClick(frame: *Frame, target: *Node, event_target: *Node) !void {
switch (html_element._type) {
.anchor => {
const anchor = html_element.subtype(Element.Html.Anchor);
const href = element.getAttributeSafe(comptime .wrap("href")) orelse return;
const href = element.getAttributeInterned("href") orelse return;
return followLink(frame, target, element, href, anchor.getTarget());
},
.input => {
@@ -794,7 +794,7 @@ fn enterActivates(element: *Element) bool {
return true;
}
if (html_element._type == .anchor) {
return element.getAttributeSafe(comptime .wrap("href")) != null;
return element.getAttributeInterned("href") != null;
}
if (element.is(Element.Html.Input)) |input| {
return switch (input._input_type) {
@@ -861,7 +861,7 @@ fn moveFocus(frame: *Frame, forward: bool) !void {
}
const candidate_tab_index = blk: {
if (candidate.getAttributeSafe(comptime .wrap("tabindex"))) |attr| {
if (candidate.getAttributeInterned("tabindex")) |attr| {
if (Element.Html.parseInteger(attr)) |tab_index| {
if (tab_index < 0) {
continue;
@@ -875,7 +875,7 @@ fn moveFocus(frame: *Frame, forward: bool) !void {
const focusable = switch (candidate.getTag()) {
.button, .select, .textarea, .iframe => true,
.input => candidate.as(Element.Html.Input)._input_type != .hidden,
.anchor, .area => candidate.getAttributeSafe(comptime .wrap("href")) != null,
.anchor, .area => candidate.getAttributeInterned("href") != null,
else => false,
};
if (focusable == false) {
+8 -8
View File
@@ -242,16 +242,16 @@ fn walkInteractive(
.listener_types = listener_types,
.disabled = el.isDisabled(),
.tab_index = html_el.getTabIndex(),
.id = el.getAttributeSafe(comptime .wrap("id")),
.class = el.getAttributeSafe(comptime .wrap("class")),
.href = if (el.getAttributeSafe(comptime .wrap("href"))) |href|
.id = el.getId(),
.class = el.getClassName(),
.href = if (el.getAttributeInterned("href")) |href|
URL.resolve(arena, frame.base(), href, .{ .encoding = frame.charset }) catch href
else
null,
.input_type = getInputType(el),
.value = getInputValue(el),
.element_name = el.getAttributeSafe(comptime .wrap("name")),
.placeholder = el.getAttributeSafe(comptime .wrap("placeholder")),
.element_name = el.getName(),
.placeholder = el.getAttributeInterned("placeholder"),
});
if (filter.max) |m| {
@@ -306,7 +306,7 @@ pub fn classifyInteractivity(
switch (el.getTag()) {
.button, .summary, .details, .select, .textarea => return .native,
.anchor, .area => {
if (el.getAttributeSafe(comptime .wrap("href")) != null) return .native;
if (el.getAttributeInterned("href") != null) return .native;
},
.input => {
if (el.is(Element.Html.Input)) |input| {
@@ -334,7 +334,7 @@ pub fn classifyInteractivity(
// Only count elements with an EXPLICIT tabindex attribute,
// since getTabIndex() returns 0 for all interactive tags by default
// (including anchors without href and hidden inputs).
if (el.getAttributeSafe(comptime .wrap("tabindex"))) |_| {
if (el.getAttributeInterned("tabindex")) |_| {
if (html_el.getTabIndex() >= 0) return .focusable;
}
@@ -391,7 +391,7 @@ pub fn isContentRole(role: []const u8) bool {
// ARIA `role` is a space-separated fallback list; the first token wins.
pub fn explicitRole(el: *Element) ?[]const u8 {
const attr = el.getAttributeSafe(comptime .wrap("role")) orelse return null;
const attr = el.getAttributeInterned("role") orelse return null;
var it = std.mem.tokenizeAny(u8, attr, " \t\n\r");
return it.next();
}
+6 -6
View File
@@ -62,7 +62,7 @@ fn shouldAddSpacing(tag: Element.Tag) bool {
}
fn getAnchorLabel(el: *Element) ?[]const u8 {
return el.getAttributeSafe(comptime .wrap("aria-label")) orelse el.getAttributeSafe(comptime .wrap("title"));
return el.getAttributeInterned("aria-label") orelse el.getAttributeInterned("title");
}
const Context = struct {
@@ -230,11 +230,11 @@ const Context = struct {
},
.img => {
try self.writer.writeAll("![");
if (el.getAttributeSafe(comptime .wrap("alt"))) |alt| {
if (el.getAttributeInterned("alt")) |alt| {
try self.escape(alt);
}
try self.writer.writeAll("](");
if (el.getAttributeSafe(comptime .wrap("src"))) |src| {
if (el.getAttributeInterned("src")) |src| {
const frame = self.frame;
const absolute_src = URL.resolve(frame.call_arena, frame.base(), src, .{ .encoding = frame.charset }) catch src;
try self.writer.writeAll(absolute_src);
@@ -247,7 +247,7 @@ const Context = struct {
const frame = self.frame;
const info = RenderTree.analyzeContent(el.asNode(), frame);
const label = getAnchorLabel(el);
const href_raw = el.getAttributeSafe(comptime .wrap("href"));
const href_raw = el.getAttributeInterned("href");
if (!info.has_visible and label == null and href_raw == null) return;
@@ -291,9 +291,9 @@ const Context = struct {
return;
},
.input => {
const type_attr = el.getAttributeSafe(comptime .wrap("type")) orelse return;
const type_attr = el.getAttributeInterned("type") orelse return;
if (std.ascii.eqlIgnoreCase(type_attr, "checkbox")) {
const checked = el.getAttributeSafe(comptime .wrap("checked")) != null;
const checked = el.getAttributeInterned("checked") != null;
try self.writer.writeAll(if (checked) "[x] " else "[ ] ");
self.state.last_char_was_newline = false;
}
+5 -5
View File
@@ -886,7 +886,7 @@ const Builder = struct {
return;
},
.img => {
const alt = el.getAttributeSafe(comptime .wrap("alt")) orelse return;
const alt = el.getAttributeInterned("alt") orelse return;
if (isAllWhitespace(alt)) return;
self.italic += 1;
self.muted += 1;
@@ -896,17 +896,17 @@ const Builder = struct {
return;
},
.input => {
const type_attr = el.getAttributeSafe(comptime .wrap("type")) orelse return;
const type_attr = el.getAttributeInterned("type") orelse return;
if (std.ascii.eqlIgnoreCase(type_attr, "checkbox")) {
const checked = el.getAttributeSafe(comptime .wrap("checked")) != null;
const checked = el.getAttributeInterned("checked") != null;
try self.appendWord(if (checked) "" else "");
self.pending_space = true;
}
return;
},
.anchor => {
const href = el.getAttributeSafe(comptime .wrap("href"));
const label = el.getAttributeSafe(comptime .wrap("aria-label")) orelse el.getAttributeSafe(comptime .wrap("title"));
const href = el.getAttributeInterned("href");
const label = el.getAttributeInterned("aria-label") orelse el.getAttributeInterned("title");
const info = RenderTree.analyzeContent(el.asNode(), self.frame);
if (!info.has_visible and label == null) return;
+10 -10
View File
@@ -173,7 +173,7 @@ pub fn collectStructuredData(
// Extract language from the root <html> element.
if (root.is(Element)) |root_el| {
if (root_el.getAttributeSafe(comptime .wrap("lang"))) |lang| {
if (root_el.getAttributeInterned("lang")) |lang| {
try meta.append(arena, .{ .key = "language", .value = lang });
}
} else {
@@ -182,7 +182,7 @@ pub fn collectStructuredData(
while (children.next()) |child| {
const el = child.is(Element) orelse continue;
if (el.getTag() == .html) {
if (el.getAttributeSafe(comptime .wrap("lang"))) |lang| {
if (el.getAttributeInterned("lang")) |lang| {
try meta.append(arena, .{ .key = "language", .value = lang });
}
break;
@@ -368,7 +368,7 @@ fn collectJsonLd(
arena: Allocator,
json_ld: *std.ArrayList([]const u8),
) !void {
const type_attr = el.getAttributeSafe(comptime .wrap("type")) orelse return;
const type_attr = el.getAttributeInterned("type") orelse return;
if (!std.ascii.eqlIgnoreCase(type_attr, "application/ld+json")) return;
var buf: std.Io.Writer.Allocating = .init(arena);
@@ -387,11 +387,11 @@ fn collectMeta(
arena: Allocator,
) !void {
// charset: <meta charset="..."> (no content attribute needed).
if (el.getAttributeSafe(comptime .wrap("charset"))) |charset| {
if (el.getAttributeInterned("charset")) |charset| {
try meta.append(arena, .{ .key = "charset", .value = charset });
}
const content = el.getAttributeSafe(comptime .wrap("content")) orelse return;
const content = el.getAttributeInterned("content") orelse return;
// Open Graph: <meta property="og:...">
if (el.getAttributeSafe(comptime .wrap("property"))) |property| {
@@ -412,7 +412,7 @@ fn collectMeta(
}
// Twitter Cards: <meta name="twitter:...">
if (el.getAttributeSafe(comptime .wrap("name"))) |name| {
if (el.getName()) |name| {
if (std.mem.startsWith(u8, name, "twitter:")) {
try twitter_card.append(arena, .{ .key = name[8..], .value = content });
return;
@@ -457,16 +457,16 @@ fn collectLink(
links: *std.ArrayList(Property),
alternate: *std.ArrayList(AlternateLink),
) !void {
const rel = el.getAttributeSafe(comptime .wrap("rel")) orelse return;
const raw_href = el.getAttributeSafe(comptime .wrap("href")) orelse return;
const rel = el.getAttributeInterned("rel") orelse return;
const raw_href = el.getAttributeInterned("href") orelse return;
const href = URL.resolve(arena, frame.base(), raw_href, .{ .encoding = frame.charset }) catch raw_href;
if (std.ascii.eqlIgnoreCase(rel, "alternate")) {
try alternate.append(arena, .{
.href = href,
.hreflang = el.getAttributeSafe(comptime .wrap("hreflang")),
.type = el.getAttributeSafe(comptime .wrap("type")),
.title = el.getAttributeSafe(comptime .wrap("title")),
.type = el.getAttributeInterned("type"),
.title = el.getAttributeInterned("title"),
});
return;
}
+38
View File
@@ -281,3 +281,41 @@
testing.expectEqual('', nsa.getAttribute('arr'));
}
</script>
<script id=reflect-absent>
{
// id/class/dir/slot reflect as '' when the content attribute is ABSENT.
// Internally these getters return an optional, so absent and
// present-but-empty are distinct there; through the IDL they are not.
const el = document.createElement('div');
testing.expectEqual('', el.id);
testing.expectEqual('', el.className);
testing.expectEqual('', el.dir);
testing.expectEqual('', el.slot);
testing.expectEqual(false, el.hasAttribute('id'));
testing.expectEqual(false, el.hasAttribute('class'));
testing.expectEqual(false, el.hasAttribute('slot'));
// present-but-empty reflects identically, but the attribute now exists
el.setAttribute('id', '');
el.setAttribute('class', '');
el.setAttribute('slot', '');
testing.expectEqual('', el.id);
testing.expectEqual('', el.className);
testing.expectEqual('', el.slot);
testing.expectEqual(true, el.hasAttribute('id'));
testing.expectEqual(true, el.hasAttribute('class'));
testing.expectEqual(true, el.hasAttribute('slot'));
// setters round-trip through the content attribute
el.id = 'x';
el.className = 'a b';
el.slot = 's';
testing.expectEqual('x', el.id);
testing.expectEqual('a b', el.className);
testing.expectEqual('s', el.slot);
testing.expectEqual('x', el.getAttribute('id'));
testing.expectEqual('a b', el.getAttribute('class'));
testing.expectEqual('s', el.getAttribute('slot'));
}
</script>
+1 -1
View File
@@ -454,7 +454,7 @@ pub fn getElementById(self: *Document, id: []const u8, frame: *Frame) ?*Element
if (self._removed_ids.remove(id)) {
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
while (tw.next()) |el| {
const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue;
const element_id = el.getId() orelse continue;
if (std.mem.eql(u8, element_id, id)) {
// we ignore this error to keep getElementById easy to call
// if it really failed, then we're out of memory and nothing's
+1 -1
View File
@@ -76,7 +76,7 @@ pub fn getElementById(self: *DocumentFragment, id: []const u8) ?*Element {
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
while (tw.next()) |el| {
if (el.getAttributeSafe(comptime .wrap("id"))) |element_id| {
if (el.getId()) |element_id| {
if (std.mem.eql(u8, element_id, id)) {
return el;
}
+42 -15
View File
@@ -630,32 +630,43 @@ pub fn setHTMLUnsafe(self: *Element, html: []const u8, frame: *Frame) !void {
return parent.setHTML(html, .{ .allow_declarative_shadow = true }, frame);
}
pub fn getId(self: *const Element) []const u8 {
return self.getAttributeInterned("id") orelse "";
pub fn getId(self: *const Element) ?[]const u8 {
return self.getAttributeInterned("id");
}
pub fn setId(self: *Element, value: []const u8, frame: *Frame) !void {
return self.setAttributeSafe(comptime .wrap("id"), .wrap(value), frame);
}
pub fn getSlot(self: *const Element) []const u8 {
return self.getAttributeSafe(comptime .wrap("slot")) orelse "";
// ** INTERN ONL **. Unlike other getters, e.g. getClassName, getSlot, this
// isn't a WebApi (some individual types DO have a name getter, but not Element).
// BUT, enough code internally needs this, that the helper exists.
pub fn getName(self: *const Element) ?[]const u8 {
return self.getAttributeInterned("name");
}
pub fn hasName(self: *const Element) bool {
return self.hasAttributeInterned("name");
}
pub fn getSlot(self: *const Element) ?[]const u8 {
return self.getAttributeSafe(comptime .wrap("slot"));
}
pub fn setSlot(self: *Element, value: []const u8, frame: *Frame) !void {
return self.setAttributeSafe(comptime .wrap("slot"), .wrap(value), frame);
}
pub fn getDir(self: *const Element) []const u8 {
return self.getAttributeInterned("dir") orelse "";
pub fn getDir(self: *const Element) ?[]const u8 {
return self.getAttributeInterned("dir");
}
pub fn setDir(self: *Element, value: []const u8, frame: *Frame) !void {
return self.setAttributeSafe(comptime .wrap("dir"), .wrap(value), frame);
}
pub fn getClassName(self: *const Element) []const u8 {
return self.getAttributeInterned("class") orelse "";
pub fn getClassName(self: *const Element) ?[]const u8 {
return self.getAttributeInterned("class");
}
pub fn setClassName(self: *Element, value: []const u8, frame: *Frame) !void {
@@ -752,7 +763,7 @@ pub fn isDisabled(self: *const Element) bool {
return false;
}
if (self.getAttributeSafe(comptime .wrap("disabled")) != null) {
if (self.getAttributeInterned("disabled") != null) {
return true;
}
@@ -764,7 +775,7 @@ pub fn isDisabled(self: *const Element) bool {
if (self.asConstNode()._parent) |parent_node| {
if (parent_node.is(Element)) |parent_el| {
if (parent_el.getTag() == .optgroup and
parent_el.getAttributeSafe(comptime .wrap("disabled")) != null)
parent_el.getAttributeInterned("disabled") != null)
{
return true;
}
@@ -779,7 +790,7 @@ pub fn isDisabled(self: *const Element) bool {
current = node._parent;
const ancestor = node.is(Element) orelse continue;
if (ancestor.getTag() == .fieldset and ancestor.getAttributeSafe(comptime .wrap("disabled")) != null) {
if (ancestor.getTag() == .fieldset and ancestor.getAttributeInterned("disabled") != null) {
var child = ancestor.firstElementChild();
while (child) |c| {
if (c.getTag() == .legend) {
@@ -2405,8 +2416,16 @@ pub const JsApi = struct {
}
pub const localName = bridge.accessor(Element.getLocalName, null, .{});
pub const id = bridge.accessor(Element.getId, Element.setId, .{ .ce_reactions = true });
pub const slot = bridge.accessor(Element.getSlot, Element.setSlot, .{ .ce_reactions = true });
pub const id = bridge.accessor(struct {
fn wrap(self: *const Element) []const u8 {
return self.getId() orelse "";
}
}.wrap, Element.setId, .{ .ce_reactions = true });
pub const slot = bridge.accessor(struct {
fn wrap(self: *const Element) []const u8 {
return self.getSlot() orelse "";
}
}.wrap, Element.setSlot, .{ .ce_reactions = true });
pub const role = ariaAccessor("role");
pub const ariaAtomic = ariaAccessor("aria-atomic");
pub const ariaAutoComplete = ariaAccessor("aria-autocomplete");
@@ -2451,8 +2470,16 @@ pub const JsApi = struct {
pub const ariaValueMin = ariaAccessor("aria-valuemin");
pub const ariaValueNow = ariaAccessor("aria-valuenow");
pub const ariaValueText = ariaAccessor("aria-valuetext");
pub const dir = bridge.accessor(Element.getDir, Element.setDir, .{ .ce_reactions = true });
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{ .ce_reactions = true });
pub const dir = bridge.accessor(struct {
fn wrap(self: *const Element) []const u8 {
return self.getDir() orelse "";
}
}.wrap, Element.setDir, .{ .ce_reactions = true });
pub const className = bridge.accessor(struct {
fn wrap(self: *const Element) []const u8 {
return self.getClassName() orelse "";
}
}.wrap, Element.setClassName, .{ .ce_reactions = true });
pub const classList = bridge.accessor(Element.getClassList, Element.setClassList, .{ .ce_reactions = true });
pub const part = bridge.accessor(Element.getPartList, null, .{});
pub const dataset = bridge.accessor(Element.getDataset, null, .{});
+1 -1
View File
@@ -162,7 +162,7 @@ pub fn getElementById(self: *ShadowRoot, id: []const u8, frame: *Frame) ?*Elemen
// Do a tree walk to find another element with this ID
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
while (tw.next()) |el| {
const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue;
const element_id = el.getId() orelse continue;
if (std.mem.eql(u8, element_id, id)) {
// we ignore this error to keep getElementById easy to call
// if it really failed, then we're out of memory and nothing's
@@ -57,7 +57,7 @@ pub fn getByName(self: *HTMLAllCollection, name: []const u8, frame: *Frame) ?*El
if (!isAllNamed(el)) {
continue;
}
if (el.getAttributeSafe(comptime .wrap("name"))) |attr_name| {
if (el.getName()) |attr_name| {
if (std.mem.eql(u8, attr_name, name)) {
return el;
}
@@ -291,13 +291,13 @@ pub const JsApi = struct {
const len = self.length(frame);
for (0..len) |i| {
const element = self.getAtIndex(i, frame) orelse break;
if (element.getAttributeSafe(comptime .wrap("id"))) |id| {
if (element.getId()) |id| {
if (id.len > 0 and !contains(names.items, id)) {
try names.append(arena, id);
}
}
if (element._namespace == .html) {
if (element.getAttributeSafe(comptime .wrap("name"))) |name| {
if (element.getName()) |name| {
if (name.len > 0 and !contains(names.items, name)) {
try names.append(arena, name);
}
@@ -75,12 +75,12 @@ pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, frame: *Fr
var it = try self.iterator();
while (it.next()) |element| {
const is_match = blk: {
if (element.getAttributeSafe(comptime .wrap("id"))) |id| {
if (element.getId()) |id| {
if (std.mem.eql(u8, id, name)) {
break :blk true;
}
}
if (element.getAttributeSafe(comptime .wrap("name"))) |elem_name| {
if (element.getName()) |elem_name| {
if (std.mem.eql(u8, elem_name, name)) {
break :blk true;
}
@@ -73,7 +73,7 @@ pub fn getValue(self: *RadioNodeList) ![]const u8 {
if (!input.getChecked()) {
continue;
}
return element.getAttributeSafe(comptime .wrap("value")) orelse "on";
return element.getAttributeInterned("value") orelse "on";
}
return "";
}
@@ -86,7 +86,7 @@ pub fn setValue(self: *RadioNodeList, value: []const u8, frame: *Frame) !void {
continue;
}
const input_value = element.getAttributeSafe(comptime .wrap("value"));
const input_value = element.getAttributeInterned("value");
const matches_value = blk: {
if (std.mem.eql(u8, value, "on")) {
break :blk input_value == null or (input_value != null and std.mem.eql(u8, input_value.?, "on"));
@@ -103,12 +103,12 @@ pub fn setValue(self: *RadioNodeList, value: []const u8, frame: *Frame) !void {
}
fn matches(self: *const RadioNodeList, element: *Element) bool {
if (element.getAttributeSafe(comptime .wrap("id"))) |id| {
if (element.getId()) |id| {
if (std.mem.eql(u8, id, self._name)) {
return true;
}
}
if (element.getAttributeSafe(comptime .wrap("name"))) |elem_name| {
if (element.getName()) |elem_name| {
if (std.mem.eql(u8, elem_name, self._name)) {
return true;
}
+4 -4
View File
@@ -239,7 +239,7 @@ pub fn NodeLive(comptime mode: Mode) type {
if (element._namespace != .html) {
continue;
}
const element_name = element.getAttributeSafe(comptime .wrap("name")) orelse continue;
const element_name = element.getName() orelse continue;
if (std.mem.eql(u8, element_name, name)) {
return element;
}
@@ -321,7 +321,7 @@ pub fn NodeLive(comptime mode: Mode) type {
const el = node.is(Element) orelse return false;
// getElementsByName only considers HTML elements.
if (el._namespace != .html) return false;
const name_attr = el.getAttributeInterned("name") orelse return false;
const name_attr = el.getName() orelse return false;
return std.mem.eql(u8, name_attr, self._filter);
},
.all_elements => return node._type == .element,
@@ -361,14 +361,14 @@ pub fn NodeLive(comptime mode: Mode) type {
const el = node.is(Element) orelse return false;
const Anchor = Element.Html.Anchor;
if (el.is(Anchor) == null) return false;
return el.hasAttributeSafe(comptime .wrap("href"));
return el.hasAttributeInterned("href");
},
.anchors => {
// Anchors are <a> elements with name attribute
const el = node.is(Element) orelse return false;
const Anchor = Element.Html.Anchor;
if (el.is(Anchor) == null) return false;
return el.hasAttributeSafe(comptime .wrap("name"));
return el.hasName();
},
.form => {
const el = node.is(Element) orelse return false;
@@ -44,7 +44,7 @@ pub fn parseInlineStyle(self: *CSSStyleDeclaration, frame: *Frame) !void {
return;
}
const el = self._element orelse return;
const attr_value = el.getAttributeSafe(comptime .wrap("style")) orelse return;
const attr_value = el.getAttributeInterned("style") orelse return;
try self.applyDeclarations(attr_value, frame);
}
+8 -8
View File
@@ -441,7 +441,7 @@ pub fn click(self: *HtmlElement, frame: *Frame) !void {
// TODO: Per spec, hidden is a tristate: true | false | "until-found".
// We only support boolean for now; "until-found" would need bridge union support.
pub fn getHidden(self: *HtmlElement) bool {
return self.asElement().getAttributeSafe(comptime .wrap("hidden")) != null;
return self.asElement().getAttributeInterned("hidden") != null;
}
pub fn setHidden(self: *HtmlElement, hidden: bool, frame: *Frame) !void {
@@ -479,7 +479,7 @@ pub fn setTranslate(self: *HtmlElement, translate: bool, frame: *Frame) !void {
// auto state, which defaults to true only for <img> and <a> with an href.
// https://html.spec.whatwg.org/multipage/dnd.html#the-draggable-attribute
pub fn getDraggable(self: *HtmlElement) bool {
if (self.asElement().getAttributeSafe(comptime .wrap("draggable"))) |value| {
if (self.asElement().getAttributeInterned("draggable")) |value| {
if (std.ascii.eqlIgnoreCase(value, "true")) {
return true;
}
@@ -489,7 +489,7 @@ pub fn getDraggable(self: *HtmlElement) bool {
}
return switch (self._type) {
.img => true,
.anchor => self.asElement().getAttributeSafe(comptime .wrap("href")) != null,
.anchor => self.asElement().getAttributeInterned("href") != null,
else => false,
};
}
@@ -536,7 +536,7 @@ pub fn togglePopover(self: *HtmlElement, force: ?bool, frame: *Frame) !bool {
}
pub fn getTabIndex(self: *HtmlElement) i32 {
if (self.asElement().getAttributeSafe(comptime .wrap("tabindex"))) |attr| {
if (self.asElement().getAttributeInterned("tabindex")) |attr| {
if (parseInteger(attr)) |tab_index| {
return tab_index;
}
@@ -555,7 +555,7 @@ pub fn setTabIndex(self: *HtmlElement, value: i32, frame: *Frame) !void {
}
pub fn getDir(self: *HtmlElement) []const u8 {
return reflection.enumeratedValue(self.asElement().getAttributeSafe(comptime .wrap("dir")), &.{ "ltr", "rtl", "auto" }, "", "").?;
return reflection.enumeratedValue(self.asElement().getDir(), &.{ "ltr", "rtl", "auto" }, "", "").?;
}
pub fn getAccessKey(self: *HtmlElement) []const u8 {
@@ -567,7 +567,7 @@ pub fn setAccessKey(self: *HtmlElement, value: []const u8, frame: *Frame) !void
}
pub fn getAutofocus(self: *HtmlElement) bool {
return self.asElement().getAttributeSafe(comptime .wrap("autofocus")) != null;
return self.asElement().getAttributeInterned("autofocus") != null;
}
pub fn setAutofocus(self: *HtmlElement, autofocus: bool, frame: *Frame) !void {
@@ -587,7 +587,7 @@ pub fn setNonce(self: *HtmlElement, value: []const u8, frame: *Frame) !void {
}
pub fn getLang(self: *HtmlElement) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("lang")) orelse "";
return self.asElement().getAttributeInterned("lang") orelse "";
}
pub fn setLang(self: *HtmlElement, value: []const u8, frame: *Frame) !void {
@@ -595,7 +595,7 @@ pub fn setLang(self: *HtmlElement, value: []const u8, frame: *Frame) !void {
}
pub fn getTitle(self: *HtmlElement) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("title")) orelse "";
return self.asElement().getAttributeInterned("title") orelse "";
}
pub fn setTitle(self: *HtmlElement, value: []const u8, frame: *Frame) !void {
+3 -3
View File
@@ -44,7 +44,7 @@ pub fn asNode(self: *Anchor) *Node {
}
pub fn getHref(self: *Anchor, frame: *Frame) ![]const u8 {
const href = self.asElement().getAttributeSafe(comptime .wrap("href")) orelse return "";
const href = self.asElement().getAttributeInterned("href") orelse return "";
if (href.len == 0) {
return "";
}
@@ -197,7 +197,7 @@ pub fn setText(self: *Anchor, value: []const u8, frame: *Frame) !void {
}
fn getResolvedHref(self: *Anchor, frame: *Frame) !?[:0]const u8 {
const href = self.asElement().getAttributeSafe(comptime .wrap("href")) orelse return null;
const href = self.asElement().getAttributeInterned("href") orelse return null;
if (href.len == 0) {
return null;
}
@@ -210,7 +210,7 @@ fn getResolvedHref(self: *Anchor, frame: *Frame) !?[:0]const u8 {
}
pub fn getTarget(self: *Anchor) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("target")) orelse "";
return self.asElement().getAttributeInterned("target") orelse "";
}
pub const JsApi = struct {
+2 -2
View File
@@ -44,7 +44,7 @@ pub fn asNode(self: *Area) *Node {
}
pub fn getHref(self: *Area, frame: *Frame) ![]const u8 {
const href = self.asElement().getAttributeSafe(comptime .wrap("href")) orelse return "";
const href = self.asElement().getAttributeInterned("href") orelse return "";
if (href.len == 0) {
return "";
}
@@ -198,7 +198,7 @@ pub fn getRelList(self: *Area, frame: *Frame) !?*DOMTokenList {
}
fn getResolvedHref(self: *Area, frame: *Frame) !?[:0]const u8 {
const href = self.asElement().getAttributeSafe(comptime .wrap("href")) orelse return null;
const href = self.asElement().getAttributeInterned("href") orelse return null;
if (href.len == 0) {
return null;
}
+2 -2
View File
@@ -24,7 +24,7 @@ pub fn asNode(self: *Base) *Node {
pub fn getHref(self: *Base, frame: *Frame) ![]const u8 {
const element = self.asElement();
const href = element.getAttributeSafe(comptime .wrap("href")) orelse return "";
const href = element.getAttributeInterned("href") orelse return "";
if (href.len == 0) {
return "";
}
@@ -50,7 +50,7 @@ pub fn setHref(self: *Base, value: []const u8, frame: *Frame) !void {
owner.base_url = null;
return;
};
const href = first.getAttributeSafe(comptime .wrap("href")) orelse {
const href = first.getAttributeInterned("href") orelse {
owner.base_url = null;
return;
};
+3 -3
View File
@@ -52,7 +52,7 @@ pub fn asNode(self: *Button) *Node {
}
pub fn getType(self: *const Button) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("type")) orelse "submit";
return self.asConstElement().getAttributeInterned("type") orelse "submit";
}
pub fn getForm(self: *Button, frame: *Frame) ?*Form {
@@ -208,11 +208,11 @@ pub fn setPopoverTargetAction(self: *Button, value: []const u8, frame: *Frame) !
}
pub fn getDisabled(self: *const Button) bool {
return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
return self.asConstElement().getAttributeInterned("disabled") != null;
}
pub fn getValue(self: *const Button) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("value")) orelse "";
return self.asConstElement().getAttributeInterned("value") orelse "";
}
pub const JsApi = struct {
+2 -2
View File
@@ -54,12 +54,12 @@ pub fn asNode(self: *Canvas) *Node {
}
pub fn getWidth(self: *const Canvas) u32 {
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("width")) orelse return 300;
const attr = self.asConstElement().getAttributeInterned("width") orelse return 300;
return std.fmt.parseUnsigned(u32, attr, 10) catch 300;
}
pub fn getHeight(self: *const Canvas) u32 {
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("height")) orelse return 150;
const attr = self.asConstElement().getAttributeInterned("height") orelse return 150;
return std.fmt.parseUnsigned(u32, attr, 10) catch 150;
}
+1 -1
View File
@@ -42,7 +42,7 @@ pub fn asNode(self: *Embed) *Node {
pub fn getSrc(self: *const Embed, frame: *Frame) ![]const u8 {
const element = self.asConstElement();
const src = element.getAttributeSafe(comptime .wrap("src")) orelse return "";
const src = element.getAttributeInterned("src") orelse return "";
if (src.len == 0) {
return "";
}
+4 -4
View File
@@ -83,7 +83,7 @@ pub fn normalizeEnctype(attr: ?[]const u8, missing_default: []const u8) []const
}
pub fn getMethod(self: *const Form) []const u8 {
return normalizeMethod(self.asConstElement().getAttributeSafe(comptime .wrap("method")), "get");
return normalizeMethod(self.asConstElement().getAttributeInterned("method"), "get");
}
pub fn setMethod(self: *Form, method: []const u8, frame: *Frame) !void {
@@ -101,7 +101,7 @@ pub fn getElements(self: *Form, frame: *Frame) !*collections.HTMLFormControlsCol
}
pub fn iterator(self: *Form, frame: *Frame) collections.NodeLive(.form) {
const form_id = self.asElement().getAttributeSafe(comptime .wrap("id"));
const form_id = self.asElement().getId();
const root = if (form_id != null)
self.asNode().getRootNode(.{}) // Has ID: walk entire document to find form=ID controls
else
@@ -113,7 +113,7 @@ pub fn iterator(self: *Form, frame: *Frame) collections.NodeLive(.form) {
pub fn getAction(self: *Form, frame: *Frame) ![]const u8 {
const element = self.asElement();
const owner_url = element.ownerFrame(frame).url;
const action = element.getAttributeSafe(comptime .wrap("action")) orelse return owner_url;
const action = element.getAttributeInterned("action") orelse return owner_url;
if (action.len == 0) {
return owner_url;
}
@@ -218,7 +218,7 @@ fn checkElementValidity(element: *Element, frame: *Frame) !bool {
}
pub fn getNoValidate(self: *const Form) bool {
return self.asConstElement().getAttributeSafe(comptime .wrap("novalidate")) != null;
return self.asConstElement().getAttributeInterned("novalidate") != null;
}
pub const JsApi = struct {
+3 -3
View File
@@ -59,7 +59,7 @@ pub fn getContentDocument(self: *const IFrame) ?*Document {
// loading=lazy iframes are still but don't delay the page's "load" event
pub fn isLazyLoading(self: *IFrame) bool {
const loading = self.asElement().getAttributeSafe(comptime .wrap("loading")) orelse return false;
const loading = self.asElement().getAttributeInterned("loading") orelse return false;
return std.ascii.eqlIgnoreCase(loading, "lazy");
}
@@ -71,7 +71,7 @@ pub fn getSrc(self: *IFrame, frame: *Frame) ![]const u8 {
pub fn setSrc(self: *IFrame, src: []const u8, frame: *Frame) !void {
const element = self.asElement();
try element.setAttributeSafe(comptime .wrap("src"), .wrap(src), frame);
self._src = element.getAttributeSafe(comptime .wrap("src")) orelse unreachable;
self._src = element.getAttributeInterned("src") orelse unreachable;
if (element.asNode().isConnected()) {
// unlike script, an iframe is reloaded every time the src is set
// even if it's set to the same URL.
@@ -134,7 +134,7 @@ pub const Build = struct {
pub fn complete(node: *Node, _: *Frame) !void {
const self = node.as(IFrame);
const element = self.asElement();
self._src = element.getAttributeSafe(comptime .wrap("src")) orelse "";
self._src = element.getAttributeInterned("src") orelse "";
}
pub fn attributeChange(element: *Element, name: String, _: String, frame: *Frame) !void {
+3 -3
View File
@@ -47,7 +47,7 @@ pub fn asNode(self: *Image) *Node {
pub fn getSrc(self: *const Image, frame: *Frame) ![]const u8 {
const element = self.asConstElement();
const src = element.getAttributeSafe(comptime .wrap("src")) orelse return "";
const src = element.getAttributeInterned("src") orelse return "";
if (src.len == 0) {
return "";
}
@@ -59,7 +59,7 @@ pub fn setSrc(self: *Image, value: []const u8, frame: *Frame) !void {
}
pub fn getLoading(self: *const Image) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("loading")) orelse "eager";
return self.asConstElement().getAttributeInterned("loading") orelse "eager";
}
pub fn setLoading(self: *Image, value: []const u8, frame: *Frame) !void {
@@ -102,7 +102,7 @@ pub fn imageAddedCallback(self: *Image, frame: *Frame) !void {
const element = self.asElement();
// Exit if src not set.
const src = element.getAttributeSafe(comptime .wrap("src")) orelse return;
const src = element.getAttributeInterned("src") orelse return;
if (src.len == 0) return;
// If image loading not desired, we just do fake "load" event.
+10 -10
View File
@@ -501,7 +501,7 @@ const RadioGroupIterator = struct {
const other_element = node.is(Element) orelse continue;
const other_input = other_element.is(Input) orelse continue;
if (other_input._input_type != .radio) continue;
const other_name = other_element.getAttributeSafe(comptime .wrap("name")) orelse continue;
const other_name = other_element.getName() orelse continue;
if (!std.mem.eql(u8, self.name, other_name)) continue;
return other_input;
}
@@ -515,7 +515,7 @@ const RadioGroupIterator = struct {
/// because nothing in the iteration mutates the tree.
fn radioGroupIterator(self: *const Input) ?RadioGroupIterator {
const element = self.asConstElement();
const name = element.getAttributeSafe(comptime .wrap("name")) orelse return null;
const name = element.getName() orelse return null;
if (name.len == 0) return null;
const root = @constCast(element.asConstNode()).getRootNode(.{});
return .{
@@ -589,7 +589,7 @@ fn codepointCount(value: []const u8) usize {
}
pub fn getDisabled(self: *const Input) bool {
return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
return self.asConstElement().getAttributeInterned("disabled") != null;
}
pub fn setDisabled(self: *Input, disabled: bool, frame: *Frame) !void {
@@ -609,7 +609,7 @@ pub fn getMinLength(self: *const Input) i32 {
}
pub fn getSrc(self: *const Input, frame: *Frame) ![]const u8 {
const src = self.asConstElement().getAttributeSafe(comptime .wrap("src")) orelse return "";
const src = self.asConstElement().getAttributeInterned("src") orelse return "";
return self.asConstElement().asConstNode().resolveURLReflect(src, frame, .{});
}
@@ -777,7 +777,7 @@ fn sanitizeValue(self: *Input, comptime dupe: bool, value: []const u8, frame: *F
.@"datetime-local" => return try sanitizeDatetimeLocal(dupe, value, frame.arena),
.number => return if (isValidFloatingPoint(value)) if (comptime dupe) try frame.dupeString(value) else value else "",
.range => {
const value_attr = self.asConstElement().getAttributeSafe(comptime .wrap("value")) orelse "";
const value_attr = self.asConstElement().getAttributeInterned("value") orelse "";
return try sanitizeRange(dupe, value, self.getMin(), self.getMax(), self.getStep(), value_attr, frame);
},
.color => {
@@ -895,7 +895,7 @@ fn stepBy(self: *Input, n: i32, frame: *Frame) !void {
fn stepBase(self: *const Input) f64 {
const typ = self._input_type;
if (valueToNumber(typ, self.getMin())) |min| return min;
if (valueToNumber(typ, self.asConstElement().getAttributeSafe(comptime .wrap("value")) orelse "")) |v| return v;
if (valueToNumber(typ, self.asConstElement().getAttributeInterned("value") orelse "")) |v| return v;
return if (typ == .week) -259_200_000 else 0;
}
@@ -1434,7 +1434,7 @@ pub fn getMin(self: *const Input) []const u8 {
}
pub fn getRequired(self: *const Input) bool {
return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null;
return self.asConstElement().getAttributeInterned("required") != null;
}
pub fn getStep(self: *const Input) []const u8 {
@@ -1522,12 +1522,12 @@ pub const Build = struct {
const element = self.asElement();
// Store initial values from attributes
self._default_value = element.getAttributeSafe(comptime .wrap("value"));
self._default_checked = element.getAttributeSafe(comptime .wrap("checked")) != null;
self._default_value = element.getAttributeInterned("value");
self._default_checked = element.getAttributeInterned("checked") != null;
self._checked = self._default_checked;
self._input_type = if (element.getAttributeSafe(comptime .wrap("type"))) |type_attr|
self._input_type = if (element.getAttributeInterned("type")) |type_attr|
Type.fromString(type_attr)
else
.text;
+4 -4
View File
@@ -23,7 +23,7 @@ pub fn asNode(self: *Label) *Node {
}
pub fn getControl(self: *Label, frame: *Frame) ?*Element {
if (self.asElement().getAttributeSafe(comptime .wrap("for"))) |id| {
if (self.asElement().getAttributeInterned("for")) |id| {
const el = frame.getElementByIdFromNode(self.asElement().asNode(), id) orelse return null;
if (!isLabelable(el)) {
return null;
@@ -72,7 +72,7 @@ pub const LabelByForIndex = struct {
var it = TreeWalker.Full.Elements.init(root, .{});
while (it.next()) |el| {
if (el.getTag() != .label) continue;
const for_attr = el.getAttributeSafe(comptime .wrap("for")) orelse continue;
const for_attr = el.getAttributeInterned("for") orelse continue;
if (for_attr.len == 0) continue;
const gop = try self.map.getOrPut(allocator, for_attr);
if (!gop.found_existing) gop.value_ptr.* = el;
@@ -92,14 +92,14 @@ pub fn getControlLabels(control: *Element, frame: *Frame) !js.Array {
var arr = local.newArray(0);
var idx: u32 = 0;
if (control.getAttributeSafe(comptime .wrap("id"))) |id_value| {
if (control.getId()) |id_value| {
if (id_value.len > 0) {
const doc = control.asNode().ownerDocument(frame);
const search_root: *Node = if (doc) |d| d.asNode() else control.asNode();
var it = TreeWalker.Full.Elements.init(search_root, .{});
while (it.next()) |el| {
if (el.getTag() != .label) continue;
const for_attr = el.getAttributeSafe(comptime .wrap("for")) orelse continue;
const for_attr = el.getAttributeInterned("for") orelse continue;
if (!std.mem.eql(u8, for_attr, id_value)) continue;
_ = try arr.set(idx, el, .{});
idx += 1;
+5 -5
View File
@@ -51,7 +51,7 @@ pub fn asNode(self: *Link) *Node {
pub fn getHref(self: *Link, frame: *Frame) ![]const u8 {
const element = self.asElement();
const href = element.getAttributeSafe(comptime .wrap("href")) orelse return "";
const href = element.getAttributeInterned("href") orelse return "";
if (href.len == 0) {
return "";
}
@@ -68,7 +68,7 @@ pub fn setHref(self: *Link, value: []const u8, frame: *Frame) !void {
}
pub fn getRel(self: *Link) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("rel")) orelse return "";
return self.asElement().getAttributeInterned("rel") orelse return "";
}
pub fn setRel(self: *Link, value: []const u8, frame: *Frame) !void {
@@ -76,7 +76,7 @@ pub fn setRel(self: *Link, value: []const u8, frame: *Frame) !void {
}
pub fn getMedia(self: *Link) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("media")) orelse return "";
return self.asElement().getAttributeInterned("media") orelse return "";
}
pub fn setMedia(self: *Link, value: []const u8, frame: *Frame) !void {
@@ -108,12 +108,12 @@ pub fn linkAddedCallback(self: *Link, frame: *Frame) !void {
const element = self.asElement();
const href = element.getAttributeSafe(comptime .wrap("href")) orelse return;
const href = element.getAttributeInterned("href") orelse return;
if (href.len == 0) {
return;
}
const rel = element.getAttributeSafe(comptime .wrap("rel")) orelse return;
const rel = element.getAttributeInterned("rel") orelse return;
// Opt-in fetch for `rel="stylesheet"` — drives `frame.loadExternalStylesheet`,
// which fires the load/error event itself.
+1 -1
View File
@@ -250,7 +250,7 @@ pub fn setCurrentTime(self: *Media, value: f64) void {
pub fn getSrc(self: *const Media, frame: *Frame) ![]const u8 {
const element = self.asConstElement();
const src = element.getAttributeSafe(comptime .wrap("src")) orelse return "";
const src = element.getAttributeInterned("src") orelse return "";
if (src.len == 0) {
return "";
}
+5 -5
View File
@@ -48,7 +48,7 @@ pub fn asNode(self: *Meta) *Node {
}
pub fn getName(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("name")) orelse return "";
return self.asElement().getName() orelse "";
}
pub fn setName(self: *Meta, value: []const u8, frame: *Frame) !void {
@@ -64,7 +64,7 @@ pub fn setHttpEquiv(self: *Meta, value: []const u8, frame: *Frame) !void {
}
pub fn getContent(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("content")) orelse return "";
return self.asElement().getAttributeInterned("content") orelse return "";
}
pub fn setContent(self: *Meta, value: []const u8, frame: *Frame) !void {
@@ -72,7 +72,7 @@ pub fn setContent(self: *Meta, value: []const u8, frame: *Frame) !void {
}
pub fn getMedia(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("media")) orelse return "";
return self.asElement().getAttributeInterned("media") orelse return "";
}
pub fn setMedia(self: *Meta, value: []const u8, frame: *Frame) !void {
@@ -165,9 +165,9 @@ pub const Build = struct {
const el = self.asElement();
// <meta name=referrer> sets the document's referrer policy.
if (el.getAttributeSafe(comptime .wrap("name"))) |name| {
if (el.getName()) |name| {
if (std.ascii.eqlIgnoreCase(name, "referrer")) {
if (el.getAttributeSafe(comptime .wrap("content"))) |content| {
if (el.getAttributeInterned("content")) |content| {
if (referrer.parseMeta(content)) |rp| {
frame.referrer_policy = rp;
}
+1 -1
View File
@@ -38,7 +38,7 @@ pub fn asNode(self: *OL) *Node {
}
pub fn getType(self: *OL) []const u8 {
return self.asElement().getAttributeSafe(comptime .wrap("type")) orelse "1";
return self.asElement().getAttributeInterned("type") orelse "1";
}
pub fn setType(self: *OL, value: []const u8, frame: *Frame) !void {
+4 -4
View File
@@ -104,7 +104,7 @@ fn ownerSelect(self: *Option) ?*Select {
}
pub fn getDefaultSelected(self: *const Option) bool {
return self.asConstElement().hasAttributeSafe(comptime .wrap("selected"));
return self.asConstElement().hasAttributeInterned("selected");
}
pub fn setDefaultSelected(self: *Option, value: bool, frame: *Frame) !void {
@@ -153,10 +153,10 @@ pub const Build = struct {
const element = self.asElement();
// Check for value attribute
self._value = element.getAttributeSafe(comptime .wrap("value"));
self._value = element.getAttributeInterned("value");
// Check for selected attribute
self._default_selected = element.getAttributeSafe(comptime .wrap("selected")) != null;
self._default_selected = element.getAttributeInterned("selected") != null;
self._selected = self._default_selected;
}
@@ -166,7 +166,7 @@ pub const Build = struct {
switch (attribute) {
// `value` is passed by value; for <= 12 bytes, str() points into our
// own parameter copy, so we have to re-read the owned bytes.
.value => self._value = element.getAttributeSafe(comptime .wrap("value")),
.value => self._value = element.getAttributeInterned("value"),
.selected => {
self._default_selected = true;
self._selected = true;
+3 -3
View File
@@ -59,7 +59,7 @@ pub fn setSrc(self: *Script, src: []const u8, frame: *Frame) !void {
}
pub fn getAsync(self: *const Script) bool {
return self._force_async or self.asConstElement().getAttributeSafe(comptime .wrap("async")) != null;
return self._force_async or self.asConstElement().getAttributeInterned("async") != null;
}
pub fn setAsync(self: *Script, value: bool, frame: *Frame) !void {
@@ -127,7 +127,7 @@ pub const Build = struct {
pub fn complete(node: *Node, _: *Frame) !void {
const self = node.as(Script);
const element = self.asElement();
self._src = element.getAttributeSafe(comptime .wrap("src")) orelse "";
self._src = element.getAttributeInterned("src") orelse "";
}
pub fn attributeChange(element: *Element, name: String, _: String, frame: *Frame) !void {
@@ -136,7 +136,7 @@ pub const Build = struct {
}
const self = element.as(Script);
self._src = element.getAttributeSafe(comptime .wrap("src")) orelse "";
self._src = element.getAttributeInterned("src") orelse "";
if (self._src.len > 0 and element.asNode().isConnected()) {
try frame.scriptAddedCallback(false, self);
}
+3 -3
View File
@@ -373,15 +373,15 @@ pub fn suffersValueMissing(self: *const Select) bool {
}
pub fn getDisabled(self: *const Select) bool {
return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
return self.asConstElement().getAttributeInterned("disabled") != null;
}
pub fn getMultiple(self: *const Select) bool {
return self.asConstElement().getAttributeSafe(comptime .wrap("multiple")) != null;
return self.asConstElement().getAttributeInterned("multiple") != null;
}
pub fn getRequired(self: *const Select) bool {
return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null;
return self.asConstElement().getAttributeInterned("required") != null;
}
pub const JsApi = struct {
+1 -1
View File
@@ -150,7 +150,7 @@ pub fn assign(self: *Slot, values: []const js.Value, frame: *Frame) !void {
}
pub fn getName(self: *const Slot) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
return self.asConstElement().getName() orelse "";
}
pub const JsApi = struct {
+1 -1
View File
@@ -25,7 +25,7 @@ pub fn asNode(self: *Source) *Node {
pub fn getSrc(self: *const Source, frame: *Frame) ![]const u8 {
const element = self.asConstElement();
const src = element.getAttributeSafe(comptime .wrap("src")) orelse return "";
const src = element.getAttributeInterned("src") orelse return "";
if (src.len == 0) {
return "";
}
+1 -1
View File
@@ -86,7 +86,7 @@ pub fn styleAddedCallback(self: *Style, frame: *Frame) !void {
}
pub fn getType(self: *const Style) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("type")) orelse "";
return self.asConstElement().getAttributeInterned("type") orelse "";
}
pub const JsApi = struct {
+2 -2
View File
@@ -265,11 +265,11 @@ pub fn suffersTooShort(self: *const TextArea) bool {
}
pub fn getDisabled(self: *const TextArea) bool {
return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
return self.asConstElement().getAttributeInterned("disabled") != null;
}
pub fn getRequired(self: *const TextArea) bool {
return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null;
return self.asConstElement().getAttributeInterned("required") != null;
}
pub const JsApi = struct {
+1 -1
View File
@@ -59,7 +59,7 @@ pub fn findSlot(slottable: *Node, comptime open_only: bool, frame: *Frame) ?*Slo
const slottable_name = blk: {
const el = slottable.is(Element) orelse break :blk "";
break :blk el.getAttributeSafe(comptime .wrap("slot")) orelse "";
break :blk el.getSlot() orelse "";
};
return findNamedSlot(shadow_node, slottable_name);
}
+1 -1
View File
@@ -58,7 +58,7 @@ pub fn getElementById(self: *Svg, id: []const u8) ?*Element {
var tw = TreeWalker.Full.Elements.init(self.asNode(), .{});
while (tw.next()) |el| {
const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue;
const element_id = el.getId() orelse continue;
if (std.mem.eql(u8, element_id, id)) {
return el;
}
+2 -2
View File
@@ -811,7 +811,7 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, charset:
continue;
}
const name = element.getAttributeSafe(comptime .wrap("name"));
const name = element.getName();
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";
try appendString(&list, arena, x_key, "0");
@@ -820,7 +820,7 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, charset:
}
}
const name = element.getAttributeSafe(comptime .wrap("name")) orelse continue;
const name = element.getName() orelse continue;
const value = blk: {
if (element.is(Form.Input)) |input| {
const input_type = input._input_type;
+18 -18
View File
@@ -392,8 +392,8 @@ pub const Writer = struct {
try self.writeAXProperty(.{ .name = .settable, .value = .{ .booleanOrUndefined = true } }, w);
}
try self.writeAXProperty(.{ .name = .multiline, .value = .{ .boolean = false } }, w);
try self.writeAXProperty(.{ .name = .readonly, .value = .{ .boolean = el.hasAttributeSafe(comptime .wrap("readonly")) } }, w);
try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe(comptime .wrap("required")) } }, w);
try self.writeAXProperty(.{ .name = .readonly, .value = .{ .boolean = el.hasAttributeInterned("readonly") } }, w);
try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeInterned("required") } }, w);
},
.button, .submit, .reset, .image => {
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
@@ -406,7 +406,7 @@ pub const Writer = struct {
if (!is_disabled) {
try self.writeAXProperty(.{ .name = .focusable, .value = .{ .booleanOrUndefined = true } }, w);
}
const is_checked = el.hasAttributeSafe(comptime .wrap("checked"));
const is_checked = el.hasAttributeInterned("checked");
try self.writeAXProperty(.{ .name = .checked, .value = .{ .token = if (is_checked) "true" else "false" } }, w);
},
else => {},
@@ -424,8 +424,8 @@ pub const Writer = struct {
try self.writeAXProperty(.{ .name = .settable, .value = .{ .booleanOrUndefined = true } }, w);
}
try self.writeAXProperty(.{ .name = .multiline, .value = .{ .boolean = true } }, w);
try self.writeAXProperty(.{ .name = .readonly, .value = .{ .boolean = el.hasAttributeSafe(comptime .wrap("readonly")) } }, w);
try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe(comptime .wrap("required")) } }, w);
try self.writeAXProperty(.{ .name = .readonly, .value = .{ .boolean = el.hasAttributeInterned("readonly") } }, w);
try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeInterned("required") } }, w);
},
.select => {
const is_disabled = el.isDisabled();
@@ -827,7 +827,7 @@ pub const AXRole = enum(u8) {
},
.textarea => .textbox,
.select => {
if (el.getAttributeSafe(comptime .wrap("multiple")) != null) {
if (el.getAttributeInterned("multiple") != null) {
return .listbox;
}
if (el.getAttributeSafe(comptime .wrap("size"))) |size| {
@@ -847,7 +847,7 @@ pub const AXRole = enum(u8) {
// Interactive Elements
.anchor, .area => {
if (el.getAttributeSafe(comptime .wrap("href")) == null) {
if (el.getAttributeInterned("href") == null) {
return .none;
}
@@ -1030,7 +1030,7 @@ fn writeName(
}
}
if (el.getAttributeSafe(comptime .wrap("aria-label"))) |aria_label| {
if (el.getAttributeInterned("aria-label")) |aria_label| {
try w.write(aria_label);
return .aria_label;
}
@@ -1041,7 +1041,7 @@ fn writeName(
}
}
if (el.getAttributeSafe(comptime .wrap("alt"))) |alt| {
if (el.getAttributeInterned("alt")) |alt| {
try w.write(alt);
return .alt;
}
@@ -1090,12 +1090,12 @@ fn writeName(
}
}
if (el.getAttributeSafe(comptime .wrap("title"))) |title| {
if (el.getAttributeInterned("title")) |title| {
try w.write(title);
return .title;
}
if (el.getAttributeSafe(comptime .wrap("placeholder"))) |placeholder| {
if (el.getAttributeInterned("placeholder")) |placeholder| {
try w.write(placeholder);
return .placeholder;
}
@@ -1154,7 +1154,7 @@ fn writeAccessibleNameFallback(node: *DOMNode, writer: *std.Io.Writer, frame: *F
}
fn hasAriaHiddenTrue(elt: *DOMNode.Element) bool {
if (elt.getAttributeSafe(comptime .wrap("aria-hidden"))) |value| {
if (elt.getAttributeInterned("aria-hidden")) |value| {
return std.mem.eql(u8, value, "true");
}
return false;
@@ -1242,7 +1242,7 @@ fn writeLabelName(
label_index: *Label.LabelByForIndex,
w: anytype,
) !?AXSource {
if (el.getAttributeSafe(comptime .wrap("id"))) |id_value| {
if (el.getId()) |id_value| {
if (id_value.len > 0) {
if (node.ownerDocument(frame)) |doc| {
if (try label_index.lookup(doc.asNode(), id_value, frame.call_arena)) |label_el| {
@@ -1281,13 +1281,13 @@ fn scratchAllocator(temp_arena: ?*lp.Arena, frame: *Frame) std.mem.Allocator {
}
fn isHidden(elt: *DOMNode.Element, frame: *Frame, cache: *DOMNode.Element.VisibilityCache) bool {
if (elt.getAttributeSafe(comptime .wrap("aria-hidden"))) |value| {
if (elt.getAttributeInterned("aria-hidden")) |value| {
if (std.mem.eql(u8, value, "true")) {
return true;
}
}
if (elt.hasAttributeSafe(comptime .wrap("hidden"))) {
if (elt.hasAttributeInterned("hidden")) {
return true;
}
@@ -1367,7 +1367,7 @@ fn isIgnore(self: AXNode, frame: *Frame, cache: *DOMNode.Element.VisibilityCache
// zig fmt: on
.img => {
// Check for empty decorative images
const alt_ = elt.getAttributeSafe(comptime .wrap("alt"));
const alt_ = elt.getAttributeInterned("alt");
if (alt_ == null or alt_.?.len == 0) {
return true;
}
@@ -1398,8 +1398,8 @@ fn isIgnore(self: AXNode, frame: *Frame, cache: *DOMNode.Element.VisibilityCache
// Generic containers with no semantic value
if (tag == .div or tag == .span) {
const has_role = elt.hasAttributeSafe(comptime .wrap("role"));
const has_aria_label = elt.hasAttributeSafe(comptime .wrap("aria-label"));
const has_role = elt.hasAttributeInterned("role");
const has_aria_label = elt.hasAttributeInterned("aria-label");
const has_aria_labelledby = elt.hasAttributeSafe(.wrap("aria-labelledby"));
if (!has_role and !has_aria_label and !has_aria_labelledby) {