mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-23 21:15:31 -04:00
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:
53 files changed
+275
-210
No files matched your search
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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| {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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, .{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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("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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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, .{});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user